.. _whatsnew-6.0:

**************************
What's New in Astropy 6.0?
**************************

Overview
========

Astropy 6.0 is a major release that adds significant new functionality since
the 5.3 release.

In particular, this release includes:

* :ref:`whatsnew-6.0-geodetic-representation-geometry`
* :ref:`whatsnew-6.0-planetary-wcs-bodyfixed`
* :ref:`whatsnew-6.0-broadcasting-frame-attributes`
* :ref:`whatsnew-6.0-cosmology-latex-export`
* :ref:`whatsnew-6.0-iers-data`
* :ref:`whatsnew-6.0-masked-time`
* :ref:`whatsnew-6.0-model-annotation-in-votable`
* :ref:`whatsnew-6.0-time-delta-quantity-str`
* :ref:`whatsnew-6.0-votable-parquet-serialization`
* :ref:`whatsnew-6.0-fits-decompression`
* :ref:`whatsnew-6.0-general-sersic2d`

In addition to these major changes, Astropy v6.0 includes a large number of
smaller improvements and bug fixes, which are described in the :ref:`changelog`.
By the numbers:

* 1044 commits have been added since 5.3
* 157 issues have been closed since 5.3
* 313 pull requests have been merged since 5.3
* 57 people have contributed since 5.3
* 24 of which are new contributors

.. _whatsnew-6.0-geodetic-representation-geometry:

Define Geodetic and Bodycentric Representations via their geometric parameters
==============================================================================

The user may now define custom spheroidal models for the Earth or other planetary
bodies by subclassing `~astropy.coordinates.BaseGeodeticRepresentation` or
`~astropy.coordinates.BaseBodycentricRepresentation` and defining
``_equatorial_radius`` and ``_flattening`` attributes::


    >>> from astropy.coordinates import (BaseGeodeticRepresentation,
    ...     WGS84GeodeticRepresentation, BaseBodycentricRepresentation)
    >>> from astropy import units as u
    >>> class IAU1976EarthGeodeticRepresentation(BaseGeodeticRepresentation):
    ...     _equatorial_radius = 6378140 * u.m
    ...     _flattening = 0.3352805 * u.percent
    >>> representation = IAU1976EarthGeodeticRepresentation(lon=45.8366*u.deg,
    ...     lat=56.1499*u.deg, height=367*u.m)
    >>> representation.to_cartesian() # doctest: +FLOAT_CMP
    <CartesianRepresentation (x, y, z) in m
        (2481112.60371134, 2554647.09482601, 5274064.55958489)>
    >>> representation.represent_as(WGS84GeodeticRepresentation) # doctest: +FLOAT_CMP
    <WGS84GeodeticRepresentation (lon, lat, height) in (rad, rad, m)
        (0.79999959, 0.98000063, 370.01796023)>
    >>> class IAU1976EarthBodycentricRepresentation(BaseBodycentricRepresentation):
    ...     _equatorial_radius = 6378140 * u.m
    ...     _flattening = 0.3352805 * u.percent
    >>> representation.represent_as(IAU1976EarthBodycentricRepresentation) # doctest: +FLOAT_CMP
    <IAU1976EarthBodycentricRepresentation (lon, lat, height) in (rad, rad, m)
        (0.79999959, 0.9768896, 336.12620429)>

See :ref:`astropy-coordinates-create-geodetic` for more details.


.. _whatsnew-6.0-planetary-wcs-bodyfixed:

Body-fixed planetary reference frames and their WCS description
===============================================================

Body-fixed planetary reference frames for Solar System planetary
bodies can be read and written via WCS structures in FITS images, following
`Definitions, Applications, and Best Practices for Planetary FITS <https://doi.org/10.1029/2018EA000388>`_.

See :ref:`creating_planetary_wcs` for an example.

Planetary images or spectral cube WCS description can be manipulated using the
:mod:`~astropy.wcs` module.


.. _whatsnew-6.0-broadcasting-frame-attributes:

Support for Numpy broadcasting over frame data and attributes
=============================================================

Frames in `astropy.coordinates` now support
:doc:`Numpy broadcasting rules <numpy:user/basics.broadcasting>` over both
frame data and frame attributes. Previously, broadcasting was only supported
over framed data. This makes it much easier and faster to do positional
astronomy calculations and transformations on sweeps of parameters.

For example, the user can now create frame objects with scalar data but vector
frame attributes, such as::

    from astropy.coordinates import FK4
    from astropy import units as u

    FK4(1 * u.deg, 2 * u.deg, obstime=["J2000", "J2001"])

Where this really shines is doing fast observability calculations over arrays.
The following example constructs an `~astropy.coordinates.EarthLocation` array
of length :samp:`{L}`, a `~astropy.coordinates.SkyCoord` array of length
:samp:`{M}`, and a `~astropy.time.Time` array of length :samp:`N`. It uses
Numpy broadcasting rules to evaluate a boolean array of shape
:samp:`({L}, {M}, {N})` that is `True` for those observing locations, times,
and sky coordinates, for which the target is above an altitude limit::

    >>> from astropy.coordinates import EarthLocation, AltAz, SkyCoord
    >>> from astropy.coordinates.angles import uniform_spherical_random_surface
    >>> from astropy.time import Time
    >>> from astropy import units as u
    >>> import numpy as np

    >>> L = 25
    >>> M = 100
    >>> N = 50

    >>> # Earth locations of length L
    >>> c = uniform_spherical_random_surface(L)
    >>> locations = EarthLocation.from_geodetic(c.lon, c.lat)

    >>> # Celestial coordinates of length M
    >>> coords = SkyCoord(uniform_spherical_random_surface(M))

    >>> # Observation times of length N
    >>> obstimes = Time('2023-08-04') + np.linspace(0, 24, N) * u.hour

    >>> # AltAz coordinates of shape (L, M, N)
    >>> frame = AltAz(
    ...     location=locations[:, np.newaxis, np.newaxis],
    ...     obstime=obstimes[np.newaxis, np.newaxis, :])
    >>> altaz = coords[np.newaxis, :, np.newaxis].transform_to(frame)  # doctest: +REMOTE_DATA

    >>> min_altitude = 30 * u.deg
    >>> is_above_altitude_limit = (altaz.alt > min_altitude)  # doctest: +REMOTE_DATA
    >>> is_above_altitude_limit.shape  # doctest: +REMOTE_DATA
    (25, 100, 50)

.. _whatsnew-6.0-cosmology-latex-export:

Updates to `~astropy.cosmology`
===============================

Writing to LaTeX
----------------

The :class:`~astropy.cosmology.Cosmology` class in :mod:`~astropy.cosmology` now
supports the latex format in its :attr:`~astropy.cosmology.Cosmology.write()`
method, allowing users to export a cosmology object to a LaTeX table.::

    >>> from astropy.cosmology import Planck18
    >>> Planck18.write("example_cosmology.tex", format="ascii.latex")

This will write the cosmology object to a file in LaTeX format,
with appropriate formatting of units and table alignment.

Renaming fields in I/O
----------------------

Most I/O methods in :mod:`~astropy.cosmology` (accessed by
:meth:`~astropy.cosmology.Cosmology.read`,
:meth:`~astropy.cosmology.Cosmology.write`,
:meth:`~astropy.cosmology.Cosmology.from_format`,
:meth:`~astropy.cosmology.Cosmology.to_format`) now support
:ref:`renaming fields<cosmology_io_renaming_fields>`.

For example, to rename the ``H0`` field to ``Hubble`` when converting to a table
format::

    >>> from astropy.cosmology import Planck18
    >>> Planck18.to_format("astropy.table")  # No renaming
    <QTable length=1>
      name        H0        Om0    Tcmb0    Neff      m_nu      Ob0
             km / (Mpc s)            K                 eV
      str8     float64    float64 float64 float64  float64[3] float64
    -------- ------------ ------- ------- ------- ----------- -------
    Planck18        67.66 0.30966  2.7255   3.046 0.0 .. 0.06 0.04897

    >>> Planck18.to_format("astropy.table", rename={"H0": "Hubble"})
    <QTable length=1>
      name      Hubble      Om0    Tcmb0    Neff      m_nu      Ob0
             km / (Mpc s)            K                 eV
      str8     float64    float64 float64 float64  float64[3] float64
    -------- ------------ ------- ------- ------- ----------- -------
    Planck18        67.66 0.30966  2.7255   3.046 0.0 .. 0.06 0.04897


New properties to access |Cosmology| parameters
-----------------------------------------------

The |Cosmology| class now has a new property to access the parameters of the cosmology:
:attr:`~astropy.cosmology.Cosmology.parameters`. This property return a
:class:`~types.MappingProxyType` object, which is a read-only dictionary of all the
non-derived parameter values on the |Cosmology| instance. For example::

    >>> from astropy.cosmology import Planck18
    >>> Planck18.parameters["H0"]
    <Quantity 67.66 km / (Mpc s)>

When accessed from the cosmology class itself, the returned dictionary is
not the parameter values but :class:`~astropy.cosmology.Parameter` objects
with information about the parameter used when setting up the cosmology::

    >>> from astropy.cosmology import FlatLambdaCDM
    >>> FlatLambdaCDM.parameters["H0"]
    Parameter(derived=False, unit=Unit("km / (Mpc s)"), equivalencies=[], ...)


:class:`~astropy.cosmology.Parameter` as a :func:`~dataclasses.dataclass`
-------------------------------------------------------------------------

The :class:`~astropy.cosmology.Parameter` class is now a :func:`~dataclasses.dataclass`.
This means that the :mod:`dataclasses` machinery can be used to work with
:class:`~astropy.cosmology.Parameter` objects. For example::

    >>> from dataclasses import replace
    >>> from astropy.cosmology import FlatLambdaCDM
    >>> m_nu = FlatLambdaCDM.parameters["m_nu"]
    >>> m_nu
    Parameter(default=<Quantity 0. eV>, derived=False, unit=Unit("eV"), ...)
    >>> replace(m_nu, derived=True)
    Parameter(default=<Quantity 0. eV>, derived=True, unit=Unit("eV"), ...)

    >>> from dataclasses import asdict
    >>> asdict(m_nu)
    {'default': <Quantity 0. eV>, 'derived': False, 'unit': Unit("eV"), ...}


It's also much easier to create new :class:`~astropy.cosmology.Parameter` subclasses

    >>> from dataclasses import make_dataclass, field, fields
    >>> from astropy.cosmology import Parameter
    >>> NewP = make_dataclass("NewP", [("newfield", float, field(default=None))], bases=(Parameter,), frozen=True)
    >>> tuple(f.name for f in fields(NewP))
    (..., 'newfield')


.. _whatsnew-6.0-iers-data:

Updates to how IERS data are handled
====================================

Some parts of astropy, such as coordinate and time transformations, rely on
tables from the International Earth Rotation and Reference Systems (IERS)
service (these are the IERS-A, IERS-B, and leap second tables). IERS-A used
to always be automatically downloaded, whereas the IERS-B and leap second
tables were bundled with astropy. All tables are now bundled in the standalone
`astropy-iers-data <https://github.com/astropy/astropy-iers-data>`_ package
which is regularly updated and installed automatically when astropy is
installed.

The main benefit of moving the files to this package is to make it easier to
use astropy without an internet connection, and to facilitate updating the
tables if needed. Users that want to ensure they have the latest available
IERS data can now install the latest version of the
`astropy-iers-data`_ package using ``pip`` or ``conda``, or alternatively
download the package manually and transfer it to a computer that has no
public internet connection.

.. _whatsnew-6.0-masked-time:

Masked Time values now use Masked arrays internally
===================================================

|Time| can now be initialized with masked input that either uses |Masked|, from
astropy's `astropy.utils.masked` package, or `numpy.ma.MaskedArray`, from numpy,
and will now use |Masked| ``jd1`` and ``jd2`` internally to represent
the mask. As a result, all output from masked |Time| instances will now be
masked as well. For instance, converting a |TimeDelta| to a |Quantity| will
give a masked quantity (instead of a regular quantity with masked entries set
to ``np.nan``).

Small example::

  >>> from astropy.time import Time
  >>> from astropy.utils.masked import Masked
  >>> t = Time(Masked([52000., 52001, 52002], mask=[False, True, False]), format='mjd')
  >>> t
  <Time object: scale='utc' format='mjd' value=[52000.      ——— 52002. ]>
  >>> t.isot
  MaskedNDArray(['2001-04-01T00:00:00.000',                       ———,
                 '2001-04-03T00:00:00.000'], dtype='<U23')
  >>> (t-t[0]).to('s')
  <MaskedQuantity [     0.,     ———, 172800.] s>

.. note:: The type of masked output will now be astropy's |Masked|.  For
          backward compatibility, a configuration item,
          `~astropy.time.Conf.masked_array_type`, allows one to choose the
          type of masked array, with "astropy" (default) to always use
          |Masked|, and "numpy" to use `~numpy.ma.MaskedArray` when possible.


.. _whatsnew-6.0-model-annotation-in-votable:

Reading and writing VO model annotations
========================================

Model Instances in VOTables (`MIVOT <https://ivoa.net/documents/MIVOT/20230620/REC-mivot-1.0.pdf>`_)
defines a syntax to map VOTable data to any model serialised in VO-DML (Virtual Observatory Data Modeling Language).
The data model elements are grouped in an independent annotation block complying with
the MIVOT XML schema which is added as an extra resource above the table element.
In Astropy, the MIVOT block is implemented as a new component of the Resource element (MivotBlock class).
MivotBlock instances can only be held by resources with "type=meta".
In this new feature, Astropy is able to read and write MIVOT annotations from and within VOTables.
There is no function processing data models, they will be delegated to affiliated packages such as PyVO.

See :ref:`votable_mivot` for more details.

.. _whatsnew-6.0-time-delta-quantity-str:

TimeDelta string format "quantity_str"
======================================

A new :ref:`TimeDelta<time-delta>` format ``"quantity_str"`` is now available that
represents the time delta as a string with one or more Quantity components. This format
provides a human-readable multi-scale string representation of a time delta. It is
convenient for applications like a configuration file or a command line option.

.. Warning:: The default output format is not yet finalized and may change in version
    6.1 of astropy. The input format is stable. Please see
    `issue 15485 <https://github.com/astropy/astropy/issues/15485>`_ for more details.

The format is a string with one or more time Quantity components separated by optional
whitespace, for example ``"1yr 2d 3hr 4min 5.6s"``. In more detail:

- The string is a sequence of one or more components.
- Each component is a number followed by an astropy unit of time.
- For input, whitespace within the string is allowed but optional.
- For output, there is a single space between components.
- The order (yr, d, hr, min, s) is fixed but individual components are optional.

The allowed component units are shown below and correspond to scaling relations
defined by the astropy units:

- "yr": years (365.25 days)
- "d": days (24 hours)
- "hr": hours (60 minutes)
- "min": minutes (60 seconds)
- "s": seconds

.. Note:: These definitions correspond to physical units of time and are NOT
    calendar date intervals. Thus adding "1yr" to "2000-01-01 00:00:00" will give
    "2000-12-31 06:00:00" instead of "2001-01-01 00:00:00".

See :class:`~astropy.time.formats.TimeDeltaQuantityString` for more details.

.. _whatsnew-6.0-votable-parquet-serialization:

VOTable now supports PARQUET serialization
==========================================

The PARQUET file format allows a more efficient handling of large data amounts. However,
one problem of PARQUET is that it only provides a limited number of column metadata keywords.
A way to make it consistent with VO standards is to embed it into a VOTable file.

This serialization works similar to the VOTable FITS serialization that already existed. It
basically creates two files, on VOTable file and one PARQUET file, which are linked together.
The advantage of this method is that any column metadata can be saved along with the PARQUET file,
following VO standards.

Reading and writing of the VOTable PARQUET serialization is fully supported by
`astropy.io.votable` and the unified ``Table`` read/write interface.
This serialization can be used by setting the ``format`` argument to
``'votable.parquet'``, while ``'votable'`` can be used for reading in such a file.
The method works for both absolute and relative parquet file paths.

Example for writing:

.. doctest-skip::

    >>> import numpy as np
    >>> from astropy.table import Table
    >>>
    >>> # Create some fake data
    >>> number_of_objects = 10
    >>> ids = [f"COSMOS_{ii:03g}" for ii in range(number_of_objects)]
    >>> redshift = np.random.uniform(low=0, high=3, size=number_of_objects)
    >>> mass = np.random.uniform(low=1e8, high=1e10, size=number_of_objects)
    >>> sfr = np.random.uniform(low=1, high=100, size=number_of_objects)
    >>> cosmos = Table([ids, redshift, mass, sfr], names=["id", "z", "mass", "sfr"])
    >>>
    >>> # Create Column metadata
    >>> column_metadata = {
    ...    "id": {"unit": "", "ucd": "meta.id", "utype": "none"},
    ...    "z": {"unit": "", "ucd": "src.redshift", "utype": "none"},
    ...    "mass": {"unit": "solMass", "ucd": "phys.mass", "utype": "none"},
    ...    "sfr": {"unit": "solMass / yr", "ucd": "phys.SFR", "utype": "none"},
    ... }
    >>>
    >>> # Write VOTable with Parquet serialization
    >>> filename = "votable_with_parquet.vot"
    >>> cosmos.write(filename, column_metadata=column_metadata, format="votable.parquet")

Example for reading a votable with a separate parquet file. Note the
metadata is accessible on the column level:

.. doctest-skip::

    >>> from astropy.table import Table
    >>>
    >>> # Open VOTable with PARQUET serialization
    >>> cosmos_table = Table.read("votable_with_parquet.vot", format='votable')
    >>>
    >>> cosmos_table
    <Table length=10>
        id             z                 mass               sfr
       ---            ---              solMass          solMass / yr
      str10         float64            float64            float64
    ---------- ------------------ ------------------ ------------------
    COSMOS_000 0.2399334343209477 3777315779.8348713  31.82322447540133
    COSMOS_001 0.0647935880275512   9392519748.07293  4.532295061239315
    COSMOS_002  2.738748364941223 4411572229.2340555  86.54423711854747
    COSMOS_003  2.013180712201346  2813958500.958293 11.142967938935586
    COSMOS_004 2.5044578163101794   1533373563.21987    48.320129287388
    COSMOS_005 2.1113936608027988  7431616021.640879  29.67334486542601
    COSMOS_006 1.5525290310888193   8700624063.99011  36.19567476784732
    COSMOS_007 2.5879551130469074  6501853315.057587  54.19908247198407
    COSMOS_008 0.3978276727610941 1865149084.3401675  76.53909767648796
    COSMOS_009 1.5021072916190177  4394424029.923725  91.68600618578257
    >>>
    >>> # Check out fields and column metadata
    >>> cosmos_table['sfr'].meta
    OrderedDict([('ucd', 'phys.SFR'), ('utype', 'none')])


.. _whatsnew-6.0-fits-decompression:

Faster FITS file decompression
==============================

By default compressed FITS files (with gzip, bzip2 or zip) are decompressed
progressively depending on what data is needed. This allows to limit the memory
usage when accessing only some part of a file but it can be much slower than
decompressing the whole file at once. With the new keyword ``fits.open(...,
decompress_in_memory=True)`` it is now possible to decompress the whole file in
memory, which will be faster in some cases.

.. _whatsnew-6.0-general-sersic2d:

New ``GeneralSersic2D`` model
=============================

A new :class:`~astropy.modeling.functional_models.GeneralSersic2D` model
has been added to the modeling package. This model is a generalized two
dimensional Sersic surface brightness profile that allows for "boxy" or
"disky" (kite-like) isophote shapes.


Full change log
===============

To see a detailed list of all changes in version v6.0, including changes in
API, please see the :ref:`changelog`.

Contributors to the v6.0 release
================================

The people who have contributed to the code for this release are:

.. hlist::
  :columns: 4

  -  AMHermansen  *
  -  Alexandre R. Bomfim Junior  *
  -  Alpha-Ursae-Minoris  *
  -  Andreas Faisst  *
  -  arthurxvtv  *
  -  Benjamin Alan Weaver
  -  Bill Cleveland  *
  -  Brett Morris
  -  Brigitta Sipőcz
  -  Caden Gobat
  -  CaioCoutinhoP  *
  -  Chiara Marmo
  -  Clément Robert
  -  Dany Vohl  *
  -  David Stansby
  -  Derek Homeier
  -  Doron Behar  *
  -  E.C. Herenz  *
  -  Eero Vaher
  -  Erik Tollerud
  -  Hans Moritz Günther
  -  Heinz-Alexander Fuetterer  *
  -  Hélvio Peixoto  *
  -  JP Maia  *
  -  James O'Keeffe  *
  -  Jero Bado
  -  Larry Bradley
  -  Laurent MICHEL  *
  -  Leo Singer
  -  Manon Marchand
  -  Marcello Nascif  *
  -  Marten van Kerkwijk
  -  MatCat776  *
  -  Matteo Bachetti
  -  Matthew Craig
  -  Michele Peresano  *
  -  Mihai Cara
  -  Mubin Manasia
  -  Nadia Dencheva
  -  Nathaniel Starkman
  -  Ole Streicher
  -  P. L. Lim
  -  Prajwel Joseph  *
  -  Raghuram Devarakonda  *
  -  Roy Smart
  -  Saransh Chopra  *
  -  Simon Conseil
  -  Somia Floret  *
  -  Stuart Mumford
  -  Thais Borges  *
  -  Thomas Robitaille
  -  Timothy P. Ellsworth Bowers
  -  Tom Aldcroft
  -  William Jamieson

Where a * indicates that this release contains their first contribution to astropy.
