diff --git a/CHANGES.rst b/CHANGES.rst index f5bc53d44..aa7209769 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,12 +8,55 @@ Update supported versions [#427]: New Features ------------ +sbpy.dynamics +^^^^^^^^^^^^^ + +- Plot syndynes and synchrones with the new ``plot()`` methods on + `sbpy.dynamics.syndynes.Syndyne`, `sbpy.dynamics.syndynes.Syndynes`, + `sbpy.dynamics.syndynes.Synchrone`, and `sbpy.dynamics.syndynes.Synchrones`. + [#434] + +- New `sbpy.dynamics.syndynes.SourceOrbit` class to encapsulate a collection of + points along an object's orbit. [#434] + sbpy.photometry ^^^^^^^^^^^^^^^ - Added Rubin Observatory's LSSTCam filter set to `bandpass()`. [#431] +API Changes +----------- + +sbpy.dynamics +^^^^^^^^^^^^^ + +- Indexing `sbpy.dynamics.syndynes.Syndynes` or + `sbpy.dynamics.syndynes.Synchrones` with an integer, e.g., ``syndynes[0]``, + will return a single `sbpy.dynamics.syndynes.Syndyne` or + `sbpy.dynamics.syndynes.Synchrone`. Indexing with a tuple or slice, e.g., + ``syndynes[:2]`` will return `sbpy.dynamics.syndynes.Syndynes` or + `sbpy.dynamics.syndynes.Synchrones`. Previously a list was returned. [#434] + +- `sbpy.dynamics.syndynes.SynGenerator.source_orbit` now returns a + `sbpy.dynamics.syndynes.SourceOrbit` object. Previously the states and + projected coordinates were returned as separate objects. [#434] + +- Converting `sbpy.dynamics.syndynes.Syndynes` and + `sbpy.dynamics.syndynes.Synchrones` to ``Ephem`` objects no longer include the + ``SkyCoord`` column "coords", but all the data (RA, Dec, etc.) should still be + present as separate columns. [#434] + + +Bug Fixes +--------- + +sbpy.dynamics +^^^^^^^^^^^^^ + +- Added missing `sbpy.dynamics.state.State.to_ephem`. [#434] + + v0.6.0 (2025-12-02) =================== diff --git a/docs/sbpy/dynamics.rst b/docs/sbpy/dynamics.rst index 60549fc2b..92b5223a8 100644 --- a/docs/sbpy/dynamics.rst +++ b/docs/sbpy/dynamics.rst @@ -56,19 +56,21 @@ Convert to/from `Ephem` and `SkyCoord` State objects may be initialized from ephemeris objects (`~sbpy.data.Ephem`), provided they contain time, and 3D position and velocity: .. doctest-requires:: astroquery +.. doctest-remote-data:: >>> from sbpy.data import Ephem >>> eph = Ephem.from_horizons("9P", ... epochs=Time("2005-07-04"), ... id_type="designation", - ... closest_apparition=True) # doctest: +REMOTE_DATA - >>> tempel1 = State.from_ephem(eph) # doctest: +REMOTE_DATA + ... closest_apparition=True) + >>> tempel1 = State.from_ephem(eph) And `State` may be converted to an `Ephem` object: .. doctest-requires:: astroquery +.. doctest-remote-data:: - >>> eph = tempel1.to_ephem() # doctest: +REMOTE_DATA + >>> eph = tempel1.to_ephem() `astropy`'s `~astropy.coordinates.SkyCoord` objects may also be used, assuming the time and 3D vectors are fully defined: @@ -168,6 +170,25 @@ Or, get the sky coordinates of the comet as seen from the Earth: The result, a `~astropy.coordinates.SkyCoord` object, is expressed in the reference frame of the observer. +Fetching states from Horizons +----------------------------- + +`Ephem.from_horizons` returns equatorial coordinates in the ICRF reference frame, which has its origin at the Solar System barycenter. For `State` to correctly convert the ephemeris object to vectors, we need to set the Horizons observer to the Solar System barycenter (``"@ssb"``). However, dynamical integrations are done in a heliocentric reference frame, so we transform the result to ``"heliocentriceclipticiau76"``: + +.. doctest-requires:: astroquery +.. doctest-remote-data:: + + >>> eph = Ephem.from_horizons( + ... "48P", + ... id_type="designation", + ... closest_apparition=True, + ... epochs=Time("2004-10-13T21:08:23.894"), + ... location="@ssb", + ... ) + >>> comet = State.from_ephem(eph, frame="icrs") + >>> comet = comet.transform_to("heliocentriceclipticiau76") + + Dynamical integrators ===================== @@ -203,13 +224,14 @@ Dust syndynes and synchrones Syndynes are lines in space connecting particles that are experiencing the same forces. A syndyne is parameterized by :math:`\beta`, the ratio of the force from solar radiation to the force from solar gravity, :math:`F_r / F_g`, and age (or time of release). Thus, all particles in a syndyne have a constant :math:`\beta` but variable age. Similarly, synchrones are lines of constant particle age, but variable :math:`\beta`. +Start with a `SynGenerator` +--------------------------- -Syndynes --------- +sbpy has the `sbpy.dynamics.syndynes.SynGenerator` class to assist the user in the generation of syndynes, synchrones, and points along a source object's orbit. Use the `~sbpy.dynamics.syndynes.SynGenerator`'s :func:`~sbpy.dynamics.syndynes.SynGenerator.syndynes`, :func:`~sbpy.dynamics.syndynes.SynGenerator.synchrones`, and :func:`~sbpy.dynamices.syndynes.SynGenerator.source_orbit` methods to produce `~sbpy.dynamics.syndynes.Syndynes`, `~sbpy.dynamics.syndynes.Synchrones`, and `~sbpy.dynamics.syndynes.SourceOrbit` objects, described in further detail below. -Zero-ejection velocity syndynes are generated with the `~sbpy.dynamics.syndynes.SynGenerator` class. The class requires a dust source described by a `~sbpy.dynamics.state.State` object, :math:`\beta` values, and particle ages from which to generate the syndynes. +Setting up a `~sbpy.dynamics.syndynes.SynGenerator` instance requires a dust source, and dust :math:`\beta` values and ages from which to generate the syndynes/synchrones. -First, define the source of the syndynes, a comet at 2 au from the Sun: +First, define the dust source, a comet at 2 au from the Sun: .. doctest:: @@ -222,7 +244,7 @@ First, define the source of the syndynes, a comet at 2 au from the Sun: >>> t = Time("2023-12-08") >>> comet = State(r, v, t) -Next, initialize the syndyne object: +Next, initialize the syn-generator object. Here, we request dust with :math:`\beta` values from 0 to 1, generated over a 100-day period: .. doctest-requires:: scipy @@ -247,7 +269,28 @@ The computed particle positions are saved in the :attr:`~sbpy.dynamics.syndynes. >>> print(len(dust.particles)) 104 -Get the results with the :func:`~sbpy.dynamics.syndynes.SynGenerator.syndynes` method, which returns a list-like collection of `~sbpy.dynamics.syndynes.Syndyne` objects. `Syndyne` objects are specialized `State` objects. For example, we can compute the linear distance from the comet to farthest particle in each syndyne: + +Syndynes +-------- + +Zero-ejection velocity syndynes are generated with the `~sbpy.dynamics.syndynes.SynGenerator` class, and this example uses the generator above. Get the syndynes with the :func:`~sbpy.dynamics.syndynes.SynGenerator.syndynes` method, which returns `~sbpy.dynamics.syndynes.Syndynes` a list-like collection of `~sbpy.dynamics.syndynes.Syndyne` objects (note the use of plural and singular forms). + +.. doctest-requires:: scipy + + >>> syndynes = dust.syndynes() + >>> syndynes + + >>> syndynes[0] # doctest: +FLOAT_CMP + ): + r + [[ 2.99195741e+08 0.00000000e+00 0.00000000e+00] + [ 2.99284224e+08 -2.04410384e+03 0.00000000e+00] + [ 2.99549029e+08 -1.63231106e+04 0.00000000e+00] + [ 2.99988249e+08 -5.49241301e+04 0.00000000e+00] + [ 3.00598735e+08 -1.29642418e+05 0.00000000e+00] + ... + +`Syndyne` objects are specialized `State` objects. For example, we can compute the linear distance from the comet to farthest particle in each syndyne: .. doctest-requires:: scipy @@ -259,7 +302,7 @@ Get the results with the :func:`~sbpy.dynamics.syndynes.SynGenerator.syndynes` m 0.01, 0.003 AU 0.0, 0.000 AU -Individual syndynes may be retrieved with the :func:`~sbpy.dynamics.syndynes.SynGenerator.syndyne` method and a syndyne index. The index for the syndyne matches the index of the `betas` array, i.e., to get the :math:`\beta=0.1` syndyne from our example: +Individual syndynes may also be retrieved directly from the `~sbpy.dynamics.syndynes.SynGenerator` object using the :func:`~sbpy.dynamics.syndynes.SynGenerator.syndyne` method and a syndyne index. The index for the syndyne matches the index of the `betas` array, i.e., to get the :math:`\beta=0.1` syndyne from our example: .. doctest-requires:: scipy @@ -293,6 +336,28 @@ To support generating synchrones at specific times, the :func:`~sbpy.dynamics.sy