Skip to content

Conversation

@mkelley
Copy link
Member

@mkelley mkelley commented Dec 28, 2025

Summary

  • Add plot() methods to syndyne and synchrone classes
  • New SourceOrbit class to encapsulate a collection of states that make up an orbit.
  • Update SynGenerator.source_orbit to use the new SourceOrbit class.
  • Add a new documentation subsection to show how to get State objects from Horizons via Ephem.from_horizons.
  • Ephem.from_horizons is populated with masked quantities, but WCS can crash trying to convert masked coordinates to pixels. Added a new sbpy.utils._unmasked function which will return an unmasked object using an astropy 7.0 function (or fall back on an internal method for astropy < 7).
  • Add an example of plotting syndynes on an image.
  • State.to_ephem was in the documentation, but not the code! Added this function.

.plot() methods

Syndyne and synchrone plotting is simplified. Previously one might have written:

coords0 = observer.observe(comet)
def plot(ax, coords, **kwargs):
    dRA = coords.ra - coords0.ra
    dDec = coords.dec - coords0.dec
    ax.plot(dRA.arcsec, dDec.arcsec, **kwargs)

for syndyne in dust.syndynes():
    plot(ax, syndyne.coords, label=f"$\\beta={syndyne.beta:.2g}$")

for synchrone in dust.synchrones():
    plot(ax, synchrone.coords, ls="--", label=f"$\Delta t={synchrone.age.to(u.d):.2g}$")

Now, much of that is incorporated into sbpy:

dust = SynGenerator(comet, betas, ages, observer=observer)
dust.syndynes().plot()
dust.synchrones().plot()

See the documentation for more examples.

API changes with SourceOrbit

SynGenerator.source_orbit returned a tuple including the states and coordinates of the orbit at the requested times. But these can be collected into a single object, just like Syndyne and Synchrone include their own particle states and coordinates. Furthermore, it would be great to use the new plot() approach that Syndyne and Synchrone use. To that end, I've created the new SourceOrbit class.

Previously:

states, coords = dust.source_orbit(dt)
coords0 = observer.observe(comet)
dRA = coords.ra - coords0.ra
dDec = coords.dec - coords0.dec
ax.plot(dRA.arcsec, dDec.arcsec,  color="k", ls=":", label="Orbit")

Now with SourceOrbit the same machinery behind Syndynes and Synchrones can be used:

dust.source_orbit(dt).plot(color="k", ls=":", label="Orbit")

API changes with Syndynes and Synchrones

Indexing Syndynes or Synchrones would return single Syndyne / Synchrone objects, or a list thereof:

>>> syndynes = dust.syndynes()
>>> type(syndynes[0])
sbpy.dynamics.syndynes.Syndyne
>>> type(syndynes[:2])
list

This behavior was OK, but with the new .plot() methods, returning a list drops the fancy machinery behind, e.g., Synchrones.plot(). As a result we would have to use the individual plot methods, which also means the label needs to be specified to achieve the same behavior:

# plot every 5th synchrone
for syn in dust.synchrones()[::5]:
    syn.plot(label="$\\Delta t = {age}$".format(syn.age))

Now indexing with a slice with return a Syndynes or Synchrones object, and we can immediately plot the results:

# plot every 5th synchrone
dust.synchrones()[::5].plot()

@mkelley mkelley marked this pull request as draft December 28, 2025 16:26
@github-actions
Copy link

Thank you for your contribution to sbpy, an Astropy affiliated package! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the sbpy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related?
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label.
  • Is a milestone added?

@mkelley
Copy link
Member Author

mkelley commented Dec 28, 2025

Keeping this a draft until #427 is done.

@codecov
Copy link

codecov bot commented Dec 28, 2025

Codecov Report

❌ Patch coverage is 93.54839% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.01%. Comparing base (162139f) to head (0852cf7).

Files with missing lines Patch % Lines
sbpy/dynamics/syndynes.py 88.23% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #434      +/-   ##
==========================================
- Coverage   84.72%   82.01%   -2.72%     
==========================================
  Files          92       53      -39     
  Lines        8459     4325    -4134     
==========================================
- Hits         7167     3547    -3620     
+ Misses       1292      778     -514     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant