.. _whatsnew-4.0:

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

Overview
========

Astropy 4.0 is a major release that ...  since
the 3.2.x series of releases.

In particular, this release includes:

* :ref:`whatsnew-4.0-planck2018`
* :ref:`whatsnew-4.0-constants-units`
* :ref:`whatsnew-4.0-galactocentric`
* :ref:`whatsnew-4.0-time-ymdhms`
* :ref:`whatsnew-4.0-time-plotting`
* :ref:`whatsnew-4.0-time-high-precision`
* :ref:`whatsnew-4.0-time-leap-seconds`
* :ref:`whatsnew-4.0-quantity`
* :ref:`whatsnew-4.0-wcsaxes-1d`
* :ref:`whatsnew-4.0-wcsaxes-default`
* :ref:`whatsnew-4.0-wcs-fit`
* :ref:`whatsnew-4.0-wcs-time`
* :ref:`whatsnew-4.0-timeseries-fold`
* :ref:`whatsnew-4.0-table-new`
* :ref:`whatsnew-4.0-table-performance`
* :ref:`whatsnew-4.0-modeling-new-models`
* :ref:`whatsnew-4.0-cache`
* :ref:`whatsnew-4.0-modeling-api`
* :ref:`whatsnew-4.0-table-api`
* :ref:`whatsnew-4.0-uncertainty-api`

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

* 948 issues have been closed since v3.2
* 494 pull requests have been merged since v3.2
* 74 distinct people have contributed code, 26 of which are first time contributors to Astropy

.. _whatsnew-4.0-planck2018:

Pre-Publication Planck 2018 Cosmological Parameters
===================================================

A pre-publication version of the Planck 2018 cosmological parameters
has been included based on the second version of the submitted paper. This
will be replaced with a final version when the paper is accepted.

.. doctest-requires:: scipy

    >>> from astropy.cosmology import Planck18_arXiv_v2
    >>> Planck18_arXiv_v2.age(0)  # doctest: +FLOAT_CMP
    <Quantity 13.7868853 Gyr>

.. _whatsnew-4.0-constants-units:

Improved Consistency of Physical Constants and Units
====================================================

Physical constants and the units using them are based on measurements that improve
over time and therefore are not "constant" numerically. Generally, you want to
use the latest values, but sometimes it is necessary to reproduce earlier
results by going back in time. For that purpose, we have now introduced new
``astropy.physical_constants`` and ``astropy.astronomical_constants`` science
state objects, which can be used to enable previous versions of the constants,
and to make sure that units and physical constants are self-consistent. For more
details, see :ref:`astropy-constants-prior`.

.. _whatsnew-4.0-galactocentric:

Updates to Galactocentric Frame
===============================

Most coordinate frames implemented in :ref:`astropy.coordinates
<astropy-coordinates>` have standard parameters that are set by IAU consensus
(e.g., the ``ICRS`` frame). Unlike these, the
`~astropy.coordinates.Galactocentric` coordinate frame does not have an absolute
definition: its parameters (the solar motion and position relative to the
Galactic center) are measurements that continue to be refined as newer stellar
surveys are executed and analyzed. When it was added, the default parameter
values used by the `~astropy.coordinates.Galactocentric` frame (i.e., the
parameter values assumed when defining a frame without explicitly setting
values, like ``galcen = Galactocentric()``) were set to commonly used values at
the time, but these are now somewhat out of date. With v4.0, we have added
functionality for globally controlling the default parameter values used by this
frame by setting the `~astropy.coordinates.galactocentric_frame_defaults` object
with the name of a parameter set. The parameter set names can currently be one
of ``"pre-v4.0"`` (to get the original, pre-version-4.0 values of the parameters),
``"v4.0"`` (to get a more modern set of values adopted in v4.0), and ``"latest"``
(which is currently an alias for ``"v4.0"`` and will always alias the most recent
set of parameters).

If your code depends sensitively on the choice of
`~astropy.coordinates.Galactocentric` frame parameters, make sure to explicitly
set the parameter set in your code, for example, after importing
:ref:`astropy.coordinates <astropy-coordinates>`::

    >>> import astropy.coordinates as coord
    >>> coord.galactocentric_frame_defaults.set('v4.0')  # doctest: +IGNORE_OUTPUT

The `~astropy.coordinates.Galactocentric` frame now also maintains a list of
references to scientific papers for the default values of the frame attributes.
For example, after adopting the v4.0 parameter set and defining a frame, we can
retrieve the references (as a dictionary of links to ADS) for the parameters
using the ``.frame_attribute_references`` attribute::

    >>> import astropy.coordinates as coord
    >>> coord.galactocentric_frame_defaults.set('v4.0')  # doctest: +IGNORE_OUTPUT
    >>> galcen = coord.Galactocentric()
    >>> galcen  # doctest: +FLOAT_CMP
    <Galactocentric Frame (galcen_coord=<ICRS Coordinate: (ra, dec) in deg
        (266.4051, -28.936175)>, galcen_distance=8.122 kpc, galcen_v_sun=(12.9, 245.6, 7.78) km / s, z_sun=20.8 pc, roll=0.0 deg)>
    >>> galcen.frame_attribute_references
    {'galcen_coord': 'http://adsabs.harvard.edu/abs/2004ApJ...616..872R',
     'galcen_distance': 'https://ui.adsabs.harvard.edu/abs/2018A%26A...615L..15G',
     'galcen_v_sun': ['https://ui.adsabs.harvard.edu/abs/2018RNAAS...2..210D',
      'https://ui.adsabs.harvard.edu/abs/2018A%26A...615L..15G',
      'https://ui.adsabs.harvard.edu/abs/2004ApJ...616..872R'],
     'z_sun': 'https://ui.adsabs.harvard.edu/abs/2019MNRAS.482.1417B'}

Note, however, if a frame parameter is set by the user, it is removed from the
reference list::

    >>> import astropy.units as u
    >>> galcen = coord.Galactocentric(z_sun=10*u.pc)
    >>> galcen.frame_attribute_references
    {'galcen_coord': 'http://adsabs.harvard.edu/abs/2004ApJ...616..872R',
     'galcen_distance': 'https://ui.adsabs.harvard.edu/abs/2018A%26A...615L..15G',
     'galcen_v_sun': ['https://ui.adsabs.harvard.edu/abs/2018RNAAS...2..210D',
      'https://ui.adsabs.harvard.edu/abs/2018A%26A...615L..15G',
      'https://ui.adsabs.harvard.edu/abs/2004ApJ...616..872R']}

More information can be found in the documentation for the frame class:
`~astropy.coordinates.Galactocentric`.

.. _whatsnew-4.0-time-ymdhms:

New ``ymdhms`` Time Format
==========================

A new `~astropy.time.Time` format was added to allow convenient input and output
of times via year, month, day, hour, minute, and second values. For example::

    >>> from astropy.time import Time
    >>> t = Time({'year': 2015, 'month': 2, 'day': 3,
    ...           'hour': 12, 'minute': 13, 'second': 14.567})
    >>> t.iso
    '2015-02-03 12:13:14.567'
    >>> t.ymdhms.year
    2015

.. _whatsnew-4.0-time-plotting:

New Context Manager for Plotting Time Values
============================================

Matplotlib natively provides a mechanism for plotting dates and times on one
or both of the axes, as described in
`Date tick labels <https://matplotlib.org/3.1.0/gallery/text_labels_and_annotations/date.html>`_.
To make use of this, you can use the ``plot_date`` attribute of :class:`~astropy.time.Time` to get
values in the time system used by Matplotlib.

However, in many cases, you will probably want to have more control over the
precise scale and format to use for the tick labels, in which case you can make
use of the `~astropy.visualization.time_support` function which can be called
either directly or as a context manager, and after which :class:`~astropy.time.Time` objects can be
passed to matplotlib plotting functions. The axes are then automatically labeled
with times formatted using the :class:`~astropy.time.Time` class:

