Beispiel #1
0
 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
Beispiel #2
0
 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
Beispiel #3
0
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
Beispiel #4
0
 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!")
Beispiel #5
0
 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
Beispiel #6
0
 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
Beispiel #7
0
 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
Beispiel #8
0
 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
Beispiel #9
0
 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!")
Beispiel #10
0
 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))
Beispiel #11
0
 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