Use LMMSE estimator by constructing mean and cov matrix using randomized IRI runs

In [317]:
%pylab inline
from pyglow import pyglow
from datetime import datetime, timedelta
import sys
import time
from IPython.display import display, clear_output

# Make default figure size bigger
matplotlib.rcParams['savefig.dpi'] = 150
matplotlib.rcParams['figure.figsize'] = (4,3)
matplotlib.rcParams['font.size'] = 8
Populating the interactive namespace from numpy and matplotlib
WARNING: pylab import has clobbered these variables: ['datetime']
`%matplotlib` prevents importing * from pylab and numpy
In [2]:
def get_solar_zenith_angle(pt):
    '''
    Calculate the angle from zenith to the sun at the time and
    location of the pyglow.Point pt.
    INPUT:
        pt - pyglow.Point
    OUTPUT:
        sza - Solar zenith angle (radians)
    '''
    import ephem
    sun = ephem.Sun()
    obs = ephem.Observer()
    obs.lon = str(pt.lon)
    obs.lat = str(pt.lat)
    obs.date = pt.dn.strftime('%Y/%m/%d %H:%M:%S')
    obs.pressure = 0. # ignore refraction. This makes a negligible difference.
    obs.elevation = 1000*pt.alt # This makes a negligible difference.
    sun.compute(obs)
    sza = np.pi/2 - float(sun.alt) # radians
    return sza

def get_rand_nighttime_pt(altmax = 600):
    stop = False
    while(not stop):
        year = randint(2001,2013)
        doy = randint(1,366)
        t = datetime(year,1,1) + timedelta(days=doy-1)
        lon = 360*rand()
        lat = 90*rand()-45
        pt = pyglow.Point(t,lat,lon,altmax)
        # Check if it's night
        sza = get_solar_zenith_angle(pt)
        stop = sza > pi/2
    return t,lat,lon
In [140]:
N = 100 # samples per profile
M = 500 # number of profiles

altvec = linspace(150,800,N)

P = zeros((N,M))

for m in range(M):
    t,lat,lon = get_rand_nighttime_pt()
    ne = zeros(N)
    for i in range(N):
        pt = pyglow.Point(t,lat,lon,altvec[i])
        pt.run_iri()
        ne[i] = pt.ne  
    P[:,m] = ne
    clear_output(wait=True)
    time.sleep(0.01)
    print '%03i/%03i' % (m+1,M)
    sys.stdout.flush()
500/500
In [486]:
sc = 1e6
for m in range(M):
    plot(P[:,m]/sc,altvec,'k-',linewidth=0.2,alpha=0.4)
xlabel('$n_e [cm^{-3} \cdot 10^{-6}]$')
ylabel('Altitude [km]')
title('500 Random Nighttime IRI profiles')
Out[486]:
<matplotlib.text.Text at 0x7f127facadd0>
In [142]:
mux = mean(P,axis=1)
#Sig = 1./(M-1)*(P.dot(P.T) - outer(mu,mu))
Sigx = cov(P)

#Sig = Sig + diag(2e1*ones(70))

imshow(Sigx)
colorbar()
Out[142]:
<matplotlib.colorbar.Colorbar instance at 0x7f1287be25f0>
In [143]:
U,s,V = svd(Sigx)
In [144]:
s
Out[144]:
array([  3.50003717e+12,   3.04689693e+11,   4.31592528e+10,
         7.89405079e+09,   1.80227116e+09,   8.76517173e+08,
         2.14227350e+08,   9.99631976e+07,   3.43183064e+07,
         1.42262266e+07,   9.69662774e+06,   4.91740582e+06,
         2.94734872e+06,   1.83720305e+06,   1.52815088e+06,
         1.00301485e+06,   5.96784664e+05,   4.72746655e+05,
         4.07735601e+05,   2.80916201e+05,   2.03274607e+05,
         1.36518344e+05,   9.29780345e+04,   7.56312707e+04,
         5.56431559e+04,   2.47717542e+04,   1.73758168e+04,
         1.33858788e+04,   9.18796520e+03,   6.42127385e+03,
         4.80505347e+03,   4.13195143e+03,   2.06284388e+03,
         1.69968067e+03,   1.29322269e+03,   9.47274982e+02,
         5.88812346e+02,   5.64390077e+02,   4.89124809e+02,
         3.44086780e+02,   2.79918760e+02,   2.32660246e+02,
         1.44986673e+02,   2.05367864e+01,   3.34320428e+00,
         4.47809235e-01,   6.46653843e-02,   3.66110307e-03,
         9.26529921e-04,   7.21857762e-04,   6.50804925e-04,
         5.68078117e-04,   3.76073493e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   3.58489040e-04,
         3.58489040e-04,   3.58489040e-04,   2.40322805e-04,
         8.30298751e-05])
In [145]:
cond(Sigx)
Out[145]:
1.2989739534148186e+18
In [472]:
sigma = sqrt(diag(Sigx))
errorbar(mu/sc,altvec,xerr=sigma/sc,fmt='k-',alpha=0.5)
xlabel('$n_e [cm^{-3} \cdot 10^{-6}]$')
ylabel('Altitude [km]')
title('Mean and Cov of ne from IRI')
Out[472]:
<matplotlib.text.Text at 0x7f128168abd0>
In [147]:
Siginv = np.linalg.inv(Sigx)
imshow(Siginv)
colorbar()
Out[147]:
<matplotlib.colorbar.Colorbar instance at 0x7f1287efc518>
In [148]:
from scipy.linalg import sqrtm
Lest = sqrtm(Siginv)
In [149]:
L = real(Lest)
imshow(L)
colorbar()
Out[149]:
<matplotlib.colorbar.Colorbar instance at 0x7f128c5f8680>
In [ ]:
 
In [ ]:
 
In [ ]:
 

Simulate inversion

In [325]:
import MIGHTI
import ICON

satalt = 850.
#Sign = 

th0 = MIGHTI.tanht2angle(altvec[0],satalt)
th1 = MIGHTI.tanht2angle(altvec[-1],satalt)
thvec = linspace(th1,th0,N)

A,rtop,rmid,rbot = ICON.create_cells_Matrix_spherical_symmetry(thvec,satalt)

A = A*1e-6 # units?
In [326]:
imshow(A)
colorbar()
Out[326]:
<matplotlib.colorbar.Colorbar instance at 0x7f12848394d0>
In [489]:
idx = randint(M)
idx = 67
print idx
xtrue = P[:,idx] # pick a "random" column

ztrue = A.dot(xtrue)

Sign = diag(ztrue)
n = random.multivariate_normal(zeros(N), Sign)

z = ztrue + n
67

Pick a single profile, do forward model, then invert.

This is the measurement, with noise:

$$ z = Ax $$
In [490]:
plot(ztrue,altvec,'r')
plot(z,altvec,'k')
xlabel('Measurement [arb]')
ylabel('Tangent Altitude [km]')
Out[490]:
<matplotlib.text.Text at 0x7f127ef8ba50>

Solve LMMSE problem with IRI-generated statistics

Solution to Linear MMSE problem: $$ x_{LMMSE} = \Sigma_x A^T (A \Sigma_x A^T + \Sigma_z)^{-1} (z - A \mu_x) + \mu_x$$

Note similarity with Bayesian MAP estimator using Gaussian prior:

$$ x \sim \mathcal{N}(\mu_x, \Sigma_x)$$$$ z \sim \mathcal{N}(A x, \Sigma_z)$$

Also note that Tikhonov regularization is of an similar form, except $\Sigma_x$ is replaced with $ (\alpha^2 L^T L)^{-1}$. So the difference here is that:

  • We're using IRI to create $\Sigma_x$ instead of pre-supposing a form for it.
  • We don't have to choose $\alpha$.
In [491]:
R = A.dot(Sigx.dot(A.T)) + Sign
x_lmmse = Sigx.dot(A.T).dot(np.linalg.solve(R, z-A.dot(mux))) + mux
# TODO: Calculate Sig_x_lmmse
In [492]:
sc = 1e6
plot(xtrue/sc,altvec,'k-',linewidth=2,label='Truth')
plot(x_lmmse/sc,altvec,'r-',label='Rec. IRI prior')
#plot(x_t/sc,altvec,'g-',label='Rec. Tikh 2nd order, Lcurve')
xlabel('$n_e [cm^{-3} \cdot 10^{-6}]$')
ylabel('Altitude [km]')
legend(loc='best',fancybox=True)
Out[492]:
<matplotlib.legend.Legend at 0x7f127ea94950>

Solve using 2nd order Tikhonov with L-curve

In [493]:
L = -2*diag(ones(N)) + diag(ones(N-1),1) + diag(ones(N-1),-1)
L = L[1:-1,:]
Signinv = np.linalg.inv(Sign)
In [501]:
def get_x_t(alpha):
    R = A.T.dot(Signinv.dot(A)) + alpha* L.T.dot(L)
    x_t = np.linalg.solve(R, A.T.dot(Signinv.dot(z)))
    res = A.dot(x_t) - z
    #Y = log10(res.dot(Siginv.dot(res)))
    Y = log10(res.dot(res))
    X = log10(norm(L.dot(x_t))**2)
    return x_t,X,Y

Nalpha = 300
alphavec = logspace(-10,0,Nalpha)
Xvec = zeros(Nalpha)
Yvec = zeros(Nalpha)
for i in range(Nalpha):
    x_t,X,Y = get_x_t(alphavec[i])
    Xvec[i] = X
    Yvec[i] = Y
    
# Evaluate second derivative at every point and find max
Cvec = zeros(Nalpha)
for i in range(1,Nalpha-1):
    da = alphavec[i+1]-alphavec[i-1]
    dxda = (Xvec[i+1]-Xvec[i-1])/da
    dyda = (Yvec[i+1]-Yvec[i-1])/da
    da2 = ((alphavec[i+1]-alphavec[i-1])/2)**2
    d2yda2 = (Yvec[i-1] - 2*Yvec[i] + Yvec[i+1])/da2
    d2xda2 = (Xvec[i-1] - 2*Xvec[i] + Xvec[i+1])/da2
    Cvec[i] = -(dxda*d2yda2 - dyda*d2xda2)/(dxda**2 + dyda**2)**(1.5)
    
    #da2 = (alphavec[i+1] - alphavec[i-1])/2
    #d2y = (Yvec[i+1] - 2*Yvec[i] + Yvec[i-1])/da2
    #d2x = (Xvec[i+1] - 2*Xvec[i] + Xvec[i-1])/da2
    #Cvec[i] = d2y/d2x
In [502]:
istar = argmax(Cvec)
In [503]:
figure(figsize=(6,6))
subplot(2,2,1)
plot(Xvec,Yvec,'k.-')
plot(Xvec[istar],Yvec[istar],'ro')

subplot(2,2,2)
plot(Xvec,Yvec,'k.-')
plot(Xvec[istar],Yvec[istar],'ro')

nspread = 10
xlim((Xvec[istar+nspread],Xvec[istar-nspread]))
ylim((Yvec[istar-nspread],Yvec[istar+nspread]))

subplot(2,2,3)
plot(Xvec,Cvec,'g.-')

subplot(2,2,4)
plot(Xvec,Cvec,'g.-')

xlim((Xvec[istar+nspread],Xvec[istar-nspread]))
ylim((min(Cvec[istar-nspread:istar+nspread]),max(Cvec[istar-nspread:istar+nspread])))
Out[503]:
(0.6164740762890395, 0.91480782313620734)
In [504]:
alphastar = alphavec[istar]
x_t,_,_ = get_x_t(alphastar)

Here is the comparison with 2nd-order Tikhonov regularization using L-curve:

In [507]:
sc = 1e6
plot(xtrue/sc,altvec,'k-',linewidth=2,label='Truth')
plot(x_lmmse/sc,altvec,'r-',label='Rec. IRI prior')
plot(x_t/sc,altvec,'g-',label='Rec. Tikh 2nd order, Lcurve')
xlabel('$n_e [cm^{-3} \cdot 10^{-6}]$')
ylabel('Altitude [km]')
legend(loc='best',fancybox=True,prop={'size':8})
Out[507]:
<matplotlib.legend.Legend at 0x7f127ec01a90>
In [ ]:
figure(figsize=(3,3))
plot(Xvec,Yvec,'k-')
plot(Xvec[istar],Yvec[istar],'ro')
title('L-curve')
Out[ ]:
<matplotlib.text.Text at 0x7f127e4326d0>

Put it all together

In [500]:
idx = randint(M)
print idx
xtrue = P[:,idx] # pick a "random" column

ztrue = A.dot(xtrue)

Sign = diag(ztrue)
n = random.multivariate_normal(zeros(N), Sign)

z = ztrue + n


R = A.dot(Sigx.dot(A.T)) + Sign
x_lmmse = Sigx.dot(A.T).dot(np.linalg.solve(R, z-A.dot(mux))) + mux




L = -2*diag(ones(N)) + diag(ones(N-1),1) + diag(ones(N-1),-1)
L = L[1:-1,:]
Signinv = np.linalg.inv(Sign)

    
def get_x_t(alpha):
    R = A.T.dot(Signinv.dot(A)) + alpha* L.T.dot(L)
    x_t = np.linalg.solve(R, A.T.dot(Signinv.dot(z)))
    res = A.dot(x_t) - z
    #Y = log10(res.dot(Siginv.dot(res)))
    Y = log10(res.dot(res))
    X = log10(norm(L.dot(x_t))**2)
    return x_t,X,Y

Nalpha = 200
alphavec = logspace(-10,0,Nalpha)
Xvec = zeros(Nalpha)
Yvec = zeros(Nalpha)
for i in range(Nalpha):
    x_t,X,Y = get_x_t(alphavec[i])
    Xvec[i] = X
    Yvec[i] = Y
    
# Evaluate second derivative at every point and find max
Cvec = zeros(Nalpha)
for i in range(1,Nalpha-1):
    da = alphavec[i+1]-alphavec[i-1]
    dxda = (Xvec[i+1]-Xvec[i-1])/da
    dyda = (Yvec[i+1]-Yvec[i-1])/da
    da2 = ((alphavec[i+1]-alphavec[i-1])/2)**2
    d2yda2 = (Yvec[i-1] - 2*Yvec[i] + Yvec[i+1])/da2
    d2xda2 = (Xvec[i-1] - 2*Xvec[i] + Xvec[i+1])/da2
    Cvec[i] = -(dxda*d2yda2 - dyda*d2xda2)/(dxda**2 + dyda**2)**(1.5)
    
    #da2 = (alphavec[i+1] - alphavec[i-1])/2
    #d2y = (Yvec[i+1] - 2*Yvec[i] + Yvec[i-1])/da2
    #d2x = (Xvec[i+1] - 2*Xvec[i] + Xvec[i-1])/da2
    #Cvec[i] = d2y/d2x
    
    
istar = argmax(Cvec)
alphastar = alphavec[istar]
x_t,_,_ = get_x_t(alphastar)


plot(Xvec,Yvec,'k.-')
plot(Xvec[istar],Yvec[istar],'ro')
#xlim((Xvec[istar]-0.1,Xvec[istar]+0.1))
#ylim((Yvec[istar]-0.1,Yvec[istar]+0.1))

figure()
sc = 1e6
plot(xtrue/sc,altvec,'k-',linewidth=2,label='Truth')
plot(x_lmmse/sc,altvec,'r-',label='Rec. IRI prior')
plot(x_t/sc,altvec,'g-',label='Rec. Tikh 2nd order, Lcurve')
xlabel('$n_e [cm^{-3} \cdot 10^{-6}]$')
ylabel('Altitude [km]')
legend(loc='best',fancybox=True)
245
Out[500]:
<matplotlib.legend.Legend at 0x7f127ebe65d0>

What if ne isn't close to IRI?

In [ ]:
idx = 67
xtrue = P[:,idx] # pick a "random" column
xtrue[]

ztrue = A.dot(xtrue)

Sign = diag(ztrue)
n = random.multivariate_normal(zeros(N), Sign)

z = ztrue + n
In [ ]:
 
In [511]:
import subprocess
import shutil
nb_name = 'FUV_Choosing_L_using_IRI.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/')
print('http://remote2.ece.illinois.edu/~bhardin2/slideshows/%s' % html_name)
http://remote2.ece.illinois.edu/~bhardin2/slideshows/FUV_Choosing_L_using_IRI.slides.html
In [ ]: