def norm_at(E, Qaxis, Ei): "compute normalization(Q) array for the given energy transfer" ki = conversion.e2k(Ei) Ef = Ei - E kf = conversion.e2k(Ef) Qmin = np.abs(ki - kf) Qmax = ki + kf rt = np.zeros(Qaxis.shape) in_range = (Qaxis < Qmax) * (Qaxis > Qmin) rt[in_range] = (Qaxis * (2 * np.pi / ki / kf))[in_range] return rt
def dgs_setEi(sample, Ei): """set incident energy for DGS experiment A sample has kernels with parameters that may be affected by Ei. For example, a powder SQE kernel has Qrange and Erange. When this sample is put into a beam in a DGS instrument, it is better to simulate only within the dynamical range determined by Ei and instrument geometry. Here we do some quick calculation to estimate the dynamical range for the given Ei, without considering the instrument geometry. The main purpose of this is to limit the simulation dynamical range to speed up the simulation Ei: float. unit: meV """ from mcni.utils import conversion ki = conversion.e2k(Ei) # \AA^-1 Qrange = (0., 2*ki) Erange = (-Ei, Ei*.99) for excitation in sample.excitations: if 'Qrange' in excitation.__dict__: excitation.Qrange = ','.join(_to_str_tuple_with_unit( _combine_range(Qrange, _to_float_tuple(excitation.Qrange, '1./angstrom')), '1./angstrom')) if 'Erange' in excitation.__dict__: excitation.Erange = ','.join(_to_str_tuple_with_unit( _combine_range(Erange, _to_float_tuple(excitation.Erange, 'meV')), 'meV')) continue return
def test(): class powdersqe: type = "powderSQE" SQEhist = "Al-iqe.h5" Erange = "-50*meV,50*meV" Qrange = "1/angstrom, 10/angstrom" class mysample: class lattice: constants = 1., 1., 1., 90., 90., 90. basis_vectors = [[1,0,0],[0,1,0],[0,0,1]] chemical_formula="V" class shape: class sphere: radius = 3 mysample.excitations = [powdersqe] Ei = 30.; ki = C.e2k(Ei) sample.dgs_setEi(mysample, Ei) Qmin, Qmax = sample._to_float_tuple(mysample.excitations[0].Qrange, '1./angstrom') assert np.isclose(Qmin, 1.) assert np.isclose(Qmax, 2*ki) Emin, Emax = sample._to_float_tuple(mysample.excitations[0].Erange, 'meV') assert np.isclose(Emin, -Ei) assert np.isclose(Emax, Ei*.99) return
def Eresidual(xtalori, hkl, Etarget, angles, Ei): """compute residual of energy transfer This method compute a series of psi angle and corresponding residual (E - Etarget). We only need to simulate crystal orientation where the residual is close to zero. """ from mcni.utils import conversion as conv ki = conv.e2k(Ei) kiv = np.array([ki,0,0]) r = np.zeros((len(angles), 2)) for i, psi in enumerate(angles): xtalori.psi = psi / 180. * np.pi hkl2cartesian = xtalori.hkl2cartesian_mat() # cart2hkl = xtalori.cartesian2hkl_mat() Qcart = np.dot(hkl, hkl2cartesian) # print hkl, np.dot(Qcart, cart2hkl) # print Qcart kfv = kiv - Qcart kf = np.linalg.norm(kfv) Ef = conv.k2e(kf) E = Ei - Ef r[i] = psi, E-Etarget continue return r
def convolveSQE(self, IQE, Ei, *args): max_det_angle = 140. * np.pi / 180. a = self.calc_res_a(Ei, *args) E_new, Q, I_new = convolveSQE(a, IQE, Ei) mask = np.zeros(I_new.shape, dtype=bool) from mcni.utils import conversion ki = conversion.e2k(Ei) for iE in range(E_new.size): E1 = E_new[iE] if E1 > Ei: mask[:, iE] = 1 continue Ef1 = Ei - E1 kf1 = conversion.e2k(Ef1) Qmin = abs(ki - kf1) Qmax = np.sqrt(ki * ki + kf1 * kf1 - 2 * ki * kf1 * np.cos(max_det_angle)) mask[Q < Qmin, iE] = 1 mask[Q > Qmax, iE] = 1 I_new[mask] = np.nan return E_new, Q, I_new
def hklE2rtz(hkl, E, xtalori, Ei, r): """convert from hkl to cylinderical coordinate sytem """ ki_scalar = conv.e2k(Ei) ki = [ki_scalar, 0, 0] Q = hkl2Q(hkl, xtalori) kf = ki - Q kf_dir = kf/np.linalg.norm(kf) scale_factor = r/np.linalg.norm(kf_dir[:, :3]) position = kf_dir * scale_factor np.allclose(np.linalg.norm(position[:, :3]), r) theta = np.arctan2(position[:, 1], position[:, 0]) return r, theta, position[:, 2]
def sqe( E, g, Qmax=None, Qmin=0, dQ=None, T=300, M=50, N=5, starting_order=2, Emax=None, ): """compute sum of multiphonon SQE from dos S = \sum_{i=2,N} S_i(Q,E) Note: single phonon scattering is not included. only 2-phonons and up E,g: input DOS data energy axis is inferred from input DOS data Q axis is defined by Qmax, Qmin, and dQ T: temperature (Kelvin) M: atomic mass N: maximum number of order for multi-phonon scattering starting_order: 2: start with 2 phonon scattering """ dos_sample = len(E) e0 = E[0] de = E[1] - E[0] emax = E[-1] # expand E Emax = Emax or e0+de*3*dos_sample E = np.arange(e0, Emax, de) g = np.concatenate((g, np.zeros(len(E)-len(g)))) # normalize int_g = np.sum(g) * de g/=int_g # Q axis if Qmax is None: from mcni.utils import conversion Qmax = conversion.e2k(emax) * 3 if dQ is None: dQ = (Qmax-Qmin)/200 Q = np.arange(Qmin, Qmax, dQ) # beta kelvin2mev = 0.0862 beta = 1./(T*kelvin2mev) # compute S S = None for i, (Q,E,S1) in enumerate(iterSQESet(N, Q, dQ, E, de, M, g, beta)): if i < starting_order - 1: continue if S is None: S = S1; continue S += S1 continue # sum over 2..N # S = S_set[starting_order-1:].sum(axis=0) return Q, E, S
def computeKf(Ei, E, Q, log): ki = Conv.e2k(Ei); log.write( "* ki=%s\n" % (ki,) ) kiv = np.array([ki, 0, 0]) kfv = kiv - Q log.write( "* vectors ki=%s, kf=%s\n" % (kiv, kfv) ) Ef = Ei - E # ** Verify the momentum and energy transfers ** log.write( "These two numbers should be very close:\n") log.write( " %s\n" % (Ei-Conv.k2e(np.linalg.norm(kfv)),) ) log.write( " %s\n" % (Ei-Ef,) ) assert np.isclose(Ef, Conv.k2e(np.linalg.norm(kfv))) log.write( " Ei=%s, Ef=%s\n" % (Ei,Ef) ) return kfv, Ef
def rtzE2hkl(r, theta, z, E, xtalori, Ei): """convert from cylinderical coordinate sytem to hkl """ kf_dir = [r*np.cos(theta), r*np.sin(theta), z] kf_dir = np.array(kf_dir).T # (N,3) kf_dir /= np.linalg.norm(kf_dir, axis=-1)[:, np.newaxis] # (N,3) Ef = Ei - E # (N,) kf_scalar = (conv.SE2V*conv.V2K) * Ef**.5 # (N,) del Ef kf = kf_dir * kf_scalar[:, np.newaxis] # (N,3) del kf_scalar ki_scalar = conv.e2k(Ei) ki = [ki_scalar, 0, 0] Q = ki - kf; del ki, kf return Q2hkl(Q, xtalori)
def compute_xE_curve(xaxis, hkl0, ex, mat, Ei): """given xaxis and ex, compute hkl = hkl0+ x*ex and then compute energy transfer E mat: convert hkl to Q """ ki = Conv.e2k(Ei) kiv = np.array([ki, 0, 0]) hkl = hkl0 + xaxis[:, np.newaxis] * ex Q = np.dot(hkl, mat) # NX3 kfv = kiv - Q # NX3 kf2 = np.sum(kfv**2, -1) # N kf = np.sqrt(kf2) # N # theta is the angle in the scattering plane (x-y) theta = np.arctan2(kfv[:, 1], kfv[:, 0]) # N # phi is the angle off the scattering plane. sin(phi) = z/r phi = np.arcsin(kfv[:, 2] / kf) Ef = kf2 * (Conv.K2V**2 * Conv.VS2E) E = Ei - Ef return E, theta, phi
def total_scattering_cross_section(Ei, dp): """compute the total scattering cross section by powder diffraction from the given known list of peaks. This cross section is per unit cell. According to Squires, cross section by one Debye Scherrer cone is \sigma_{\tau} = 1/v_0 \lambda^3/(4sin(\theta/2)) \sum_{all \tau\prime of same length} |F_N(\tau\prime)|^2 The total cross section is the sum of all such cones (number of cones is limited by Ei) Limitation: * the diffraction peaks list usually could be limited if they are loaded from a file such as laz. Probably better be computed directly from the structure. When the peaks list is limited, the calculation would be wrong when Ei gets larger (should see more diff peaks but they are not counted). * only consider the ideal powder diffraction without vibrations. """ from mcni.utils import conversion as conv import numpy as np k = conv.e2k(Ei) l = np.pi * 2 / k ucvol = dp.structure.lattice.getVolume() # loop over peaks, for each peak that contributes # add its contribution sigma = 0 for peak in dp.peaks: q = peak.q if q > 2*k: continue sinthetaover2 = q/2/k F_squared = peak.F_squared mul = peak.multiplicity sigma += l**3 / ucvol / 4 / sinthetaover2 * F_squared * mul continue return sigma
def compute_hE_curve(hmin, hmax, dh, k, l, mat, Ei): """given k, l values, compute h, E mat: convert hkl to Q """ ki = Conv.e2k(Ei) kiv = np.array([ki, 0, 0]) h = np.arange(hmin, hmax, dh) # N hkl = np.vstack((h, np.repeat(k, h.size), np.repeat(l, h.size))).T # NX3 Q = np.dot(hkl, mat) # NX3 kfv = kiv - Q # NX3 kf2 = np.sum(kfv**2, -1) # N kf = np.sqrt(kf2) # N # theta is the angle in the scattering plane (x-y) theta = np.arctan2(kfv[:, 1], kfv[:, 0]) # N # phi is the angle off the scattering plane. sin(phi) = z/r phi = np.arcsin(kfv[:, 2], kf) Ef = kf2 * (Conv.K2V**2 * Conv.VS2E) E = Ei - Ef return h, E, theta, phi
def total_scattering_cross_section(Ei, dp): """compute the total scattering cross section by powder diffraction from the given known list of peaks. This cross section is per unit cell. According to Squires, cross section by one Debye Scherrer cone is \sigma_{\tau} = 1/v_0 \lambda^3/(4sin(\theta/2)) \sum_{all \tau\prime of same length} |F_N(\tau\prime)|^2 The total cross section is the sum of all such cones (number of cones is limited by Ei) Limitation: * the diffraction peaks list usually could be limited if they are loaded from a file such as laz. Probably better be computed directly from the structure. When the peaks list is limited, the calculation would be wrong when Ei gets larger (should see more diff peaks but they are not counted). * only consider the ideal powder diffraction without vibrations. """ from mcni.utils import conversion as conv import numpy as np k = conv.e2k(Ei) l = np.pi * 2 / k ucvol = dp.structure.lattice.getVolume() # loop over peaks, for each peak that contributes # add its contribution sigma = 0 for peak in dp.peaks: q = peak.q if q > 2 * k: continue sinthetaover2 = q / 2 / k F_squared = peak.F_squared mul = peak.multiplicity sigma += l**3 / ucvol / 4 / sinthetaover2 * F_squared * mul continue return sigma
def test1(self): 'kernel orientation' # source from mcni.components.MonochromaticSource import MonochromaticSource import mcni, numpy as np Ei = 100 from mcni.utils import conversion as Conv ki = Conv.e2k(Ei) vi = Conv.e2v(Ei) Qdir = np.array([np.sqrt(3)/2, 0, -1./2]) Q = Qdir * 2 kf = np.array([0,0,ki]) - Q Ef = Conv.k2e(np.linalg.norm(kf)) E = Ei-Ef dv = Qdir * Conv.k2v(Q) vf = np.array([0,0,vi]) - dv # print ki, Q, kf # print Ei, Ef, E neutron = mcni.neutron(r=(0,0,-1), v=(0,0,vi), prob=1) source = MonochromaticSource('s', neutron, dx=0.001, dy=0.001, dE=0) # sample from mccomponents.sample import samplecomponent scatterer = samplecomponent('sa', 'cyl/sampleassembly.xml' ) # incident N = 1000 neutrons = mcni.neutron_buffer(N) neutrons = source.process(neutrons) # print neutrons # scatter scatterer.process(neutrons) # print neutrons self.assertEqual(len(neutrons), N) for neutron in neutrons: np.allclose(neutron.state.velocity, vf) self.assert_(neutron.probability > 0) continue return
def compute(sample_yml, Ei, dynamics, psi_scan, instrument, pixel, tofwidths, beamdivs, samplethickness, plot=False): # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # should P be the T0 chopper? L_PM = mcvine.units.parse( instrument.L_m2fc ) / mcvine.units.meter # P chopper to M chopper distance L_PS = mcvine.units.parse( instrument.L_m2s) / mcvine.units.meter # P chopper to sample L_MS = L_PS - L_PM # R = mcvine.units.parse(instrument.detsys_radius) / mcvine.units.meter # hkl0 = dynamics.hkl0 hkl_dir = dynamics.hkl_dir # projection psimin = psi_scan.min psimax = psi_scan.max dpsi = psi_scan.step # dynamics calculations E = dynamics.E dq = dynamics.dq hkl = hkl0 + dq * hkl_dir from mcni.utils import conversion as Conv vi = Conv.e2v(Ei) ti = L_PM / vi * 1e6 # microsecond Ef = Ei - E vf = Conv.e2v(Ef) # find the psi angle from mcvine.workflow.singlextal.io import loadXtalOriFromSampleYml xtalori = loadXtalOriFromSampleYml(sample_yml) from mcvine.workflow.singlextal.solve_psi import solve results = solve(xtalori, Ei, hkl, E, psimin, psimax, Nsegments=NSEGMENTS_SOLVE_PSI) from mcvine.workflow.singlextal.coords_transform import hkl2Q for r in results: xtalori.psi = r * np.pi / 180 print("psi=%s, Q=%s" % (r, hkl2Q(hkl, xtalori))) print("hkl2Q=%r\n(Q = hkl dot hkl2Q)" % (xtalori.hkl2cartesian_mat(), )) # these are the psi angles that the particular point of interest will be measured # print results assert len(results) # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # only select the first one. this is OK for most cases but there are cases where more than # one psi angles satisfy the condition psi = results[0] xtalori.psi = psi * np.pi / 180 Q = hkl2Q(hkl, xtalori) hkl2Q_mat = xtalori.hkl2cartesian_mat() # print Q # print hkl2Q_mat # Q_len = np.linalg.norm(Q) # print Q_len ki = Conv.e2k(Ei) # print ki kiv = np.array([ki, 0, 0]) kfv = kiv - Q # print kfv # # ** Verify the momentum and energy transfers ** # print Ei-Conv.k2e(np.linalg.norm(kfv)) # print Ei-Ef assert np.isclose(Ei - Ef, E) # ** Compute detector pixel position ** z = kfv[2] / (kfv[0]**2 + kfv[1]**2)**.5 * R L_SD = (z**2 + R**2)**.5 # print z, L_SD # ### Constants eV = 1.60218e-19 meV = eV * 1e-3 mus = 1.e-6 hbar = 1.0545718e-34 AA = 1e-10 m = 1.6750e-24 * 1e-3 #kg from numpy import sin, cos # dE calcuation starts here # ## Differentials pE_pt = -m * (vi**3 / L_PM + vf**3 / L_SD * L_MS / L_PM) # convert to eV/microsecond pE_pt /= meV / mus # print pE_pt pE_ptMD = m * vf**3 / L_SD pE_ptMD /= meV / mus # print pE_ptMD pE_pLPM = m / L_PM * (vi**2 + vf**3 / vi * L_MS / L_SD) pE_pLPM /= meV # print pE_pLPM pE_pLMS = -m / L_SD * (vf**3 / vi) pE_pLMS /= meV # print pE_pLMS pE_pLSD = -m * vf * vf / L_SD pE_pLSD /= meV # print pE_pLSD # we don't need pE_pLSD, instead we need pE_pR and pE_pz. R and z are cylinder radius and z coordinate pE_pR = pE_pLSD * (R / L_SD) pE_pz = pE_pLSD * (z / L_SD) # print pE_pR, pE_pz # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ## ** Paramters: Estimate of standard deviations # tau_P = 10 # microsecond # tau_M = 8 # microsecond tau_P = tofwidths.P tau_M = tofwidths.M # # tau_D = 10 # microsecond tau_D = mcvine.units.parse( pixel.radius) / mcvine.units.meter * 2 / vf * 1e6 # microsecond # ## Calculations pE_p_vec = [pE_pt, pE_ptMD, pE_pLPM, pE_pLMS, pE_pR, pE_pz] pE_p_vec = np.array(pE_p_vec) J_E = pE_p_vec / E # print J_E sigma_t = (tau_P**2 + tau_M**2)**.5 sigma_tMD = (tau_M**2 + tau_D**2)**.5 # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX div = (beamdivs.theta**2 + beamdivs.phi**2)**.5 # a crude approx sigma_LPM = L_PM * div * div # mainly due to sample size sigma_LMS = samplethickness # mainly due to det tube diameter sigma_R = mcvine.units.parse(pixel.radius) / mcvine.units.meter * 2 # pixel size sigma_z = mcvine.units.parse(pixel.height) / mcvine.units.meter sigma = np.array( [sigma_t, sigma_tMD, sigma_LPM, sigma_LMS, sigma_R, sigma_z]) # print sigma sigma2 = sigma * sigma # print sigma2 sigma2 = np.diag(sigma2) # print J_E # print np.dot(sigma2, J_E) cov = np.dot(J_E, np.dot(sigma2, J_E)) # print cov, np.sqrt(cov) sigma_E = E * np.sqrt(cov) # print sigma_E # Not sure if this is right: # # * ** Note: this may be more like FWHM than sigma_E because of the approx I made ** # * ** FWHM is 2.355 sigma ** # ## Include Q # print "ti=",ti tf = L_SD / vf * 1e6 # print "tf=",tf thetai = 0 phii = 0 # print "R=", R # print "Q=", Q eeta = np.arctan2(kfv[1], kfv[0]) # print "eeta=", eeta pQx_pt = -m / hbar * (L_PM / ti / ti / mus / mus * cos(thetai) * cos(phii) + R / tf / tf / mus / mus * L_MS / L_PM * cos(eeta)) pQx_pt /= 1. / AA / mus # print pQx_pt pQx_ptMD = m / hbar * R / tf / tf * cos(eeta) / mus / mus pQx_ptMD /= 1. / AA / mus # print pQx_ptMD pQx_pLPM = m / hbar * (cos(thetai) * cos(phii) / ti + ti / tf / tf * R * L_MS / L_PM / L_PM * cos(eeta)) / mus pQx_pLPM /= 1. / AA # print pQx_pLPM pQx_pLMS = -m / hbar * R / tf / tf * ti / L_PM * cos(eeta) / mus pQx_pLMS /= 1. / AA # print pQx_pLMS pQx_pR = -m / hbar / tf * cos(eeta) / mus pQx_pR /= 1. / AA # print pQx_pR pQx_peeta = m / hbar * R / tf * sin(eeta) / mus pQx_peeta /= 1. / AA # print pQx_peeta pQx_pthetai = -m / hbar * L_PM / ti * sin(thetai) * cos(phii) / mus pQx_pthetai /= 1. / AA # print pQx_pthetai pQx_pphii = -m / hbar * L_PM / ti * cos(thetai) * sin(phii) / mus pQx_pphii /= 1. / AA # print pQx_pphii pQx_p_vec = [ pQx_pt, pQx_ptMD, pQx_pLPM, pQx_pLMS, pQx_pR, 0, pQx_peeta, pQx_pthetai, pQx_pphii ] pQx_p_vec = np.array(pQx_p_vec) J_Qx = pQx_p_vec / Q_len # **Qy** pQy_pt = -m / hbar * (L_PM / ti / ti * sin(thetai) * cos(phii) + R / tf / tf * L_MS / L_PM * sin(eeta)) / mus / mus pQy_pt /= 1. / AA / mus # print pQy_pt pQy_ptMD = m / hbar * R / tf / tf * sin(eeta) / mus / mus pQy_ptMD /= 1. / AA / mus # print pQy_ptMD pQy_pLPM = m / hbar * (sin(thetai) * cos(phii) / ti + ti / tf / tf * R * L_MS / L_PM / L_PM * sin(eeta)) / mus pQy_pLPM /= 1. / AA # print pQy_pLPM pQy_pLMS = -m / hbar * R / tf / tf * ti / L_PM * sin(eeta) / mus pQy_pLMS /= 1. / AA # print pQy_pLMS pQy_pR = -m / hbar / tf * sin(eeta) / mus pQy_pR /= 1. / AA # print pQy_pR pQy_peeta = -m / hbar * R / tf * cos(eeta) / mus pQy_peeta /= 1. / AA # print pQy_peeta pQy_pthetai = m / hbar * L_PM / ti * cos(thetai) * cos(phii) / mus pQy_pthetai /= 1. / AA # print pQy_pthetai pQy_pphii = -m / hbar * L_PM / ti * sin(thetai) * sin(phii) / mus pQy_pphii /= 1. / AA # print pQy_pphii pQy_p_vec = [ pQy_pt, pQy_ptMD, pQy_pLPM, pQy_pLMS, pQy_pR, 0, pQy_peeta, pQy_pthetai, pQy_pphii ] pQy_p_vec = np.array(pQy_p_vec) J_Qy = pQy_p_vec / Q_len # ** Qz ** pQz_pt = -m / hbar * (L_PM / ti / ti * sin(phii) + z / tf / tf * L_MS / L_PM) / mus / mus pQz_pt /= 1. / AA / mus # print pQz_pt pQz_ptMD = m / hbar * z / tf / tf / mus / mus pQz_ptMD /= 1. / AA / mus # print pQz_ptMD pQz_pLPM = m / hbar * (sin(phii) / ti + ti / tf / tf * z * L_MS / L_PM / L_PM) / mus pQz_pLPM /= 1. / AA # print pQz_pLPM pQz_pLMS = -m / hbar * z / tf / tf * ti / L_PM / mus pQz_pLMS /= 1. / AA # print pQz_pLMS pQz_pz = -m / hbar / tf / mus pQz_pz /= 1. / AA # print pQz_pz pQz_pphii = m / hbar * L_PM / ti * cos(phii) / mus pQz_pphii /= 1. / AA # print pQz_pphii pQz_p_vec = [ pQz_pt, pQz_ptMD, pQz_pLPM, pQz_pLMS, 0, pQz_pz, 0, 0, pQz_pphii ] pQz_p_vec = np.array(pQz_p_vec) J_Qz = pQz_p_vec / Q_len # ** Here we need to extend the J vector for E to include the additional variables eeta, thetai, and phii ** pE_p_vec = [pE_pt, pE_ptMD, pE_pLPM, pE_pLMS, pE_pR, pE_pz, 0, 0, 0] pE_p_vec = np.array(pE_p_vec) J_E = pE_p_vec / E J = np.array((J_Qx, J_Qy, J_Qz, J_E)) # ## ** Parameters sigma_eeta = mcvine.units.parse(pixel.radius) / mcvine.units.parse( instrument.detsys_radius) # sigma_thetai = 0.01 sigma_thetai = beamdivs.theta # sigma_phii = 0.01 sigma_phii = beamdivs.phi sigma = np.array([ sigma_t, sigma_tMD, sigma_LPM, sigma_LMS, sigma_R, sigma_z, sigma_eeta, sigma_thetai, sigma_phii ]) sigma2 = sigma**2 sigma2 = np.diag(sigma2) # print J.shape, sigma2.shape cov = np.dot(J, np.dot(sigma2, J.T)) # print cov M = np.linalg.inv(cov) # print M # ## Ellipsoid # hkl = hkl0+hkl_dir*x # dh,dk,dl = dx2dhkl*dx dx2dhkl = np.array(hkl_dir) # dQ = dx * dx2dhkl dot hkl2Q # so dx2dQ = dx2dhkl * hkl2Q # dQ = dx * dx2dQ dx2dQ = np.dot(dx2dhkl, hkl2Q_mat) # print dx2dQ # [dQx,dQy,dQz,dE] = [dx dE] dot dxdE2dQdE L = dxdE2dQdE = np.array([list(dx2dQ) + [0], [0., 0., 0., 1]]) # np.dot([1,1], dxdE2dQdE) # $ [dX1,\; dX2,\; dX3,\; dX4]\; M\; [dX1,\; dX2,\; dX3,\; dX4 ]^T = 2ln(2)$ # # $ dX_i = \frac{dQ_i}{|Q|}$ for i = 1,2,3 # # $ dX_4 = \frac{dE}{E}$ # # Let # $ U = diag\big( \frac{1}{|Q|},\; \frac{1}{|Q|},\; \frac{1}{|Q|},\; 1/E \big) $ # # $ [dx,\; dE]\; L U MU^TL^T [dx,\; dE ]^T = 2ln(2)$ # # Let $N=L U MU^TL^T $ # print Q_len, E U = np.diag([1. / Q_len, 1. / Q_len, 1. / Q_len, 1. / E]) N = LUMUTLT = np.dot(L, np.dot(U, np.dot(M, np.dot(U.T, L.T)))) # print N # print 2*np.log(2) r = np.linalg.eig(N) mR = r[1] lambdas = r[0] # print np.dot(mR, mR.T) # print np.dot(np.dot(mR.T, N), mR) # Make 4-D inverse covariance matrix: InvCov4D = UMUT = np.dot(U, np.dot(M, U.T)) hklE2QE = scipy.linalg.block_diag(hkl2Q_mat, 1.) InvCov4D = np.dot(np.dot(hklE2QE, InvCov4D), hklE2QE.T) # print np.dot(cov, M) # should be Eye # $ u = [dx,\;dE]$ # # $ u N u^T = 2ln(2)$ .... (1) # # Find eigen values ($\lambda_1$, $\lambda_2$) and eigne vectors ($e_1$, $e_2$, column vectors) of N, # and let # # $ R = [e_1,\;e_2] $ # # Then # # $ N' = R^T N R = diag([\lambda_1, \lambda_2]) $ # # or # # $ N = R N' R^T $ # # With $N'$ we can rewrite (1) as # # $ u'N'{u'}^T = 2ln2 = \lambda_1 {u'}_1^2 + \lambda_2 {u'}_2^2 $ # # where # # $ u' = u . R $ # ${u'}_1 = \sqrt{2ln2/\lambda_1}*cos(\theta)$ # # ${u'}_2 = \sqrt{2ln2/\lambda_2}*sin(\theta)$ # In[ ]: RR = 2 * np.log(2) theta = np.arange(0, 360, 1.) * np.pi / 180 u1p = np.sqrt(RR / lambdas[0]) * np.cos(theta) u2p = np.sqrt(RR / lambdas[1]) * np.sin(theta) up = np.array([u1p, u2p]).T # print up.shape u = np.dot(up, mR.T) if plot: from matplotlib import pyplot as plt plt.plot(u[:, 0], u[:, 1], '.') # plt.xlim(-.35, .1) # plt.ylim(-5., 5.) plt.show() # u: 2D ellipsoid coordinates # mR: 2D eigen vectors # lambdas: and 2D eigen values of the scaled inverse covariance # QxQyQzE_cov: 4D covariance matrix for Qx,Qy,Qz,E in instrument coordinate system # hklE_inv_cov: inverse 4D covariance matrix for hklE return dict(u=u, mR=mR, lambdas=lambdas, QxQyQzE_cov=cov, hklE_inv_cov=InvCov4D)
def updateSQEConvolution(self, uploaded_contents, uploaded_filename, qgrid_dim, Nqsamples, temperature, Ei, *args): if uploaded_contents is None: return binfile = binfile_from_uploaded(uploaded_contents, uploaded_filename) if binfile.endswith('.zip'): zipfile = binfile # compute sqe Eaxis = np.linspace(-0. * Ei, .9 * Ei, 120) from mcni.utils import conversion Qmax = conversion.e2k(Ei) * 2 Qaxis = np.linspace(0, Qmax, 100) from phonon import SQE_from_FCzip print temperature sqe = SQE_from_FCzip(Qaxis, Eaxis, zipfile, Ei, max_det_angle=140., T=temperature, qgrid_dim=qgrid_dim, Nqpoints=Nqsamples) os.unlink(zipfile) elif binfile.endswith('.h5'): import histogram.hdf as hh sqe = hh.load(binfile) # plot sqe z = sqe.I.T zmedian = np.median(z[z > 0]) import plotly.graph_objs as go fig = go.Figure(data=[ go.Heatmap(z=z, x=sqe.Q, y=sqe.E, zmin=0., zmax=zmedian * 5, colorscale='Viridis') ], layout={ 'title': 'Original', }) # convolve E_new, Q, I_new = self.convolveSQE(sqe, Ei, *args) z = I_new.T zmedian = np.median(z[z > 0]) fig2 = go.Figure(data=[ go.Heatmap(z=z, x=Q, y=E_new, zmin=0., zmax=zmedian * 5, colorscale='Viridis') ], layout={ 'title': 'Convolved', }) graph_style = {'height': '25em', 'width': '30em'} inline = {"display": "inline-flex"} return html.Div([ html.Div([dcc.Graph(figure=fig, style=graph_style)], style=inline), html.Div([dcc.Graph(figure=fig2, style=graph_style)], style=inline), ], style={"display": "inline-flex"})
def apply_corrections(I, Qbb, Ebb, N, mass, uc, doshist, T, Ei, max_det_angle): """apply remaining corrections to IQE. this needs to be combined with calcIQE""" from mcni.utils import conversion from multiphonon.forward.phonon import kelvin2mev from multiphonon.forward import phonon import histogram as H Q = (Qbb[1:] + Qbb[:-1]) / 2 # correction 1 ki = conversion.e2k(Ei) E = (Ebb[1:] + Ebb[:-1]) / 2 Ef = Ei - E kf = conversion.e2k(Ef) v0 = uc.lattice.volume beta = 1. / (T * kelvin2mev) thermal_factor = 1. / 2 * (1. / np.tanh(E / 2 * beta) + 1) correction_E = thermal_factor * (2 * np.pi)**3 / v0 / (2 * ki * kf) correction_Q = 1. / Q # the additional 1/4pi comes from powder average. See notes correction = 1. / 4 / np.pi * np.outer(correction_Q, correction_E) I *= correction # # Correction 2 - DW dos_e = doshist.E if hasattr(doshist, 'E') else doshist.energy dos_dE = dos_e[1] - dos_e[0] dos_g = doshist.I / np.sum(doshist.I) / dos_dE DW2 = phonon.DWExp(Q, M=mass, E=dos_e, g=dos_g, beta=beta, dE=dos_e[1] - dos_e[0]) I *= np.exp(-DW2)[:, np.newaxis] IQEhist = H.histogram('IQE', (H.axis('Q', boundaries=Qbb, unit='1./angstrom'), H.axis('E', boundaries=Ebb, unit='meV')), data=I) ## Normalize # first normalize by "event" count and make it density dQ = Q[1] - Q[0] dE = E[1] - E[0] IQEhist.I /= dQ * dE # Simulate "Vanadium" data def norm_at(E, Qaxis, Ei): "compute normalization(Q) array for the given energy transfer" ki = conversion.e2k(Ei) Ef = Ei - E kf = conversion.e2k(Ef) Qmin = np.abs(ki - kf) Qmax = ki + kf rt = np.zeros(Qaxis.shape) in_range = (Qaxis < Qmax) * (Qaxis > Qmin) rt[in_range] = (Qaxis * (2 * np.pi / ki / kf))[in_range] return rt norm_hist = IQEhist.copy() norm_hist.I[:] = 0 for E_ in norm_hist.E: norm_hist[(), E_].I[:] = norm_at(E_, Q, Ei) # normalize IQEhist = IQEhist / norm_hist # ## Dynamical range DR_Qmin = ki - kf DR_Qmax = ((ki * ki + kf * kf - 2 * ki * kf * np.cos(max_det_angle * np.pi / 180)))**.5 I = IQEhist.I for iE, (E1, Qmin1, Qmax1) in enumerate(zip(E, DR_Qmin, DR_Qmax)): I[Q < Qmin1, iE] = np.nan I[Q > Qmax1, iE] = np.nan return IQEhist