.. plot::
   :include-source:
   :context: reset

    import matplotlib.pyplot as plt
    from astropy.time import Time
    from astropy.visualization import time_support
    time_support(format='isot', scale='tai')  # doctest: +IGNORE_OUTPUT
    plt.figure(figsize=(5,3))  # doctest: +IGNORE_OUTPUT
    plt.plot(Time([52000, 53000, 54000], format='mjd'), [1.2, 3.3, 2.3])  # doctest: +IGNORE_OUTPUT

For more information, see :ref:`plotting-times`.

.. _whatsnew-4.0-time-high-precision:

Support for Parsing High-Precision Values with Time
===================================================

For numerical formats, :class:`~astropy.time.Time` can now be instantiated
from strings, quadruple precision ``numpy`` floats (if available on a given
platform), and :class:`~decimal.Decimal` instances. For instance, in the example below
you can see how in a string we can give full precision, while entering the same
number as a float gives precision loss::

    >>> from astropy.time import Time
    >>> t = Time('2450000.123456789012345', format='jd')
    >>> t - Time(2450000, 0.123456789012345, format='jd')
    <TimeDelta object: scale='tai' format='jd' value=0.0>
    >>> t - Time(2450000.123456789012345, format='jd')  # doctest: +FLOAT_CMP
    <TimeDelta object: scale='tai' format='jd' value=-1.6829138083096495e-10>

You can also output values as string, etc.::

    >>> t.to_value('jd', subfmt='str')
    '2450000.123456789012345'

.. _whatsnew-4.0-time-leap-seconds:

Improved Handling of Leap Second Updates
========================================

``astropy`` now automatically checks for and applies new leap seconds the first
time a :class:`~astropy.time.Time` is instantiated. This is done with the new
:class:`~astropy.utils.iers.LeapSeconds` class, which can, in the hopefully
unlikely case it is needed, also be used directly.

.. _whatsnew-4.0-quantity:

Major Improvements in Compatibility of Quantity Objects with NumPy Functions
============================================================================

While :class:`~astropy.units.Quantity` objects have worked well in arithmetic
operations via ``numpy``'s "universal functions" (ufuncs), for other ``numpy``
functions it has been a bit hit and miss. For instance, units would be lost
when trying to concatenate quantities, make histograms, or in functions
such as `numpy.where`.

For ``numpy`` version 1.17 and later, however, it is possible to override the
behavior of ``numpy`` functions, and this is used in ``astropy`` 4.0 to make
essentially all functions work as expected with quantities. If a ``numpy``
function does not work as expected, it is now a bug that we can fix!

Two concise examples:

.. doctest-requires:: numpy>=1.17

    >>> import numpy as np
    >>> from astropy import units as u
    >>> np.where([True, False, False], [1., 2., 3.]*u.m, 1.*u.cm)
    <Quantity [1.  , 0.01, 0.01] m>
    >>> np.hstack(([1., 2., 3.]*u.m, 1.*u.cm))
    <Quantity [1.  , 2.  , 3.  , 0.01] m>

.. note:: for NumPy 1.16, one can get the same behaviour by setting
          environment variable ``NUMPY_EXPERIMENTAL_ARRAY_FUNCTION=1``.
          For details, see
          `NEP 18 <https://numpy.org/neps/nep-0018-array-function-protocol.html>`_

.. _whatsnew-4.0-wcsaxes-1d:

Plotting 1D Profile Plots with WCSAxes
=======================================

The :ref:`astropy.visualization.wcsaxes <wcsaxes>` module now supports plotting
data with one-dimensional WCS (including 1D profiles extracted from higher
dimensional objects). The following example shows a plot of a 1D profile
extracted from a 3D spectral cube — because all world coordinates vary along
that slice, all three coordinates are still shown in the final plot:

.. plot::
   :context: reset
   :align: center
   :include-source:

    import matplotlib.pyplot as plt
    import astropy.units as u
    from astropy.wcs import WCS
    from astropy.io import fits
    from astropy.utils.data import get_pkg_data_filename

    filename = get_pkg_data_filename('l1448/l1448_13co.fits')
    hdu = fits.open(filename)[0]

    ax = plt.subplot(projection=WCS(hdu.header), slices=(50, 'x', 'y'))
    ax.imshow(hdu.data[:, :, 50])

.. _whatsnew-4.0-wcsaxes-default:

Default Labelling with WCSAxes
==============================

As seen in the example in :ref:`whatsnew-4.0-wcsaxes-1d`, axis
labels are now shown by default, using either the names of the
coordinates axes, if available, or the physical types of the
axes. To disable this, you can use::

   ax.coords[0].set_auto_axislabel(False)
   ax.coords[1].set_auto_axislabel(False)
   ax.coords[2].set_auto_axislabel(False)

Or you can also set the axis label to what you want instead::

   ax.coords[0].set_axislabel("Right Ascension")
   ax.coords[1].set_axislabel("Declination")
   ax.coords[2].set_axislabel("Velocity (m/s)")

.. _whatsnew-4.0-wcs-fit:

New Function to Fit WCS to Pairs of Pixel/World Coordinates
===========================================================

A new function :func:`astropy.wcs.utils.fit_wcs_from_points` has been added
to fit a (FITS) WCS to a set of points for which both pixel and world coordinates
are available. For instance, if we have an image in which four stars have known
pixel and celestial coordinates::

    >>> import numpy as np
    >>> from astropy.coordinates import SkyCoord
    >>> stars_pixel = [np.array([153, 64, 593, 663]),
    ...                np.array([581, 199, 190, 445])]
    >>> stars_world = SkyCoord([266.729, 266.872, 266.031, 265.921],
    ...                        [-28.627, -29.156, -29.170, -28.815],
    ...                        unit='deg', frame='fk5')

we can find the best-fitting WCS with::

.. doctest-requires:: scipy

    >>> from astropy.wcs.utils import fit_wcs_from_points
    >>> fit_wcs_from_points(stars_pixel, stars_world)  # doctest: +FLOAT_CMP
    WCS Keywords
    <BLANKLINE>
    Number of WCS axes: 2
    CTYPE : 'RA---TAN'  'DEC--TAN'
    CRVAL : 266.3952562116589  -28.89933479456074
    CRPIX : 364.8753661483385  385.9573650918672
    CD1_1 CD1_2  : -0.001388743579175224  4.580696254922365e-08
    CD2_1 CD2_2  : -8.098819668907673e-07  0.0013876745578212755
    NAXIS : 599  391

.. _whatsnew-4.0-wcs-time:

Support for WCS Transformations between Pixel and Time Values
=============================================================

The :meth:`WCS.world_to_pixel <astropy.wcs.WCS.world_to_pixel>` and
:meth:`WCS.pixel_to_world <astropy.wcs.WCS.pixel_to_world>` methods can now
take and return :class:`~astropy.time.Time` objects for WCS transformations
that involve time::

    >>> from astropy.io import fits
    >>> from astropy.wcs import WCS
    >>> header = fits.Header()
    >>> header['CTYPE1'] = 'TIME'
    >>> header['CDELT1'] = 86400.
    >>> header['MJDREF'] = 58788.
    >>> wcs = WCS(header)
    >>> wcs.pixel_to_world([2, 3, 4])
    <Time object: scale='utc' format='mjd' value=[58791. 58792. 58793.]>
    >>> wcs.world_to_pixel(Time('2019-11-02T10:30:22'))
    array(0.43775463)

.. _whatsnew-4.0-timeseries-fold:

Improvements to Folding for Time Series
=======================================

The :meth:`TimeSeries.fold <astropy.timeseries.TimeSeries.fold>` method now
includes more options for controlling the resulting phase values. First, the
``midpoint_epoch`` argument has been renamed to ``epoch_time`` so as to be more
general, and the ``epoch_phase`` can be used to specify the phase at which the
epoch is given. In addition, a new ``wrap_phase`` argument can be used to
specify at what phase to wrap — for example, if this is set to half the period,
the resulting phase will go from minus half the period to half the period,
whereas if it is set to the period the resulting phase will go from zero to the
period::

    >>> from astropy import units as u
    >>> from astropy.timeseries import TimeSeries
    >>> ts = TimeSeries(time_start='2019-11-01T00:00:00', time_delta=0.3 * u.day,
    ...                 n_samples=10)
    >>> tf1 = ts.fold(1 * u.day, epoch_time='2019-11-01T12:00:00',
    ...               wrap_phase=1 * u.day)
    >>> tf1  # doctest: +FLOAT_CMP
    <TimeSeries length=10>
            time
           object
    -------------------
                    0.5
                    0.8
                    0.1
                    0.4
                    0.7
                    0.0
                    0.3
                    0.6
                    0.9
                    0.2

Finally, the new ``normalize_phase`` keyword argument can be used to
specify whether the final phase should be a relative time or whether it should
be normalized to a dimensionless value in the range 0 to 1::

    >>> tf2 = ts.fold(1 * u.day, epoch_time='2019-11-01T12:00:00',
    ...               normalize_phase=True)
    >>> tf2  # doctest: +IGNORE_OUTPUT
    <TimeSeries length=10>
            time
    <BLANKLINE>
          float64
    --------------------
                    -0.5
                    -0.2
                     0.1
                     0.4
                    -0.3
                     0.0
                     0.3
                    -0.4
                    -0.1
                     0.2

.. _whatsnew-4.0-table-new:

New Table Methods and Options
=============================

A new method `~astropy.table.dstack` was added to allow depth-wise stacking
of tables to turn a list of similar tables into a single "3D" table with
shape ``(rows, columns, depth)``.

A new method to compare tables `~astropy.table.Table.values_equal` was added
to allow element-wise comparison of a table to either another table, a list of
values, or a single value. This returns a new ``Table`` with the boolean result
of the comparisons.

The `~astropy.table.join` operation now supports Cartesian joins,
enumerating all possible combinations of the left and right table rows.

A `~astropy.table.Table` can now be initialized with a list of dict where
the dict keys are not the same in every row. The table column names are the set
of all keys found in the input data, and any missing key/value pairs are turned
into missing data in the table.

The `~astropy.table.Table.add_column` and `~astropy.table.Table.add_columns`
methods can now accept any object(s) which can be converted or broadcasted
to a valid column for the table. Previously, these methods required a valid
`~astropy.table.Column` or mixin column object.

.. _whatsnew-4.0-table-performance:

Improvements to Performance for Tables
======================================

A number of performance improvements were introduced in version 4.0 that can
substantially improve the speed of ``Table`` manipulations.

A key area was the handling of replacing and adding columns, which is now two to
ten times faster in common cases. The implementation was changed so that the time
for replacing or adding is independent of the number of existing columns (like a
``dict``). This means you can now *efficiently* build a table from scratch by
creating an empty table and then adding columns one at a time.

Another improvement was in the performance of table and column slicing. In
addition to internal implementation changes, there was a change to reduce
unnecessary copy and deepcopy of table and column ``meta`` attributes. In
particular, table or column slices will now get a shallow key-only copy of the
metadata instead of a deep copy.

Table row access speed was improved by a factor of a few, and getting the length
of a table is now typically three to ten times faster. A new method
`~astropy.table.Table.iterrows` was added to make row-wise iteration even faster
for the common case of only needing a subset of the available columns::

    >>> from astropy.table.table_helpers import simple_table
    >>> t = simple_table(size=2, cols=10)
    >>> print(t)
    a   b   c   d   e   f   g   h   i   j
    --- --- --- --- --- --- --- --- --- ---
        1 1.0   c   4 4.0   f   7 7.0   i  10
        2 2.0   d   5 5.0   g   8 8.0   j  11
    >>> for a, f in t.iterrows('a', 'f'):
    ...     print(a, f)
    ...
    1 f
    2 g

.. _whatsnew-4.0-modeling-new-models:

New Models
==========

The following models have now been added:

