From 128470d9296a7c8d39aa8defa00f99f5ca5c36fd Mon Sep 17 00:00:00 2001 From: shivesh Date: Mon, 19 Mar 2018 12:00:12 -0500 Subject: refactor --- chainer_plot.py | 10 +- fr.py | 249 ++++++++++++++++++++ mcmc_scan.py | 671 ------------------------------------------------------ utils/__init__.py | 0 utils/enums.py | 68 ++++++ utils/fr.py | 353 ++++++++++++++++++++++++++++ utils/gf.py | 105 +++++++++ utils/mcmc.py | 167 ++++++++++++++ utils/misc.py | 230 +++++++++++++++++++ 9 files changed, 1177 insertions(+), 676 deletions(-) create mode 100755 fr.py delete mode 100755 mcmc_scan.py create mode 100644 utils/__init__.py create mode 100644 utils/enums.py create mode 100644 utils/fr.py create mode 100644 utils/gf.py create mode 100644 utils/mcmc.py create mode 100644 utils/misc.py diff --git a/chainer_plot.py b/chainer_plot.py index 8964bf5..3facb30 100755 --- a/chainer_plot.py +++ b/chainer_plot.py @@ -17,7 +17,7 @@ import getdist from getdist import plots from getdist import mcsamples -import mcmc_scan +from utils.fr import angles_to_u, angles_to_fr rc('text', usetex=False) rc('font', **{'family':'serif', 'serif':['Computer Modern'], 'size':18}) @@ -116,14 +116,14 @@ def plot(infile, angles, outfile, measured_ratio, sigma_ratio, fix_sfr, print 'ranges', ranges def flat_angles_to_u(x): - return abs(mcmc_scan.angles_to_u(x)).astype(np.float32).flatten().tolist() + return abs(angles_to_u(x)).astype(np.float32).flatten().tolist() raw = np.load(infile) print 'raw.shape', raw.shape if not angles: nuisance, raw = raw[:,5:], raw[:,-5:] if fix_mixing: - fr_elements = np.array(map(mcmc_scan.angles_to_fr, raw[:,-2:])) + fr_elements = np.array(map(angles_to_fr, raw[:,-2:])) sc_elements = raw[:,:-2] Tchain = np.column_stack([sc_elements, fr_elements]) elif fix_sfr: @@ -135,11 +135,11 @@ def plot(infile, angles, outfile, measured_ratio, sigma_ratio, fix_sfr, Tchain = np.column_stack([m_elements, sc_elements]) else: if fix_scale: - fr_elements = np.array(map(mcmc_scan.angles_to_fr, raw[:,-2:])) + fr_elements = np.array(map(angles_to_fr, raw[:,-2:])) m_elements = np.array(map(flat_angles_to_u, raw[:,:-2])) Tchain = np.column_stack([m_elements, fr_elements]) else: - fr_elements = np.array(map(mcmc_scan.angles_to_fr, raw[:,-2:])) + fr_elements = np.array(map(angles_to_fr, raw[:,-2:])) sc_elements = raw[:,-3:-2] m_elements = np.array(map(flat_angles_to_u, raw[:,:-3])) Tchain = np.column_stack([m_elements, sc_elements, fr_elements]) diff --git a/fr.py b/fr.py new file mode 100755 index 0000000..b92f87f --- /dev/null +++ b/fr.py @@ -0,0 +1,249 @@ +#! /usr/bin/env python +# author : S. Mandalia +# s.p.mandalia@qmul.ac.uk +# +# date : March 17, 2018 + +""" +HESE BSM flavour ratio analysis script +""" + +from __future__ import absolute_import, division + +import argparse +from functools import partial + +import numpy as np + +from utils import mcmc as mcmc_utils +from utils import misc as misc_utils +from utils.fr import MASS_EIGENVALUES, normalise_fr +from utils.gf import gf_argparse +from utils.misc import Param, ParamSet + +import chainer_plot + + +def define_nuisance(): + """Define the nuisance parameters to scan over with default values, + ranges and sigma. + """ + nuisance = ParamSet( + Param(name='convNorm' , value=1. , ranges=[0., 5.], std=0.3), + Param(name='promptNorm' , value=0. , ranges=[0., 5.], std=0.05), + Param(name='muonNorm' , value=1. , ranges=[0., 5.], std=0.1), + Param(name='astroNorm' , value=1. , ranges=[0., 5.], std=0.1), + Param(name='astroDeltaGamma', value=2. , ranges=[0., 5.], std=0.1) + ) + return nuisance + + +def nuisance_argparse(parser): + nuisance_paramset = define_nuisance() + for parm in nuisance_paramset: + parser.add_argument( + '--'+parm.name, type=float, default=parm.value, + help=parm.name+' to inject' + ) + + +def get_paramsets(args): + """Make the paramsets for generating the Asmimov MC sample and also running + the MCMC. + """ + nuisance_paramset = define_nuisance() + for parm in nuisance_paramset: + parm.value = args.__getattribute__(parm.name) + asimov_paramset = ParamSet( + nuisance_paramset.params + + [Param(name='astroENorm' , value=args.measured_ratio[0], ranges=[0., 1.], std=0.2), + Param(name='astroMuNorm' , value=args.measured_ratio[1], ranges=[0., 1.], std=0.2), + Param(name='astroTauNorm' , value=args.measured_ratio[2], ranges=[0., 1.], std=0.2)] + ) + + mcmc_paramset = [] + if not args.fix_mixing: + mcmc_paramset.extend([ + Param(name='s_12^2', value=0.5, ranges=[0., 1.], std=0.2, tex=r'\tilde{s}_{12}^2'), + Param(name='c_13^4', value=0.5, ranges=[0., 1.], std=0.2, tex=r'\tilde{c}_{13}^4'), + Param(name='s_23^2', value=0.5, ranges=[0., 1.], std=0.2, tex=r'\tilde{s}_{23}^4'), + Param(name='dcp', value=np.pi, ranges=[0., 2*np.pi], std=0.2, tex=r'\tilde{\delta_{CP}}') + ]) + if not args.fix_scale: + logLam, scale_region = np.log10(args.scale), np.log10(args.scale_region) + lL_range = (logLam-scale_region, logLam+scale_region) + mcmc_paramset.append( + Param(name='logLam', value=logLam, ranges=lL_range, std=3, tex=r'{\rm log}_{10}\Lambda') + ) + if not args.fix_source_ratio: + mcmc_paramset.extend([ + Param(name='s_phi4', value=0.5, ranges=[0., 1.], std=0.2, tex=r'sin^4(\phi)'), + Param(name='c_2psi', value=0.5, ranges=[0., 1.], std=0.2, tex=r'cos(2\psi)') + ]) + mcmc_paramset = ParamSet(nuisance_paramset.params + mcmc_paramset) + return nuisance_paramset, mcmc_paramset + + +def process_args(args): + """Process the input args.""" + if args.fix_mixing and args.fix_source_ratio: + raise NotImplementedError('Fixed mixing and sfr not implemented') + if args.fix_mixing and args.fix_scale: + raise NotImplementedError('Fixed mixing and scale not implemented') + + args.measured_ratio = normalise_fr(args.measured_ratio) + if args.fix_source_ratio: + args.source_ratio = normalise_fr(args.source_ratio) + + if not args.fix_scale: + args.scale = np.power( + 10, np.round(np.log10(MASS_EIGENVALUES[1]/args.energy)) - \ + np.log10(args.energy**(args.dimension-3)) + ) + """Estimate the scale of when to expect the BSM term to contribute.""" + + +def parse_args(): + """Parse command line arguments""" + parser = argparse.ArgumentParser(description="BSM flavour ratio analysis") + parser.add_argument( + '--measured-ratio', type=int, nargs=3, default=[1, 1, 1], + help='Set the central value for the measured flavour ratio at IceCube' + ) + parser.add_argument( + '--sigma-ratio', type=float, default=0.01, + help='Set the 1 sigma for the measured flavour ratio for a gaussian LLH' + ) + parser.add_argument( + '--fix-source-ratio', type=misc_utils.parse_bool, default='False', + help='Fix the source flavour ratio' + ) + parser.add_argument( + '--source-ratio', type=int, nargs=3, default=[2, 1, 0], + help='Set the source flavour ratio for the case when you want to fix it' + ) + parser.add_argument( + '--no-bsm', type=misc_utils.parse_bool, default='False', + help='Turn off BSM terms' + ) + parser.add_argument( + '--fix-mixing', type=misc_utils.parse_bool, default='False', + help='Fix all mixing parameters except one' + ) + parser.add_argument( + '--fix-scale', type=misc_utils.parse_bool, default='False', + help='Fix the new physics scale' + ) + parser.add_argument( + '--scale', type=float, default=1e-30, + help='Set the new physics scale' + ) + parser.add_argument( + '--scale-region', type=float, default=1e10, + help='Set the size of the box to scan for the scale' + ) + parser.add_argument( + '--dimension', type=int, default=3, + help='Set the new physics dimension to consider' + ) + parser.add_argument( + '--energy', type=float, default=1000, + help='Set the energy scale' + ) + parser.add_argument( + '--seed', type=int, default=99, + help='Set the random seed value' + ) + parser.add_argument( + '--threads', type=misc_utils.thread_type, default='1', + help='Set the number of threads to use (int or "max")' + ) + parser.add_argument( + '--outfile', type=str, default='./untitled', + help='Path to output chains' + ) + nuisance_argparse(parser) + gf_argparse(parser) + mcmc_utils.mcmc_argparse(parser) + return parser.parse_args() + + +def main(): + args = parse_args() + process_args(args) + misc_utils.print_args(args) + + np.random.seed(args.seed) + + asmimov_paramset, mcmc_paramset = get_paramsets(args) + outfile = misc_utils.gen_outfile_name(args) + print '== {0:<25} = {1}'.format('outfile', outfile) + + if args.run_mcmc: + triangle_llh = partial( + mcmc_utils.triangle_llh, args=args + ) + lnprior = partial( + mcmc_utils.lnprior, paramset=mcmc_paramset + ) + + ndim = len(mcmc_paramset) + ntemps=1 + p0 = mcmc_utils.gaussian_seed( + mcmc_paramset, ntemps=ntemps, nwalkers=args.nwalkers + ) + + samples = mcmc_utils.mcmc( + p0 = p0, + triangle_llh = triangle_llh, + lnprior = lnprior, + ndim = ndim, + nwalkers = args.nwalkers, + burnin = args.burnin, + nsteps = args.nsteps, + ntemps = ntemps, + threads = args.threads + ) + mcmc_utils.save_chains(samples, outfile) + + scale_bounds = (args.scale/args.scale_region, args.scale*args.scale_region) + print "Making triangle plots" + chainer_plot.plot( + infile = outfile+'.npy', + angles = True, + outfile = outfile[:5]+outfile[5:].replace('data', 'plots')+'_angles.pdf', + measured_ratio = args.measured_ratio, + sigma_ratio = args.sigma_ratio, + fix_sfr = args.fix_source_ratio, + fix_mixing = args.fix_mixing, + fix_scale = args.fix_scale, + source_ratio = args.source_ratio, + scale = args.scale, + dimension = args.dimension, + energy = args.energy, + scale_bounds = scale_bounds, + ) + # chainer_plot.plot( + # infile=outfile+'.npy', + # angles=False, + # outfile=outfile[:5]+outfile[5:].replace('data', 'plots')+'.pdf', + # measured_ratio=args.measured_ratio, + # sigma_ratio=args.sigma_ratio, + # fix_sfr=args.fix_source_ratio, + # fix_mixing=args.fix_mixing, + # fix_scale=FIX_SCALE, + # source_ratio=args.source_ratio, + # scale=args.scale, + # dimension=args.dimension, + # energy=args.energy, + # scale_bounds=scale_bounds, + # ) + print "DONE!" + + +main.__doc__ = __doc__ + + +if __name__ == '__main__': + main() + diff --git a/mcmc_scan.py b/mcmc_scan.py deleted file mode 100755 index 90003a9..0000000 --- a/mcmc_scan.py +++ /dev/null @@ -1,671 +0,0 @@ -#! /usr/bin/env python -""" -From a gaussian likelihood run an MCMC scan to find the posteriors -""" - -from __future__ import absolute_import, division - -import os, sys -import errno - -import argparse -import multiprocessing -from copy import deepcopy - -import numpy as np -from scipy import linalg - -import emcee -import tqdm - -import GolemFitPy as gf -import chainer_plot - - -RUN_SCAN = True - -GOLEMFIT = None -FIX_MIXING = False -FIX_SFR = True -SOURCE_FR = [1, 2, 0] -FIX_SCALE = True -NO_BSM = False - -DIMENSION = 3 -ENERGY = 1000000 # GeV -MEASURED_FR = [1, 1, 1] -SIGMA = 0.001 -SCALE_RATIO = 100. -MASS_EIGENVALUES = [7.40E-23, 2.515E-21] -# MASS_EIGENVALUES = [7.40E-100, 2.515E-100] -FLAT = False - -CHANGE_MASS = False -if CHANGE_MASS: - SCALE = 1E-27 -else: - SCALE = np.power(10, np.round(np.log10(MASS_EIGENVALUES[1]/ENERGY)) - np.log10(ENERGY**(DIMENSION-3))) -SCALE2_BOUNDS = (SCALE*1E-10, SCALE*1E10) - - -def set_up_gf(): - steering_params = gf.SteeringParams() - datapaths = gf.DataPaths() - npp = gf.NewPhysicsParams() - - steering_params.logEbinWidth=(7.0-4.78)/20. - steering_params.cosThbinWidth=0.2 - steering_params.correct_prompt_knee=True - steering_params.fastmode=True - # TODO(shivesh): figure out right number for missid - steering_params.track_to_shower_missid = 0.3 - golemfitsourcepath = os.environ['GOLEMSOURCEPATH'] + '/GolemFit' - datapaths.compact_file_path = golemfitsourcepath + '/compact_data/' # only used for FastMode; not currently working - datapaths.data_path = golemfitsourcepath + '/data/' - datapaths.mc_path = golemfitsourcepath + '/monte_carlo/' - - golemfit = gf.GolemFit(datapaths, steering_params, npp) - return golemfit - - -def set_up_as(golemfit, parms): - asimov_parameters = gf.FitParameters() - asimov_parameters.convNorm = parms['convNorm'] - asimov_parameters.promptNorm = parms['promptNorm'] - asimov_parameters.muonNorm = parms['muonNorm'] - asimov_parameters.astroNorm = parms['astroNorm'] - asimov_parameters.astroDeltaGamma = parms['astroDeltaGamma'] - asimov_parameters.astroENorm = parms['astroENorm'] - asimov_parameters.astroMuNorm = parms['astroMuNorm'] - asimov_parameters.astroTauNorm = parms['astroTauNorm'] - golemfit.SetupAsimov(asimov_parameters) - - -def get_llh(golemfit, parms): - fitparams = gf.FitParameters() - fitparams.convNorm = parms['convNorm'] - fitparams.promptNorm = parms['promptNorm'] - fitparams.muonNorm = parms['muonNorm'] - fitparams.astroNorm = parms['astroNorm'] - fitparams.astroDeltaGamma = parms['astroDeltaGamma'] - fitparams.astroENorm = parms['astroENorm'] - fitparams.astroMuNorm = parms['astroMuNorm'] - fitparams.astroTauNorm = parms['astroTauNorm'] - return golemfit.EvalLLH(fitparams) - - -def test_uni(x): - """Test the unitarity of a matrix.""" - # print 'Unitarity test:\n{0}'.format(abs(np.dot(x, x.conj().T))) - return abs(np.dot(x, x.conj().T)) - - -def angles_to_fr(angles): - sphi4, c2psi = angles - - psi = (0.5)*np.arccos(c2psi) - - sphi2 = np.sqrt(sphi4) - cphi2 = 1. - sphi2 - spsi2 = np.sin(psi)**2 - cspi2 = 1. - spsi2 - - x = sphi2*cspi2 - y = sphi2*spsi2 - z = cphi2 - return x, y, z - - -def angles_to_u(angles): - s12_2, c13_4, s23_2, dcp = angles - dcp = np.complex128(dcp) - - c12_2 = 1. - s12_2 - c13_2 = np.sqrt(c13_4) - s13_2 = 1. - c13_2 - c23_2 = 1. - s23_2 - - t12 = np.arcsin(np.sqrt(s12_2)) - t13 = np.arccos(np.sqrt(c13_2)) - t23 = np.arcsin(np.sqrt(s23_2)) - - c12 = np.cos(t12) - s12 = np.sin(t12) - c13 = np.cos(t13) - s13 = np.sin(t13) - c23 = np.cos(t23) - s23 = np.sin(t23) - - p1 = np.array([[1 , 0 , 0] , [0 , c23 , s23] , [0 , -s23 , c23]] , dtype=np.complex128) - p2 = np.array([[c13 , 0 , s13*np.exp(-1j*dcp)] , [0 , 1 , 0] , [-s13*np.exp(1j*dcp) , 0 , c13]] , dtype=np.complex128) - p3 = np.array([[c12 , s12 , 0] , [-s12 , c12 , 0] , [0 , 0 , 1]] , dtype=np.complex128) - - u = np.dot(np.dot(p1, p2), p3) - return u - - -def cardano_eqn(ham): - """Diagonalise the effective Hamiltonian 3x3 matrix into the form - h_{eff} = UE_{eff}U^{dagger} using the procedure in PRD91, 052003 (2015) - """ - a = -np.trace(ham) - b = (0.5) * ((np.trace(ham))**2 - np.trace(np.dot(ham, ham))) - c = -linalg.det(ham) - - Q = (1/9.) * (a**2 - 3*b) - R = (1/54.) * (2*a**3 - 9*a*b + 27*c) - theta = np.arccos(R / np.sqrt(Q**3)) - - E1 = -2 * np.sqrt(Q) * np.cos(theta/3.) - (1/3.)*a - E2 = -2 * np.sqrt(Q) * np.cos((theta - 2.*np.pi)/3.) - (1/3.)*a - E3 = -2 * np.sqrt(Q) * np.cos((theta + 2.*np.pi)/3.) - (1/3.)*a - - A1 = ham[1][2] * (ham[0][0] - E1) - ham[1][0]*ham[0][2] - A2 = ham[1][2] * (ham[0][0] - E2) - ham[1][0]*ham[0][2] - A3 = ham[1][2] * (ham[0][0] - E3) - ham[1][0]*ham[0][2] - - B1 = ham[2][0] * (ham[1][1] - E1) - ham[2][1]*ham[1][0] - B2 = ham[2][0] * (ham[1][1] - E2) - ham[2][1]*ham[1][0] - B3 = ham[2][0] * (ham[1][1] - E3) - ham[2][1]*ham[1][0] - - C1 = ham[1][0] * (ham[2][2] - E1) - ham[1][2]*ham[2][0] - C2 = ham[1][0] * (ham[2][2] - E2) - ham[1][2]*ham[2][0] - C3 = ham[1][0] * (ham[2][2] - E3) - ham[1][2]*ham[2][0] - - N1 = np.sqrt(abs(A1*B1)**2 + abs(A1*C1)**2 + abs(B1*C1)**2) - N2 = np.sqrt(abs(A2*B2)**2 + abs(A2*C2)**2 + abs(B2*C2)**2) - N3 = np.sqrt(abs(A3*B3)**2 + abs(A3*C3)**2 + abs(B3*C3)**2) - - mm = np.array([ - [np.conjugate(B1)*C1 / N1, np.conjugate(B2)*C2 / N2, np.conjugate(B3)*C3 / N3], - [A1*C1 / N1, A2*C2 / N2, A3*C3 / N3], - [A1*B1 / N1, A2*B2 / N2, A3*B3 / N3] - ]) - return mm - - -# s_12^2, c_13^4, s_23^2, dcp -NUFIT_U = angles_to_u((0.307, (1-0.02195)**2, 0.565, 3.97935)) - - -def params_to_BSMu(theta): - if FIX_MIXING: - s12_2, c13_4, s23_2, dcp, sc2 = 0.5, 1.0-1E-6, 0.5, 0., theta - elif FIX_SCALE: - s12_2, c13_4, s23_2, dcp = theta - sc2 = np.log10(SCALE) - else: - s12_2, c13_4, s23_2, dcp, sc2 = theta - sc2 = np.power(10., sc2) - sc1 = sc2 / SCALE_RATIO - - mass_matrix = np.array( - [[0, 0, 0], [0, MASS_EIGENVALUES[0], 0], [0, 0, MASS_EIGENVALUES[1]]] - ) - sm_ham = (1./(2*ENERGY))*np.dot(NUFIT_U, np.dot(mass_matrix, NUFIT_U.conj().T)) - if NO_BSM: - eg_vector = cardano_eqn(sm_ham) - tu = test_uni(eg_vector) - if not abs(np.trace(tu) - 3.) < 1e-5 or not abs(np.sum(tu) - 3.) < 1e-5: - raise AssertionError('Matrix is not unitary!\neg_vector\n{0}\ntest u\n{1}'.format(eg_vector, tu)) - return eg_vector - - new_physics_u = angles_to_u((s12_2, c13_4, s23_2, dcp)) - scale_matrix = np.array( - [[0, 0, 0], [0, sc1, 0], [0, 0, sc2]] - ) - bsm_term = (ENERGY**(DIMENSION-3)) * np.dot(new_physics_u, np.dot(scale_matrix, new_physics_u.conj().T)) - - bsm_ham = sm_ham + bsm_term - - eg_vector = cardano_eqn(bsm_ham) - tu = test_uni(eg_vector) - if not abs(np.trace(tu) - 3.) < 1e-5 or not abs(np.sum(tu) - 3.) < 1e-5: - raise AssertionError('Matrix is not unitary!\neg_vector\n{0}\ntest u\n{1}'.format(eg_vector, tu)) - return eg_vector - - -def u_to_fr(initial_fr, matrix): - """Compute the observed flavour ratio assuming decoherence.""" - # TODO(shivesh): energy dependence - composition = np.einsum( - 'ai, bi, a -> b', abs(matrix)**2, abs(matrix)**2, initial_fr - ) - ratio = composition / np.sum(initial_fr) - return ratio - - -def triangle_llh(theta): - """-Log likelihood function for a given theta.""" - convNorm, promptNorm, muonNorm, astroNorm, astroDeltaGamma = theta[:5] - theta = deepcopy(theta[5:]) - - if FIX_SFR: - fr1, fr2, fr3 = SOURCE_FR - u = params_to_BSMu(theta) - else: - fr1, fr2, fr3 = angles_to_fr(theta[-2:]) - u = params_to_BSMu(theta[:-2]) - - fr = u_to_fr((fr1, fr2, fr3), u) - - fit_values = { - 'convNorm': convNorm, - 'promptNorm': promptNorm, - 'muonNorm': muonNorm, - 'astroNorm': astroNorm, - 'astroDeltaGamma': astroDeltaGamma, - 'astroENorm': fr[0], - 'astroMuNorm': fr[1], - 'astroTauNorm': fr[2] - } - if FLAT: - return 10. - else: - return get_llh(GOLEMFIT, fit_values) - - -def lnprior(theta): - """Priors on theta.""" - convNorm, promptNorm, muonNorm, astroNorm, astroDeltaGamma = theta[:5] - theta = deepcopy(theta[5:]) - - # Nuisance parameter bounds - if 0. < convNorm < 5. and 0. < promptNorm < 5. and 0. < muonNorm < 5. and \ - 0. < astroNorm < 5. and 0. < astroDeltaGamma < 5.: - pass - else: return -np.inf - - if FIX_SFR: - if FIX_MIXING: - s12_2, sc2 = theta - elif FIX_SCALE: - s12_2, c13_4, s23_2, dcp = theta - else: - s12_2, c13_4, s23_2, dcp, sc2 = theta - else: - if FIX_MIXING: - sc2, sphi4, c2psi = theta - elif FIX_SCALE: - s12_2, c13_4, s23_2, dcp, sphi4, c2psi = theta - else: - s12_2, c13_4, s23_2, dcp, sc2, sphi4, c2psi = theta - if not FIX_SCALE: - sc2 = np.power(10., sc2) - - if not FIX_SFR: - # Flavour ratio bounds - if 0. <= sphi4 <= 1.0 and -1.0 <= c2psi <= 1.0: - pass - else: return -np.inf - - # Mixing angle bounds - if not FIX_MIXING: - if 0. <= s12_2 <= 1. and 0. <= c13_4 <= 1. and 0. <= s23_2 <= 1. \ - and 0. <= dcp <= 2*np.pi: - pass - else: return -np.inf - - # Scale bounds - if not FIX_SCALE: - if SCALE2_BOUNDS[0] <= sc2 <= SCALE2_BOUNDS[1]: - pass - else: return -np.inf - - return 0. - - -def lnprob(theta): - """Prob function for mcmc.""" - lp = lnprior(theta) - if not np.isfinite(lp): - return -np.inf - return lp + triangle_llh(theta) - - -def parse_args(): - """Parse command line arguments""" - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - '--measured-ratio', type=int, nargs=3, default=[1, 1, 1], - help='Set the central value for the measured flavour ratio at IceCube' - ) - parser.add_argument( - '--sigma-ratio', type=float, default=0.01, - help='Set the 1 sigma for the measured flavour ratio' - ) - parser.add_argument( - '--fix-source-ratio', type=str, default='False', - help='Fix the source flavour ratio' - ) - parser.add_argument( - '--source-ratio', type=int, nargs=3, default=[2, 1, 0], - help='Set the source flavour ratio for the case when you want to fix it' - ) - parser.add_argument( - '--no-bsm', type=str, default='False', - help='Turn off BSM terms' - ) - parser.add_argument( - '--fix-mixing', type=str, default='False', - help='Fix all mixing parameters except one' - ) - parser.add_argument( - '--fix-scale', type=str, default='False', - help='Fix the new physics scale' - ) - parser.add_argument( - '--scale', type=float, default=1e-30, - help='Set the new physics scale' - ) - parser.add_argument( - '--dimension', type=int, default=3, - help='Set the new physics dimension to consider' - ) - parser.add_argument( - '--energy', type=float, default=1000, - help='Set the energy scale' - ) - parser.add_argument( - '--flat-llh', type=str, default='False', - help='Run with a flat likelihood' - ) - parser.add_argument( - '--burnin', type=int, default=100, - help='Amount to burnin' - ) - parser.add_argument( - '--nwalkers', type=int, default=100, - help='Number of walkers' - ) - parser.add_argument( - '--nsteps', type=int, default=2000, - help='Number of steps to run' - ) - parser.add_argument( - '--seed', type=int, default=99, - help='Set the random seed value' - ) - parser.add_argument( - '--outfile', type=str, default='./untitled', - help='Path to output chains' - ) - args = parser.parse_args() - return args - - -def main(): - args = parse_args() - - np.random.seed(args.seed) - - global DIMENSION - global ENERGY - global MEASURED_FR - global SIGMA - global FLAT - global FIX_SFR - global SOURCE_FR - global FIX_MIXING - global FIX_SCALE - global SCALE - global SCALE2_BOUNDS - global NO_BSM - - DIMENSION = args.dimension - ENERGY = args.energy - - MEASURED_FR = np.array(args.measured_ratio) / float(np.sum(args.measured_ratio)) - SIGMA = args.sigma_ratio - - if args.flat_llh.lower() == 'true': - FLAT = True - elif args.flat_llh.lower() == 'false': - FLAT = False - else: - raise ValueError - - if args.fix_source_ratio.lower() == 'true': - FIX_SFR = True - elif args.fix_source_ratio.lower() == 'false': - FIX_SFR = False - else: - raise ValueError - - if args.fix_mixing.lower() == 'true': - FIX_MIXING = True - elif args.fix_mixing.lower() == 'false': - FIX_MIXING = False - else: - raise ValueError - - if args.fix_scale.lower() == 'true': - FIX_SCALE = True - elif args.fix_scale.lower() == 'false': - FIX_SCALE = False - else: - raise ValueError - - if args.no_bsm.lower() == 'true': - NO_BSM = True - elif args.no_bsm.lower() == 'false': - NO_BSM = False - else: - raise ValueError - - if FIX_SFR: - SOURCE_FR = np.array(args.source_ratio) / float(np.sum(args.source_ratio)) - - if FIX_SCALE: - SCALE = args.scale - else: - if CHANGE_MASS: - SCALE = 1E-27 - else: - SCALE = np.power(10, np.round(np.log10(MASS_EIGENVALUES[1]/ENERGY)) - np.log10(ENERGY**(DIMENSION-3))) - - if FIX_MIXING and FIX_SFR: - raise NotImplementedError('Fixed mixing and sfr not implemented') - if FIX_MIXING and FIX_SCALE: - raise NotImplementedError('Fixed mixing and scale not implemented') - - SCALE2_BOUNDS = (SCALE*1E-10, SCALE*1E10) - - print 'RUN_SCAN = {0}'.format(RUN_SCAN) - print 'MEASURED_FR = {0}'.format(MEASURED_FR) - print 'SIGMA = {0}'.format(SIGMA) - print 'FLAT = {0}'.format(FLAT) - print 'NO_BSM = {0}'.format(NO_BSM) - print 'ENERGY = {0}'.format(ENERGY) - print 'DIMENSION = {0}'.format(DIMENSION) - print 'SCALE2_BOUNDS = {0}'.format(SCALE2_BOUNDS) - print 'FIX_SFR = {0}'.format(FIX_SFR) - if FIX_SFR: - print 'SOURCE_FR = {0}'.format(SOURCE_FR) - print 'FIX_MIXING = {0}'.format(FIX_MIXING) - print 'FIX_SCALE = {0}'.format(FIX_SCALE) - if FIX_SCALE: - print 'SCALE = {0}'.format(SCALE) - - global GOLEMFIT - if RUN_SCAN: - GOLEMFIT = set_up_gf() - - # TODO(shivesh): make all these into args - inject = { - 'convNorm': 1., - 'promptNorm': 0., - 'muonNorm': 1., - 'astroNorm': 1., - 'astroDeltaGamma': 2., - 'astroENorm': MEASURED_FR[0], - 'astroMuNorm': MEASURED_FR[1], - 'astroTauNorm': MEASURED_FR[2], - } - - print "Injecting this model: ", inject - set_up_as(GOLEMFIT, inject) - - if FIX_SFR: - if FIX_MIXING: - ndim = 7 - elif FIX_SCALE: - ndim = 9 - else: - ndim = 10 - else: - if FIX_MIXING: - ndim = 8 - elif FIX_SCALE: - ndim = 11 - else: - ndim = 12 - nwalkers = args.nwalkers - ntemps = 1 - burnin = args.burnin - betas = np.array([1e0, 1e-1, 1e-2, 1e-3, 1e-4]) - s2 = np.average(np.log10(SCALE2_BOUNDS)) - if FIX_SFR: - if FIX_MIXING: - p0_base = [1., 0., 1., 1., 2., 0.5, s2] - p0_std = [0.3, 0.05, 0.1, 0.1, 0.1, 0.2, 3] - elif FIX_SCALE: - p0_base = [1., 0., 1., 1., 2., 0.5, 0.5, 0.5, np.pi] - p0_std = [0.3, 0.05, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.2] - else: - p0_base = [1., 0., 1., 1., 2., 0.5, 0.5, 0.5, np.pi, s2] - p0_std = [0.3, 0.05, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.2, 3] - else: - if FIX_MIXING: - p0_base = [1., 0., 1., 1., 2., s2, 0.5, 0.0] - p0_std = [0.3, 0.05, 0.1, 0.1, 0.1, 3, 0.2, 0.2] - elif FIX_SCALE: - p0_base = [1., 0., 1., 1., 2., 0.5, 0.5, 0.5, np.pi, 0.5, 0.0] - p0_std = [0.3, 0.05, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2] - else: - p0_base = [1., 0., 1., 1., 2., 0.5, 0.5, 0.5, np.pi, s2, 0.5, 0.0] - p0_std = [0.3, 0.05, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.2, 3, 0.2, 0.2] - - print 'p0_base', p0_base - print 'p0_std', p0_std - p0 = np.random.normal(p0_base, p0_std, size=[ntemps, nwalkers, ndim]) - print map(lnprior, p0[0]) - - if RUN_SCAN: - # threads = multiprocessing.cpu_count() - threads = 1 - sampler = emcee.PTSampler( - ntemps, nwalkers, ndim, triangle_llh, lnprior, threads=threads - ) - - if RUN_SCAN: - print "Running burn-in" - for result in tqdm.tqdm(sampler.sample(p0, iterations=burnin), total=burnin): - pos, prob, state = result - sampler.reset() - print "Finished burn-in" - - nsteps = args.nsteps - - print "Running" - for _ in tqdm.tqdm(sampler.sample(pos, iterations=nsteps), total=nsteps): - pass - print "Finished" - - if FIX_SFR: - if FIX_MIXING: - outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_sfr_{4:03d}_{5:03d}_{6:03d}_DIM{7}_fix_mixing'.format( - int(MEASURED_FR[0]*100), int(MEASURED_FR[1]*100), int(MEASURED_FR[2]*100), int(SIGMA*1000), - int(SOURCE_FR[0]*100), int(SOURCE_FR[1]*100), int(SOURCE_FR[2]*100), DIMENSION - ) - elif FIX_SCALE: - outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_sfr_{4:03d}_{5:03d}_{6:03d}_DIM{7}_fixed_scale_{8}'.format( - int(MEASURED_FR[0]*100), int(MEASURED_FR[1]*100), int(MEASURED_FR[2]*100), int(SIGMA*1000), - int(SOURCE_FR[0]*100), int(SOURCE_FR[1]*100), int(SOURCE_FR[2]*100), DIMENSION, SCALE - ) - else: - outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_sfr_{4:03d}_{5:03d}_{6:03d}_DIM{7}_single_scale'.format( - int(MEASURED_FR[0]*100), int(MEASURED_FR[1]*100), int(MEASURED_FR[2]*100), int(SIGMA*1000), - int(SOURCE_FR[0]*100), int(SOURCE_FR[1]*100), int(SOURCE_FR[2]*100), DIMENSION - ) - else: - if FIX_MIXING: - outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_DIM{4}_fix_mixing'.format( - int(MEASURED_FR[0]*100), int(MEASURED_FR[1]*100), int(MEASURED_FR[2]*100), - int(SIGMA*1000), DIMENSION - ) - elif FIX_SCALE: - outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_DIM{4}_fixed_scale_{5}'.format( - int(MEASURED_FR[0]*100), int(MEASURED_FR[1]*100), int(MEASURED_FR[2]*100), - int(SIGMA*1000), DIMENSION, SCALE - ) - else: - outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_DIM{4}'.format( - int(MEASURED_FR[0]*100), int(MEASURED_FR[1]*100), int(MEASURED_FR[2]*100), - int(SIGMA*1000), DIMENSION - ) - - if RUN_SCAN: - samples = sampler.chain[0, :, :, :].reshape((-1, ndim)) - print 'acceptance fraction', sampler.acceptance_fraction - print 'sum of acceptance fraction', np.sum(sampler.acceptance_fraction) - print 'np.unique(samples[:,0]).shape', np.unique(samples[:,0]).shape - - try: - print 'autocorrelation', sampler.acor - except: - print 'WARNING : NEED TO RUN MORE SAMPLES FOR FILE ' + outfile - - if FLAT: - outfile += '_flat' - - if RUN_SCAN: - try: - os.makedirs(outfile[:-len(os.path.basename(outfile))]) - except OSError as exc: # Python >2.5 - if exc.errno == errno.EEXIST and os.path.isdir(outfile[:-len(os.path.basename(outfile))]): - pass - else: - raise - np.save(outfile+'.npy', samples) - - print "Making triangle plots" - chainer_plot.plot( - infile=outfile+'.npy', - angles=True, - outfile=outfile[:5]+outfile[5:].replace('data', 'plots')+'_angles.pgf', - measured_ratio=MEASURED_FR, - sigma_ratio=SIGMA, - fix_sfr=FIX_SFR, - fix_mixing=FIX_MIXING, - fix_scale=FIX_SCALE, - source_ratio=SOURCE_FR, - scale=SCALE, - dimension=DIMENSION, - energy=ENERGY, - scale_bounds=SCALE2_BOUNDS, - ) - # chainer_plot.plot( - # infile=outfile+'.npy', - # angles=False, - # outfile=outfile[:5]+outfile[5:].replace('data', 'plots')+'.pdf', - # measured_ratio=MEASURED_FR, - # sigma_ratio=SIGMA, - # fix_sfr=FIX_SFR, - # fix_mixing=FIX_MIXING, - # fix_scale=FIX_SCALE, - # source_ratio=SOURCE_FR, - # scale=SCALE, - # dimension=DIMENSION, - # energy=ENERGY, - # scale_bounds=SCALE2_BOUNDS, - # ) - print "DONE!" - - -main.__doc__ = __doc__ - - -if __name__ == '__main__': - main() - diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/enums.py b/utils/enums.py new file mode 100644 index 0000000..31885de --- /dev/null +++ b/utils/enums.py @@ -0,0 +1,68 @@ +# author : S. Mandalia +# s.p.mandalia@qmul.ac.uk +# +# date : March 17, 2018 + +""" +Define Enums for the BSM flavour ratio analysis +""" + +from enum import Enum + + +class DataType(Enum): + REAL = 1 + FAKE = 2 + ASMIMOV = 3 + + +class FitCateg(Enum): + HESESPL = 1 + HESEDPL = 2 + ZPSPL = 3 + ZPDPL = 4 + NUNUBAR2 = 5 + + +class FitFlagCateg(Enum): + DEFAULT = 1 + XS = 2 + + +class FitPriorsCateg(Enum): + DEFAULT = 1 + XS = 2 + + +class Likelihood(Enum): + FLAT = 1 + GAUSSIAN = 2 + GOLEMFIT = 3 + + +class Priors(Enum): + UNIFORM = 1 + LOGUNIFORM = 2 + JEFFREYS = 3 + + +class SteeringCateg(Enum): + BASELINE = 1 + HOLEICE = 2 + ABSORPTION = 3 + SCATTERING = 4 + SCATTERING_ABSORPTION = 5 + STD = 6 + STD_HALF1 = 7 + STD_HALF2 = 8 + LONGLIFE = 9 + DPL = 10 + + +class XSCateg(Enum): + HALF = 1 + NOM = 2 + TWICE = 3 + TWICE01 = 4 + TWICE02 = 5 + diff --git a/utils/fr.py b/utils/fr.py new file mode 100644 index 0000000..ddcb5d2 --- /dev/null +++ b/utils/fr.py @@ -0,0 +1,353 @@ +# author : S. Mandalia +# s.p.mandalia@qmul.ac.uk +# +# date : March 17, 2018 + +""" +Useful functions for the BSM flavour ratio analysis +""" + +from __future__ import absolute_import, division + +import sys + +import numpy as np +from scipy import linalg + + +MASS_EIGENVALUES = [7.40E-23, 2.515E-21] +"""SM mass eigenvalues""" + + +def angles_to_fr(src_angles): + """Convert angular projection of the source flavour ratio back into the + flavour ratio. + + Parameters + ---------- + src_angles : list, length = 2 + sin(phi)^4 and cos(psi)^2 + + Returns + ---------- + flavour ratios (nue, numu, nutau) + + Examples + ---------- + >>> print angles_to_fr((0.3, 0.4)) + (0.38340579025361626, 0.16431676725154978, 0.45227744249483393) + + """ + sphi4, c2psi = src_angles + + psi = (0.5)*np.arccos(c2psi) + + sphi2 = np.sqrt(sphi4) + cphi2 = 1. - sphi2 + spsi2 = np.sin(psi)**2 + cspi2 = 1. - spsi2 + + x = sphi2*cspi2 + y = sphi2*spsi2 + z = cphi2 + return x, y, z + + +def angles_to_u(bsm_angles): + """Convert angular projection of the mixing matrix elements back into the + mixing matrix elements. + + Parameters + ---------- + bsm_angles : list, length = 4 + sin(12)^2, cos(13)^4, sin(23)^2 and deltacp + + Returns + ---------- + unitary numpy ndarray of shape (3, 3) + + Examples + ---------- + >>> from fr import angles_to_u + >>> print angles_to_u((0.2, 0.3, 0.5, 1.5)) + array([[ 0.66195018+0.j , 0.33097509+0.j , 0.04757188-0.6708311j ], + [-0.34631487-0.42427084j, 0.61741198-0.21213542j, 0.52331757+0.j ], + [ 0.28614067-0.42427084j, -0.64749908-0.21213542j, 0.52331757+0.j ]]) + + """ + s12_2, c13_4, s23_2, dcp = bsm_angles + dcp = np.complex128(dcp) + + c12_2 = 1. - s12_2 + c13_2 = np.sqrt(c13_4) + s13_2 = 1. - c13_2 + c23_2 = 1. - s23_2 + + t12 = np.arcsin(np.sqrt(s12_2)) + t13 = np.arccos(np.sqrt(c13_2)) + t23 = np.arcsin(np.sqrt(s23_2)) + + c12 = np.cos(t12) + s12 = np.sin(t12) + c13 = np.cos(t13) + s13 = np.sin(t13) + c23 = np.cos(t23) + s23 = np.sin(t23) + + p1 = np.array([[1 , 0 , 0] , [0 , c23 , s23] , [0 , -s23 , c23]] , dtype=np.complex128) + p2 = np.array([[c13 , 0 , s13*np.exp(-1j*dcp)] , [0 , 1 , 0] , [-s13*np.exp(1j*dcp) , 0 , c13]] , dtype=np.complex128) + p3 = np.array([[c12 , s12 , 0] , [-s12 , c12 , 0] , [0 , 0 , 1]] , dtype=np.complex128) + + u = np.dot(np.dot(p1, p2), p3) + return u + + +def cardano_eqn(ham): + """Diagonalise the effective Hamiltonian 3x3 matrix into the form + h_{eff} = UE_{eff}U^{dagger} using the procedure in PRD91, 052003 (2015). + + Parameters + ---------- + ham : numpy ndarray of shape (3, 3) + sin(12)^2, cos(13)^4, sin(23)^2 and deltacp + + Returns + ---------- + unitary numpy ndarray of shape (3, 3) + + Examples + ---------- + >>> import numpy as np + >>> from fr import cardano_eqn + >>> ham = np.array( + >>> [[ 0.66195018+0.j , 0.33097509+0.j , 0.04757188-0.6708311j ], + >>> [-0.34631487-0.42427084j, 0.61741198-0.21213542j, 0.52331757+0.j ], + >>> [ 0.28614067-0.42427084j, -0.64749908-0.21213542j, 0.52331757+0.j ]] + >>> ) + >>> print cardano_eqn(ham) + array([[-0.11143379-0.58863683j, -0.09067747-0.48219068j, 0.34276625-0.08686465j], + [ 0.14835519+0.47511473j, -0.18299305+0.40777481j, 0.31906300+0.82514223j], + [-0.62298966+0.07231745j, -0.61407815-0.42709603j, 0.03660313+0.30160428j]]) + + """ + if np.shape(ham) != (3, 3): + raise ValueError( + 'Input matrix should be a square and dimension 3, ' + 'got\n{0}'.format(ham) + ) + + a = -np.trace(ham) + b = (0.5) * ((np.trace(ham))**2 - np.trace(np.dot(ham, ham))) + c = -linalg.det(ham) + + Q = (1/9.) * (a**2 - 3*b) + R = (1/54.) * (2*a**3 - 9*a*b + 27*c) + theta = np.arccos(R / np.sqrt(Q**3)) + + E1 = -2 * np.sqrt(Q) * np.cos(theta/3.) - (1/3.)*a + E2 = -2 * np.sqrt(Q) * np.cos((theta - 2.*np.pi)/3.) - (1/3.)*a + E3 = -2 * np.sqrt(Q) * np.cos((theta + 2.*np.pi)/3.) - (1/3.)*a + + A1 = ham[1][2] * (ham[0][0] - E1) - ham[1][0]*ham[0][2] + A2 = ham[1][2] * (ham[0][0] - E2) - ham[1][0]*ham[0][2] + A3 = ham[1][2] * (ham[0][0] - E3) - ham[1][0]*ham[0][2] + + B1 = ham[2][0] * (ham[1][1] - E1) - ham[2][1]*ham[1][0] + B2 = ham[2][0] * (ham[1][1] - E2) - ham[2][1]*ham[1][0] + B3 = ham[2][0] * (ham[1][1] - E3) - ham[2][1]*ham[1][0] + + C1 = ham[1][0] * (ham[2][2] - E1) - ham[1][2]*ham[2][0] + C2 = ham[1][0] * (ham[2][2] - E2) - ham[1][2]*ham[2][0] + C3 = ham[1][0] * (ham[2][2] - E3) - ham[1][2]*ham[2][0] + + N1 = np.sqrt(abs(A1*B1)**2 + abs(A1*C1)**2 + abs(B1*C1)**2) + N2 = np.sqrt(abs(A2*B2)**2 + abs(A2*C2)**2 + abs(B2*C2)**2) + N3 = np.sqrt(abs(A3*B3)**2 + abs(A3*C3)**2 + abs(B3*C3)**2) + + mm = np.array([ + [np.conjugate(B1)*C1 / N1, np.conjugate(B2)*C2 / N2, np.conjugate(B3)*C3 / N3], + [A1*C1 / N1, A2*C2 / N2, A3*C3 / N3], + [A1*B1 / N1, A2*B2 / N2, A3*B3 / N3] + ]) + return mm + + +def normalise_fr(fr): + """Normalise an input flavour combination to a flavour ratio. + + Parameters + ---------- + fr : list, length = 3 + flavour combination + + Returns + ---------- + numpy ndarray flavour ratio + + Examples + ---------- + >>> from fr import normalise_fr + >>> print normalise_fr((1, 2, 3)) + array([ 0.16666667, 0.33333333, 0.5 ]) + + """ + return np.array(fr) / float(np.sum(fr)) + + +NUFIT_U = angles_to_u((0.307, (1-0.02195)**2, 0.565, 3.97935)) +"""NuFIT mixing matrix (s_12^2, c_13^4, s_23^2, dcp)""" + + +def params_to_BSMu(theta, dim, energy, mass_eigenvalues=MASS_EIGENVALUES, + nufit_u=NUFIT_U, no_bsm=False, fix_mixing=False, + fix_scale=False, scale=None, check_uni=True): + """Construct the BSM mixing matrix from the BSM parameters. + + Parameters + ---------- + theta : list, length > 3 + BSM parameters + + dim : int + Dimension of BSM physics + + energy : float + Energy in GeV + + mass_eigenvalues : list, length = 2 + SM mass eigenvalues + + nufit_u : numpy ndarray, dimension 3 + SM NuFIT mixing matrix + + no_bsm : bool + Turn off BSM behaviour + + fix_mixing : bool + Fix the BSM mixing angles + + fix_scale : bool + Fix the BSM scale + + scale : float + Used with fix_scale - scale at which to fix + + check_uni : bool + Check the resulting BSM mixing matrix is unitary + + Returns + ---------- + unitary numpy ndarray of shape (3, 3) + + Examples + ---------- + >>> from fr import params_to_BSMu + >>> print params_to_BSMu((0.2, 0.3, 0.5, 1.5, -20), dim=3, energy=1000) + array([[ 0.18658169 -6.34190523e-01j, -0.26460391 +2.01884200e-01j, 0.67247096 -9.86808417e-07j], + [-0.50419832 +2.14420570e-01j, -0.36013768 +5.44254868e-01j, 0.03700961 +5.22039894e-01j], + [-0.32561308 -3.95946524e-01j, 0.64294909 -2.23453580e-01j, 0.03700830 +5.22032403e-01j]]) + + """ + if np.shape(nufit_u) != (3, 3): + raise ValueError( + 'Input matrix should be a square and dimension 3, ' + 'got\n{0}'.format(ham) + ) + + if fix_mixing: + s12_2, c13_4, s23_2, dcp, sc2 = 0.5, 1.0-1E-6, 0.5, 0., theta + elif fix_scale: + s12_2, c13_4, s23_2, dcp = theta + sc2 = np.log10(scale) + else: + s12_2, c13_4, s23_2, dcp, sc2 = theta + sc2 = np.power(10., sc2) + sc1 = sc2 / 100. + + mass_matrix = np.array( + [[0, 0, 0], [0, mass_eigenvalues[0], 0], [0, 0, mass_eigenvalues[1]]] + ) + sm_ham = (1./(2*energy))*np.dot(nufit_u, np.dot(mass_matrix, nufit_u.conj().T)) + if no_bsm: + eg_vector = cardano_eqn(sm_ham) + else: + new_physics_u = angles_to_u((s12_2, c13_4, s23_2, dcp)) + scale_matrix = np.array( + [[0, 0, 0], [0, sc1, 0], [0, 0, sc2]] + ) + bsm_term = (energy**(dim-3)) * np.dot(new_physics_u, np.dot(scale_matrix, new_physics_u.conj().T)) + + bsm_ham = sm_ham + bsm_term + eg_vector = cardano_eqn(bsm_ham) + + if check_uni: + tu = test_unitarity(eg_vector) + if not abs(np.trace(tu) - 3.) < 1e-5 or \ + not abs(np.sum(tu) - 3.) < 1e-5: + raise AssertionError( + 'Matrix is not unitary!\neg_vector\n{0}\ntest ' + 'u\n{1}'.format(eg_vector, tu) + ) + return eg_vector + + +def test_unitarity(x, prnt=False): + """Test the unitarity of a matrix. + + Parameters + ---------- + x : numpy ndarray + Matrix to evaluate + + prnt : bool + Print the result + + Returns + ---------- + numpy ndarray + + Examples + ---------- + >>> from fr import test_unitarity + >>> x = np.identity(3) + >>> print test_unitarity(x) + array([[ 1., 0., 0.], + [ 0., 1., 0.], + [ 0., 0., 1.]]) + + """ + f = abs(np.dot(x, x.conj().T)) + if prnt: + print 'Unitarity test:\n{0}'.format(f) + return f + + +def u_to_fr(source_fr, matrix): + """Compute the observed flavour ratio assuming decoherence. + + Parameters + ---------- + source_fr : list, length = 3 + Source flavour ratio components + + matrix : numpy ndarray, dimension 3 + Mixing matrix + + Returns + ---------- + Measured flavour ratio + + Examples + ---------- + >>> from fr import params_to_BSMu, u_to_fr + >>> print u_to_fr((1, 2, 0), params_to_BSMu((0.2, 0.3, 0.5, 1.5, -20), 3, 1000)) + array([ 0.33740075, 0.33176584, 0.33083341]) + + """ + # TODO(shivesh): energy dependence + composition = np.einsum( + 'ai, bi, a -> b', abs(matrix)**2, abs(matrix)**2, source_fr + ) + ratio = composition / np.sum(source_fr) + return ratio + diff --git a/utils/gf.py b/utils/gf.py new file mode 100644 index 0000000..766c161 --- /dev/null +++ b/utils/gf.py @@ -0,0 +1,105 @@ +# author : S. Mandalia +# s.p.mandalia@qmul.ac.uk +# +# date : March 17, 2018 + +""" +Useful GolemFit wrappers for the BSM flavour ratio analysis +""" + +from __future__ import absolute_import, division + +import argparse +from functools import partial + +import GolemFitPy as gf + +from utils.enums import * +from utils.misc import enum_keys, enum_parse + + +def data_distributions(fitter): + hdat = fitter.GetDataDistribution() + binedges = np.asarray([fitter.GetZenithBinsData(), fitter.GetEnergyBinsData()]) + return hdat, binedges + + +def fit_flags(fitflag_categ): + flags = gf.FitParametersFlag() + if fitflag_categ is FitFlagCateg.xs: + # False means it's not fixed in minimization + flags.NeutrinoAntineutrinoRatio = False + return flags + + +def fit_params(fit_categ): + params = gf.FitParameters() + params.astroNorm = 7.5 + params.astroDeltaGamma = 0.9 + if fit_categ is FitCateg.hesespl: + params.astroNormSec = 0 + elif fit_categ is FitCateg.hesedpl: + params.astroNormSec=7.0 + elif fit_categ is FitCateg.zpspl: + # zero prompt, single powerlaw + params.promptNorm = 0 + params.astroNormSec = 0 + elif fit_categ is FitCateg.zpdpl: + # zero prompt, double powerlaw + params.promptNorm = 0 + params.astroNormSec=7.0 + elif fit_categ is FitCateg.nunubar2: + params.NeutrinoAntineutrinoRatio = 2 + + +def fit_priors(fitpriors_categ): + priors = gf.Priors() + if fitpriors_categ == FitPriorsCateg.xs: + priors.promptNormCenter = 1 + priors.promptNormWidth = 3 + priors.astroDeltaGammaCenter = 0 + priors.astroDeltaGammaWidth = 1 + return priors + + +def gen_steering_params(steering_categ, quiet=False): + params = gf.SteeringParams() + if quiet: params.quiet = True + params.fastmode = False + params.do_HESE_reshuffle = False + params.numc_tag = steering_categ.name + params.baseline_astro_spectral_index = -2. + if steering_categ is SteeringCateg.LONGLIFE: + params.years = [999] + params.numc_tag = 'std_half1' + if steering_categ is SteeringCateg.DPL: + params.diffuse_fit_type = gf.DiffuseFitType.DoublePowerLaw + params.numc_tag = 'std_half1' + return params + + +def gf_argparse(parser): + parser.add_argument( + '--data', default='real', type=partial(enum_parse, c=DataType), + choices=enum_keys(DataType), help='select datatype' + ) + parser.add_argument( + '--ast', default='baseline', type=partial(enum_parse, c=SteeringCateg), + choices=enum_keys(SteeringCateg), + help='use asimov/fake dataset with specific steering' + ) + parser.add_argument( + '--aft', default='hesespl', type=partial(enum_parse, c=FitCateg), + choices=enum_keys(FitCateg), + help='use asimov/fake dataset with specific Fit' + ) + parser.add_argument( + '--axs', default='nom', type=partial(enum_parse, c=XSCateg), + choices=enum_keys(XSCateg), + help='use asimov/fake dataset with xs scaling' + ) + parser.add_argument( + '--priors', default='uniform', type=partial(enum_parse, c=Priors), + choices=enum_keys(Priors), help='Bayesian priors' + ) + diff --git a/utils/mcmc.py b/utils/mcmc.py new file mode 100644 index 0000000..d91764f --- /dev/null +++ b/utils/mcmc.py @@ -0,0 +1,167 @@ +# author : S. Mandalia +# s.p.mandalia@qmul.ac.uk +# +# date : March 17, 2018 + +""" +Useful functions to use an MCMC for the BSM flavour ratio analysis +""" + +from __future__ import absolute_import, division + +import os +import errno + +import argparse +from functools import partial + +import emcee +import tqdm + +import numpy as np +from scipy.stats import multivariate_normal + +from utils import fr as fr_utils +from utils.enums import Likelihood +from utils.misc import enum_keys, enum_parse, parse_bool + + +def lnprior(theta, paramset): + """Priors on theta.""" + ranges = paramset.ranges + for value, range in zip(theta, ranges): + if range[0] <= value <= range[1]: + pass + else: return -np.inf + return 0. + + +def mcmc(p0, triangle_llh, lnprior, ndim, nwalkers, burnin, nsteps, ntemps=1, threads=1): + """Run the MCMC.""" + sampler = emcee.PTSampler( + ntemps, nwalkers, ndim, triangle_llh, lnprior, threads=threads + ) + + print "Running burn-in" + for result in tqdm.tqdm(sampler.sample(p0, iterations=burnin), total=burnin): + pos, prob, state = result + sampler.reset() + print "Finished burn-in" + + print "Running" + for _ in tqdm.tqdm(sampler.sample(pos, iterations=nsteps), total=nsteps): + pass + print "Finished" + + samples = sampler.chain[0, :, :, :].reshape((-1, ndim)) + print 'acceptance fraction', sampler.acceptance_fraction + print 'sum of acceptance fraction', np.sum(sampler.acceptance_fraction) + print 'np.unique(samples[:,0]).shape', np.unique(samples[:,0]).shape + try: + print 'autocorrelation', sampler.acor + except: + print 'WARNING : NEED TO RUN MORE SAMPLES' + + return samples + + +def mcmc_argparse(parser): + parser.add_argument( + '--likelihood', default='gaussian', type=partial(enum_parse, c=Likelihood), + choices=enum_keys(Likelihood), help='likelihood contour' + ) + parser.add_argument( + '--run-mcmc', type=parse_bool, default='True', + help='Run the MCMC' + ) + parser.add_argument( + '--burnin', type=int, default=100, + help='Amount to burnin' + ) + parser.add_argument( + '--nwalkers', type=int, default=100, + help='Number of walkers' + ) + parser.add_argument( + '--nsteps', type=int, default=2000, + help='Number of steps to run' + ) + + +def gaussian_llh(fr, fr_bf, sigma): + """Multivariate gaussian likelihood.""" + cov_fr = np.identity(3) * sigma + return np.log(multivariate_normal.pdf(fr, mean=fr_bf, cov=cov_fr)) + + +def gaussian_seed(paramset, ntemps, nwalkers): + """Get gaussian seed values for the MCMC.""" + ndim = len(paramset) + p0 = np.random.normal( + paramset.values, paramset.stds, size=[ntemps, nwalkers, ndim] + ) + return p0 + + +def save_chains(chains, outfile): + """Save the chains. + + Parameters + ---------- + chains : numpy ndarray + MCMC chains to save + + outfile : str + Output file location of chains + + """ + try: + os.makedirs(outfile[:-len(os.path.basename(outfile))]) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(outfile[:-len(os.path.basename(outfile))]): + pass + else: + raise + print 'Saving chains to location {0}'.format(outfile+'.npy') + np.save(outfile+'.npy', chains) + + +def triangle_llh(theta, args): + """-Log likelihood function for a given theta.""" + if args.fix_source_ratio: + fr1, fr2, fr3 = args.source_ratio + bsm_angles = theta[-5:] + else: + fr1, fr2, fr3 = fr_utils.angles_to_fr(theta[-2:]) + bsm_angles = theta[-7:-2] + + u = fr_utils.params_to_BSMu( + theta = bsm_angles, + dim = args.dimension, + energy = args.energy, + no_bsm = args.no_bsm, + fix_mixing = args.fix_mixing, + fix_scale = args.fix_scale, + scale = args.scale + ) + + fr = fr_utils.u_to_fr((fr1, fr2, fr3), u) + fr_bf = args.measured_ratio + if args.likelihood is Likelihood.FLAT: + return 1. + elif args.likelihood is Likelihood.GAUSSIAN: + return gaussian_llh(fr, fr_bf, args.sigma_ratio) + elif args.likelihood is Likelihood.GOLEMFIT: + raise NotImplementedError('TODO') + import GolemFitPy as gf + from collections import namedtuple + datapaths = gf.DataPaths() + IceModels = namedtuple('IceModels', 'std_half2') + fitters = IceModels(*[ + gf.GolemFit(datapaths, + gf.gen_steering_params(SteeringCateg.__members__[ice]), + xs_params(XSCateg.nom)) for ice in IceModels._fields]) + for fitter in fitters: + fitter.SetFitParametersFlag(fit_flags(FitFlagCateg.xs)) + fitter.SetFitPriors(fit_priors(FitPriorsCateg.xs)) + diff --git a/utils/misc.py b/utils/misc.py new file mode 100644 index 0000000..5c3eb2e --- /dev/null +++ b/utils/misc.py @@ -0,0 +1,230 @@ +# author : S. Mandalia +# s.p.mandalia@qmul.ac.uk +# +# date : March 17, 2018 + +""" +Misc functions for the BSM flavour ratio analysis +""" + +from __future__ import absolute_import, division + +import os +import errno +from collections import Sequence +import multiprocessing + +import numpy as np + +from utils.enums import Likelihood + + +class Param(object): + """Parameter class to store parameters. + """ + def __init__(self, name, value, ranges, std=None, tex=None): + self._ranges = None + self._tex = None + + self.name = name + self.value = value + self.ranges = ranges + self.std = std + self.tex = tex + + @property + def ranges(self): + return tuple(self._ranges) + + @ranges.setter + def ranges(self, values): + self._ranges = [val for val in values] + + @property + def tex(self): + return r'{0}'.format(self.tex) + + @tex.setter + def tex(self, t): + self._tex = t if t is not None else r'{\rm %s}' % self.name + + +class ParamSet(Sequence): + """Container class for a set of parameters. + """ + def __init__(self, *args): + param_sequence = [] + for arg in args: + try: + param_sequence.extend(arg) + except TypeError: + param_sequence.append(arg) + + # Disallow duplicated params + all_names = [p.name for p in param_sequence] + unique_names = set(all_names) + if len(unique_names) != len(all_names): + duplicates = set([x for x in all_names if all_names.count(x) > 1]) + raise ValueError('Duplicate definitions found for param(s): ' + + ', '.join(str(e) for e in duplicates)) + + # Elements of list must be Param type + assert all([isinstance(x, Param) for x in param_sequence]), \ + 'All params must be of type "Param"' + + self._params = param_sequence + + def __len__(self): + return len(self._params) + + def __getitem__(self, i): + if isinstance(i, int): + return self._params[i] + elif isinstance(i, basestring): + return self._by_name[i] + + def __getattr__(self, attr): + try: + return super(ParamSet, self).__getattribute__(attr) + except AttributeError: + t, v, tb = sys.exc_info() + try: + return self[attr] + except KeyError: + raise t, v, tb + + def __iter__(self): + return iter(self._params) + + @property + def _by_name(self): + return {obj.name: obj for obj in self._params} + + @property + def names(self): + return tuple([obj.name for obj in self._params]) + + @property + def values(self): + return tuple([obj.value for obj in self._params]) + + @property + def ranges(self): + return tuple([obj.ranges for obj in self._params]) + + @property + def stds(self): + return tuple([obj.std for obj in self._params]) + + @property + def params(self): + return self._params + + def to_dict(self): + return {obj.name: obj.value for obj in self._params} + + +def gen_outfile_name(args): + """Generate a name for the output file based on the input args. + + Parameters + ---------- + args : argparse + argparse object to print + + """ + mr = args.measured_ratio + si = args.sigma_ratio + if args.fix_source_ratio: + sr = args.source_ratio + if args.fix_mixing: + outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_sfr_{4:03d}_{5:03d}_{6:03d}_DIM{7}_fix_mixing'.format( + int(mr[0]*100), int(mr[1]*100), int(mr[2]*100), int(si*1000), + int(sr[0]*100), int(sr[1]*100), int(sr[2]*100), args.dimension + ) + elif args.fix_scale: + outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_sfr_{4:03d}_{5:03d}_{6:03d}_DIM{7}_fixed_scale_{8}'.format( + int(mr[0]*100), int(mr[1]*100), int(mr[2]*100), int(si*1000), + int(sr[0]*100), int(sr[1]*100), int(sr[2]*100), args.dimension, + args.scale + ) + else: + outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_sfr_{4:03d}_{5:03d}_{6:03d}_DIM{7}_single_scale'.format( + int(mr[0]*100), int(mr[1]*100), int(mr[2]*100), int(si*1000), + int(sr[0]*100), int(sr[1]*100), int(sr[2]*100), args.dimension + ) + else: + if args.fix_mixing: + outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_DIM{4}_fix_mixing'.format( + int(mr[0]*100), int(mr[1]*100), int(mr[2]*100), + int(si*1000), args.dimension + ) + elif args.fix_scale: + outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_DIM{4}_fixed_scale_{5}'.format( + int(mr[0]*100), int(mr[1]*100), int(mr[2]*100), + int(si*1000), args.dimension, args.scale + ) + else: + outfile = args.outfile+'_{0:03d}_{1:03d}_{2:03d}_{3:04d}_DIM{4}'.format( + int(mr[0]*100), int(mr[1]*100), int(mr[2]*100), + int(si*1000), args.dimension + ) + if args.likelihood is Likelihood.FLAT: outfile += '_flat' + return outfile + + +def parse_bool(s): + """Parse a string to a boolean. + + Parameters + ---------- + s : str + String to parse + + Returns + ---------- + bool + + Examples + ---------- + >>> from misc import parse_bool + >>> print parse_bool('true') + True + + """ + if s.lower() == 'true': + return True + elif s.lower() == 'false': + return False + else: + raise ValueError + + +def print_args(args): + """Print the input arguments. + + Parameters + ---------- + args : argparse + argparse object to print + + """ + arg_vars = vars(args) + for key in arg_vars.iterkeys(): + print '== {0:<25} = {1}'.format(key, arg_vars[key]) + + +def enum_keys(e): + return e.__members__.keys() + + +def enum_parse(s, c): + return c[s.upper()] + + +def thread_type(t): + if t.lower() == 'max': + return multiprocessing.cpu_count() + else: + return int(t) + -- cgit v1.2.3