Skip to content

Syndynes plots and fixes#434

Merged
mkelley merged 34 commits into
NASA-Planetary-Science:mainfrom
mkelley:syndynes-plots-2025.12
May 14, 2026
Merged

Syndynes plots and fixes#434
mkelley merged 34 commits into
NASA-Planetary-Science:mainfrom
mkelley:syndynes-plots-2025.12

Conversation

@mkelley

@mkelley mkelley commented Dec 28, 2025

Copy link
Copy Markdown
Member

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 with int, slice, or tuple

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 (or tuple) will return a Syndynes or Synchrones object, and we can immediately plot the results:

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

Drop "coords" from to_ephem() result

The heliocentric ecliptic IAU76 reference frame needs observation time. When creating an Ephem object from Synchrones in that frame, the coordinates of each synchrone could not be concatenated. I think it is because the observation times in the reference frame did not match, so astropy thought the reference frames were different, and in a way that is right. Rather than hack something here, just drop the coords column. RA, Dec, etc. are already included as separate columns, so there is no loss of information.

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

Copy link
Copy Markdown

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

mkelley commented Dec 28, 2025

Copy link
Copy Markdown
Member Author

Keeping this a draft until #427 is done.

@codecov

codecov Bot commented Dec 28, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.09%. Comparing base (59a8a49) to head (408c258).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #434      +/-   ##
==========================================
+ Coverage   81.81%   82.09%   +0.28%     
==========================================
  Files          53       53              
  Lines        4267     4335      +68     
==========================================
+ Hits         3491     3559      +68     
  Misses        776      776              

☔ 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.

@mkelley

mkelley commented Dec 31, 2025

Copy link
Copy Markdown
Member Author

Updated comment with API-breaking change in to_ephem.

@mkelley
mkelley force-pushed the syndynes-plots-2025.12 branch from 4786e0c to 8327d22 Compare April 28, 2026 23:46
@mkelley
mkelley marked this pull request as ready for review April 28, 2026 23:49
@mkelley
mkelley requested a review from hhsieh00 April 28, 2026 23:49
@hhsieh00

hhsieh00 commented May 5, 2026

Copy link
Copy Markdown
Collaborator

I didn't see any notable issues in the code changes and the functionality seems good after the various API and other changes to the previous way of doing things. My main comments are on the documentation:

  • From the documentation, I was confused for a while about the relationship between SourceOrbit and source_orbit, and which the user was meant to use. The SourceOrbit class is mentioned but there is no example of its use in the documentation, while source_orbit (from SynGenerator) is the method used in the example to plot the object's orbit, so the need for a new SourceOrbit class was initially unclear. The change log makes it clearer that the source_orbit method produces a SourceOrbit object (so the SourceOrbit class mainly just enables backend functionality but is never directly called by name by the user?), but maybe this distinction should be made in the documentation itself? At the moment, in the documentation, under "Plotting syndynes and synchrones", it should probably say: "Syndynes, synchrones, and source_orbit have plot() methods to assist with this." since the example shows source_orbit.plot(), not SourceOrbit.plot().

  • I'm not sure if this is a system-specific thing, but the synchrones in the syndyne-synchrone plot example in the documentation do not show up as dashed lines for me as in the example plot (shows up as all solid lines for me), so that plotting call should be "dust.synchrones()[4::5].plot(ax,ls="--")"

  • It would probably be useful in the documentation to move the example where different syndynes are plotted with different line styles above the Spitzer example, because the variables get set to different values in the Spitzer example, whereas the sample output plot showing different line styles uses syndyne data from before the Spitzer example. Also, in that last example (plotting different line styles), while it probably shouldn't be too difficult for a user to figure out, it might still be better to write out the full code for generating the sample plot, rather than just showing the lines that are different from the original example code and leaving it to the user to fill in the blanks.

  • At one point, "telescope" is incorrectly spelled as the "Spitzer Space Telesocpe"

@mkelley

mkelley commented May 7, 2026

Copy link
Copy Markdown
Member Author

From the documentation, I was confused for a while about the relationship between SourceOrbit and source_orbit, and which the user was meant to use. The SourceOrbit class is mentioned but there is no example of its use in the documentation, while source_orbit (from SynGenerator) is the method used in the example to plot the object's orbit, so the need for a new SourceOrbit class was initially unclear. The change log makes it clearer that the source_orbit method produces a SourceOrbit object (so the SourceOrbit class mainly just enables backend functionality but is never directly called by name by the user?), but maybe this distinction should be made in the documentation itself?

Yeah, I see what you are saying. I'll move the orbit section to be contiguous with the syndyne and synchrone sections, and add some words before all this to clarify what is happening here: the SynGenerator object has methods syndynes(), synchrones(), and source_orbit() which produce Syndynes, Synchrones, and SourceOrbit objects.

At the moment, in the documentation, under "Plotting syndynes and synchrones", it should probably say: "Syndynes, synchrones, and source_orbit have plot() methods to assist with this." since the example shows source_orbit.plot(), not SourceOrbit.plot().

I think the above edits should help explain why this doesn't work. source_orbit is a function that produces the SourceOrbit object. source_orbit itself does not have a plot method. I think my attempt to be brief has confused things a bit, so I'll expand the example to help.

@mkelley

mkelley commented May 7, 2026

Copy link
Copy Markdown
Member Author

I'm not sure if this is a system-specific thing, but the synchrones in the syndyne-synchrone plot example in the documentation do not show up as dashed lines for me as in the example plot (shows up as all solid lines for me), so that plotting call should be "dust.synchrones()[4::5].plot(ax,ls="--")"

fixed

@mkelley

mkelley commented May 7, 2026

Copy link
Copy Markdown
Member Author

It would probably be useful in the documentation to move the example where different syndynes are plotted with different line styles above the Spitzer example, because the variables get set to different values in the Spitzer example, whereas the sample output plot showing different line styles uses syndyne data from before the Spitzer example.

That makes sense. Moved!

Also, in that last example (plotting different line styles), while it probably shouldn't be too difficult for a user to figure out, it might still be better to write out the full code for generating the sample plot, rather than just showing the lines that are different from the original example code and leaving it to the user to fill in the blanks.

Instead of repeating everything, I have enable the full "source code" link for the figure. Is that OK?

mkelley added 16 commits May 7, 2026 13:32
* Move to_ephem to StateBase
* compute coordinates if an observer is given
* put frame in Ephem metadata
to_ephem:
* Remove coords column from to_ephem due to problem vstacking SkyCoords columns when frame contains obstime
* Use StateBase's to_ephem for most of the work.

plot()
* Fix cosine correction
* Allow spherical coords other than RA/Dec

Fix SourceOrbit.epoch

SynCollection:
* __getitem__ should return SynCollections for tuples and slices
* Use Ephem's vstack in to_ephem
@mkelley
mkelley force-pushed the syndynes-plots-2025.12 branch from 89e09dc to ccc5ebd Compare May 7, 2026 17:35
@mkelley

mkelley commented May 7, 2026

Copy link
Copy Markdown
Member Author

At one point, "telescope" is incorrectly spelled as the "Spitzer Space Telesocpe"

And, done.

@mkelley

mkelley commented May 7, 2026

Copy link
Copy Markdown
Member Author

@hhsieh00 ready for your reconsideration

@hhsieh00 hhsieh00 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Finally went through all the changes and new documentation text. Looks good now!

@mkelley
mkelley merged commit db06f0f into NASA-Planetary-Science:main May 14, 2026
9 checks passed
@mkelley
mkelley deleted the syndynes-plots-2025.12 branch May 14, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants