Source code for modelfitting.mixingcalculations

import numpy as np


[docs]def calculate_quark_observables(mass_matrix_u=np.identity(3), mass_matrix_d=np.identity(3), parameterization='standard') -> dict: """ Calculates the quark observables of up and down-type mass matrices. :param mass_matrix_u: The up-type quark mass matrix M, for Phi_left M Phi_right. :type mass_matrix_u: 3x3 matrix :param mass_matrix_d: The down-type quark mass matrix M, for Phi_left M Phi_right. :type mass_matrix_d: 3x3 matrix :param parameterization: Specify whether you want the result in standard or wolfenstein parametrization. Has to be either \'standard\' or \'wolfenstein\'. :type parameterization: str, default:\'standard\' :return: dict Contains the standard or wolfenstein parameters as well as the quark mass ratios. """ # Singular Value decomposition for digonalization Vul_tilde, mu, Vurh_tilde = np.linalg.svd(mass_matrix_u) # Mu = tVul * mu * tVurh Vdl_tilde, md, Vdrh_tilde = np.linalg.svd(mass_matrix_d) # correct the fact that svd sorts its singular values descending permut = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) mu, md = np.dot(permut, mu), np.dot(permut, md) Vul, Vdl = np.dot(permut, np.linalg.inv(Vul_tilde)), np.dot(permut, np.linalg.inv(Vdl_tilde)) # construct CKM Matrix and calculate its parameterization CKM = np.dot(Vul, Vdl.conj().T) if parameterization == 'wolfenstein': CKM_p = get_wolfenstein_parameters(CKM) elif parameterization == 'standard': CKM_p = get_standard_parameters_ckm(CKM) else: raise NotImplementedError('''The value of \'parameterization\' has to be either \'wolfenstein\' or \'standard\'.''') return {'mu/mc': mu[0] / mu[1], 'mc/mt': mu[1] / mu[2], 'md/ms': md[0] / md[1], 'ms/mb': md[1] / md[2], **CKM_p}
[docs]def calculate_lepton_dimensionless_observables(mass_matrix_e=np.identity(3), mass_matrix_n=np.identity(3), ordering='NO') -> dict: """ Calculates the dimensionless observables of a charged lepton and neutrino mass matrix. :param mass_matrix_e: The charged lepton mass matrix M, for Phi_left M Phi_right, where left and right indicates left- and right-handed chiral fields, respectively. I.e. L_i^c M_ij e_j, where L refers to the left-handed lepton doublet and e is the right-handed charged lepton field, and i,j=1,2,3. If you use the other convention, i.e. left-handed fields on the right-hand-side, simply transpose your mass matrix. :type mass_matrix_e: 3x3 matrix :param mass_matrix_n: The neutrino mass matrix M, for Phi_left M Phi_right. :type mass_matrix_n: 3x3 matrix :param ordering: Specify whether the neutrino spectrum is normal or inverted ordered. Has to be either \'NO\' or \'IO\'. Default is \'NO\'. :type ordering: str :return: Contains the PMNS-parameters and the charged lepton mass matrices. It also contains wrongly scaled neutrino masses, that are needed for the function \'calculate_lepton_observables\'. :rtype: dict """ # Singular Value decomposition for digonalization. There are also some takagi decompositions in the utils.py file. tVel, me, tVerh = np.linalg.svd(mass_matrix_e) # tVel * diag(me) * tVerh = Me tVnl, mn, tVnrh = np.linalg.svd(mass_matrix_n) # tVnl * diag(mn) * tVnrh = Mn # correct the fact that svd sorts its singular values (=physical masses) in descending order. # Idea: one could add some code here that automatically detects, whether the spectrum is normal or inverted ordered # by checking if mn[0]-mn[1] >< mn[1]-mn[2]. The ordering would then be an observable. I don't know if this # would be useful for fitting, because the experimental data is specific for normal or inverted ordering. # But maybe one could take the absolute value of m21^2/mel^2 and simply fit to the NO data to get a rough # estimate because the exp data for NO and IO are very similar. # Or the experimental data set simply needs to contain both, the data for NO and for IO. permutE = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) if ordering == 'NO': permutN = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) elif ordering == 'IO': permutN = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) else: raise NotImplementedError('''The value of \'ordering\' has to be either \'NO\' or \'IO\'.''') me, mn = np.dot(permutE, me), np.dot(permutN, mn) Vel, Vnl = np.dot(permutE, np.linalg.inv(tVel)), np.dot(permutN, np.linalg.inv(tVnl)) # construct PMNS Matrix and calculate its parameterization PMNS = np.dot(Vel.conj(), Vnl.T) # PMNS = np.dot( Vel.conj().T, Vnl) pmns_parameters = get_standard_parameters_pmns(PMNS) # calculate neutrino mass-squared-differences. m21sq = np.power(mn[1], 2) - np.power(mn[0], 2) if ordering == 'NO': m3lsq = np.power(mn[2], 2) - np.power(mn[0], 2) elif ordering == 'IO': m3lsq = np.power(mn[2], 2) - np.power(mn[1], 2) else: raise NotImplementedError('''The value of \'ordering\' has to be either \'NO\' or \'IO\'.''') return {'me/mu': me[0] / me[1], 'mu/mt': me[1] / me[2], 'r': m21sq / m3lsq, **pmns_parameters, 'm1_wrong_scaled': mn[0], 'm2_wrong_scaled': mn[1], 'm3_wrong_scaled': mn[2], 'm21^2_wrong_scaled': m21sq, 'm3l^2_wrong_scaled': m3lsq}
[docs]def calculate_lepton_observables(mass_matrix_e=np.identity(3), mass_matrix_n=np.identity(3), ordering='NO', m21sq_best=None, m3lsq_best=None) -> dict: """ Calculates the observables of the lepton sector from charged lepton and neutrino mass matrices. :param mass_matrix_e: The charged lepton mass matrix M, for Phi_left M Phi_right, where left and right indicates left- and right-handed chiral fields, respectively. I.e. L_i^c M_ij e_j, where L refers to the left-handed lepton doublet and e is the right-handed charged lepton field, and i,j=1,2,3. If you use the other convention, i.e. left-handed fields on the right-hand-side, simply transpose your mass matrix. :type mass_matrix_e: 3x3 matrix :param mass_matrix_n: The neutrino mass matrix M, for Phi_left M Phi_right. :type mass_matrix_n: 3x3 matrix :param ordering: Specify whether the neutrino spectrum is normal or inverted ordered. Has to be either \'NO\' or \'IO\'. Default is \'NO\'. :type ordering: str :param m21sq_best: The best fit value for the squared neutrino mass difference m_1^2 - m_2^2. Default is None, which will yield 7.41e-05. :type m21sq_best: float :param m3lsq_best: The best fit value for the squared neutrino mass difference m_3^2 - m_l^2, where l=1 for NO and l=2 for IO. Default is None, yielding 2.507e-03 for NO and -2.486e-03 for IO. :type m3lsq_best: float :return: Contains the PMNS parameters as well as the neutrino masses and charged lepton mass ratios. :rtype: dict """ # Get dimensionless observables from calculate_lepton_dimensionless_observables() dimless_obs = calculate_lepton_dimensionless_observables(mass_matrix_e, mass_matrix_n, ordering) s12sq = dimless_obs['s12^2'] s13sq = dimless_obs['s13^2'] s23sq = dimless_obs['s23^2'] c12sq = np.cos(np.arcsin(np.sqrt(s12sq))) ** 2 c13sq = np.cos(np.arcsin(np.sqrt(s13sq))) ** 2 c23sq = np.cos(np.arcsin(np.sqrt(s23sq))) ** 2 eta1 = dimless_obs['eta1'] eta2 = dimless_obs['eta2'] d = dimless_obs['d/pi'] # Jarlskog Jmax = np.sqrt(c12sq * s12sq * c23sq * s23sq * s13sq) * c13sq J = Jmax * np.sin(d * np.pi) # Correctly scale neutrino masses if m21sq_best is None: m21sq_best = 7.41e-05 if m3lsq_best is None: if ordering == 'NO': m3lsq_best = 2.507e-03 elif ordering == 'IO': m3lsq_best = -2.486e-03 else: raise NotImplementedError('''The value of \'ordering\' has to be either \'NO\' or \'IO\'.''') mn = np.array([dimless_obs['m1_wrong_scaled'], dimless_obs['m2_wrong_scaled'], dimless_obs['m3_wrong_scaled']]) m21sq, m3lsq = dimless_obs['m21^2_wrong_scaled'], dimless_obs['m3l^2_wrong_scaled'] nscale = np.sqrt((m21sq_best / m21sq + m3lsq_best / m3lsq) / 2) mn = nscale * mn m21sq = nscale * nscale * m21sq m3lsq = nscale * nscale * m3lsq # Calculate effective neutrino mass for beta-decay and for neutrinoless double beta-decay. if ordering == 'NO': m_beta = np.sqrt(mn[0] ** 2 + m21sq * (1 - c13sq * c12sq) + m3lsq * s13sq) m_betabeta = np.abs(mn[0] * c12sq * c13sq + np.sqrt(m21sq + mn[0] ** 2) * s12sq * c13sq * np.exp(2j * np.pi * (eta2 - eta1)) + np.sqrt(m3lsq + m21sq + mn[0] ** 2) * s13sq * np.exp(-2j * np.pi * (d + eta1))) elif ordering == 'IO': m_beta = np.sqrt(mn[2] ** 2 + m21sq * c13sq * c12sq - m3lsq * c13sq) m_betabeta = np.abs(mn[2] * s13sq + np.sqrt(mn[2] ** 2 - m3lsq) * s12sq * c13sq * np.exp(2j * np.pi * (eta2 + d)) + np.sqrt(mn[2] ** 2 - m3lsq - m21sq) * c12sq * c13sq * np.exp(2j * np.pi * (eta1 + d))) else: raise NotImplementedError('''The value of \'ordering\' has to be either \'NO\' or \'IO\'.''') return {'me/mu': dimless_obs['me/mu'], 'mu/mt': dimless_obs['mu/mt'], 's12^2': dimless_obs['s12^2'], 's13^2': dimless_obs['s13^2'], 's23^2': dimless_obs['s23^2'], 'd/pi': dimless_obs['d/pi'], 'r': dimless_obs['r'], 'm21^2': m21sq, 'm3l^2': m3lsq, 'm1': mn[0], 'm2': mn[1], 'm3': mn[2], 'eta1': dimless_obs['eta1'], 'eta2': dimless_obs['eta2'], 'J': J, 'Jmax': Jmax, 'Sum(m_i)': np.sum(mn), 'm_b': m_beta, 'm_bb': m_betabeta, 'nscale': nscale}
[docs]def get_wolfenstein_parameters(CKM) -> dict: """ Get values of the Wolfenstein-parameterization of CKM matrix, according to PDG. :param CKM: The CKM matrix in a 3x3-matrix-shape. :type CKM: 3x3 matrix :return: Dictionary that contains the parameters :rtype: dict """ l = np.abs(CKM[0, 1]) / np.sqrt(np.power(np.abs(CKM[0, 0]), 2) + np.power(np.abs(CKM[0, 1]), 2)) A = 1 / l * np.abs(CKM[1, 2] / CKM[0, 1]) rhobar = np.real(-CKM[0, 0] * np.conj(CKM[0, 2]) / CKM[1, 0] / np.conj(CKM[1, 2])) etabar = np.imag(-CKM[0, 0] * np.conj(CKM[0, 2]) / CKM[1, 0] / np.conj(CKM[1, 2])) return {'lambda': l, 'A': A, 'rhobar': rhobar, 'etabar': etabar}
[docs]def get_standard_parameters_ckm(CKM) -> dict: """ Get the values of the standard-parametrization of CKM matrix. :param CKM: The CKM matrix in a 3x3-matrix-shape. :type CKM: 3x3 matrix :return: Dictionary that contains the parameters :rtype: dict """ t13 = np.arcsin(np.abs(CKM[0, 2])) t12 = np.arctan(np.abs(CKM[0, 1]) / np.abs(CKM[0, 0])) t23 = np.arctan(np.abs(CKM[1, 2]) / np.abs(CKM[2, 2])) d = np.mod(-1 * np.angle( ((np.conjugate(CKM[0, 0]) * CKM[0, 2] * CKM[2, 0] * np.conjugate(CKM[2, 2])) / (np.cos(t12) * np.power(np.cos(t13), 2) * np.cos(t23) * np.sin(t13)) + np.cos(t12) * np.cos(t23) * np.sin( t13)) / (np.sin(t12) * np.sin(t23))) / np.pi, 2) * 180 return {'t12': t12 * 180 / np.pi, 't13': t13 * 180 / np.pi, 't23': t23 * 180 / np.pi, 'dq': d}
[docs]def get_standard_parameters_pmns(PMNS) -> dict: """ Get the values of the standard-parameterization of PMNS matrix. :param PMNS: The PMNS matrix in a 3x3-matrix-shape. :type PMNS: 3x3 matrix :return: Dictionary that contains the parameters. :rtype: dict """ t13 = np.arcsin(np.abs(PMNS[0, 2])) t12 = np.arctan(np.abs(PMNS[0, 1])/np.abs(PMNS[0, 0])) t23 = np.arctan(np.abs(PMNS[1, 2])/np.abs(PMNS[2, 2])) d = np.mod(-1*np.angle( ((np.conjugate(PMNS[0, 0])*PMNS[0, 2]*PMNS[2, 0]*np.conjugate(PMNS[2, 2])) / (np.cos(t12)*np.power(np.cos(t13), 2)*np.cos(t23)*np.sin(t13)) + np.cos(t12)*np.cos(t23)*np.sin(t13)) / (np.sin(t12)*np.sin(t23)))/np.pi, 2) eta1 = np.mod(np.angle(PMNS[0, 0]/PMNS[0, 2])/np.pi - d, 2) eta2 = np.mod(np.angle(PMNS[0, 1]/PMNS[0, 2])/np.pi - d, 2) return {'s12^2': np.sin(t12)**2, 's13^2': np.sin(t13)**2, 's23^2': np.sin(t23)**2, 'd/pi': d, 'eta1': eta1, 'eta2': eta2}
[docs]def calculate_ckm(Mu=np.identity(3), Md=np.identity(3)) -> np.ndarray: """ Calculates the CKM matrix out of the up and down-type quark mass matrices. :param Mu: The up-type quark mass matrix M, for Phi_left M Phi_right. :type Mu: 3x3 matrix :param Md: The down-type quark mass matrix M, for Phi_left M Phi_right. :type Md: 3x3 matrix :return: The CKM matrix :rtype: 3x3 matrix """ # This function is not necessarily needed for the package. It's just here in case someone might find it useful. # Singular Value decomposition for digonalization tVul, mu, tVurh = np.linalg.svd(Mu) # Mu = tVul * mu * tVurh tVdl, md, tVdrh = np.linalg.svd(Md) # correct the fact that svd sorts its singular values descending permut = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) mu, md = np.dot(permut, mu), np.dot(permut, md) Vul, Vdl = np.dot(permut, np.linalg.inv(tVul)), np.dot(permut, np.linalg.inv(tVdl)) # construct CKM Matrix CKM = np.dot(Vul, Vdl.conj().T) return CKM
[docs]def calculate_pmns(Me=np.identity(3), Mn=np.identity(3), ordering='NO') -> np.ndarray: """ Calculates the PMNS matrix out of the charged lepton and light neutrino mass matrices. :param Me: The charged lepton mass matrix M, for Phi_left M Phi_right. :type Me: 3x3 matrix :param Mn: The light neutrino mass matrix M, for Phi_left M Phi_right. :type Mn: 3x3 matrix :param ordering: Specify whether the neutrino spectrum is normal or inverted ordered. Has to be either \'NO\' or \'IO\'. :type ordering: str :return: The PMNS matrix. :rtype: 3x3 matrix """ # This function is not necessarily needed for the package. It's just here in case someone might find it useful. # Singular Value decomposition for digonalization tVel, me, tVerh = np.linalg.svd(Me) # tVel * diag(me) * tVerh = Me tVnl, mn, tVnrh = np.linalg.svd(Mn) # tVnl * diag(mn) * tVnrh = Mn # correct the fact that svd sorts its singular values (=physical masses) in descending order permutE = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) if ordering == 'NO': permutN = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) elif ordering == 'IO': permutN = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) else: raise NotImplementedError('''The value of \'ordering\' has to be either \'NO\' or \'IO\'.''') me, mn = np.dot(permutE, me), np.dot(permutN, mn) Vel, Vnl = np.dot(permutE, np.linalg.inv(tVel)), np.dot(permutN, np.linalg.inv(tVnl)) # construct PMNS Matrix PMNS = np.dot(Vel.conj(), Vnl.T) # PMNS = np.dot( Vel.conj().T, Vnl) return PMNS