Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 2010 TK7 trojan analysis example #126

Merged
merged 2 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added worked example of calculating if an object is an Earth Trojan.

### Fixed

- Epoch and Perihelion time conversion when loading JPL Horizons covariance matrices was
Expand Down
96 changes: 96 additions & 0 deletions src/examples/earth_trojan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Earth Trojan 2010 TK7
=====================

Following some of the analysis from the following papers:

- `"2002 AA29: Earth's recurrent quasi-satellite?" - Paweł Wajer - Icarus - 2009 <https://doi.org/10.1016/j.icarus.2008.10.018>`_
- `"Dynamical evolution of Earth’s quasi-satellites: 2004 GU9 and 2006 FV35" - Paweł Wajer - Icarus - 2010 <https://doi.org/10.1016/j.icarus.2010.05.012>`_

We can perform some dynamical analysis to see if the asteroid 2010 TK7 appears
to be an Earth trojan.

The end result of this is a plot which shows the current energy level of the
orbit, if the current position of the object is in a gravitational "low", and
the energy line (in red) is below the height of the walls of that low point, then
the object is at least temporarily captured.

"""

import numpy as np
import matplotlib.pyplot as plt
import kete


# Grab 2010 TK7 and propagate it to 2010
state_a = kete.HorizonsProperties.fetch("2010 TK7", update_name=False).state
state_a = kete.propagate_n_body([state_a], kete.Time.from_ymd(2010, 1, 1))[0]

state_b = kete.spice.get_state("Earth", state_a.jd)
# Earth mass in Solar masses
mass_planet = 3.0435727639549377e-06

# n_sigma is the number of step on the x axis in the final plot
n_sigma = 500

# m_steps is the number of steps in the numerical integration
m_steps = 100


jd = state_a.jd

elem_a = state_a.elements
elem_b = state_b.elements

period_a = elem_a.orbital_period
period_b = elem_b.orbital_period

sigma_steps = np.linspace(0, 1, n_sigma + 1)[:-1]

energy_vals = []
for shift in sigma_steps:

total = 0
for frac in np.linspace(0, 1, m_steps + 1)[:-1]:
state_a = kete.propagate_two_body([state_a], jd + (frac + shift) * period_a)[0]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not quite right. I don't believe you want to do propagation here. I think rather you want to sweep mean anomaly for the instantaneous orbits, then evaluate the state at each of those MA clockings. Time would be a third axis to consider; e.g. make one plot for time=jd, then integrate the jd of the system a step forward and make another plot.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e., I'm not sure that this and the following line maintain the same delta-longitude for all steps in m_steps when period_a and period_b aren't the same, and I think you need to maintain that spacing to get the energy correct

state_b = kete.propagate_two_body([state_b], jd + frac * period_b)[0]
pos_a = state_a.pos
pos_b = state_b.pos
total += 1 / (pos_b - pos_a).r - np.dot(pos_b, pos_a) / pos_b.r**3

energy_vals.append(total / m_steps)

cur_energy = 3 * (elem_a.semi_major - 1) ** 2 / (8.0 * mass_planet) + energy_vals[0]

# energy vals start from the current positions and go a full orbit.
# These may be unrolled so that the Earth is in the middle, the following
# is a series of index manipulations to find the correct position for that.

longitude_b = elem_b.mean_anomaly + elem_b.peri_arg + elem_b.lon_of_ascending
longitude_a = elem_a.mean_anomaly + elem_a.peri_arg + elem_a.lon_of_ascending
i = np.digitize(((longitude_a - longitude_b) % 360) / 360, sigma_steps)

# here are the unrolled values, which may be plotted
vals_unrolled = np.roll(energy_vals, i + n_sigma // 2)
x_unrolled = np.linspace(-180, 180, n_sigma + 1)[:-1]

# plot the energy curve
plt.plot(x_unrolled, vals_unrolled)

# plot the current position of the object
plt.scatter(x_unrolled[(i + n_sigma // 2) % (n_sigma)], energy_vals[0])

# plot the current energy of the object
plt.axhline(cur_energy, c="r")
plt.axvline(0, c="k")

# We can then find and plot the inflection points
local_max = np.argwhere(np.diff(np.sign(np.diff(vals_unrolled))) < 0).ravel() + 1
for x in vals_unrolled[local_max]:
plt.axhline(x, c="k", ls="--")


plt.xlabel(r"$\sigma$ (deg)")
plt.ylabel(r"R($\sigma)$")
plt.title(f"{elem_a.desig}")
plt.show()
12 changes: 3 additions & 9 deletions src/kete/horizons.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,15 @@ def fetch(name, update_name=True, cache=True, update_cache=False, exact_name=Fal
]
if len(val) == 0:
continue
if kete_v == "peri_time":
phys[kete_v] = Time(val[0], scaling="utc").jd
else:
phys[kete_v] = val[0]
phys[kete_v] = val[0]

phys["epoch"] = Time(float(props["orbit"]["epoch"]), scaling="utc").jd
phys["epoch"] = float(props["orbit"]["epoch"])
if "moid" in props["orbit"] and props["orbit"]["moid"] is not None:
phys["moid"] = float(props["orbit"]["moid"])
if "data_arc" in props["orbit"] and props["orbit"]["data_arc"] is not None:
phys["arc_len"] = float(props["orbit"]["data_arc"])
if props["orbit"]["covariance"] is not None:
cov_epoch = Time(
float(props["orbit"]["covariance"]["epoch"]), scaling="utc"
).jd
cov_epoch = float(props["orbit"]["covariance"]["epoch"])
mat = np.nan_to_num(
np.array(props["orbit"]["covariance"]["data"], dtype=float)
)
Expand All @@ -118,7 +113,6 @@ def fetch(name, update_name=True, cache=True, update_cache=False, exact_name=Fal
elements = {
lab: phys[lookup_rev[lab]] for lab in labels if lab in lookup_rev
}
elements["tp"] = Time(elements.get("tp"), scaling="utc").jd
params = [(lookup_rev.get(x, x), elements.get(x, np.nan)) for x in labels]
phys["covariance"] = Covariance(name, cov_epoch, params, mat)
else:
Expand Down