* :class:`~astropy.modeling.physical_models.Drude1D`: a model based one the
  transport properties of electons in materials (esp. metals).
* :class:`~astropy.modeling.functional_models.KingProjectedAnalytic1D`: a model
  typically used to represent the density distribution of stars in clusters
  (`King, 1962 <http://articles.adsabs.harvard.edu/pdf/1962AJ.....67..471K>`_)
* :class:`~astropy.modeling.functional_models.Exponential1D`: a one-dimensional
  exponential model.
* :class:`~astropy.modeling.functional_models.Logarithmic1D`: a one-dimensional
  logarithmic model.

.. _whatsnew-4.0-cache:

Downloading and Caching Files from the Internet
===============================================

The existing :func:`~astropy.utils.data.download_file` mechanism has been
substantially upgraded to be more reliable and more capable, and tools have been
added to manage the collection of cached downloaded files. Most notably:

* :func:`~astropy.utils.data.download_file` can accept a list of locations where
  the file can be obtained; wherever it was obtained it will be indexed under
  its "official" location.
* :func:`~astropy.utils.data.download_file` can be told, using
  ``cache="update"``, to check the Internet to see whether a new version of the
  file is available, and if so, update the version in the cache; if something
  goes wrong the cache is left intact.
* Concurrent use of the cache by multiple processes will be more reliable, and
  :func:`~astropy.utils.data.download_files_in_parallel` will allow you to use
  this within one process.
* Part or all of the cache can be exported to or imported from a ZIP file with
  :func:`~astropy.utils.data.export_download_cache` and
  :func:`~astropy.utils.data.import_download_cache`. This can be useful for
  setting up machines that will not have an Internet connection.
* If the cache is behaving oddly
  :func:`~astropy.utils.data.check_download_cache` can be used to try to
  diagnose any problems, and :func:`~astropy.utils.data.clear_download_cache`
  can remove problem objects or completely get rid of a damaged cache.
* The amount of space being used by the cache is available with
  :func:`~astropy.utils.data.cache_total_size`.

This mechanism is also available to other packages that want to use it, so most
functions now also accept a ``pkgname`` argument, which allows different
packages to maintain separate caches.

.. _whatsnew-4.0-modeling-api:

API Changes in astropy.modeling
===============================

A number of significant changes have been made to modeling API as a result
of reworking how parameters and compound models work.

* It is no longer possible to create compound model classes (as opposed
  to compound model instances).

* Parameters now hold their values directly with the consequence that compound models
  share the same parameter instances as the constituent model they are constructed
  from (previously the values were copied and changes to one or the other had no
  effect on the corresponding model).

* In compound models, the constituent models are references, not copies
  (if copies are desired, an explicit ``copy()`` should be used in the compound
  model expression).

There are other more minor changes to the API that are detailed in :ref:`modeling-major-changes-for-4.0`:

* ``inputs`` and ``outputs`` were deprecated as class variables and are instance variables,
  while ``n_inputs`` and ``n_outputs`` are now class variables. As a result ``inputs`` and
  ``outputs`` of a model can be renamed.

* Assigning slices of the model parameter array does not automatically get
  reflected in parameter values

* Previously it was possible to use arbitrary slices on compound models (which had
  the possibility of returning submodes with entirely different meanings than they
  had in the original compound model). Now only a restricted set of slices is permitted.

* Use of “inputed” units is much more restricted. Previously these could end up with
  unexpected units being assigned.

* Many private methods have been added, changed, or deleted.

In addition, a new :class:`~astropy.modeling.physical_models.BlackBody` class has been
added and replaces the now-deprecated :class:`~astropy.modeling.blackbody.BlackBody1D` class
and the :func:`~astropy.modeling.blackbody.blackbody_nu`
and :func:`~astropy.modeling.blackbody.blackbody_lambda` functions. To find out more
about the new class, see :ref:`blackbody-planck-law`, and for information about
the correspondance between :class:`~astropy.modeling.physical_models.BlackBody` and
the deprecated class and functions, see :ref:`deprecated-blackbody`.

.. _whatsnew-4.0-table-api:

API Changes in astropy.table
============================

The handling of masked columns in tables has changed in a way that may impact
program behavior. Now a :class:`~astropy.table.Table` object with
``masked=False`` may contain both ``Column`` and ``MaskedColumn`` objects, and
adding a masked column or row to a table no longer "upgrades" the table and all
other columns to masked. This means that tables with masked data which are read
via ``Table.read()`` will now always have ``masked=False``, though specific
columns will be masked as needed. The same applies to the output of table
operations like `~astropy.table.join`, `~astropy.table.vstack`, and
`~astropy.table.hstack`. Two new table properties ``has_masked_columns``
and ``has_masked_values`` were added. See the `Masking change in astropy 4.0
<https://docs.astropy.org/en/v4.0.x/table/masking.html#masking-change-in-astropy-4-0>`_
section for details.

As noted earlier, the handling of table and column ``meta`` attributes has
changed and users no longer get deep copies in most cases.

.. _whatsnew-4.0-uncertainty-api:

API Changes in astropy.uncertainty
==================================

For the experimental :class:`~astropy.uncertainty.Distribution` class, the
earlier ``pdf_mean``, ``pdf_var``, etc., properties were turned into methods,
both for consistency with the ``numpy`` methods that are used underneath, and to
allow users to pass on parameters (such as the degrees of freedom ``ddof`` for
:meth:`~astropy.uncertainty.Distribution.pdf_var`).

While the module remains experimental, and further enhancements and
refactoring are planned, we do not foresee any further significant changes in
the API.


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

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


Contributors to the v4.0 release
================================

.. hlist::
  :columns: 4

  *    Aarya Patil
  *    Adam Ginsburg
  *    Adrian Price-Whelan
  *    Albert Y. Shih
  *    Alex Conley
  *    Anne Archibald
  *    Arthur Eigenbrot  *
  *    Benjamin Alan Weaver
  *    Brett M. Morris  *
  *    Brigitta Sipőcz
  *    Bryce Kalmbach  *
  *    Christoph Deil
  *    Clara Brasseur
  *    Clare Shanahan  *
  *    Dan Foreman-Mackey
  *    Daria Cara
  *    David Shupe
  *    David Stansby
  *    Derek Homeier
  *    Douglas Burke
  *    Drew Leonard  *
  *    Erik M. Bray
  *    Erik Tollerud
  *    Frédéric Chapoton
  *    Geert Barentsen
  *    Gregory Dubois-Felsmann  *
  *    Hannes Breytenbach
  *    Hans Moritz Günther
  *    Harry Ferguson
  *    Himanshu Pathak
  *    James Davies
  *    John Fisher  *
  *    John Parejko
  *    Johnny Greco
  *    Juan Luis Cano Rodríguez
  *    Julien Woillez
  *    Karl Gordon
  *    Kewei Li  *
  *    Larry Bradley
  *    Lauren Glattly
  *    Leo Singer
  *    Lia Corrales  *
  *    M Atakan Gürkan  *
  *    Mark Fardal  *
  *    Marten van Kerkwijk
  *    Matteo Bachetti
  *    Matthew Craig
  *    Maximilian Nöthe
  *    Michael Seifert
  *    Mihai Cara
  *    Nadia Dencheva
  *    Nora Luetzgendorf  *
  *    Perry Greenfield
  *    Pey Lian Lim
  *    Rasmus Handberg  *
  *    Rui Xue  *
  *    SF Graves  *
  *    Sadie Bartholomew  *
  *    Semyeong Oh
  *    Shreyas Bapat  *
  *    Simon Conseil
  *    Simon Torres  *
  *    Stuart Mumford
  *    Thomas Robitaille
  *    Tiffany Jansen  *
  *    Tom Aldcroft
  *    Tom Donaldson  *
  *    Tom J Wilson  *
  *    Vishnunarayan K I
  *    Wilfred Tyler Gee  *
  *    Yash Sharma  *
  *    Yingqi Ying  *
  *    Zachary Kurtz  *
  *    rtolesnikov  *
