Incorporating lightcurves

The goal of this notebook is to demonstrate the use of lightcurves within sorcha.

This will be done in two different ways:

The idea is that the user can, in principle, implement their own lightcurves, and incorporate them in their simulation. The goal of sorcha-addons is for both the development team, as well as for the community, to share their implementations of custom lightcurve models.

For more information on creating custom lightcurve models for Sorcha, see (https://sorcha.readthedocs.io/en/latest/postprocessing.html#lightcurve-template-class)

[1]:
import pandas as pd
import numpy as np
import astropy.units as u
from astroquery.jplhorizons import Horizons
from sorcha_addons.lightcurve.sinusoidal.sinusoidal_lightcurve import SinusoidalLightCurve
from sorcha.modules.PPCalculateApparentMagnitudeInFilter import PPCalculateApparentMagnitudeInFilter
import matplotlib.pyplot as plt

This notebook will not use a realistic set of observations (as in the demo_ApparentMagnitudeValidation notebook), but rather create a toy scenario with a simple to understand and interpret set of results. The general structure of the notebook will be the same.

We will create a dataframe for observations in a similar structure as in the demo_ApparentMagnitudeValidation notebook:

[2]:
observations_df = pd.DataFrame(
    {
        "fieldMJD_TAI": np.linspace(
            0, 100, 1001
        ),  # time of observation - note these values are bogus, we only care about the Delta t for this demo
        "H_filter": 10 * np.ones(1001),
        "GS": 0.15 * np.ones(1001),
        "G1": 0.62 * np.ones(1001),
        "G2": 0.14 * np.ones(1001),
        "G12": 0.68 * np.ones(1001),
        "S": 0.04 * np.ones(1001),
        "Range_LTC_km": 1.495978707e8 * np.ones(1001),  # 1 au
        "Obj_Sun_LTC_km": 1.495978707e8 * np.ones(1001),  # 1 au
        "phase_deg": np.linspace(0, 10, 1001),
    }
)  # some phase angle variation so we can see the phase curve on top of the lightcurve
[3]:
observations_df
[3]:
fieldMJD_TAI H_filter GS G1 G2 G12 S Range_LTC_km Obj_Sun_LTC_km phase_deg
0 0.0 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.00
1 0.1 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.01
2 0.2 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.02
3 0.3 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.03
4 0.4 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.04
... ... ... ... ... ... ... ... ... ... ...
996 99.6 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 9.96
997 99.7 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 9.97
998 99.8 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 9.98
999 99.9 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 9.99
1000 100.0 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 10.00

1001 rows × 10 columns

Now we calculate the magnitude using the various models in PPCalculateApparentMagnitudeInFilter.

[4]:
observations_df = PPCalculateApparentMagnitudeInFilter(observations_df.copy(), "HG", "r", "HG_mag")
observations_df = PPCalculateApparentMagnitudeInFilter(observations_df.copy(), "HG12", "r", "HG12_mag")
observations_df = PPCalculateApparentMagnitudeInFilter(observations_df.copy(), "HG1G2", "r", "HG1G2_mag")
observations_df = PPCalculateApparentMagnitudeInFilter(observations_df.copy(), "linear", "r", "linear_mag")
observations_df = PPCalculateApparentMagnitudeInFilter(observations_df.copy(), "none", "r", "Simple_mag")
[5]:
observations_df
[5]:
fieldMJD_TAI H_filter GS G1 G2 G12 S Range_LTC_km Obj_Sun_LTC_km phase_deg HG_mag HG12_mag HG1G2_mag linear_mag Simple_mag
0 0.0 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.00 10.000000 10.000000 10.000000 10.0000 10.0
1 0.1 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.01 10.001390 10.000361 10.000366 10.0004 10.0
2 0.2 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.02 10.002776 10.000884 10.000884 10.0008 10.0
3 0.3 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.03 10.004158 10.001562 10.001549 10.0012 10.0
4 0.4 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 0.04 10.005537 10.002388 10.002352 10.0016 10.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
996 99.6 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 9.96 10.656917 10.628635 10.624403 10.3984 10.0
997 99.7 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 9.97 10.657299 10.629045 10.624827 10.3988 10.0
998 99.8 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 9.98 10.657681 10.629454 10.625251 10.3992 10.0
999 99.9 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 9.99 10.658064 10.629864 10.625675 10.3996 10.0
1000 100.0 10.0 0.15 0.62 0.14 0.68 0.04 149597870.7 149597870.7 10.00 10.658445 10.630273 10.626099 10.4000 10.0

1001 rows × 15 columns

Now we can plot the magnitudes and compare them.

[6]:
fig, ax = plt.subplots(figsize=(10, 8))
ax.plot(observations_df["fieldMJD_TAI"], observations_df["Simple_mag"], linestyle="-", label="No phase curve")
ax.plot(observations_df["fieldMJD_TAI"], observations_df["HG_mag"], label="HG")
ax.plot(observations_df["fieldMJD_TAI"], observations_df["HG12_mag"], label="HG12")
ax.plot(observations_df["fieldMJD_TAI"], observations_df["HG1G2_mag"], label="HG1G2")
ax.plot(observations_df["fieldMJD_TAI"], observations_df["linear_mag"], label="Linear")

ax.legend()
ax.set_xlabel("Time since first observation (days)")
ax.set_ylabel("Apparent magnitude")
plt.gca().invert_yaxis()
plt.grid()
plt.show()
../_images/notebooks_demo_Lightcurve_10_0.png

The effect of the lightcurve is to add an extra term to the apparent magnitude, that, in principle, can be a function of the characteristics of the observations, such as time of observation, phase angle or topocentric and heliocentric distances. The entire observational_df dataframe is exposed to the lightcurve, so any dependencies can be added.

Let’s use the basic sinusoidal lightcurve from sorcha_addons. We need the following columns in our dataframe:

  • LCA - lightcurve amplitude [magnitudes].

  • Period - period of the sinusoidal oscillation [days]. Should be a positive value.

  • Time0 - phase for the light curve [days].

Let’s create a lightcurve with a period of 20 days, phased so that the first observation is at zero variation, and with 0.5 mag peak-to-peak amplitude.

[7]:
from sorcha.lightcurves.lightcurve_registration import LC_METHODS, update_lc_subclasses

# LC_METHODS is the dictionary that contains all lightcurve implementations
# update_lc_subclasses adds newly defined classes to this dictionary
# this is run by default inside sorcha - we are just showing it here for completeness
update_lc_subclasses()
print(LC_METHODS)
{'identity': <class 'sorcha.lightcurves.identity_lightcurve.IdentityLightCurve'>, 'sinusoidal': <class 'sorcha_addons.lightcurve.sinusoidal.sinusoidal_lightcurve.SinusoidalLightCurve'>}
[8]:
observations_df["LCA"] = 0.25  # note peak-to-peak is 2LCA!
observations_df["Period"] = 20.0
observations_df["Time0"] = 0.0
[9]:
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "none", "r", "LCA_mag", "sinusoidal"
)
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "HG", "r", "LCA_HG_mag", "sinusoidal"
)
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "HG12", "r", "LCA_HG12_mag", "sinusoidal"
)
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "HG1G2", "r", "LCA_HG1G2_mag", "sinusoidal"
)
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "linear", "r", "LCA_linear_mag", "sinusoidal"
)
[10]:
from matplotlib.lines import Line2D

fig, ax = plt.subplots(figsize=(10, 8))
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["Simple_mag"],
    linestyle="--",
    label="__none__",
    color="m",
)
ax.plot(
    observations_df["fieldMJD_TAI"], observations_df["LCA_mag"], linestyle="-", label="__none__", color="m"
)

ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["linear_mag"],
    linestyle="--",
    label="__none__",
    color="r",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["LCA_linear_mag"],
    linestyle="-",
    label="__none__",
    color="r",
)


ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["HG_mag"],
    linestyle="--",
    label="__none__",
    color="b",
)
ax.plot(
    observations_df["fieldMJD_TAI"], observations_df["LCA_HG_mag"], linestyle="-", label="__none__", color="b"
)

ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["HG12_mag"],
    linestyle="--",
    label="__none__",
    color="g",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["LCA_HG12_mag"],
    linestyle="-",
    label="__none__",
    color="g",
)

ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["HG1G2_mag"],
    linestyle="--",
    label="__none__",
    color="c",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["LCA_HG1G2_mag"],
    linestyle="-",
    label="__none__",
    color="c",
)


custom_legend = [
    Line2D([0], [0], color="m", linestyle="-"),
    Line2D([0], [0], color="r", linestyle="-"),
    Line2D([0], [0], color="b", linestyle="-"),
    Line2D([0], [0], color="g", linestyle="-"),
    Line2D([0], [0], color="c", linestyle="-"),
    Line2D([0], [0], color="k", linestyle="-"),
    Line2D([0], [0], color="k", linestyle="--"),
]

ax.legend(
    custom_legend,
    ["No phase curve", "Linear", "HG", "HG12", "HG1G2", "Lightcurve added", "No lightcurve"],
    ncol=2,
)
ax.set_xlabel("Time since first observation (days)")
ax.set_ylabel("Apparent magnitude")
ax.set_ylim(9.5, 11.5)
plt.gca().invert_yaxis()
plt.grid()
plt.show()
../_images/notebooks_demo_Lightcurve_15_0.png

Incorporating your own lightcurve

You can also implement a custom lightcurve. To do so, you need to inherit from the AbstractLightCurve class inside sorcha. Let’s implement a simple extension of this sinusoidal model, where we have two sine terms at once. The implementation will be very similar to the SinusoidalLightCurve class used above.

[11]:
from sorcha.lightcurves.base_lightcurve import AbstractLightCurve

from sorcha.lightcurves.base_lightcurve import AbstractLightCurve

from typing import List
import pandas as pd
import numpy as np


class DoubleSinusoidalLightCurve(AbstractLightCurve):
    def __init__(
        self, required_column_names: List[str] = ["fieldMJD_TAI", "LCA", "Period1", "Period2", "Time0"]
    ) -> None:
        super().__init__(required_column_names)

    def compute(self, df: pd.DataFrame) -> np.array:
        # Verify that the input data frame contains each of the required columns.
        self._validate_column_names(df)

        time1 = 2 * np.pi * (df["fieldMJD_TAI"] - df["Time0"]) / df["Period1"]
        time2 = 2 * np.pi * (df["fieldMJD_TAI"] - df["Time0"]) / df["Period2"]

        return df["LCA"] * np.sin(time1) * np.sin(time2)

    # this method defines the same of the class inside LC_METHODS
    @staticmethod
    def name_id() -> str:
        return "doublesinusoidal"
[12]:
update_lc_subclasses()
print(LC_METHODS)
{'identity': <class 'sorcha.lightcurves.identity_lightcurve.IdentityLightCurve'>, 'sinusoidal': <class 'sorcha_addons.lightcurve.sinusoidal.sinusoidal_lightcurve.SinusoidalLightCurve'>, 'doublesinusoidal': <class '__main__.DoubleSinusoidalLightCurve'>}
[13]:
# let's add the required columns that are different from the sinusoidal lightcurve
observations_df["Period1"] = 20.0
observations_df["Period2"] = 4.0
[14]:
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "none", "r", "DLCA_mag", "doublesinusoidal"
)
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "HG", "r", "DLCA_HG_mag", "doublesinusoidal"
)
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "HG12", "r", "DLCA_HG12_mag", "doublesinusoidal"
)
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "HG1G2", "r", "DLCA_HG1G2_mag", "doublesinusoidal"
)
observations_df = PPCalculateApparentMagnitudeInFilter(
    observations_df.copy(), "linear", "r", "DLCA_linear_mag", "doublesinusoidal"
)
[15]:
from matplotlib.lines import Line2D

fig, ax = plt.subplots(figsize=(10, 8))
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["Simple_mag"],
    linestyle=":",
    label="__none__",
    color="m",
)
ax.plot(
    observations_df["fieldMJD_TAI"], observations_df["LCA_mag"], linestyle="--", label="__none__", color="m"
)
ax.plot(
    observations_df["fieldMJD_TAI"], observations_df["DLCA_mag"], linestyle="-", label="__none__", color="m"
)


ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["linear_mag"],
    linestyle=":",
    label="__none__",
    color="r",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["LCA_linear_mag"],
    linestyle="--",
    label="__none__",
    color="r",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["DLCA_linear_mag"],
    linestyle="-",
    label="__none__",
    color="r",
)


ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["HG_mag"],
    linestyle=":",
    label="__none__",
    color="b",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["LCA_HG_mag"],
    linestyle="--",
    label="__none__",
    color="b",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["DLCA_HG_mag"],
    linestyle="-",
    label="__none__",
    color="b",
)

ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["HG12_mag"],
    linestyle=":",
    label="__none__",
    color="g",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["LCA_HG12_mag"],
    linestyle="--",
    label="__none__",
    color="g",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["DLCA_HG12_mag"],
    linestyle="-",
    label="__none__",
    color="g",
)

ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["HG1G2_mag"],
    linestyle=":",
    label="__none__",
    color="c",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["LCA_HG1G2_mag"],
    linestyle="--",
    label="__none__",
    color="c",
)
ax.plot(
    observations_df["fieldMJD_TAI"],
    observations_df["DLCA_HG1G2_mag"],
    linestyle="-",
    label="__none__",
    color="c",
)


custom_legend = [
    Line2D([0], [0], color="m", linestyle="-"),
    Line2D([0], [0], color="r", linestyle="-"),
    Line2D([0], [0], color="b", linestyle="-"),
    Line2D([0], [0], color="g", linestyle="-"),
    Line2D([0], [0], color="c", linestyle="-"),
    Line2D([0], [0], color="k", linestyle="-"),
    Line2D([0], [0], color="k", linestyle="--"),
    Line2D([0], [0], color="k", linestyle=":"),
]

ax.legend(
    custom_legend,
    ["No phase curve", "Linear", "HG", "HG12", "HG1G2", "Double sinusoidal", "Sinusoidal", "No lightcurve"],
    ncol=2,
)
ax.set_xlabel("Time since first observation (days)")
ax.set_ylabel("Apparent magnitude")
ax.set_ylim(9.5, 11.5)
plt.gca().invert_yaxis()
plt.grid()
plt.show()
../_images/notebooks_demo_Lightcurve_21_0.png