def dscM(l, en, zen): """ Update the MSW-induced mass-squared matrix aMSW with the current density, and update the state-transition matrix modVAC to the current time/position. """ global lCache, aMSW, modVAC # if l did not change, the update is unnecessary if l == lCache: return # modVAC is the time-dependent state-transition matrix that brings a # state vector to the interaction basis, i.e., to the basis where # the vacuum oscillations are flat if isAnti: modVAC = dot(svdVAC0a * exp(-2.533866j/en*svdVAC1*(L-l)), svdVAC2a) else: modVAC = dot(svdVAC0n * exp(-2.533866j/en*svdVAC1*(L-l)), svdVAC2n) # <==> modVAC = dot(dot(svdVAC0, diag(exp(-2.533866j/mcEn*svdVAC1*(L-l)))), svdVAC2) # distance from the center of Earth r = ssqrt( l*l + rDet*rDet - 2*l*rDet*scos(pi - zen) ) if r <= rICore: aMSW = aMSWWoRhoICore * profInt(r) elif r <= rOCore: aMSW = aMSWWoRhoOCore * profInt(r) else: aMSW = aMSWWoRho * profInt(r) lCache = l
def dscM(l, en, zen): """ Update the MSW-induced mass-squared matrix aMSW with the current density, and update the state-transition matrix modVAC to the current time/position. """ global lCache, aMSW, modVAC # if l did not change, the update is unnecessary if l == lCache: return # modVAC is the time-dependent state-transition matrix that brings a state # vector to the interaction basis, i.e., to the basis where the vacuum # oscillations are flat; see calcProb docstring for magic-number explanation if isAnti: modVAC = dot(svdVAC0a * exp(-2.533866j/en*svdVAC1*(L-l)), svdVAC2a) else: modVAC = dot(svdVAC0n * exp(-2.533866j/en*svdVAC1*(L-l)), svdVAC2n) # <==> modVAC = dot(dot(svdVAC0, diag(exp(-2.533866j/mcEn*svdVAC1*(L-l)))), svdVAC2) # distance from the center of Earth r = ssqrt( l*l + rDet*rDet - 2*l*rDet*scos(pi - zen) ) if r <= rICore: aMSW = aMSWWoRhoICore * profInt(r) elif r <= rOCore: aMSW = aMSWWoRhoOCore * profInt(r) else: aMSW = aMSWWoRho * profInt(r) lCache = l
def successors(img, state, carWidth): # Turning angles thetas = [math.pi/6, math.pi/3] # 10 pixels at a time r = 10 successorDeltas = [] # Straight successorDeltas.append((0, r, 0)) for theta in thetas: dx = r * math.sin(theta) dy = r * math.scos(theta) successorDeltas.append((-dx, dy, -theta)) successorDeltas.append((dx, dy, theta)) # Convert to world frame successors = [] for successorDelta in successorDeltas: dx, dy, dtheta = successorDelta t = state[2] newX = math.cos(t) * dx - math.sin(t) * dy + state[0] newY = math.sin(t) * dx + math.cos(t) * dy + state[1] newTheta = (t + dtheta) % (2 * math.pi) if newX < len(img[0]) and newX > 0 and newY < len(img) and newY > 0 and check(img, (newX, newY), carWidth): successors.append((newX, newY, newTheta)) return successors
def InteractionAlt(self, mcType, mcEn, mcZen, mode): """ Return a list of weight-altitude tuples for atmospheric propagation. Helper method; depending on mode, returns a list of one or more tuples of weights and altitudes in which the neutrinos should start to be propagated. The weights have to add up to one. mode 0: returns self.earthModel.rE + self.atmHeight with weight 1., which means that the interaction is expected to happen at a fixed hight (default 20 km) above ground level mode 1: samples a single altitude from a parametrization to the atmospheric interaction model presented in "Path length distributions of atmospheric neutrinos", Gaisser and Stanev, PhysRevD.57.1977 mode 2 and mode 3: returns eight equally probable altitudes from the whole range of values allowed by the parametrization also used in mode 1 """ if mode == 0: return [(1., self.earthModel.rE + self.atmHeight)] # get the cosine of the zenith angle, and compensate for not having a parametrization # for the effective zenith angle (i.e., the zenith angle relative to the Earth's curvature # averaged through the atmosphere) at cos(zen) < 0.05 cosZen = (fabs(scos(mcZen))**3 + 0.000125)**0.333333333 if mcType in (12, -12): # electron neutrinos mu = 1.285e-9*(cosZen-4.677)**14. + 2.581 sigma = 0.6048*cosZen**0.7667 - 0.5308*cosZen + 0.1823 else: mu = 1.546e-9*(cosZen-4.618)**14. + 2.553 sigma = 1.729*cosZen**0.8938 - 1.634*cosZen + 0.1844 logn = lognorm(sigma, scale=2*exp(mu), loc=-12) if mode == 1: z = logn.rvs()*cosZen while z < 0: z = logn.rvs()*cosZen return [(1., z+self.earthModel.rE)] elif mode in [2, 3]: cdf0 = logn.cdf(0) qList = cdf0 + array([0.0625,0.1875,0.3125,0.4375,0.5625,0.6875,0.8125,0.9375])*(1.-cdf0) return list(zip(ones(8)/8., logn.ppf(qList)*cosZen + self.earthModel.rE)) else: raise NotImplementedError("Unrecognized mode for neutrino interaction height estimation!")
def dscM(l, zen): """ Update the MSW-induced mass-squared matrix aMSW with the current density. """ global lCache, aMSW # if l did not change, the update is unnecessary if l == lCache: return # distance from the center of Earth r = ssqrt( l*l + rDet*rDet - 2*l*rDet*scos(pi - zen) ) if r <= self.earthModel.rICore: aMSW = aMSWWoRhoICore * self.earthModel.profInt(r) elif r <= self.earthModel.rOCore: aMSW = aMSWWoRhoOCore * self.earthModel.profInt(r) else: aMSW = aMSWWoRho * self.earthModel.profInt(r) lCache = l
def calcProb(inTuple): """ Calculate the oscillation probabilities of an individual nu. The constant -2.533866j that appears throughout this function is -j*1.266933, where j is the imaginary unit and the other factor is GeV*fm/(4*hbar*c), which is the factor required to transition from natural units to SI units. It is hard-coded as it is not a free parameter and will never change. """ # python 3 does not support tuple parameter unpacking anymore mcType, mcEn, mcZen = inTuple def dscM(l, zen): """ Update the MSW-induced mass-squared matrix aMSW with the current density. """ global lCache, aMSW # if l did not change, the update is unnecessary if l == lCache: return # distance from the center of Earth r = ssqrt( l*l + rDet*rDet - 2*l*rDet*scos(pi - zen) ) if r <= self.earthModel.rICore: aMSW = aMSWWoRhoICore * self.earthModel.profInt(r) elif r <= self.earthModel.rOCore: aMSW = aMSWWoRhoOCore * self.earthModel.profInt(r) else: aMSW = aMSWWoRho * self.earthModel.profInt(r) lCache = l def f(l, psi, en, zen): dscM(l, zen) # see calcProb docstring for magic-number explanation if isAnti: return -2.533866j/en * dot((VACa + aMSW), psi) else: return -2.533866j/en * dot((VACn + aMSW), psi) def jac(l, psi, en, zen): dscM(l, zen) if isAnti: return -2.533866j/en * (VACa + aMSW) else: return -2.533866j/en * (VACn + aMSW) try: mcType = self.mcTypeDict[mcType] except KeyError: raise KeyError("The mcType %d is not known to nuCraft!" % mcType) isAnti = mcType[0] == -1 # inefficient performance-wise, but nicer code, and vacuum is fast enough anyway if vacuum: aMSWWoRho = diag(zeros_like(self.earthModel.A)) aMSWWoRhoOCore = aMSWWoRho aMSWWoRhoICore = aMSWWoRho else: aMSWWoRho = diag(mcType[0] * self.earthModel.A * mcEn) aMSWWoRhoOCore = diag(mcType[0] * self.earthModel.AOCore * mcEn) aMSWWoRhoICore = diag(mcType[0] * self.earthModel.AICore * mcEn) L = ssqrt( rAtm*rAtm + rDet*rDet - 2*rAtm*rDet*scos( mcZen - arcsin(sin(pi-mcZen)/rAtm*rDet) ) ) dscM(L, mcZen) solver = integrate.ode(f, jac).set_integrator('zvode', method='adams', order=5, with_jacobian=True, nsteps=12000000, atol=numPrec*2e-2, rtol=numPrec*2e-2) solver.set_initial_value(mcType[1], L).set_f_params(mcEn, mcZen).set_jac_params(mcEn, mcZen) solver.integrate(0.) if not solver.successful(): raise ArithmeticError("ODE solver was not successful, check for warnings about 'excess work done'!") prob = square(absolute(solver.y)) if abs(1-sum(prob)) > numPrec: warnings.warn("The computed unitarity does not meet the specified precision: %.2e > %.2e" % (abs(1-sum(prob)), numPrec)) return prob
def calcProb(inTuple): """ Calculate the oscillation probabilities of an individual nu. The constant -2.533866j that appears throughout this function is -2*j*1.266933, where j is the imaginary unit and the other factor is GeV*fm/(4*hbar*c), which is the factor required to transition from natural units to SI units. It is hard-coded as it is not a free parameter and will never change. """ # python 3 does not support tuple parameter unpacking anymore mcType, mcEn, mcZen = inTuple def dscM(l, en, zen): """ Update the MSW-induced mass-squared matrix aMSW with the current density, and update the state-transition matrix modVAC to the current time/position. """ global lCache, aMSW, modVAC # if l did not change, the update is unnecessary if l == lCache: return # modVAC is the time-dependent state-transition matrix that brings a state # vector to the interaction basis, i.e., to the basis where the vacuum # oscillations are flat; see calcProb docstring for magic-number explanation if isAnti: modVAC = dot(svdVAC0a * exp(-2.533866j/en*svdVAC1*(L-l)), svdVAC2a) else: modVAC = dot(svdVAC0n * exp(-2.533866j/en*svdVAC1*(L-l)), svdVAC2n) # <==> modVAC = dot(dot(svdVAC0, diag(exp(-2.533866j/mcEn*svdVAC1*(L-l)))), svdVAC2) # distance from the center of Earth r = ssqrt( l*l + rDet*rDet - 2*l*rDet*scos(pi - zen) ) if r <= rICore: aMSW = aMSWWoRhoICore * profInt(r) elif r <= rOCore: aMSW = aMSWWoRhoOCore * profInt(r) else: aMSW = aMSWWoRho * profInt(r) lCache = l def f(l, psi, en, zen): dscM(l, en, zen) return -2.533866j/en * dot(modVAC, aMSW*dot(psi, conj(modVAC))) # <==> return -2.533866j/en * dot(modVAC, dot(diag(aMSW), dot(conj(modVAC).T, psi))) def jac(l, psi, en, zen): dscM(l, en, zen) return -2.533866j/en * dot(modVAC*aMSW, conj(modVAC).T) # <==> return -2.533866j/en * dot(dot(modVAC, diag(aMSW)), conj(modVAC).T) try: mcType = self.mcTypeDict[mcType] except KeyError: raise KeyError("The mcType %d is not known to nuCraft!" % mcType) isAnti = mcType[0] == -1 # inefficient performance-wise, but nicer code, and vacuum is fast enough anyway if vacuum: aMSWWoRho = zeros_like(self.earthModel.A) aMSWWoRhoOCore = aMSWWoRho aMSWWoRhoICore = aMSWWoRho else: aMSWWoRho = mcType[0] * self.earthModel.A * mcEn aMSWWoRhoOCore = mcType[0] * self.earthModel.AOCore * mcEn aMSWWoRhoICore = mcType[0] * self.earthModel.AICore * mcEn # depending on the mode, get a list of interaction altitude tuples, see method doc string; # the first number of the tuples is the weight, the second the distance of the interaction # point to the center of the Earth; the weights have to add up to 1. if atmMode == 3: # first get the tuples and propagate only the lowest-altitude neutrino: rAtmTuples = self.InteractionAlt(mcType, mcEn, mcZen, 2) rAtm = rAtmTuples[0][1] L = ssqrt( rAtm*rAtm + rDet*rDet - 2*rAtm*rDet*scos( mcZen - arcsin(sin(pi-mcZen)/rAtm*rDet) ) ) dscM(L, mcEn, mcZen) solver = integrate.ode(f, jac).set_integrator('zvode', method='adams', order=5, with_jacobian=True, nsteps=1200000, atol=numPrec*2e-3, rtol=numPrec*2e-3) solver.set_initial_value(dot(modVAC, mcType[1]), L).set_f_params(mcEn, mcZen).set_jac_params(mcEn, mcZen) solver.integrate(0.) if isAnti: endVAC = dot(svdVAC0a * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2a) else: endVAC = dot(svdVAC0n * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2n) # <==> endVAC = dot(dot(svdVAC0, diag(exp(-2.533866j/mcEn*svdVAC1*L))), svdVAC2) results = [rAtmTuples[0][0] * square(absolute( dot(conj(endVAC).T, solver.y) ))] # now for all the other neutrinos, add the missing lengths as vacuum oscillations # at the end of the track; keep in mind that atmosphere is always handled as vacuum for rAtmWeight, rAtm in rAtmTuples[1:]: L = ssqrt( rAtm*rAtm + rDet*rDet - 2*rAtm*rDet*scos( mcZen - arcsin(sin(pi-mcZen)/rAtm*rDet) ) ) if isAnti: endVAC = dot(svdVAC0a * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2a) else: endVAC = dot(svdVAC0n * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2n) results.append( rAtmWeight * square(absolute( dot(conj(endVAC).T, solver.y) )) ) else: # in this case, just stupidly propagate every neutrino in the list... results = [] for rAtmWeight, rAtm in self.InteractionAlt(mcType, mcEn, mcZen, atmMode): L = ssqrt( rAtm*rAtm + rDet*rDet - 2*rAtm*rDet*scos( mcZen - arcsin(sin(pi-mcZen)/rAtm*rDet) ) ) dscM(L, mcEn, mcZen) solver = integrate.ode(f, jac).set_integrator('zvode', method='adams', order=5, with_jacobian=True, nsteps=1200000, atol=numPrec*2e-3, rtol=numPrec*2e-3) solver.set_initial_value(dot(modVAC, mcType[1]), L).set_f_params(mcEn, mcZen).set_jac_params(mcEn, mcZen) solver.integrate(0.) if isAnti: endVAC = dot(svdVAC0a * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2a) else: endVAC = dot(svdVAC0n * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2n) # <==> endVAC = dot(dot(svdVAC0, diag(exp(-2.533866j/mcEn*svdVAC1*L))), svdVAC2) results.append( rAtmWeight * square(absolute( dot(conj(endVAC).T, solver.y) )) ) if not solver.successful(): raise ArithmeticError("ODE solver was not successful, check for warnings about 'excess work done'!") prob = sum(results, 0) if abs(1-sum(prob)) > numPrec: warnings.warn("The computed unitarity does not meet the specified precision: %.2e > %.2e" % (abs(1-sum(prob)), numPrec)) return prob
def InteractionAlt(self, mcType, mcEn, mcZen, mode): """ Return a list of weight-altitude tuples for atmospheric propagation. Helper method; depending on mode, returns a list of one or more tuples of weights and altitudes in which the neutrinos should start to be propagated. The weights have to add up to one. mode 0: returns self.earthModel.rE + self.atmHeight with weight 1., which means that the interaction is expected to happen at a fixed hight (default 20 km) above ground level mode 1: samples a single altitude from a parametrization to the atmospheric interaction model presented in "Path length distributions of atmospheric neutrinos", Gaisser and Stanev, PhysRevD.57.1977 mode 2 and mode 3: returns eight equally probable altitudes from the whole range of values allowed by the parametrization also used in mode 1 The interaction height distributions in the paper quoted above are only given implicitely as differential equations without closed-form solutions. The parametrization was obtained by solving those equations numerically at a fixed energy of 2 GeV, as the energy depedence is weak and 2 GeV is the energy where oscillation effects start to become significant at the horizon, where the relative impact of the atmosphere is large. These numerical solutions were then parameterized by log-normal distributions as described in the nuCraft publication. As the equations in the paper are only given for six discrete zenith angles down to cos(zen) = 0.05, the solutions had to be inter- and extrapolated to other zenith angles. The interpolation was done by fitting the two parameters mu and sigma of the log-normal distributions as function of cos(zen), using a polynomial for mu and and a power function plus linear polynomial for sigma. The extrapolation was done by adding a cubically suppressed constant term to cos(zen) (see formula below), such that cos(zen) never falls below 0.05, thereby possibly underestimating the path length for very horizontal events, but achieving a realistic smooth transition between particles above and below the horizon. """ if mode == 0: return [(1., self.earthModel.rE + self.atmHeight)] # extrapolation formula described above: # get the cosine of the zenith angle, and compensate for not having a parametrization # for the effective zenith angle (i.e., the zenith angle relative to the Earth's curvature # averaged through the atmosphere) at cos(zen) < 0.05; 0.000125 == 0.05**3 cosZen = (fabs(scos(mcZen))**3 + 0.000125)**0.333333333 # interpolation part described above, with numbers gained from the parametrizations if mcType in (12, -12): # electron neutrinos, mostly from muon decay mu = 1.285e-9*(cosZen-4.677)**14. + 2.581 sigma = 0.6048*cosZen**0.7667 - 0.5308*cosZen + 0.1823 else: # muon neutrinos, from muon and pion/kaon decay mu = 1.546e-9*(cosZen-4.618)**14. + 2.553 sigma = 1.729*cosZen**0.8938 - 1.634*cosZen + 0.1844 # log-normal distribution, shifted 12 km upwards as required by the parametrization logn = lognorm(sigma, scale=2*exp(mu), loc=-12) if mode == 1: # draw a random non-negative production height from the parametrization z = logn.rvs()*cosZen while z < 0: z = logn.rvs()*cosZen return [(1., z+self.earthModel.rE)] elif mode in [2, 3]: # get eight equally probable altitudes, using the cumulative distribution cdf0 = logn.cdf(0) # the array contains the central values of the partition of [0,1] into eight equally-sized # intervals, in ascending order qList = cdf0 + array([0.0625,0.1875,0.3125,0.4375,0.5625,0.6875,0.8125,0.9375])*(1.-cdf0) return list(zip(ones(8)/8., logn.ppf(qList)*cosZen + self.earthModel.rE)) else: raise NotImplementedError("Unrecognized mode for neutrino interaction height estimation!")
def calcProb(inTuple): """ Calculate the oscillation probabilities of an individual nu. """ # python 3 does not support tuple parameter unpacking anymore mcType, mcEn, mcZen = inTuple def dscM(l, zen): """ Update the MSW-induced mass-squared matrix aMSW with the current density. """ global lCache, aMSW # if l did not change, the update is unnecessary if l == lCache: return # distance from the center of Earth r = ssqrt( l*l + rDet*rDet - 2*l*rDet*scos(pi - zen) ) if r <= self.earthModel.rICore: aMSW = aMSWWoRhoICore * self.earthModel.profInt(r) elif r <= self.earthModel.rOCore: aMSW = aMSWWoRhoOCore * self.earthModel.profInt(r) else: aMSW = aMSWWoRho * self.earthModel.profInt(r) lCache = l def f(l, psi, en, zen): dscM(l, zen) if isAnti: return -2.533866j/en * dot((VACa + aMSW), psi) else: return -2.533866j/en * dot((VACn + aMSW), psi) def jac(l, psi, en, zen): dscM(l, zen) if isAnti: return -2.533866j/en * (VACa + aMSW) else: return -2.533866j/en * (VACn + aMSW) try: mcType = self.mcTypeDict[mcType] except KeyError: raise KeyError("The mcType %d is not known to nuCraft!" % mcType) isAnti = mcType[0] == -1 # inefficient performance-wise, but nicer code, and vacuum is fast enough anyway if vacuum: aMSWWoRho = diag(zeros_like(self.earthModel.A)) aMSWWoRhoOCore = aMSWWoRho aMSWWoRhoICore = aMSWWoRho else: aMSWWoRho = diag(mcType[0] * self.earthModel.A * mcEn) aMSWWoRhoOCore = diag(mcType[0] * self.earthModel.AOCore * mcEn) aMSWWoRhoICore = diag(mcType[0] * self.earthModel.AICore * mcEn) L = ssqrt( rAtm*rAtm + rDet*rDet - 2*rAtm*rDet*scos( mcZen - arcsin(sin(pi-mcZen)/rAtm*rDet) ) ) dscM(L, mcZen) solver = integrate.ode(f, jac).set_integrator('zvode', method='adams', order=4, with_jacobian=True, nsteps=1200000, min_step=0.0002, max_step=500., atol=.1e-6, rtol=.1e-6) solver.set_initial_value(mcType[1], L).set_f_params(mcEn, mcZen).set_jac_params(mcEn, mcZen) solver.integrate(0.) return square(absolute(solver.y))
def calcProb(inTuple): """ Calculate the oscillation probabilities of an individual nu. """ # python 3 does not support tuple parameter unpacking anymore mcType, mcEn, mcZen = inTuple def dscM(l, en, zen): """ Update the MSW-induced mass-squared matrix aMSW with the current density, and update the state-transition matrix modVAC to the current time/position. """ global lCache, aMSW, modVAC # if l did not change, the update is unnecessary if l == lCache: return # modVAC is the time-dependent state-transition matrix that brings a # state vector to the interaction basis, i.e., to the basis where # the vacuum oscillations are flat if isAnti: modVAC = dot(svdVAC0a * exp(-2.533866j/en*svdVAC1*(L-l)), svdVAC2a) else: modVAC = dot(svdVAC0n * exp(-2.533866j/en*svdVAC1*(L-l)), svdVAC2n) # <==> modVAC = dot(dot(svdVAC0, diag(exp(-2.533866j/mcEn*svdVAC1*(L-l)))), svdVAC2) # distance from the center of Earth r = ssqrt( l*l + rDet*rDet - 2*l*rDet*scos(pi - zen) ) if r <= rICore: aMSW = aMSWWoRhoICore * profInt(r) elif r <= rOCore: aMSW = aMSWWoRhoOCore * profInt(r) else: aMSW = aMSWWoRho * profInt(r) lCache = l def f(l, psi, en, zen): dscM(l, en, zen) return -2.533866j/en * dot(modVAC, aMSW*dot(psi, conj(modVAC))) # <==> return -2.533866j/en * dot(modVAC, dot(diag(aMSW), dot(conj(modVAC).T, psi))) def jac(l, psi, en, zen): dscM(l, en, zen) return -2.533866j/en * dot(modVAC*aMSW, conj(modVAC).T) # <==> return -2.533866j/en * dot(dot(modVAC, diag(aMSW)), conj(modVAC).T) try: mcType = self.mcTypeDict[mcType] except KeyError: raise KeyError("The mcType %d is not known to nuCraft!" % mcType) isAnti = mcType[0] == -1 # inefficient performance-wise, but nicer code, and vacuum is fast enough anyway if vacuum: aMSWWoRho = zeros_like(self.earthModel.A) aMSWWoRhoOCore = aMSWWoRho aMSWWoRhoICore = aMSWWoRho else: aMSWWoRho = mcType[0] * self.earthModel.A * mcEn aMSWWoRhoOCore = mcType[0] * self.earthModel.AOCore * mcEn aMSWWoRhoICore = mcType[0] * self.earthModel.AICore * mcEn # depending on the mode, get a list of interaction altitude tuples, see method doc string; # the first number of the tuples is the weight, the second the distance of the interaction # point to the center of the Earth; the weights have to add up to 1. if atmMode == 3: # first get the tuples and propagate only the lowest-altitude neutrino: rAtmTuples = self.InteractionAlt(mcType, mcEn, mcZen, 2) rAtm = rAtmTuples[0][1] L = ssqrt( rAtm*rAtm + rDet*rDet - 2*rAtm*rDet*scos( mcZen - arcsin(sin(pi-mcZen)/rAtm*rDet) ) ) dscM(L, mcEn, mcZen) solver = integrate.ode(f, jac).set_integrator('zvode', method='adams', order=4, with_jacobian=True, nsteps=120000, min_step=0.0002, max_step=500., atol=numPrec*2e-3, rtol=numPrec*2e-3) solver.set_initial_value(dot(modVAC, mcType[1]), L).set_f_params(mcEn, mcZen).set_jac_params(mcEn, mcZen) solver.integrate(0.) if isAnti: endVAC = dot(svdVAC0a * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2a) else: endVAC = dot(svdVAC0n * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2n) # <==> endVAC = dot(dot(svdVAC0, diag(exp(-2.533866j/mcEn*svdVAC1*L))), svdVAC2) results = [rAtmTuples[0][0] * square(absolute( dot(conj(endVAC).T, solver.y) ))] # now for all the other neutrinos, add the missing lengths as vacuum oscillations # at the end of the track; keep in mind that atmosphere is always handled as vacuum for rAtmWeight, rAtm in rAtmTuples[1:]: L = ssqrt( rAtm*rAtm + rDet*rDet - 2*rAtm*rDet*scos( mcZen - arcsin(sin(pi-mcZen)/rAtm*rDet) ) ) if isAnti: endVAC = dot(svdVAC0a * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2a) else: endVAC = dot(svdVAC0n * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2n) results.append( rAtmWeight * square(absolute( dot(conj(endVAC).T, solver.y) )) ) else: # in this case, just stupidly propagate every neutrino in the list... results = [] for rAtmWeight, rAtm in self.InteractionAlt(mcType, mcEn, mcZen, atmMode): L = ssqrt( rAtm*rAtm + rDet*rDet - 2*rAtm*rDet*scos( mcZen - arcsin(sin(pi-mcZen)/rAtm*rDet) ) ) dscM(L, mcEn, mcZen) solver = integrate.ode(f, jac).set_integrator('zvode', method='adams', order=4, with_jacobian=True, nsteps=120000, min_step=0.0002, max_step=500., atol=numPrec*2e-3, rtol=numPrec*2e-3) solver.set_initial_value(dot(modVAC, mcType[1]), L).set_f_params(mcEn, mcZen).set_jac_params(mcEn, mcZen) solver.integrate(0.) if isAnti: endVAC = dot(svdVAC0a * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2a) else: endVAC = dot(svdVAC0n * exp(-2.533866j/mcEn*svdVAC1*L), svdVAC2n) # <==> endVAC = dot(dot(svdVAC0, diag(exp(-2.533866j/mcEn*svdVAC1*L))), svdVAC2) results.append( rAtmWeight * square(absolute( dot(conj(endVAC).T, solver.y) )) ) prob = sum(results, 0) if abs(1-sum(prob)) > numPrec: warnings.warn("The computed unitarity does not meet the specified precision: %.2e > %.2e" % (abs(1-sum(prob)), numPrec)) return prob