plot_sky_brightness_model

Visualizing the machine-learned (PTF) sky brightness model


In [1]:
# hack to get the path right
import sys
sys.path.append('..')

In [27]:
from ztf_sim.SkyBrightness import SkyBrightness
from ztf_sim.magnitudes import limiting_mag
import astropy.units as u
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

In [3]:
sns.set_style('ticks')
sns.set_context('talk')

In [4]:
# load the training data
df = pd.read_csv('../data/ptf-iptf_diq.csv.gz')

In [5]:
df.head()


Out[5]:
Unnamed: 0 pid mjd sky_brightness fwhmsex airmass moonphas moonillf moonalt azimuth altitude limiting_mag moon_dist filterkey sunalt
0 0 9526066.0 55855.35663 20.9546 2.55 1.28813 255.138 -0.371756 3.82645 282.3300 50.8821 21.4367 121.233998 1 -63.249279
1 1 9526067.0 55855.11654 20.4636 2.85 2.42588 258.154 -0.397358 -43.28850 79.3789 24.2281 20.9579 95.767669 1 -21.509225
2 2 9526068.0 55855.37666 20.7441 2.45 1.26798 254.885 -0.369625 9.51225 144.7150 52.0187 21.3802 66.048980 1 -59.257531
3 3 9526069.0 55855.17671 21.0459 2.65 1.35159 257.401 -0.390933 -38.17270 145.8370 47.6722 21.4349 132.815569 1 -39.405352
4 4 9526070.0 55855.11294 21.0488 2.45 1.36807 258.199 -0.397743 -43.34780 117.8580 46.9173 21.5206 134.900090 1 -20.426107

Let's look at the range of the data:


In [6]:
for col in df.columns:
    print('{}: {}-{}'.format(col, df[col].min(), df[col].max()))


Unnamed: 0: 0-1854348
pid: 9526066.0-30929549.0
mjd: 54891.11867999999-57806.373960000004
sky_brightness: 13.0019-24.9965
fwhmsex: 0.0-80.05
airmass: 1.0-13.2512
moonphas: 0.000607-360.0
moonillf: -1.0-1.0
moonalt: -82.7048-82.1672
azimuth: 0.0-359.996
altitude: 0.0-89.9013
limiting_mag: 13.4326-27.1295
moon_dist: 1.78679653575-179.826635825
filterkey: 1-4
sunalt: -80.07792245350001--10.4975803864

In [7]:
for filterkey in [1,2,4]:
    wbright = (df.filterkey==filterkey) & (df.moonillf > 0.95) & (df.moonalt > 70)
    print(filterkey, np.sum(wbright))
    plt.hist(df.loc[wbright,'limiting_mag'],normed=True,label=f'{filterkey}')
plt.legend()


1 225
2 5297
4 288
/Users/ebellm/anaconda3/envs/ztf_sim_dev/lib/python3.6/site-packages/matplotlib/axes/_axes.py:6571: UserWarning: The 'normed' kwarg is deprecated, and has been replaced by the 'density' kwarg.
  warnings.warn("The 'normed' kwarg is deprecated, and has been "
Out[7]:
<matplotlib.legend.Legend at 0x11d795d68>

In [8]:
for filterkey in [1,2,4]:
    w = (df.filterkey==filterkey)
    print(filterkey, np.percentile(df.loc[w,'limiting_mag'],[5,95]))


1 [20.1847 22.0179]
2 [19.866455 21.807   ]
4 [19.45588 21.1951 ]

So there are clearly outliers. Let's look at histograms to find good fiducial values:


In [9]:
plt.hist(np.abs(df.moonillf))
plt.xlabel('Moon Illumination Fraction')
plt.ylabel('Number of Exposures')
sns.despine()



In [10]:
plt.hist(df.moonalt)
plt.xlabel('Moon Altitude (degrees)')
plt.ylabel('Number of Exposures')
sns.despine()



In [11]:
plt.hist(df.moon_dist)
plt.xlabel('Moon Distance (degrees)')
plt.ylabel('Number of Exposures')
sns.despine()



In [12]:
plt.hexbin(df['moonalt'],df['moon_dist'])
plt.xlabel('Moon Altitude (deg)')
plt.ylabel('Moon Distance (deg)')


Out[12]:
Text(0,0.5,'Moon Distance (deg)')

In [13]:
plt.hist(df.azimuth)
plt.xlabel('Telescope Azimuth (degrees)')
plt.ylabel('Number of Exposures')
plt.xlim(0,360)
sns.despine()



In [14]:
plt.hist(df.altitude)
plt.xlabel('Telescope Altitude (degrees)')
plt.ylabel('Number of Exposures')
plt.xlim(0,90)
sns.despine()



In [15]:
plt.hist(df.sunalt)
plt.xlabel('Sun Altitude (degrees)')
plt.ylabel('Number of Exposures')
plt.xlim(-90,-10)
sns.despine()



In [16]:
w = (df['filterkey'] == 2)
wdark = (df['filterkey'] == 2) & (np.abs(df['moonillf']) < .1) & (df['moonalt'] < -10.)
wbright = (df['filterkey'] == 2) & (np.abs(df['moonillf']) > .9) & (df['moonalt'] > 70.)

print(df[w]['limiting_mag'].median())
normed=True
plt.hist(df[w]['limiting_mag'],bins=np.linspace(18,23,100),normed=normed,alpha=0.5)
plt.hist(df[wdark]['limiting_mag'],bins=np.linspace(18,23,100),normed=normed,alpha=0.5)
plt.hist(df[wbright]['limiting_mag'],bins=np.linspace(18,23,100),normed=normed,alpha=0.5)

plt.xlabel('Limiting Magnitude (R-band mag arcsec$^{-2}$)')
plt.ylabel('Number of Exposures')
sns.despine()


21.0915

In [17]:
for filterkey, filtername in {1:'g',2:'R',4:'i'}.items():
    plt.figure()
    w = (df['filterkey'] == filterkey)
    wdark = (df['filterkey'] == filterkey) & (np.abs(df['moonillf']) < .1) & (df['moonalt'] < -10.)
    wbright = (df['filterkey'] == filterkey) & (np.abs(df['moonillf']) > .9) & (df['moonalt'] > 70.)

    normed=True
    plt.hist(df[w]['limiting_mag'],bins=np.linspace(18,23,100),normed=normed,alpha=0.5)
    plt.hist(df[wdark]['limiting_mag'],bins=np.linspace(18,23,100),normed=normed,alpha=0.5)
    plt.hist(df[wbright]['limiting_mag'],bins=np.linspace(18,23,100),normed=normed,alpha=0.5)
    print(filterkey,df.loc[wdark,'limiting_mag'].median(),df.loc[wbright,'limiting_mag'].median())

    plt.xlabel(f'Limiting Magnitude ({filtername}-band mag arcsec$^{-2}$)')
    plt.ylabel('Number of Exposures')
    sns.despine()


1 21.5079 19.585
2 21.3257 20.076749999999997
4 20.630049999999997 19.9734

In [18]:
w = (df['filterkey'] == 1)
wdark = (df['filterkey'] == 1) & (np.abs(df['moonillf']) < .1) & (df['moonalt'] < -10.)
print(df[w]['limiting_mag'].median())
plt.hist(df[w]['limiting_mag'],bins=np.linspace(18,23,100))
plt.hist(df[wdark]['limiting_mag'],bins=np.linspace(18,23,100))

plt.xlabel('Limiting Magnitude (g-band mag arcsec$^{-2}$)')
plt.ylabel('Number of Exposures')
sns.despine()


21.3963

In [19]:
w = (df['filterkey'] == 4)
wdark = (df['filterkey'] == 4) & (np.abs(df['moonillf']) < .1) & (df['moonalt'] < -10.)
print(df[w]['limiting_mag'].median())
plt.hist(df[w]['limiting_mag'],bins=np.linspace(18,23,100))
plt.hist(df[wdark]['limiting_mag'],bins=np.linspace(18,23,100))
plt.xlabel('Limiting Magnitude (i-band mag arcsec$^{-2}$)')
plt.ylabel('Number of Exposures')
sns.despine()


20.5761

Let's look at the dependence of FWHM on altitude:


In [20]:
w = (df['filterkey'] == 2)
w &= (df['altitude'] > 20) & (df['fwhmsex'] < 5)
plt.hexbin(df[w]['altitude'],df[w]['fwhmsex'])
plt.xlabel('Altitude (deg)')
plt.ylabel('FWHM (arcsec)')
alts = np.linspace(20,90,100)
airmasses = 1./np.cos(np.radians(90.-alts))
plt.plot(alts,2*airmasses**(3./5))


Out[20]:
[<matplotlib.lines.Line2D at 0x1a22c39320>]

g-band


In [21]:
w = (df['filterkey'] == 1)
w &= (df['altitude'] > 20) & (df['fwhmsex'] < 5)
plt.hexbin(df[w]['altitude'],df[w]['fwhmsex'])
plt.xlabel('Altitude (deg)')
plt.ylabel('FWHM (arcsec)')
plt.plot(alts,2.4*airmasses**(3./5))


Out[21]:
[<matplotlib.lines.Line2D at 0x1a2314ee10>]

i-band


In [22]:
w = (df['filterkey'] == 4)
w &= (df['altitude'] > 20) & (df['fwhmsex'] < 5)
plt.hexbin(df[w]['altitude'],df[w]['fwhmsex'])
plt.xlabel('Altitude (deg)')
plt.ylabel('FWHM (arcsec)')
plt.plot(alts,2.0*airmasses**(3./5))


Out[22]:
[<matplotlib.lines.Line2D at 0x1a22d26eb8>]

In [5]:
# load our model
Sky = SkyBrightness()


[17:33:10] WARNING: /usr/local/miniconda/conda-bld/xgboost_1572315027083/work/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror.
[17:33:10] WARNING: /usr/local/miniconda/conda-bld/xgboost_1572315027083/work/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror.
[17:33:10] WARNING: /usr/local/miniconda/conda-bld/xgboost_1572315027083/work/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror.
/Users/ebellm/anaconda3/envs/ztf_sim/lib/python3.6/site-packages/sklearn/base.py:306: UserWarning: Trying to unpickle estimator StandardScaler from version 0.20.3 when using version 0.21.3. This might lead to breaking code or invalid results. Use at your own risk.
  UserWarning)
/Users/ebellm/anaconda3/envs/ztf_sim/lib/python3.6/site-packages/sklearn/base.py:306: UserWarning: Trying to unpickle estimator Pipeline from version 0.20.3 when using version 0.21.3. This might lead to breaking code or invalid results. Use at your own risk.
  UserWarning)

In [6]:
def test_conditions(moonillf=0,moonalt=40,moon_dist=60.,azimuth=120,altitude=60.,sunalt=-35,filter_id=2):
    """define convenience function for building inputs to SkyBrightness.
    
    Inputs should be scalars expect for one and only one vector.
    
    Note that manual inputs can yield unphysical moonalt/moon_dist pairs, but the real simulator will 
    calculate these correctly."""
    
    moonillf = np.atleast_1d(moonillf)
    moonalt = np.atleast_1d(moonalt)
    moon_dist = np.atleast_1d(moon_dist)
    azimuth = np.atleast_1d(azimuth)
    altitude = np.atleast_1d(altitude)
    sunalt = np.atleast_1d(sunalt)
    filter_id = np.atleast_1d(filter_id)
    
    maxlen = np.max([len(moonillf), len(moonalt), len(moon_dist), len(azimuth), len(altitude), len(sunalt),
                     len(filter_id)])
    
    def blow_up_array(arr,maxlen=maxlen):
        if (len(arr) == maxlen):
            return arr
        elif (len(arr) == 1):
            return np.ones(maxlen)*arr[0]
        else:
            raise ValueError
    
    moonillf = blow_up_array(moonillf)
    moonalt = blow_up_array(moonalt)
    moon_dist = blow_up_array(moon_dist)
    azimuth = blow_up_array(azimuth)
    altitude = blow_up_array(altitude)
    sunalt = blow_up_array(sunalt)
    filter_id = blow_up_array(filter_id)
    
    pars = {'moonillf':moonillf, 'moonalt':moonalt, 'moon_dist':moon_dist, 'azimuth':azimuth, 'altitude':altitude, 
           'sunalt':sunalt,'filter_id':filter_id}
    
    return pd.DataFrame(pars,index=np.arange(maxlen))

In [25]:
def select_data(df, moonillf=0,moonalt=40,moon_dist=60.,azimuth=120,altitude=60,sunalt=-35,filter_id=2):
    """return a boolean selecting data around fiducial inputs
    
    set parameter to None to skip comparison"""
    
    # start with boolean True array
    w = df['azimuth'] == df['azimuth']
    
    def anding(df,par,value,frac=0.2):
        if value is None:
            return True
        else:
            lima = value*(1-frac)
            limb = value*(1+frac)
            llim = np.atleast_1d(np.where(lima < limb, lima, limb))[0]
            ulim = np.atleast_1d(np.where(lima > limb, lima, limb))[0]
            # figure out which is smaller
            
            return (df[par] >= llim) & (df[par] <= ulim)
    
    w &= anding(df, 'moonillf', moonillf)
    w &= anding(df, 'moonalt', moonalt)
    w &= anding(df, 'moon_dist', moon_dist)
    w &= anding(df, 'azimuth', azimuth)
    w &= anding(df, 'altitude', altitude)
    w &= anding(df, 'sunalt', sunalt)
    
    # dataframe uses filterkey
    w &= (df['filterkey'] == filter_id)
    
    return w

In [7]:
# show that it works!
test_conditions(altitude=np.linspace(0,90,5))


Out[7]:
moonillf moonalt moon_dist azimuth altitude sunalt filter_id
0 0.0 40.0 60.0 120.0 0.0 -35.0 2.0
1 0.0 40.0 60.0 120.0 22.5 -35.0 2.0
2 0.0 40.0 60.0 120.0 45.0 -35.0 2.0
3 0.0 40.0 60.0 120.0 67.5 -35.0 2.0
4 0.0 40.0 60.0 120.0 90.0 -35.0 2.0

Calculate some limiting mag values in bright and dark time at zenith:


In [8]:
from ztf_sim.magnitudes import limiting_mag

In [28]:
fids = [1,2,3]
dark_condition = test_conditions(moonillf=0,moonalt=0,altitude=90,sunalt=-80,filter_id=fids)
sky_brightness = Sky.predict(dark_condition)
for fid, sky in zip(fids,sky_brightness.values):
    print(fid, sky, limiting_mag(30, 2.0, sky, filter_id = fid, altitude=90, SNR=5.))


1 20.960336685180664 [21.99426158]
2 20.456525802612305 [21.51140579]
3 19.72606086730957 [20.86588991]

In [29]:
bright_condition = test_conditions(moonillf=1.,moonalt=70.,moon_dist=20.,altitude=90,sunalt=-80,filter_id=[1,2,3])
sky_brightness = Sky.predict(bright_condition)
for fid, sky in zip(fids,sky_brightness.values):
    print(fid, sky, limiting_mag(30, 2.0, sky, filter_id = fid, altitude=90, SNR=5.))


1 16.665542602539062 [19.84686454]
2 17.646154403686523 [20.10622009]
3 16.800640106201172 [19.40317953]

Now let's walk through the various parameters and look at the model behavior.

Vary moon illilumination fraction at 45 degree altitude, R-band:

TOFIX

forgot to calculate limiting mag from the sky brightness through here


In [30]:
moonillfs = np.linspace(0,1,20)
dft = test_conditions(moonillf=moonillfs)
skies = Sky.predict(dft)
plt.plot(moonillfs,skies)
plt.gca().invert_yaxis()
plt.xlabel('Moon Illimination Fraction')
plt.ylabel('Sky Brightness (R-band mag arcsec$^{-2}$)')
sns.despine()



In [18]:
w =  select_data(df,moonillf=None)#,moonalt=45, moon_dist=45)
plt.scatter(np.abs(df[w]['moonillf']),df[w]['limiting_mag'],alpha=0.3)
plt.gca().invert_yaxis()
plt.xlabel('Moon Illimination Fraction')
plt.ylabel('Limiting Mag (R-band mag$)')
sns.despine()


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-18-9b39ab8206aa> in <module>
----> 1 w =  select_data(df,moonillf=None)#,moonalt=45, moon_dist=45)
      2 plt.scatter(np.abs(df[w]['moonillf']),df[w]['limiting_mag'],alpha=0.3)
      3 plt.gca().invert_yaxis()
      4 plt.xlabel('Moon Illimination Fraction')
      5 plt.ylabel('Limiting Mag (R-band mag$)')

NameError: name 'select_data' is not defined

In [32]:
moonillfs = np.linspace(0,1,20)
dft = test_conditions(moonillf=moonillfs,filter_id=1)
skies = Sky.predict(dft)
plt.plot(moonillfs,skies)
plt.gca().invert_yaxis()
plt.xlabel('Moon Illimination Fraction')
plt.ylabel('Sky Brightness (g-band mag arcsec$^{-2}$)')
sns.despine()


Not as smooth as we might expect. Let's look at the input data:

These data are surprisingly sparse!


In [33]:
moonillfs = np.linspace(0,1,20)
dft = test_conditions(moonillf=moonillfs,filter_id=3)
skies = Sky.predict(dft)
plt.plot(moonillfs,skies)
plt.gca().invert_yaxis()
plt.xlabel('Moon Illimination Fraction')
plt.ylabel('Sky Brightness (i-band mag arcsec$^{-2}$)')
sns.despine()



In [34]:
moon_dists = np.linspace(0,180,30)
dft = test_conditions(moonillf=0.8,moon_dist=moon_dists)
skies = Sky.predict(dft)
plt.plot(moon_dists,skies)
plt.gca().invert_yaxis()
plt.xlabel('Moon Distance (deg)')
plt.ylabel('Sky Brightness (R-band mag arcsec$^{-2}$)')
sns.despine()



In [35]:
azimuths = np.linspace(0,360,30)
dft = test_conditions(azimuth=azimuths)
skies = Sky.predict(dft)
plt.plot(azimuths,skies)
plt.gca().invert_yaxis()
plt.xlabel('Telescope Azimuth (deg)')
plt.ylabel('Sky brightness (R-band mag arcsec$^{-2}$)')
sns.despine()



In [36]:
altitudes = np.linspace(0,90,30)
dft = test_conditions(altitude=altitudes)
skies = Sky.predict(dft)
plt.plot(altitudes,skies)
plt.gca().invert_yaxis()
plt.xlabel('Telescope Altitude (deg)')
plt.ylabel('Sky Brightness (R-band mag arcsec$^{-2}$)')
sns.despine()



In [37]:
sunalts = np.linspace(-10,-80,30)
dft = test_conditions(sunalt=sunalts)
skies = Sky.predict(dft)
plt.plot(sunalts,skies)
plt.gca().invert_yaxis()
plt.xlabel('Sun Altitude (deg)')
plt.ylabel('Limiting Magnitude (R-band mag arcsec$^{-2}$)')
sns.despine()


low dec fields


In [29]:
filter_id = 2
seeing=2.0
alts = np.linspace(25,90,30)
dft = test_conditions(altitude=alts, azimuth=0, filter_id=filter_id)
skies = Sky.predict(dft)
limmag = limiting_mag(30*u.second,  seeing, skies, filter_id*np.ones(len(alts)), alts )
#plt.plot(alts,skies)
plt.plot(alts, limmag)
plt.gca().invert_yaxis()
plt.xlabel('Altitude (deg)')
plt.ylabel('Limiting Magnitude (mag)')
sns.despine()



In [15]:
from ztf_sim.utils import altitude_to_airmass

In [17]:
altitude_to_airmass(25)


Out[17]:
2.3662015831524985

In [30]:
def slot_metric(limiting_mag):
    metric = 10.**(0.6 * (limiting_mag - 21))
    # lock out -99 limiting mags even more aggressively
    return metric.where(limiting_mag > 0, -0.99)

In [32]:
metric = slot_metric(limmag)
plt.plot(alts, metric)
plt.xlabel('Altitude (deg)')
plt.ylabel('Metric value')
sns.despine()



In [52]:
seeing=2.0
alts = np.linspace(25,90,30)
for filter_id in [1,2,3]:
    dft = test_conditions(altitude=alts, azimuth=0, filter_id=filter_id)
    skies = Sky.predict(dft)
    limmag = limiting_mag(30*u.second,  seeing, skies, filter_id*np.ones(len(alts)), alts )
    metric = slot_metric(limmag)
    #airmass = altitude_to_airmass(alts)
    plt.plot(alts, metric/metric.iloc[-1], label=filter_id)
plt.plot(alts,-1e-4*(alts-90)**2.+1)
plt.legend()
plt.xlabel('Altitude (deg)')
plt.ylabel('Metric value')
sns.despine()



In [54]:
seeing=2.0
alts = np.linspace(25,90,30)
for filter_id in [1,2,3]:
    dft = test_conditions(altitude=alts, azimuth=0, filter_id=filter_id)
    skies = Sky.predict(dft)
    limmag = limiting_mag(30*u.second,  seeing, skies, filter_id*np.ones(len(alts)), alts )
    metric = slot_metric(limmag)
    #airmass = altitude_to_airmass(alts)
    plt.plot(alts, metric/(-1e-4*(alts-90)**2.+1), label=filter_id)
plt.legend()
plt.xlabel('Altitude (deg)')
plt.ylabel('Metric value')
sns.despine()



In [ ]: