Uncertainties and Randomization Demo
[1]:
from sorcha.modules.PPAddUncertainties import addUncertainties
from sorcha.modules.PPRandomizeMeasurements import randomizeAstrometryAndPhotometry, randomizeAstrometry
from sorcha.utilities.sorchaModuleRNG import PerModuleRNG
[2]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from astropy.coordinates import SkyCoord
from sorcha.utilities.sorchaConfigs import expertConfigs
This notebook demonstrates the uncertainties calculation and the randomisation of object position and magnitude based on the calculated uncertainties.
First we create the data. The expected LSST limiting magnitude for a single exposure in the g-band is 25 - we will extend our magnitude range to 30 for demonstrative purposes. The objects have identical position, velocity, seeing and limiting magnitude at the source position: uncertainties will thus depend on magnitude alone.
[3]:
obj_ids = np.arange(0, 1000)
obj_mags = np.linspace(15, 30, 1000)
psf_mags = obj_mags + 0.01
sig_limit = np.ones(len(obj_ids)) + 23.
seeing = np.ones(len(obj_ids))
astRArate = np.ones(len(obj_ids)) + 0.03
astDecrate = np.ones(len(obj_ids)) - 0.01
astRA = np.ones(len(obj_ids)) + 260.
astDec = np.ones(len(obj_ids)) -5.
texp = np.ones(len(obj_ids)) * 30.0
[4]:
observations = pd.DataFrame({'ObjID': obj_ids,
'trailedSourceMagTrue': obj_mags,
'PSFMagTrue': psf_mags,
'fiveSigmaDepth_mag': sig_limit,
'seeingFwhmGeom_arcsec': seeing,
'RARateCosDec_deg_day': astRArate,
'DecRate_deg_day': astDecrate,
'RA_deg': astRA,
'Dec_deg': astDec,
'visitExposureTime': texp,
})
[5]:
observations.columns
[5]:
Index(['ObjID', 'trailedSourceMagTrue', 'PSFMagTrue', 'fiveSigmaDepth_mag',
'seeingFwhmGeom_arcsec', 'RARateCosDec_deg_day', 'DecRate_deg_day',
'RA_deg', 'Dec_deg', 'visitExposureTime'],
dtype='object')
As can be seen from the columns, both trailedSourceMagTrue (the magnitude of the source including any trailing) and PSFMagTrue (the magnitude of the object within the PSF-fitting kernel) are included here. The code will calculate the uncertainties for each one.
[6]:
configs = {'trailing_losses_on':True, 'default_snr_cut': False}
configs = expertConfigs(**configs)
setattr(configs, "expert", configs)
rng = PerModuleRNG(2012)
[7]:
obs_uncert_1 = addUncertainties(observations, configs, rng)
obs_uncert = randomizeAstrometryAndPhotometry(obs_uncert_1, configs, rng)
[8]:
obs_uncert.columns
[8]:
Index(['ObjID', 'trailedSourceMagTrue', 'PSFMagTrue', 'fiveSigmaDepth_mag',
'seeingFwhmGeom_arcsec', 'RARateCosDec_deg_day', 'DecRate_deg_day',
'RATrue_deg', 'DecTrue_deg', 'visitExposureTime',
'astrometricSigma_deg', 'trailedSourceMagSigma', 'SNR', 'PSFMagSigma',
'trailedSourceMag', 'PSFMag', 'RA_deg', 'Dec_deg'],
dtype='object')
[9]:
obs_uncert
[9]:
| ObjID | trailedSourceMagTrue | PSFMagTrue | fiveSigmaDepth_mag | seeingFwhmGeom_arcsec | RARateCosDec_deg_day | DecRate_deg_day | RATrue_deg | DecTrue_deg | visitExposureTime | astrometricSigma_deg | trailedSourceMagSigma | SNR | PSFMagSigma | trailedSourceMag | PSFMag | RA_deg | Dec_deg | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 15.000000 | 15.010000 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.000003 | 0.000677 | 1595.148457 | 0.000680 | 15.000116 | 15.009979 | 261.000001 | -4.000001 |
| 1 | 1 | 15.015015 | 15.025015 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.000003 | 0.000682 | 1583.992536 | 0.000685 | 15.015128 | 15.025376 | 261.000001 | -3.999998 |
| 2 | 2 | 15.030030 | 15.040030 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.000003 | 0.000687 | 1572.912403 | 0.000690 | 15.030911 | 15.039235 | 260.999999 | -4.000004 |
| 3 | 3 | 15.045045 | 15.055045 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.000003 | 0.000692 | 1561.907528 | 0.000695 | 15.045742 | 15.056046 | 260.999996 | -4.000001 |
| 4 | 4 | 15.060060 | 15.070060 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.000003 | 0.000697 | 1550.977385 | 0.000700 | 15.059227 | 15.070627 | 260.999997 | -4.000005 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 995 | 995 | 29.939940 | 29.949940 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.011947 | 4.653574 | 0.013823 | 4.663437 | 29.850508 | 33.209062 | 261.003245 | -4.001115 |
| 996 | 996 | 29.954955 | 29.964955 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.012113 | 4.668383 | 0.013633 | 4.678248 | 24.605097 | 27.878347 | 260.996668 | -4.005759 |
| 997 | 997 | 29.969970 | 29.979970 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.012282 | 4.683196 | 0.013445 | 4.693062 | 37.811662 | 31.596631 | 261.010172 | -4.009016 |
| 998 | 998 | 29.984985 | 29.994985 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.012453 | 4.698010 | 0.013261 | 4.707879 | 31.139893 | 23.026854 | 260.982451 | -4.021063 |
| 999 | 999 | 30.000000 | 30.010000 | 24.0 | 1.0 | 1.03 | 0.99 | 261.0 | -4.0 | 30.0 | 0.012627 | 4.712828 | 0.013079 | 4.722698 | 34.986386 | 29.345251 | 260.981810 | -4.001609 |
1000 rows × 18 columns
As can be seen from the above, we have several new columns:
trailedSourceMag/PSFMag: a randomised magnitude as observed by the telescope, based on the ‘true’ magnitude and its calculated uncertainty
trailedSourceMagSigma/PSFMagSigma: the uncertainty on the magnitude measurement
astrometricSigma_deg: the uncertainty on the object position
SNR: the signal-to-noise ratio.
Let’s take a look at these, starting with the SNR. For all plots, the single-exposure limiting magnitude in g-band (25) is marked.
[10]:
fig, ax = plt.subplots(1)
ax.plot(obs_uncert['trailedSourceMagTrue'].values, obs_uncert['SNR'].values, linestyle='', marker='.', color='rebeccapurple')
ax.axvline(25, color='black', linestyle='--')
ax.set_xlabel('magnitude')
ax.set_ylabel('SNR')
plt.show()
The photometric uncertainty for the trailed source and PSF magnitudes:
[11]:
fig, ax = plt.subplots(1, 2, figsize=(12, 5))
ax[0].plot(obs_uncert['trailedSourceMagTrue'].values, obs_uncert['trailedSourceMagSigma'].values, linestyle='', marker='.', color='thistle')
ax[0].set_xlabel('trailed source magnitude')
ax[0].set_ylabel('trailed source magnitude uncertainty (mag)')
ax[0].axvline(25, color='black', linestyle='--')
ax[1].plot(obs_uncert['PSFMagTrue'].values, obs_uncert['PSFMagSigma'].values, linestyle='', marker='.', color='thistle')
ax[1].set_xlabel('PSF magnitude')
ax[1].set_ylabel('PSF magnitude uncertainty (mag)')
ax[1].axvline(25, color='black', linestyle='--')
plt.show()
Astrometric uncertainty:
[12]:
fig, ax = plt.subplots(1)
ax.plot(obs_uncert['trailedSourceMagTrue'].values, obs_uncert['astrometricSigma_deg'].values, linestyle='', marker='.', color='orchid')
ax.axvline(25, color='black', linestyle='--')
ax.set_xlabel('trailed source magnitude')
ax.set_ylabel('astrometric uncertainty (deg)')
plt.show()
Next we look at the randomisation of the magnitude based on the uncertainties, for both trailed source and PSF magnitudes. Note that for large magnitudes, and thus large photometric uncertainties, the randomisation leads to very large shifts in the magnitude. However, this only occurs for objects fainter than LSST’s single exposure limiting magnitude - the SNR of these objects is so low that by default, SSPP will remove them from contention.
[13]:
fig, ax = plt.subplots(1, 2, figsize=(12, 5))
ax[0].plot(obs_uncert['trailedSourceMagTrue'].values, obs_uncert['trailedSourceMag'].values, linestyle="", marker="x", color='mediumpurple')
ax[0].set_xlabel('trailed source magnitude before randomisation')
ax[0].set_ylabel('trailed source magnitude after randomisation')
ax[0].axvline(25, color='black', linestyle='--')
ax[1].plot(obs_uncert['PSFMagTrue'].values, obs_uncert['PSFMag'].values, linestyle="", marker="x", color='mediumpurple')
ax[1].set_xlabel('trailed source magnitude before randomisation')
ax[1].set_ylabel('trailed source magnitude after randomisation')
ax[1].axvline(25, color='black', linestyle='--')
plt.show()
A separate function then randomises the object position based on its astrometric uncertainty. We will recreate the data to have the same magnitude, but vary RA and Dec.
[14]:
obj_ids = np.arange(0, 1000)
obj_mags = np.ones(len(obj_ids)) + 21.
psf_mags = obj_mags + 0.01
sig_limit = np.ones(len(obj_ids)) + 23.
seeing = np.ones(len(obj_ids))
astRArate = np.ones(len(obj_ids)) + 0.03
astDecrate = np.ones(len(obj_ids)) - 0.01
astRA = np.linspace(0, 360, 1000)
astDec = np.linspace(-90, 0, 1000)
texp = np.ones(len(obj_ids)) * 30.0
[15]:
observations = pd.DataFrame({'ObjID': obj_ids,
'trailedSourceMagTrue': obj_mags,
'PSFMagTrue': psf_mags,
'fiveSigmaDepth_mag': sig_limit,
'seeingFwhmGeom_arcsec': seeing,
'RARateCosDec_deg_day': astRArate,
'DecRate_deg_day': astDecrate,
'RA_deg': astRA,
'Dec_deg': astDec,
'visitExposureTime': texp,
})
[16]:
observations = addUncertainties(observations, configs, rng)
[17]:
observations = randomizeAstrometry(observations, rng, sigName='astrometricSigma_deg', sigUnits='deg')
[18]:
true_coord = SkyCoord(ra=observations['RATrue_deg'].values, dec=observations['DecTrue_deg'].values, unit="deg")
random_coord = SkyCoord(ra=observations['RA_deg'].values, dec=observations['Dec_deg'].values, unit="deg")
separation = true_coord.separation(random_coord).mas
[19]:
fig, ax = plt.subplots(1)
ax.hist(separation, 100, color='turquoise')
ax.set_ylabel('frequency')
ax.set_xlabel('difference between true and randomised position (mas)')
plt.show()