Check Lightcurve for Solar System Contamination

Check Lightcurve for Solar System Contamination#

When collecting time series observations of stars near the ecliptic plane, often solar system objects can wander in and out of the field of view. This in principle could affect a transit or flare searches, either by contaminating the aperture (making the star appear temporilly brighter) or the background (making the star appear temporarily dimmer). We can use jorbit and its pre-computed ephemerides to check for the impact of solar system objects on a lightcurve.

import numpy as np
import matplotlib.pyplot as plt
import astropy.units as u
from astropy.coordinates import SkyCoord
from astropy.time import Time

from jorbit.mpchecker import nearest_asteroid, animate_region

If we want just a quick/easy check if any solar system objects are in the field of view, we can run:

separations, asteroids = nearest_asteroid(
    coordinate=SkyCoord(ra=0 * u.deg, dec=0 * u.deg),
    times=Time("2025-01-01") + np.arange(0, 3, 1 / 24 / 4) * u.day,
    radius=2 * u.arcmin,  # roughly ~10x10 TESS pixels, comparable to a TPF
)

This uses the same assumptions baked into the extra_precision=False version of the mpchecker function, namely a geocentric observer and Newtonian-only gravity. But, it gives us a good first look at what might be happening near our target. separations is now a vector of anglular separations to the nearest solar system object, and asteroids is a Table of all the asteroid that flew within our radius during the times we checked:

fig, ax = plt.subplots()
ax.plot(np.arange(0, 3, 1 / 24 / 4), separations)
ax.set(xlabel="Time [2025-01-01 + days]", ylabel="Dist. to nearest SSO [arcsec]");
../_images/b5bf27d6d1a0082879924ab3850b095289d6f80f34ea9c5e940bf641954fdd5b.png
asteroids
Table length=15
Unpacked NamePacked designationHGEpochMPeriNodeIncl.enaUReference#Obs#OppArcrmsCoarse PertsPrecise PertsComputerFlagslast obs
str19str7float64float64str5float64float64float64float64float64float64float64str1str9float64float64str9float64str3str3str8str4str8
(126467) 2002 CZ39C646715.680.15K2555226.25838346.4117200.0891613.128860.10467670.242521442.54665010E2025-B02758.018.02000-20250.8M-v3EkMPCLINUX000020250116
(180961) 2005 MJ35I096117.340.15K2555132.90427181.2708479.889720.035890.19192560.263438932.40999470MPO889347451.018.01998-20240.86M-v3EkMPCLINUX000020241204
(184308) 2005 EY209I430816.510.15K2555142.9895861.02093201.320481.471440.03134940.226443742.66580980E2025-B02497.018.01998-20250.86M-v3EkMPCLINUX000020250116
2005 UX268K05UQ8X20.240.15K255577.16673319.3459532.8598622.751350.11251380.359852071.95758249MPO63034912.01.01 days0.51M-v3EkPan200620051029
2013 CZ241K13CO1Z17.890.15K2555161.74732220.4216719.207115.37060.05396050.214411052.76463590E2024-T7845.06.02013-20240.4M-v3EkMPCLINUX000020241004
2013 UN46K13U46N17.620.15K255515.88923187.81528202.263333.307550.1889360.174195983.17524540MPO89340469.06.02013-20240.43M-v3EkMPCLINUX000020241204
2015 RP328K15RW8P18.350.15K255557.63518323.3962222.098174.493860.07833810.215686012.75373030E2024-SF931.04.02015-20240.71M-v3EkMPCLINUX000020240928
2016 BK84K16B84K17.320.15K2555255.37314140.79815.9715910.975670.1729490.179291543.11479530E2024-TN484.07.02010-20240.42M-v3EkMPCLINUX000020241010
2016 DA31K16D31A29.90.15K2555199.17288272.94207336.960230.021530.44355990.7156561.23784578MPO37791014.01.01 days0.44M-v3EhMPCW280320160229
2016 UR162K16UG2R18.80.15K2555102.20193264.3574222.834582.958940.18593270.237576262.58186798MPO66052324.01.03 days0.21M-v38hVeres200020161029
2023 XQ16K23X16Q30.580.15K2555158.79242133.51724257.979031.229320.0691560.934449971.03617245E2024-C3740.01.03 days0.48M-v3EkMPCLINUX280320231218
(346625) 2008 WK112Y662517.430.15K255537.24264346.5839728.748141.584450.20974660.239725842.56641070E2024-V47403.015.02001-20240.9M-v3EkMPCLINUX000020241102
(371405) 2006 RN84b140518.110.15K255522.04551195.89265210.514820.786970.1796550.271957462.35940260E2024-U19218.011.02006-20240.96M-v3EkMPCLINUX000020241018
(492869) 2014 QN378n286917.380.15K255577.61079119.11664200.286385.297880.07373110.196364742.93152570MPO891620116.012.02004-20241.04M-v3EkMPCLINUX000020241204
(610939) 2006 KB148z093917.690.15K2555278.59983301.22169200.9628912.218880.105540.240488362.56098290E2024-UB593.08.02006-20240.45M-v3EkMPCLINUX000020241024

Looks like there was a close approach by something moving rapidly around one day into our time series. To investigate its potential impact on our lightcurve, we can run:

separations, asteroids, coord_table, mag_table, total_mags = nearest_asteroid(
    coordinate=SkyCoord(ra=0 * u.deg, dec=0 * u.deg),
    times=Time("2025-01-01") + np.arange(0, 3, 1 / 24 / 4) * u.day,
    radius=2 * u.arcmin,
    compute_contamination=True,
    observer="geocentric",
)

Note that now we’ve set compute_contamination=True and specified a specific observer, which initiates several extra calculations, again like the extra_precision=True version of mpchecker. This takes considerably longer (this ~300 point time series takes ~a min on my laptop), but a lot is happening behind the scenes: we’re running the initial low precision version to flag all the relevant asteroids, running an N-body simulation to map each one to each moment in our time series, then computing its sky position and estimated magntidue as seen from a particular observer.

We can now look at a time series of the total magnitude of all asteroids in our field of view:

fig, ax = plt.subplots()
ax.plot(np.arange(0, 3, 1 / 24 / 4), total_mags)
ax.set(xlabel="Time [2025-01-01 + days]", ylabel="Total V Mag from SSOs");
../_images/ea97d800f411ecce33276ce6e9b49a8eafcd1f900cc06a9a27a2abb3a00fe948.png

This shows us that although the separation might have been small, the magnitude of the object was very faint, and so it likely had little impact on our lightcurve. The gaps here correspond to times when nothing fell within our search radius, meaning the magnitude was technially $\infty$. Finally, for fun and a sanity check, we can animate what was happening in the sky using the outputs of the above function:

anim = animate_region(
    coordinate=SkyCoord(ra=0 * u.deg, dec=0 * u.deg),
    times=Time("2025-01-01") + np.arange(0, 3, 1 / 24 / 4) * u.day,
    coord_table=coord_table,
    radius=2 * u.arcmin,
)

This should render within a notebook, but can also be saved as a gif via something like:

anim.save('animation.gif', writer='pillow')