Realistic lightcurves of ellipsoids
This notebook demonstrates how to generate lightcurves within sorcha, under the assumption that the Solar System Objects (SSOs) are simple tri-axial ellipsoids, defined by $ a \ge `b :nbsphinx-math:ge `c$, in simple short-axis rotation mode.
[1]:
import numpy as np
import pandas as pd
import rocks
import requests
import astropy.units as u
from astropy.coordinates import SkyCoord
from sorcha.modules.PPCalculateApparentMagnitudeInFilter import (
PPCalculateApparentMagnitudeInFilter,
)
import matplotlib.pyplot as plt
# Constant
au = 1.495978707e8 # AU in km
/home/bcarry/anaconda3/envs/sorcha/lib/python3.11/site-packages/assist/__init__.py:44: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
Lightweight ephemeris function
We will need to compute ephemerides, so let’s query the Miriade Virtual Observatory Web service.
[2]:
def ephemcc(ident, ep, nbd=None, step=None, observer="500", rplane="1", tcoor=5):
"""Gets asteroid ephemerides from IMCCE Miriade for a suite of JD for a single SSO
Original function by M. Mahlke
:ident: int, float, str - asteroid identifier
:ep: float, str, list - Epoch of computation
:observer: str - IAU Obs code - default to geocenter: https://minorplanetcenter.net//iau/lists/ObsCodesF.html
:returns: pd.DataFrame - Input dataframe with ephemerides columns appended
False - If query failed somehow
"""
# ------
# Miriade URL
url = "https://ssp.imcce.fr/webservices/miriade/api/ephemcc.php"
# Query parameters
params = {
"-name": f"{ident}",
"-mime": "json",
"-rplane": rplane,
"-tcoor": tcoor,
"-output": "--jd",
"-observer": observer,
"-tscale": "UTC",
}
# Single epoch of computation
if type(ep) != list:
# Set parameters
params["-ep"] = ep
if nbd != None:
params["-nbd"] = nbd
if step != None:
params["-step"] = step
# Execute query
try:
r = requests.post(url, params=params, timeout=80)
except requests.exceptions.ReadTimeout:
return False
# Multiple epochs of computation
else:
# Epochs of computation
files = {"epochs": ("epochs", "\n".join(["%.6f" % epoch for epoch in ep]))}
# Execute query
try:
r = requests.post(url, params=params, files=files, timeout=50)
except requests.exceptions.ReadTimeout:
return False
j = r.json()
# Read JSON response
try:
ephem = pd.DataFrame.from_dict(j["data"])
except KeyError:
return False
return ephem
Definition of simulation
We first define the epochs of the simulation: starting date (expressed in JD), the number of epochs to simulate, and time step between each (in days).
[3]:
# Choice of time frame
jd0 = 2461041.5 # Start date: here set to 2026-01-01
nbd = 1500 # Number of epochs
step = 0.3 # Time step between epochs (days)
We then define the target. It can be an asteroid name/designation or number. The absolute magnitude and spin properties (coordinates and period) are then retrieved from SsODNet (see Berthier et al., 2023). Alternatively, you can define all parameters by hand:
Hthe absolute magnitudera0the right ascension of the spin coordinates (equatorial frame, in degrees)dec0the declination of the spin coordinates (equatorial frame, in degrees)periodthe sidereal rotation period (in hour)G1andG2the phase curve coefficientsa_bthe ratio of equatorial diameters (a and b)a_cthe ratio between the longest (equatorial) diameter (a) and the polar diameter (c)
[4]:
# Target
sso = 22
# Retrieve the target properties from SsODNet
sc = rocks.Rock(sso)
H = sc.H.value
ra0 = sc.spin.RA0.value
dec0 = sc.spin.DEC0.value
period = sc.spin.period.value[0] / 24.0
# Arbitrary phase function and shape
G1 = 0.62
G2 = 0.14
a_b = 1.5
a_c = 2.0
[5]:
# Generate ephemerides
eph = ephemcc(sso, ep=jd0, nbd=nbd, step=step, tcoor=5, observer='X05')
[6]:
# Build the observations dataframe
observations_df = pd.DataFrame(
{
"fieldMJD_TAI": eph.Date.values,
"H_filter": H * np.ones(nbd),
"G1": G1 * np.ones(nbd),
"G2": G2 * np.ones(nbd),
"RA_deg": eph.RA.values,
"Dec_deg": eph.DEC.values,
"Period": period * np.ones(nbd),
"Time0": jd0 * np.ones(nbd),
"phi0": np.radians(0) * np.ones(nbd),
"RA0": np.radians(ra0) * np.ones(nbd),
"Dec0": np.radians(dec0) * np.ones(nbd),
"Period": period * np.ones(nbd),
"a/b": a_b * np.ones(nbd),
"a/c": a_c * np.ones(nbd),
"Range_LTC_km": eph.Dobs.values * au,
"Obj_Sun_LTC_km": eph.Dhelio.values * au,
"phase_deg": eph.Phase.values,
}
)
observations_df
[6]:
| fieldMJD_TAI | H_filter | G1 | G2 | RA_deg | Dec_deg | Period | Time0 | phi0 | RA0 | Dec0 | a/b | a/c | Range_LTC_km | Obj_Sun_LTC_km | phase_deg | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2461041.5 | 6.79 | 0.62 | 0.14 | 359.334334 | -11.523769 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 4.177865e+08 | 4.049300e+08 | 20.522908 |
| 1 | 2461041.8 | 6.79 | 0.62 | 0.14 | 359.407445 | -11.465113 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 4.183388e+08 | 4.048958e+08 | 20.502883 |
| 2 | 2461042.1 | 6.79 | 0.62 | 0.14 | 359.482166 | -11.406700 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 4.188799e+08 | 4.048617e+08 | 20.481478 |
| 3 | 2461042.4 | 6.79 | 0.62 | 0.14 | 359.555298 | -11.348389 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 4.194149e+08 | 4.048277e+08 | 20.461570 |
| 4 | 2461042.7 | 6.79 | 0.62 | 0.14 | 359.628519 | -11.289645 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 4.199635e+08 | 4.047937e+08 | 20.441432 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1495 | 2461490.0 | 6.79 | 0.62 | 0.14 | 100.442283 | 35.559274 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 3.696081e+08 | 4.089152e+08 | 21.330520 |
| 1496 | 2461490.3 | 6.79 | 0.62 | 0.14 | 100.514005 | 35.545997 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 3.702196e+08 | 4.089530e+08 | 21.336566 |
| 1497 | 2461490.6 | 6.79 | 0.62 | 0.14 | 100.583783 | 35.532027 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 3.708370e+08 | 4.089908e+08 | 21.344123 |
| 1498 | 2461490.9 | 6.79 | 0.62 | 0.14 | 100.656047 | 35.517137 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 3.714625e+08 | 4.090287e+08 | 21.349582 |
| 1499 | 2461491.2 | 6.79 | 0.62 | 0.14 | 100.729880 | 35.503386 | 0.172842 | 2461041.5 | 0.0 | 3.405137 | -0.049463 | 1.5 | 2.0 | 3.720775e+08 | 4.090665e+08 | 21.353868 |
1500 rows × 16 columns
SORCHA simulation
We first simply compute the apparent magnitude, using the \(H G_1 G_2\) formalism, as a baseline.
[7]:
observations_df = PPCalculateApparentMagnitudeInFilter(observations_df.copy(), "HG1G2", "r", "HG1G2_mag")
We then load the lightcurve methods of SORCHA add-ons:
[8]:
from sorcha.lightcurves.lightcurve_registration import LC_METHODS, update_lc_subclasses
update_lc_subclasses()
print('Lightcurve methods: ' + ', '.join(LC_METHODS))
Lightcurve methods: identity, sinusoidal, ellipsoidal, ellipsoidalwithterminator
We can now compute the apparent magnitude with the lightcurve effect (using the \(H G_1 G_2\) formalism for consistency).
[9]:
observations_df = PPCalculateApparentMagnitudeInFilter(
observations_df.copy(), "HG1G2", "r", "LC_HG1G2_mag", "ellipsoidal"
)
Graphics illustrating the lightcurve effect
[10]:
fig, ax = plt.subplots(1, 2, figsize=(10, 5), sharey=True, gridspec_kw={"wspace": 0})
# --------------------------------------------------------------------------------
# Apparent magnitude vs time
ax[0].plot(
observations_df["fieldMJD_TAI"],
observations_df["HG1G2_mag"],
linestyle="--",
color="C0",
label="HG1G2 baseline",
)
ax[0].plot(
observations_df["fieldMJD_TAI"],
observations_df["LC_HG1G2_mag"],
linestyle="-",
color="C1",
label="With lightcurve",
)
# --------------------------------------------------------------------------------
# Apparent magnitude vs phase
ax[1].plot(
observations_df["phase_deg"],
observations_df["HG1G2_mag"],
linestyle="--",
color="C0",
label="HG1G2 baseline",
)
ax[1].plot(
observations_df["phase_deg"],
observations_df["LC_HG1G2_mag"],
linestyle="-",
color="C1",
label="With lightcurve",
)
# --------------------------------------------------------------------------------
# Axes
for a in ax:
a.legend()
a.grid()
ax[0].set_xlabel("Time since first observation (days)")
ax[1].set_xlabel("Phase angle (degrees)")
ax[0].set_ylabel("Apparent magnitude")
ax[0].invert_yaxis()
fig.tight_layout()
fig.savefig("ellipsoidal_lightcurve_example.png", dpi=300)