End-to-End Validation Demo

This notebook implements an independent verification of the results from Sorcha on a one month LSST simulation for two objects.

The reference ephemerides for these objects (as CSV files) come from JPL Horizons queries. The reference query uses dates from 2023-10-08 UT to 2023-11-08, with one minute timesteps, for the objects 2011 OB60 (a TNO) and 2010 TU149 (MBA), as seen from the X05 observatory.

[1]:
import numpy as np
import astropy.table as tb
import pandas as pd
import sqlite3
[2]:
import matplotlib.pyplot as pl
[3]:
#read pointing database and restrict to our time baseline
con = sqlite3.connect('../../src/sorcha/data/demo/baseline_v2.0_1yr.db')

pointing = pd.read_sql("SELECT observationId, observationStartMJD as observationStartMJD_TAI, visitTime, visitExposureTime, filter, seeingFwhmGeom as seeingFwhmGeom_arcsec, seeingFwhmEff as seeingFwhmEff_arcsec, fiveSigmaDepth as fieldFiveSigmaDepth_mag , fieldRA as fieldRA_deg, fieldDec as fieldDec_deg, rotSkyPos as fieldRotSkyPos_deg FROM observations order by observationId", con=con, )

pointing
[3]:
observationId observationStartMJD_TAI visitTime visitExposureTime filter seeingFwhmGeom_arcsec seeingFwhmEff_arcsec fieldFiveSigmaDepth_mag fieldRA_deg fieldDec_deg fieldRotSkyPos_deg
0 0 60218.001806 34.0 30.0 y 0.667370 0.748626 22.370557 310.024480 -60.812928 62.750775
1 1 60218.002254 34.0 30.0 y 0.678175 0.761770 22.338327 310.601871 -63.561425 61.084250
2 2 60218.002703 34.0 30.0 y 0.690895 0.777245 22.295487 311.292611 -66.317774 60.726189
3 3 60218.003152 34.0 30.0 y 1.657640 1.953333 21.270421 312.140731 -69.082666 60.656781
4 4 60218.003624 34.0 30.0 y 1.713416 2.021188 21.205276 304.170163 -73.375442 49.095537
... ... ... ... ... ... ... ... ... ... ... ...
216228 216228 60583.103691 34.0 30.0 g 0.988866 1.139739 24.422508 8.496364 -29.615770 86.518379
216229 216229 60583.104140 34.0 30.0 g 1.003682 1.157764 24.397505 11.808603 -29.898183 86.777460
216230 216230 60583.104587 34.0 30.0 g 1.014421 1.170829 24.374331 13.363397 -27.303043 91.147723
216231 216231 60583.105032 34.0 30.0 g 1.026912 1.186025 24.343872 14.847339 -24.711444 94.995634
216232 216232 60583.105481 34.0 30.0 g 1.046555 1.209921 24.309309 18.100120 -24.787034 94.983283

216233 rows × 11 columns

[4]:
import astropy.time as ti
pointing = pointing[pointing['observationStartMJD_TAI'] > 60225]
pointing = pointing[pointing['observationStartMJD_TAI'] < 60255]
pointing['MJD_TDB'] = ti.Time(pointing['observationStartMJD_TAI'] + pointing['visitTime']/(2*86400.0), format='mjd', scale='tai').tdb.mjd
pointing
[4]:
observationId observationStartMJD_TAI visitTime visitExposureTime filter seeingFwhmGeom_arcsec seeingFwhmEff_arcsec fieldFiveSigmaDepth_mag fieldRA_deg fieldDec_deg fieldRotSkyPos_deg MJD_TDB
5398 5398 60225.000341 34.0 30.0 r 0.693873 0.780867 24.336680 325.777117 -40.076878 110.946937 60225.000910
5399 5399 60225.000800 34.0 30.0 r 0.696867 0.784510 24.335598 325.609267 -43.105406 102.956598 60225.001369
5400 5400 60225.001260 34.0 30.0 r 0.684560 0.769538 24.347565 332.965064 -46.169394 107.885215 60225.001829
5401 5401 60225.001709 34.0 30.0 r 0.689428 0.775460 24.340440 333.038252 -49.111447 102.767992 60225.002278
5402 5402 60225.002157 34.0 30.0 r 0.695373 0.782692 24.327080 333.059351 -52.057748 98.135653 60225.002726
... ... ... ... ... ... ... ... ... ... ... ... ...
24474 24474 60251.366258 34.0 30.0 i 0.699315 0.787488 23.270283 81.912982 -27.019050 234.493172 60251.366827
24475 24475 60251.366706 34.0 30.0 i 0.706234 0.795905 23.224385 79.796144 -24.525717 228.806467 60251.367276
24476 24476 60251.367202 34.0 30.0 i 0.702517 0.791383 23.199502 83.540274 -20.909984 218.028671 60251.367771
24477 24477 60251.367658 34.0 30.0 i 0.745439 0.843600 23.088602 78.410579 -19.065750 218.537314 60251.368227
24478 24478 60251.368161 34.0 30.0 i 0.742031 0.839454 23.098674 76.814813 -25.030965 230.902390 60251.368731

19081 rows × 12 columns

Now we have our reference pointings, on to the objects

[5]:
# read our objects
obj = {}
obj['2011 OB60'] = tb.Table.read('2011ob60.txt', format='csv')
obj['2010 TU149'] = tb.Table.read('2010tu149.txt', format='csv')
for i in obj:
    obj[i]['MJD'] = ti.Time(obj[i]['JDUT'],format='jd', scale='utc').tdb.mjd
[6]:
obj['2010 TU149']
[6]:
Table length=19908
JDUTcol1_1RADECAPmagS-brtrrdotdeltadeldotS-O-T/rS-T-OO-P-TPsAngPsAMVSky_motionSky_mot_PARelVel-ANGLun_Sky_Brtsky_SNR_2MJD
float64str1str1float64float64float64float64float64float64float64float64float64str2float64float64float64float64float64float64float64str5str7int64float64
2460225.5A--19.72102064.85670360821.4166.5271.696279513621-20.65034470.69968470063905-22.6844214173.2633/L3.96482.49276.388248.8811.6304974246.66601-58.70543n.a.n.a.--60225.00080072161
2460225.500694444A--19.7206032994.85652388321.4166.5271.696271230638-20.65041680.69967560204437-22.6836065173.2643/L3.96422.4896276.393248.8811.6307293246.66868-58.70123n.a.n.a.--60225.00149516555
2460225.501388889A--19.720185934.85634415321.4166.5271.696262947626-20.65048890.69966650377801-22.6827842173.2653/L3.96372.4892276.397248.8811.6309611246.67133-58.69703n.a.n.a.--60225.002189610415
2460225.502083333A--19.7197684944.85616441521.4166.5271.696254664586-20.65056090.69965740584296-22.6819545173.2663/L3.96312.4888276.402248.8811.6311926246.67398-58.69282n.a.n.a.--60225.00288405482
2460225.502777778A--19.719350994.85598467221.4156.5271.696246381516-20.6506330.69964830824217-22.6811174173.2673/L3.96252.4884276.407248.8811.6314239246.67662-58.6886n.a.n.a.--60225.00357849969
2460225.503472222A--19.7189334194.85580492221.4156.5271.696238098417-20.65070510.69963921097862-22.6802729173.2683/L3.9622.4879276.411248.8811.6316551246.67926-58.68438n.a.n.a.--60225.004272943625
2460225.504166667A--19.718515784.85562516621.4156.5261.69622981529-20.65077710.69963011405528-22.679421173.2693/L3.96142.4875276.416248.8811.631886246.68188-58.68016n.a.n.a.--60225.00496738849
2460225.504861111A--19.7180980754.85544540421.4156.5261.696221532134-20.65084920.69962101747509-22.6785618173.2703/L3.96082.4871276.421248.8811.6321167246.68451-58.67593n.a.n.a.--60225.00566183243
2460225.505555556A--19.7176803024.85526563521.4156.5261.696213248949-20.65092130.69961192124103-22.6776952173.2713/L3.96032.4867276.425248.8811.6323472246.68712-58.67169n.a.n.a.--60225.006356277765
........................................................................
2460256.49375N--350.607204357-7.19067628521.0787.0171.294842964898-24.38117010.44976224719555-5.9900853123.5297/T39.639617.157564.94925.2863.1818073249.62846-19.10278n.a.n.a.--60255.99455072442
2460256.494444444N--350.606369072-7.19098360621.0787.0171.294833186-24.38126630.44975984486785-5.9881083123.5281/T39.640817.157964.94925.3143.1818856249.62984-19.09659n.a.n.a.--60255.99524516836
2460256.495138889N--350.605533758-7.19129091521.0787.0171.294823407064-24.38136250.44975744333359-5.9861291123.5265/T39.64217.158264.9525.3423.1819626249.63121-19.0904n.a.n.a.--60255.99593961369
2460256.495833333N--350.604698417-7.19159821121.0787.0171.294813628089-24.38145870.44975504259367-5.9841477123.5249/T39.643317.158664.9525.373.1820383249.63257-19.08422n.a.n.a.--60255.99663405763
2460256.496527778N--350.603863048-7.19190549521.0787.0171.294803849075-24.3815550.44975264264894-5.9821642123.5234/T39.644517.158964.9525.3983.1821127249.63393-19.07803n.a.n.a.--60255.9973285025
2460256.497222222N--350.603027651-7.19221276621.0787.0171.294794070023-24.38165120.44975024350027-5.9801786123.5218/T39.645717.159364.9525.4273.1821857249.63528-19.07184n.a.n.a.--60255.998022946435
2460256.497916667N--350.602192228-7.19252002521.0787.0171.294784290932-24.38174740.44974784514848-5.9781908123.5202/T39.646917.159664.95125.4553.1822575249.63662-19.06566n.a.n.a.--60255.9987173913
2460256.498611111N--350.601356778-7.19282727121.0787.0171.294774511803-24.38184360.44974544759442-5.9762011123.5186/T39.648217.1664.95125.4833.182328249.63795-19.05947n.a.n.a.--60255.999411835706
2460256.499305556N--350.600521302-7.19313450521.0787.0171.294764732635-24.38193990.4497430508389-5.9742093123.517/T39.649417.160364.95125.5113.1823971249.63928-19.05329n.a.n.a.--60256.000106280575
2460256.5N--350.599685801-7.19344172621.0787.0171.294754953429-24.38203610.44974065488271-5.9722155123.5155/T39.650617.160764.95125.5393.182465249.64059-19.04711n.a.n.a.--60256.00080072451

Because of the very dense reference JPL ephemerides, and because we’re using an asteroid and a TNO, I’m assuming that the functions are ~linear in the <1 minute timescale needed for the interpolation (Taylor’s theorem is our friend here). I think this also means that I don’t have to bother with light time correction - linearity once again.

[7]:
interpolated = {}
for i in obj:
    interpolated[i] = tb.Table()
    for j in pointing.keys():
        interpolated[i][j] = pointing[j]
    interpolated[i]['RA_INTERP'] = np.interp(pointing['MJD_TDB'], obj[i]['MJD'], obj[i]['RA'])
    interpolated[i]['DEC_INTERP'] = np.interp(pointing['MJD_TDB'], obj[i]['MJD'], obj[i]['DEC'])
    interpolated[i]['delta'] = np.interp(pointing['MJD_TDB'], obj[i]['MJD'], obj[i]['delta'])
    interpolated[i]['r'] = np.interp(pointing['MJD_TDB'], obj[i]['MJD'], obj[i]['r'])
    interpolated[i]['S-T-O'] = np.interp(pointing['MJD_TDB'], obj[i]['MJD'], obj[i]['S-T-O'])
    interpolated[i]['rdot'] = np.interp(pointing['MJD_TDB'], obj[i]['MJD'], obj[i]['rdot'])
    interpolated[i]['deldot'] = np.interp(pointing['MJD_TDB'], obj[i]['MJD'], obj[i]['deldot'])
    interpolated[i]['Sky_motion'] = np.interp(pointing['MJD_TDB'], obj[i]['MJD'], obj[i]['Sky_motion'])
    interpolated[i]['ExpTime'] = pointing['visitExposureTime']
    interpolated[i]['seeing'] = pointing['seeingFwhmEff_arcsec']

[8]:
interpolated['2011 OB60']
[8]:
Table length=19081
observationIdobservationStartMJD_TAIvisitTimevisitExposureTimefilterseeingFwhmGeom_arcsecseeingFwhmEff_arcsecfieldFiveSigmaDepth_magfieldRA_degfieldDec_degfieldRotSkyPos_degMJD_TDBRA_INTERPDEC_INTERPdeltarS-T-OrdotdeldotSky_motionExpTimeseeing
int64float64float64float64objectfloat64float64float64float64float64float64float64float64float64float64float64float64float64float64float64float64float64
539860225.0003411571434.030.0r0.69387264238554370.780866961539590924.33668024295265325.7771174872412-40.076877551247264110.9469374974256260225.000910397271.9873554445353354-11.89347708716753435.9711041950415136.908208052607210.5412-0.42124278420669338.2513234020690830.0518470106259727230.00.7808669615395909
539960225.0008001089634.030.0r0.69686728836840720.784510083173244824.335598043680374325.6092673713696-43.10540619671949102.9565976486481460225.001369349091.9873465251489812-11.8934808833260435.9711063823155436.908207940949660.5412-0.421242718117582858.252175819416420.0518487289428459430.00.7845100831732448
540060225.00125996784534.030.0r0.68455988010003510.769537567031672924.347564772301894332.9650642821256-46.16939439980464107.885214617933560225.001829207981.9873375876621897-11.89348468746452735.97110857415983536.9082078290763460.5412-0.421242603795838948.2530329991679330.0518504987561745230.00.7695375670316729
540160225.00170880405534.030.0r0.68942833279247950.775460258871629524.340440054873458333.0382524231313-49.11144728559235102.7679918231033260225.002278044191.9873288641020699-11.89348840046760435.97111071362386536.908207719885120.5412-0.421242487265536158.2538715409226930.05185223109606005430.00.7754602588716295
540260225.00215665044434.030.0r0.69537283880320580.782692018008766224.32708021818426333.05935059163113-52.0577477922848598.1356530063949560225.0027258905761.9873201592575491-11.89349210476653235.97111284864066536.9082076109294660.5412-0.42124242277565238.2547113926799240.0518539078330398530.00.7826920180087662
540360225.0026159110834.030.0r0.69989160122619210.788189295895610824.3168344571622337.26060311437726-50.34499990813282106.2015905521011860225.003185151211.9873112321232955-11.8934959034751335.9711150382986636.908207499201240.5412-0.42124231328429218.255575424234610.05185567066205657630.00.7881892958956108
540460225.0030629213234.030.0r0.70638668062695030.796090852344221724.305780110279173337.5200318330988-53.24619200825491102.252199238179660225.003632161451.9873025428145017-11.89349960077883835.9711171697059736.908207390454730.5412077272999876-0.421242192272700048.2564182985402360.0518574009097996830.00.7960908523442217
540560225.0035091168234.030.0r0.71410205003372350.805476946513045624.286376821856962337.77910898067853-56.1568838338320698.7112165838609760225.004078356951.987293868767728-11.89350329078258835.97111929752835436.908207281900640.5412719794983109-0.42124212802050178.257263086443790.0518590714669560830.00.8054769465130456
540660225.0039546461234.030.0r0.7232033974732080.816549145344535424.267378428106024338.06419298018744-59.0851132794581795.5739782585078160225.004523886251.9872852073169596-11.89350697563553735.97112142236322536.9082071735123240.5413-0.421242027728567678.258108888946360.0518607395286204930.00.8165491453445354
..................................................................
2446960251.3639653692434.030.0i0.76585749553701320.868439775592473523.26501216346208584.92763726650861-26.406384933962286231.304199533776360251.364534611511.5372784971908364-12.05216417938767636.19373970306441636.901838668885421.0846-0.4154199223201772720.51234395818530.0353400597676227730.00.8684397755924735
2447060251.3644150661434.030.0i0.6887928805887210.774687202662677723.37162312551205287.92121132653646-25.733660553639123226.8986214502823560251.364984308411.5372722013531812-12.05216575901878336.1937450305694236.90183856100131.0846-0.415419815127611220.5123023526221470.0353372328074467530.00.7746872026626777
2447160251.3648641830734.030.0i0.69427653488296850.78135831494278423.32865989846928385.70114045455082-23.33851671640359221.363978185885160251.3654334253441.5372659139983802-12.05216733639022136.1937503511975836.901838453255581.0846-0.4154196928909298420.5122586321790120.0353343943099830830.00.781358314942784
2447260251.3653130965134.030.0i0.69930782630985740.787479107432916623.3032158996749782.75731320279174-23.961345452242394225.6565697171572860251.365882338781.537259630650622-12.05216891304507436.1937556693922936.901838345553051.0846-0.415419628247434420.512210731348880.03533161463967854430.00.7874791074329166
2447360251.3658019232334.030.0i0.68928145888480220.775281580151827523.3183543018107687.06708091095462-28.81907452563399239.5245010511735360251.36637116551.5372527890608927-12.05217062988306236.19376146042381536.901838228278721.0846421436269913-0.41541951571274620.5121554108003750.0353285878240393730.00.7752815801518275
2447460251.3662577216534.030.0i0.69931482898566830.787487626503246123.27028306889395681.91298173932086-27.01905023032415234.4931716967407260251.3668269639241.5372464100725505-12.05217223072083336.1937668601664836.9018381189308041.0847-0.415419384442736220.5121012770175960.035325757740195730.00.7874876265032461
2447560251.3667064411334.030.0i0.70623387824918980.795904961373710323.2243854054130979.79614407028939-24.52571697093717228.806466893878560251.36727568341.5372401307361665-12.05217380669340336.1937721760175936.901838011281341.0847-0.4154192552116930420.512043769203420.03532291465724765630.00.7959049613737103
2447660251.3672015827634.030.0i0.70251670068891150.791382847553420323.19950196260551783.5402742535662-20.909983893749537218.028671338441960251.367770825031.5372332026379798-12.05217554570995936.19377804180966536.901837892490521.0847-0.415419156305454720.511977122152890.03531982113455260630.00.7913828475534203
2447760251.36765820895434.030.0i0.74543933249672940.843600161188235323.0886020570643678.41057860401273-19.065749943842853218.5373140961482460251.3682274512251.537226814053712-12.0521771494550736.1937834513109336.901837782938381.0847-0.4154190811024953720.511913101643840.035316993703650230.00.8436001611882353
2447860251.3681612713634.030.0i0.7420308682339320.83945361099018523.09867436577265276.81481324316428-25.03096543470499230.9023900453170860251.368730513631.5372197764161373-12.05217891628965936.19378941089521436.901837662251771.0847-0.415418936220610120.5118379803863530.0353138787431178330.00.839453610990185

Now we need to see whether the detections are inside the circular footprint we’re using as a proxy for the LSST camera:

[9]:
import numba
@numba.njit
def haversine(ra1, dec1, ra2, dec2):
    # adapted from Mario's implementation. in his wise words:
    # "SkyCoord is slow AF"
    dlon = np.deg2rad(ra2 - ra1)
    dlat = np.deg2rad(dec2 - dec1)

    a = np.sin(dlat / 2.0) ** 2 + np.cos(np.deg2rad(dec1)) * np.cos(np.deg2rad(dec2)) * np.sin(dlon / 2.0) ** 2
    c = 2 * np.arcsin(np.sqrt(a))
    return np.degrees(c)
[10]:
for i in interpolated:
    interpolated[i]['DISTANCE'] = haversine(interpolated[i]['RA_INTERP'], interpolated[i]['DEC_INTERP'], interpolated[i]['fieldRA_deg'], interpolated[i]['fieldDec_deg'])
[11]:
insideccd = {}
for i in interpolated:
    insideccd[i] = interpolated[i][interpolated[i]['DISTANCE'] < 1.75]
    insideccd[i].sort('observationStartMJD_TAI')

Now onto magnitudes and colors

[12]:
phot = tb.Table.read('../../demo/sspp_testset_colours.txt', format='ascii')
mags = {}
for i in phot:
    b = ' '.join(i['ObjID'].split('_'))
    mags[b] = {}
    mags[b]['H_r'] = i['H_r']
    mags[b]['GS'] = i['GS']
    mags[b]['r-r'] = 0
    for j in 'ugizy':
            mags[b][f'{j}-r'] = i[f'{j}-r']
[13]:
for i in insideccd:
    colors = np.array([mags[i][f'{j}-r'] for j in insideccd[i]['filter']])

    insideccd[i]['mag'] = mags[i]['H_r'] + colors + 5 * np.log10(insideccd[i]['r'] * insideccd[i]['delta'])

Loading the (pre-computed) Sorcha results

[14]:
verification = tb.Table.read('verification_output.csv')
v = {}
v['2011 OB60'] = verification[verification['ObjID'] == '2011_OB60']
v['2010 TU149'] = verification[verification['ObjID'] == '2010_TU149']
[15]:
for i in v:
    v[i].sort('fieldMJD_TAI')

If everything went according to plan, we should have the same number of detections in both datasets

[16]:
for i in v:
    print(i,len(v[i]), len(insideccd[i]))
2011 OB60 32 32
2010 TU149 18 18

And they should be at the same times:

[17]:
for i in v:
    print(i, np.mean(v[i]['fieldMJD_TAI'] - insideccd[i]['observationStartMJD_TAI']))
2011 OB60 0.00019567419099075778
2010 TU149 0.0001957947562105902

(note that this 0.00019567419099075778 offset is the ~17 seconds we added earlier to correct for exposure midpoints - so these are the same pointings)

Finally, let’s visualize the results:

[18]:
fig, ax = pl.subplots(1,3, sharey=True)
fig.subplots_adjust(wspace=0.2)
ax[0].hist(3600*1000*(v['2011 OB60']['RA_deg'] - insideccd['2011 OB60']['RA_INTERP']), histtype='step', color='xkcd:cerulean blue')
ax[0].hist(3600*1000*(v['2010 TU149']['RA_deg'] - insideccd['2010 TU149']['RA_INTERP']), histtype='step', color='xkcd:scarlet')

ax[0].set_ylabel('Number of simulated observations')
ax[0].set_xlabel(r'RA offset (mas)')

ax[1].hist(3600*1000*(v['2011 OB60']['Dec_deg'] - insideccd['2011 OB60']['DEC_INTERP']), histtype='step',  color='xkcd:cerulean blue')
ax[1].hist(3600*1000*(v['2010 TU149']['Dec_deg'] - insideccd['2010 TU149']['DEC_INTERP']), histtype='step',color='xkcd:scarlet')
ax[1].set_xlabel(r'Dec offset (mas)')

ax[2].hist(1000_0000*(v['2011 OB60']['trailedSourceMag'] - insideccd['2011 OB60']['mag']), histtype='step', color='xkcd:cerulean blue', linestyle='-' )
ax[2].hist(1000_0000*(v['2010 TU149']['trailedSourceMag'] - insideccd['2010 TU149']['mag']), histtype='step', color='xkcd:scarlet',  linestyle='-')

ax[2].set_xlabel(r'Mag offset ($10^{-7}$ mag)')

ax[0].text(-1, 4.2, r'2010 TU$_{149}$', fontsize=16, color='xkcd:scarlet')
ax[0].text(0.1, 8, r'2011 OB$_{60}$', fontsize=16, color='xkcd:cerulean blue')

fig.set_size_inches(14,6)
pl.tight_layout()
pl.show()
../_images/notebooks_demo_Verification_28_0.png

And see the mean and standard deviation:

[19]:
pref = {'RA_deg' : 3600*1000, 'Dec_deg' : 3600*1000, 'trailedSourceMag' : 1e7} # note this means mas and 10^-7 mag!
for i in v:
    for j, k in zip(['RA_deg', 'Dec_deg', 'trailedSourceMag'], ['RA_INTERP', 'DEC_INTERP', 'mag']):
        print(i, j, np.mean(pref[j]*(v[i][j] - insideccd[i][k])), np.std(pref[j]*(v[i][j] - insideccd[i][k])))
2011 OB60 RA_deg 0.0008525950445648078 0.021086587833108447
2011 OB60 Dec_deg 8.785052685311712e-05 0.0075905242490396
2011 OB60 trailedSourceMag -1.1012382783448516 0.019616769792408627
2010 TU149 RA_deg 0.10147533791027286 1.0202124198587195
2010 TU149 Dec_deg 0.024679328869403605 0.4224011685568308
2010 TU149 trailedSourceMag -0.18841977854498915 0.16053158232152698