In [6]:
%pylab inline
from scipy import interpolate
from matplotlib.colors import LogNorm
from datetime import datetime, timedelta
from pyglow import pyglow
from scipy.stats import rv_discrete
import sys
import time
from IPython.display import display, clear_output
import mpld3

# Change matplotlib defaults
matplotlib.rcParams['savefig.dpi'] = 150
matplotlib.rcParams['figure.figsize'] = (4,3)
matplotlib.rcParams['font.size'] = 8
matplotlib.rcParams['savefig.bbox'] = 'tight'
matplotlib.rcParams['ytick.labelsize'] = 'small'
matplotlib.rcParams['xtick.labelsize'] = 'small'
matplotlib.rcParams['legend.fancybox'] = 'True'
matplotlib.rcParams['legend.fontsize'] = 'small'
matplotlib.rcParams['legend.numpoints'] = 1

import warnings
warnings.filterwarnings('once')
Populating the interactive namespace from numpy and matplotlib

Scale $d\sigma/d\Omega$ to try to match the heating profile from I92

Oxygen aurora model v0.1

Updates from v0.0:

  • Analytical calculation of O-O DSCS
  • Fix units error in Fig 3 of Ishimoto et al (1992).
  • Add missing $2\pi$ factor in O-O DSCS

What's included:

  • Initial precipitating neutral atom source at 1000 km
  • Elastic collisions of fast $O$ on atmospheric $O$
    • differential scattering cross section from Ishimoto et al. (1992).
  • Inelastic collisions:
    • Excitation to $O(^1D)$
    • excitation cross section from Ishimoto et al. (1994).
  • MSIS neutral density (assumed to be entirely $O$)

What's not included:

  • Charge exchange of precipitating $O^+$ to create fast $O$
    • Assumes $\sigma_{\textrm{chg ex}} >> \sigma_{\textrm{other things}}$ (Kozyra et al. 1982)
  • Ionization collisions
  • Stripping collisions
  • Collisions with ambient $N_2$, $O_2$, $He$
  • Thermal velocities of ambient particles (assumed to be at rest)
  • Loss of $O(^1D)$
  • Long lifetime of $O(^1D)$
  • Excitation of other particles and states (e.g., $O(^1S)$ or $N_2$-band states)
  • Gravity
  • Magnetic field
  • Momentum transfer in inelastic collisions (forward-scattering assumed)
  • Incident atoms in excited states initially

O-O elastic collision cross section

The DSCS used in Ishimoto 1992

In [63]:
DSCS_FACTOR = 2*pi
In [64]:
def p_i92(tau):
    p0 = 6e-17
    tau0 = 1e3
    tau1 = 200e3
    b1 = 0.75
    b2 = 2.
    
    if tau < tau0:
        p = p0
    elif tau < tau1:
        p = p0 * tau0**b1 * tau**(-b1)
    else:
        p = p0 * tau0**b1 * tau1**(-b1+b2) * tau**(-b2)
        
    return p
In [65]:
def F(th_rad, e, th_min_rad=1e-9):
    '''
    un-normalized CDF of scattering angle at energy e, calculated analytically (see notebook).
    F(pi, e) is the total cross section.
    F(th, e)/F(pi, e) is the CDF of the scattering angle.
    INPUTS:
        th: angle (rad)
        e: energy (eV)
    OUTPUTS:
        F: CDF of scattering angle, weighted by total cross section
    '''
    p0 = 2*pi*6e-17 # Include 2*pi factor for integration over azimuth
    tau0 = 1e3
    tau1 = 200e3
    b1 = 0.75
    b2 = 2.0
    
    # Calculate intermediate variables
    p1 = p0 * tau0**b1 * e**(-b1)
    p2 = p0 * tau0**b1 * tau1**(-b1+b2) * e**(-b2)
    th0 = tau0/e
    th1 = tau1/e
    
    th = th_rad * 180/pi # convert to degrees
    th_min = th_min_rad * 180/pi # convert to degrees
    
    if th_min > th0:
        raise Exception('Error: th_min > th0, violating assumption')
    
    if th < th0:
        F = p0*log(th/th_min)
    elif th < th1:
        F = p0*log(th0/th_min) + p1/b1*(th0**(-b1) - th**(-b1))
    else: # th >= th1
        F = p0*log(th0/th_min) + p1/b1*(th0**(-b1) - th1**(-b1)) + p2/b2*(th1**(-b2) - th**(-b2))
    
    F = DSCS_FACTOR*F
    return F
    
In [66]:
th_min = 1e-6
e_min = 1e-1
e_vec = logspace(log10(e_min),7,101)
elastic_xsec_vec = zeros(len(e_vec))

for i in range(len(e_vec)):
    e = e_vec[i]
    elastic_xsec_vec[i] = F(pi, e, th_min)
In [67]:
loglog(e_vec,elastic_xsec_vec,'k-')

xlabel('Energy [eV]');
ylabel('Elastic Scattering Cross section [cm$^2$]');
In [68]:
def elastic_xsec(e, th_min_rad = 1e-9):
    return F(pi ,e, th_min_rad)
In [69]:
def elastic_scatt_angle_icdf(e, u, th_min_rad = 1e-9):
    '''
    The inverse cdf of scattering angle. Given a random number in [0,1], calculate the
    corresponding scattering angle.
    INPUTS:
        e - energy, eV
        u - the value of the cdf, in [0,1]
    OUTPUTS:
        th - scattering angle in rad
    '''
    
    p0 = 2*pi*6e-17 # Include 2*pi factor for integration over azimuth
    tau0 = 1e3
    tau1 = 200e3
    b1 = 0.75
    b2 = 2.0
    
    # Calculate intermediate variables
    p1 = p0 * tau0**b1 * e**(-b1)
    p2 = p0 * tau0**b1 * tau1**(-b1+b2) * e**(-b2)
    th0 = tau0/e
    th1 = tau1/e
    
    th_min = th_min_rad * 180/pi # convert to degrees
    if th_min > th0:
        raise Exception('Error: th_min > th0, violating assumption')
    
    # Total cross section to normalize by
    sig = F(pi, e, th_min_rad)
    up = u*sig
    
    up = up/DSCS_FACTOR

    
    # Calculate cases (see notebook)
    F0 = p0*log(th0/th_min)
    F1 = p1/b1*(th0**(-b1) - th1**(-b1))
    
    if up < F0:
        g = th_min*exp(up/p0)
        
    elif up < F0 + F1:
        g = (th0**(-b1) - b1/p1*(up - F0))**(-1/b1)
    
    else:
        g = (th1**(-b2) - b2/p2*(up - F0 - F1))**(-1/b2)
        
    th = g*pi/180.
    return th
    

O-O excitation cross section to O(1D)

Grean-McNeal formula (see Ishimoto 1994)

In [70]:
def excitation_xsec(e):
    '''
    The excitation cross section of the O + O --> O + O(1D) reaction,
    as a function of energy, e
    INPUT:
        e - incident energy, eV
    OUTPUT:
        sigma - cross section, cm^2
    '''
    sig_max = 3.8e-18
    nu = 1.0
    om = 1.0
    J = 5.4e3   

    pre = J**nu/(2*J**(om+nu))
    sigma =  sig_max/pre * (e**nu/(J**(om+nu) + e**(om+nu)))
    
    # But if e < 1.96 eV, it can't excite
    if e < 1.96:
        sigma = 0.0
    
    return sigma

Excitation cross section

In [71]:
e = logspace(0, 6, 1000)

sigma = zeros(len(e))
for i in range(len(e)):
    sigma[i] = excitation_xsec(e[i])

loglog(e,sigma,'k-')


xlabel('Energy [eV]');
ylabel('Excitation Cross section [cm$^2$]');

Neutral density

In [72]:
lat = 45
lon = 0
t = datetime(2004,1,1,0) # Solar medium

altvec = linspace(30,1200,1000)
n_O = zeros(len(altvec))
n_N2 = zeros(len(altvec))
n_He = zeros(len(altvec))
n_tot = zeros(len(altvec))
for i in range(len(altvec)):
    pt = pyglow.Point(t,lat,lon,altvec[i])
    pt.run_msis()
    n_O[i] = pt.nn['O']
    n_N2[i] = pt.nn['N2']
    n_He[i] = pt.nn['HE']
    n_tot[i] = sum([pt.nn[x] for x in ['AR','H','HE','N','N2','O','O2']])
In [73]:
semilogx(n_tot,altvec,'k-',linewidth=2,label='total')
semilogx(n_O, altvec,'b-',label='O')
semilogx(n_N2, altvec,'r-', label='N2')
semilogx(n_He, altvec,'g-', label='He')
legend(loc='best')
xlim((1e5,1e12))
xlabel('Neutral density [cm^-3]')
ylabel('Altitude [km]')
Out[73]:
<matplotlib.text.Text at 0x7f3735dfa810>

Use total density, but assume that it's all atomic oxygen

In [74]:
density_O = interpolate.InterpolatedUnivariateSpline(altvec*1e3, n_tot)
In [75]:
altvec = linspace(60e3,1000e3,100000)
nO_interp = zeros(len(altvec))
for i in range(len(altvec)):
    nO_interp[i] = density_O(altvec[i])
    
semilogx(nO_interp, altvec/1e3, 'k-')
Out[75]:
[<matplotlib.lines.Line2D at 0x7f3735c51410>]

Following notes in notebook on 4 Nov 2015.

Variables/functions used from above:

  • density_O(altitude_m)
  • excitation_xsec(energy_ev)
  • elastic_xsec(energy_ev)
  • elastic_scatt_angle_icdf(energy_ev, u)
In [76]:
# Constants
m_O = 15.9994*1.66054e-27 # kg
q = 1.6e-19 # fundamental charge
k = 8.617e-5 # ev/K
#e_thermal = 1.5*k*200 # thermal energy at 200 K
#e_min = 5*e_thermal # stop when particle has less than this energy. Convert to heat.
th_min = 1e-6 # TODO: parameterize as e_tr_min, if it's worth doing so.
e_min = 1.96
In [81]:
# Initialize variables for this particle
e = 1e3 # eV
ru = rand() # randomized initial direction as per:
rv = rand() # http://mathworld.wolfram.com/SpherePointPicking.html
th = arccos(rv-1) # isotropic over downward hemisphere
ph = 2*pi*ru
v_mag = sqrt(2*e*q/m_O)
v = v_mag * array([sin(th)*sin(ph), sin(th)*cos(ph), cos(th)])
r = array([0, 0, 1000e3]) # m/s [x,y,z]
state = 0 # 0 = 3P, 1 = 1D

# Variables to record secondary particles 
secondary_count = 0
secondary_v = []
secondary_r = []
secondary_state = []

# For local, thermalized primaries and secondaries, record their energy and altitude
final_e = [] # eV
final_alt = [] # m

# Temporary variables for tracking particles
r_history = []
r_elastic_collision = []
r_excitation_collision = []
t_history = []

steps = 0
t = 0
steps_max = 1e6
stop = False

while(not stop):

    # Density and cross sections
    e = 0.5*m_O/q*norm(v)**2 # eV
    sigma_elastic    = elastic_xsec(e, th_min)    # cm^2
    sigma_excitation = excitation_xsec(e) # cm^2
    n_O = density_O(r[2]) # cm^-3
    
    # Mean free path
    mfp = 1e-2/(n_O*(sigma_elastic + sigma_excitation)) # m

    # Step size
    H = 50e3 # scale size, approximate for now
    ds = 0.1 * min(H,mfp) # so that step is shorter than the scale size and collisional scale
    dt = ds/norm(v) # sec

    # Determine if collision happened
    pc = 1 - exp(-ds/mfp)
    collision = rand() < pc

    if collision:
        # Determine type of collision
        collision_probs = array([sigma_elastic, sigma_excitation])
        collision_pmf = collision_probs/sum(collision_probs)
        # Randomly sample from the pmf
        collision_cdf = cumsum(collision_pmf)
        u = rand()
        collision_type = sum(u>=collision_cdf)
        ################## Elastic collision ########################
        if collision_type == 0:
            # Random scattering angle
            u = rand()
            chi = elastic_scatt_angle_icdf(e, u, th_min)
            if isnan(chi):
                print e
                print v
                raise Exception('blah')
            # Rotate to collision frame
            v1 = v.copy()
            v2 = 0.0 # assume ambient particle is at rest
            ez = v1/norm(v1)
            ey_tmp = cross(v1, array([0,1,0]))
            ey = ey_tmp/norm(ey_tmp)
            ex = cross(ey,ez)
            A = zeros((3,3))
            A[:,0] = ex
            A[:,1] = ey
            A[:,2] = ez
            v1p = A.T.dot(v1-v2)
            v2p = 0.0
            # Velocity magnitudes after collision
            m1 = m_O
            m2 = m_O
            v1p_mag_new = sqrt(m1**2 + 2*m1*m2*cos(chi) + m2**2)/(m1+m2) * norm(v1p)
            v2p_mag_new = 2*m1/(m1+m2) * norm(v1p) * sin(chi/2)
            # Scattering angles
            th1p = arctan2(m2*sin(chi), m1 + m2*cos(chi))
            th2p = (pi-chi)/2
            phi1p = 2*pi*rand()
            # New velocity vectors
            v1p_new = v1p_mag_new * array([sin(th1p)*sin(phi1p),    sin(th1p)*cos(phi1p),    cos(th1p)])
            v2p_new = v2p_mag_new * array([sin(th2p)*sin(phi1p+pi), sin(th2p)*cos(phi1p+pi), cos(th2p)])
            v1_new = A.dot(v1p_new) + v2
            v2_new = A.dot(v2p_new) + v2
            # Record secondary particle for later
            e2 = 0.5*m_O/q*norm(v2_new)**2 
            if e2 < e_min: # No need to trace secondary. Just record its energy.
                final_e.append(e2)
                final_alt.append(r[2])
            else:
                secondary_count += 1
                secondary_v.append(v2_new)
                secondary_r.append(r.copy())
                secondary_state.append(0)
            # Adjust this particle
            v = v1_new

            r_elastic_collision.append(r)

        ################# Excitation collision #######################
        elif collision_type == 1:
            # TODO: treat this as an elastic collision instead of a forward-scatter collision?
            # Determine which particle gets excited
            if rand() > 0.5: # Ambient particle
                e2 = 0.0 # It is left undisturbed (for now)
                if e2 < e_min: # Don't need to trace particle later.
                    # TODO: record O(1D) local production to be used later in emission calculation
                    pass
                else: # This can't occur, for now
                    pass

            else: # Incident particle
                state = 1 # TODO: record information for O(1D) production rate throughout
                # No need to record anything about secondary particle
            # Subtract excitation energy from incident particle (not ambient particle?)
            e -= 1.96 # eV
            v_mag_new = sqrt(2*e*q/m_O)
            v = v_mag_new * v / norm(v)
            
            r_excitation_collision.append(r)

    else:
        # Step forward in time
        r = r + ds*v/norm(v)
        t = t + dt
    r_history.append(r)
    t_history.append(t)
    

    # Check halting conditions
    steps += 1
    if r[2] > 1100e3: # Escape flux
        stop = True
        print 'Particle exited above 1100 km with energy %.1f eV' % e
    if e < e_min:
        stop = True
        print 'Particle thermalized at %.1f km with energy %.2f eV' % (r[2]/1e3, e)
    if r[2] < 20e3: # This probably shouldn't happen
        stop = True
        print 'Particle got to 20 km'
    if steps > steps_max:
        stop = True
        print 'Max steps reached (TEMPORARY)'
        
print '%i steps' % steps
print '%i elastic collisions' % len(r_elastic_collision)
print '%i excitation collisions' % len(r_excitation_collision)
print '%i secondaries to trace' % secondary_count
print 'Total simulated time: %e sec' % t
Particle thermalized at 278.4 km with energy 1.15 eV
4066 steps
357 elastic collisions
0 excitation collisions
10 secondaries to trace
Total simulated time: 3.411231e+01 sec
In [82]:
rh = array(r_history)
plot(rh[:,0]/1e3,rh[:,2]/1e3,'k-',linewidth=0.5) # Particle track
plot(rh[0,0]/1e3,rh[0,2]/1e3,'ko') # Particle start
rhel = array(r_elastic_collision)
plot(rhel[:,0]/1e3,rhel[:,2]/1e3,'k.',markersize=2)
if len(r_excitation_collision) > 0:
    rhex = array(r_excitation_collision)
    plot(rhex[:,0]/1e3,rhex[:,2]/1e3,'r.')

xlabel('x [km]');
ylabel('z [km]');
xlim((min(rh[:,0]/1e3)-10, max(rh[:,0]/1e3)+10));
ylim((50,1100));
#xlim((-3500,100));
title('Path of incident particle');

Run entire tree of particles

In [83]:
def trace_particle(r, v, state, depth, th_min = 1e-9, e_min = 1.96, max_depth = np.inf, verbose = True, steps_max=1e6):
    '''
    Recursive function to trace a single particle. It calls itself to trace its secondaries.
    '''
    
    # For local, thermalized primaries and secondaries, record their energy and altitude
    final_e = [] # eV
    final_alt = [] # m
    
    # For tracking O(1D) production
    excited_alt = []
    excited_v = []

    # Temporary variables for tracking this particle
    r_history = []
    r_elastic_collision = []
    r_excitation_collision = []
    t_history = []
    
    # For tracking secondary particle locations:
    r_histories = []
    secondary_particles = 0

    steps = 0
    t = 0
    stop = False

    while(not stop):

        # Density and cross sections
        e = 0.5*m_O/q*norm(v)**2 # eV
        sigma_elastic    = elastic_xsec(e, th_min)    # cm^2
        sigma_excitation = excitation_xsec(e) # cm^2
        n_O = density_O(r[2]) # cm^-3

        # Mean free path
        mfp = 1e-2/(n_O*(sigma_elastic + sigma_excitation)) # m

        # Step size
        H = 50e3 # scale size, approximate for now
        ds = 0.1 * min(H,mfp) # so that step is shorter than the scale size and collisional scale
        dt = ds/norm(v) # sec

        # Determine if collision happened
        pc = 1 - exp(-ds/mfp)
        collision = rand() < pc

        if collision:
            # Determine type of collision
            collision_probs = array([sigma_elastic, sigma_excitation])
            collision_pmf = collision_probs/sum(collision_probs)
            # Randomly sample from the pmf
            collision_cdf = cumsum(collision_pmf)
            u = rand()
            collision_type = sum(u>=collision_cdf)
            ################## Elastic collision ########################
            if collision_type == 0:
                # Random scattering angle
                u = rand()
                chi = elastic_scatt_angle_icdf(e, u, th_min)
                # Rotate to collision frame
                v1 = v.copy()
                v2 = 0.0 # assume ambient particle is at rest
                ez = v1/norm(v1)
                ey_tmp = cross(v1, array([0,1,0]))
                ey = ey_tmp/norm(ey_tmp)
                ex = cross(ey,ez)
                A = zeros((3,3))
                A[:,0] = ex
                A[:,1] = ey
                A[:,2] = ez
                v1p = A.T.dot(v1-v2)
                v2p = 0.0
                # Velocity magnitudes after collision
                m1 = m_O
                m2 = m_O
                v1p_mag_new = sqrt(m1**2 + 2*m1*m2*cos(chi) + m2**2)/(m1+m2) * norm(v1p)
                v2p_mag_new = 2*m1/(m1+m2) * norm(v1p) * sin(chi/2)
                # Scattering angles
                th1p = arctan2(m2*sin(chi), m1 + m2*cos(chi))
                th2p = (pi-chi)/2
                phi1p = 2*pi*rand()
                # New velocity vectors
                v1p_new = v1p_mag_new * array([sin(th1p)*sin(phi1p),    sin(th1p)*cos(phi1p),    cos(th1p)])
                v2p_new = v2p_mag_new * array([sin(th2p)*sin(phi1p+pi), sin(th2p)*cos(phi1p+pi), cos(th2p)])
                v1_new = A.dot(v1p_new) + v2
                v2_new = A.dot(v2p_new) + v2                
                # Record secondary particle for later
                e2 = 0.5*m_O/q*norm(v2_new)**2                
                if e2 < e_min: # No need to trace secondary. Just record its energy.
                    final_e.append(e2)
                    final_alt.append(r[2])
                elif depth >= max_depth: # Just record final quantities and move on
                    final_e.append(e2)
                    final_alt.append(r[2])
                else: # Trace all secondaries
                    fe,fa,ea,ev,rh = trace_particle(r, v2_new, 0, depth+1, th_min=th_min, e_min=e_min,
                                                    max_depth=max_depth, verbose=verbose)
                    r_histories.extend(rh)
                    final_e.extend(fe)
                    final_alt.extend(fa)
                    excited_alt.extend(ea)
                    excited_v.extend(ev)
                    secondary_particles += 1
                    
                # Adjust this particle
                v = v1_new

                r_elastic_collision.append(r)

            ################# Excitation collision #######################
            elif collision_type == 1:
                # TODO: treat this as an elastic collision instead of a forward-scatter collision?
                # Determine which particle gets excited
                if rand() > 0.5: # Ambient particle
                    e2 = 0.0 # It is left undisturbed (for now)
                    if e2 < e_min: # Don't need to trace particle later.
                        # TODO: record O(1D) local production to be used later in emission calculation
                        excited_alt.append(r[2])
                        excited_v.append(array([0,0,0]))
                    else: # This can't occur, for now
                        pass

                else: # Incident particle
                    # Record information for O(1D) production rate. No need to record secondary particle
                    state = 1 # TODO: record information for O(1D) production rate throughout
                    excited_alt.append(r[2])
                    excited_v.append(v)

                # Subtract excitation energy from incident particle (not ambient particle?)
                e -= 1.96 # eV
                v_mag_new = sqrt(2*e*q/m_O)
                v = v_mag_new * v / norm(v)

                r_excitation_collision.append(r)

        else:
            # Step forward in time
            r = r + ds*v/norm(v)
            t = t + dt
        r_history.append(r)
        t_history.append(t)

        # Check halting conditions
        steps += 1
        if r[2] > 1100e3: # Escape flux
            stop = True
            if verbose:
                print 'Particle exited above 1100 km with energy %.1f eV' % e
        if e < e_min:
            stop = True
            if verbose:
                print 'Particle thermalized at %.1f km with energy %.2f eV' % (r[2]/1e3, e)
        if steps > steps_max:
            stop = True
            if verbose:
                print 'Max steps reached (TEMPORARY)'
        
    # Concatenate this path with the secondaries
    r_histories.append(r_history)
    final_e.append(e)
    final_alt.append(r[2])
    if verbose:
        print '\t%i Depth level' % depth
        print '\t%i steps' % steps
        print '\t%i elastic collisions' % len(r_elastic_collision)
        print '\t%i excitation collisions' % len(excited_alt)
        print '\t%i secondaries to trace' % secondary_particles
    
    return final_e, final_alt, excited_alt, excited_v, r_histories
In [99]:
# Use monoenergetic flux
e_0 = 1e3 # eV
N_particles = 50
max_depth = np.inf
th_min = 1e-6
e_min = 1.96
verbose = False
steps_max = 1e6

final_e_all = []
final_alt_all = []
excited_alt_all = []
excited_v_all = []

t0 = datetime.now()
for i in range(N_particles):

    # Initialize variables for this particle
    e = e_0
    ru = rand() # randomized initial direction as per:
    rv = rand() # http://mathworld.wolfram.com/SpherePointPicking.html
    th = arccos(rv-1) # isotropic over downward hemisphere
    ph = 2*pi*ru
    v_mag = sqrt(2*e*q/m_O)
    v = v_mag * array([sin(th)*sin(ph), sin(th)*cos(ph), cos(th)])
    r = array([0, 0, 1000e3]) # m/s [x,y,z]
    state = 0 # 0 = 3P, 1 = 1D

    final_e, final_alt, excited_alt, excited_v, rh_all = trace_particle(r,v,state,0, th_min, e_min, 
                                                                        max_depth, verbose, steps_max)
    
    final_e_all.extend(final_e)
    final_alt_all.extend(final_alt)
    excited_alt_all.extend(excited_alt)
    excited_v_all.extend(excited_v)
    
    if not verbose: # if no detailed output, at least output particle number:
        clear_output(wait=True)
        time.sleep(0.1)
        print '%03i/%03i' % (i+1, N_particles)
        sys.stdout.flush()
t1 = datetime.now()
050/050
In [100]:
t1 = datetime.now()
In [101]:
N_particles = i+1 # However many were actually run
In [102]:
t = (t1-t0).total_seconds()
t_per = t/N_particles
print '%f sec per particle' % t_per
print '%f hours for N=1e3' % (t_per*1e3/3600)
print '%f days for N=1e6' % (t_per*1e6/86400)
6.169289 sec per particle
1.713691 hours for N=1e3
71.403809 days for N=1e6
In [103]:
final_e = array(final_e_all)
final_alt = array(final_alt_all)
excited_alt = array(excited_alt_all)
excited_v = array(excited_v_all)
rh_all = array(rh_all)

Path Visualizations

In [89]:
figure(figsize=(5,5))

rh = array(rh_all[-1])
# Create subsampled track
s = 10
idx = range(0,shape(rh)[0],s) + [-1]
plot(rh[idx,0]/1e3,rh[idx,2]/1e3,'k-',linewidth=2) # Particle track

for i in range(len(rh_all)-1):
#for i in range(2):
    rh = array(rh_all[i])
    if len(rh) > 0:
        idx = range(0,shape(rh)[0],s) + [-1]
        plot(rh[idx,0]/1e3,rh[idx,2]/1e3,'-',linewidth=0.5) # Particle track

title('Example Path')
xlabel('x [km]')
ylabel('z [km]')
#xlim((min(rh[:,0]/1e3)-10, max(rh[:,0]/1e3)+10));
ylim((75,1100))
clear_output()
#xlim((350,360));
#mpld3.display()
In [90]:
figure(figsize=(10,10))

rh = array(rh_all[-1])
plot(rh[:,0]/1e3,rh[:,2]/1e3,'k-',linewidth=2) # Particle track
xfinal = rh[-1,0]
zfinal = rh[-1,2]

for i in range(len(rh_all)-1):
    rh = array(rh_all[i])
    if len(rh) > 0:
        plot(rh[:,0]/1e3,rh[:,2]/1e3,'-',linewidth=0.5) # Particle track

rh = array(rh_all[-1])
plot(rh[:,0]/1e3,rh[:,2]/1e3,'k-',linewidth=2) # Particle track

xlabel('x [km]');
ylabel('z [km]');
#xlim((min(rh[:,0]/1e3)-10, max(rh[:,0]/1e3)+10))
#ylim((100,300));
ylim((zfinal/1e3-100,zfinal/1e3+100))
xlim((xfinal/1e3-100,xfinal/1e3+100));
In [91]:
print '%i secondaries' % len(rh_all)
414 secondaries
In [92]:
idx_esc = (final_alt >= 1100e3)
e_tot = sum(final_e) + 1.96*len(excited_alt)
e_esc = sum(final_e[idx_esc])
e_heat = sum(final_e[~idx_esc])
e_exc = 1.96*len(excited_alt)
#print 'Escape energy: %f eV (%.2f %%)' % (e_esc, 100*e_esc/e_tot)
#print 'Heat energy: %f eV (%.2f %%)' % (e_heat, 100*e_heat/e_tot)
#print 'Excitation energy: %f eV (%.2f %%)' % (e_exc, 100*e_exc/e_tot)
#print 'Total energy: %f eV' % (e_tot)

print 'Energy partition for %.0f keV monoenergetic flux' % (e_0/1e3)
print '\tPrimary simulated particles: %i' % N_particles
print '\tTotal simulated particles:   %i' % len(final_e)
print ''
print '                 My model      Ishimoto et al. 1992'
print 'Heat:             %05.2f %%             78 %%' % (100*e_heat/e_tot)
print 'Escape:           %05.2f %%             17 %%' % (100*e_esc/e_tot)
print 'Ionization:                            5 %'
print '1D Excitation:    %05.2f %%                  ' % (100*e_exc/e_tot)
Energy partition for 1 keV monoenergetic flux
	Primary simulated particles: 5
	Total simulated particles:   30222

                 My model      Ishimoto et al. 1992
Heat:             72.48 %             78 %
Escape:           27.49 %             17 %
Ionization:                            5 %
1D Excitation:    00.04 %                  

Heating

In [93]:
alt_bin_edge = logspace(log10(80e3),log10(1100e3),200)
alt  = (alt_bin_edge[1:] + alt_bin_edge[:-1])/2
e_dep = zeros(len(alt))
for i in range(len(alt)):
    idx = (final_alt < alt_bin_edge[i+1]) & (final_alt >= alt_bin_edge[i])
    e_dep[i] = sum(final_e[idx])

# Scale to a 1 erg/cm^3/s source
e_model = e_0*N_particles # particle energy * number of particles
d_alt = alt_bin_edge[1:]-alt_bin_edge[:-1]
heat_rate = e_dep/d_alt*6.2415e11/e_model*1e-2 # eV/cm^3/s. See notes.
    
#heat_rate[heat_rate==0.0] = 1e-12 # because
In [94]:
semilogx(heat_rate,alt/1e3,'k-')

xlabel('Heating rate [eV/cm^3/s]')
ylabel('Altitude [km]')
Out[94]:
<matplotlib.text.Text at 0x7f37341bb610>
In [95]:
# Extracted from Ishimoto 1992 using: http://arohatgi.info/WebPlotDigitizer/
# 20 keV
xy = array([
1.05103e+1, 4.78217e+2,
1.37053e+1, 4.63678e+2,
1.97419e+1, 4.48222e+2,
3.08968e+1, 4.30946e+2,
3.70821e+1, 4.23672e+2,
6.73799e+1, 4.01846e+2,
1.14572e+2, 3.83655e+2,
2.29970e+2, 3.54562e+2,
4.93269e+2, 3.24557e+2,
8.38747e+2, 3.04551e+2,
1.09372e+3, 2.91826e+2,
2.12368e+3, 2.69088e+2,
3.85883e+3, 2.46355e+2,
6.56150e+3, 2.28164e+2,
9.60968e+3, 2.14522e+2,
1.38424e+4, 2.00882e+2,
2.27692e+4, 1.83601e+2,
3.33468e+4, 1.70867e+2,
4.27685e+4, 1.62680e+2,
4.96553e+4, 1.57224e+2,
5.57696e+4, 1.51770e+2,
6.16063e+4, 1.45410e+2,
6.16063e+4, 1.39058e+2,
5.67025e+4, 1.34527e+2,
4.88383e+4, 1.31817e+2,
4.06921e+4, 1.28202e+2,
2.96908e+4, 1.26412e+2,
2.09568e+4, 1.23717e+2,
1.31703e+4, 1.21031e+2,
6.78285e+3, 1.18361e+2,
3.32364e+3, 1.17510e+2,
1.89085e+3, 1.15739e+2,
1.05802e+3, 1.13970e+2,
6.22226e+2, 1.14012e+2,
3.91038e+2, 1.12234e+2,
1.67797e+2, 1.09578e+2,
4.23449e+1, 1.10594e+2,
1.00000e+1, 1.09800e+2,
])
In [96]:
# Extracted from Ishimoto 1992 using: http://arohatgi.info/WebPlotDigitizer/
# 1 keV
xy = array([
4.69239e+2, 4.96767e+2,
5.88666e+2, 4.85321e+2,
9.26287e+2, 4.67719e+2,
1.82851e+3, 4.38671e+2,
4.38347e+3, 4.02582e+2,
8.93747e+3, 3.73536e+2,
1.79319e+4, 3.41844e+2,
2.56061e+4, 3.25998e+2,
3.26487e+4, 3.12789e+2,
3.83896e+4, 3.03983e+2,
4.30026e+4, 2.95173e+2,
4.73946e+4, 2.88126e+2,
4.97631e+4, 2.80193e+2,
4.89789e+4, 2.71374e+2,
4.66716e+4, 2.63434e+2,
3.84510e+4, 2.54601e+2,
2.87493e+4, 2.45760e+2,
2.18446e+4, 2.38684e+2,
1.26086e+4, 2.32469e+2,
6.39475e+3, 2.25363e+2,
2.84962e+3, 2.19128e+2,
1.02916e+3, 2.11113e+2,
3.65718e+2, 2.03979e+2,
1.43180e+2, 2.01262e+2,
4.84675e+1, 1.95888e+2,
1.06024e+1, 1.91363e+2,  
    ])
In [97]:
alt_i92 =       xy[1::2]
heat_rate_i92 = xy[0::2]

semilogx(heat_rate,alt/1e3,'k-', label='My model')
semilogx(heat_rate_i92, alt_i92, 'g-', linewidth=2, label='Ishimoto92')
legend(loc='best');
xlim((1e0, 1e6));
ylim((0,600));
xlabel('Heating rate [eV/cm^3/s]');
ylabel('Altitude [km]');
In [98]:
print '                         My model      Ishimoto et al. 1992'
print 'Peak Heating Altitude     %.0f km             %.0f km' % \
                        (alt[argmax(heat_rate)]/1e3, alt_i92[argmax(heat_rate_i92)])
                         My model      Ishimoto et al. 1992
Peak Heating Altitude     222 km             280 km

Excitation

In [87]:
alt_bin_edge = logspace(log10(80e3),log10(1100e3),100)
alt  = (alt_bin_edge[1:] + alt_bin_edge[:-1])/2
excitation_counts = zeros(len(alt))
for i in range(len(alt)):
    idx = (excited_alt < alt_bin_edge[i+1]) & (excited_alt >= alt_bin_edge[i])
    excitation_counts[i] = sum(idx)

# Scale to a 1 erg/cm^3/s source
e_model = e_0*N_particles # particle energy * number of particles
d_alt = alt_bin_edge[1:]-alt_bin_edge[:-1]
prod_rate = excitation_counts/d_alt*6.2415e11/e_model*1e-2 # cm^-3 s^-1. See notes.
    
#heat_rate[heat_rate==0.0] = 1e-12 # because
In [90]:
semilogx(prod_rate,alt/1e3,'k.-',markersize=2)

xlabel('$O(^1\!D)$ production rate [$cm^{-3} s^{-1}$]');
ylabel('Altitude [km]');
In [94]:
brightness_R = sum(prod_rate*d_alt*1e2)*1e4*1e-10
print 'Column brightness if all of those O(1D) immediately emitted: %f R' % brightness_R
Column brightness if all of those O(1D) immediately emitted: 87.131340 R
In [135]:
import subprocess
import shutil
nb_name = 'Model_v01.ipynb' # There's probably a way to get this programmatically
cmd = ['ipython', 'nbconvert', '--to', 'slides', nb_name, \
       '--reveal-prefix', '"http://cdn.jsdelivr.net/reveal.js/2.6.2"',\
      '--template','output_toggle']
# The --template command points to a custom template which opens and closes the input code cells with a click
subprocess.call(cmd)
html_name = '%s.slides.html' % (nb_name.split('.')[0])
shutil.copy(html_name, '/home/bhardin2/public_html/slideshows/')
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]: