Repository: sunpy/sunpy

Issue:
Resolve different ways to calculate Carrington longitude
Now that PR #2186 is merged, our ephemeris calculations have accurate correspondence with published sources *except* for arcmin-level discrepancies with Carrington longitudes provided by the _Astronomical Almanac_.  Here are my comments from PR #2186 (starting [here](https://github.com/sunpy/sunpy/pull/2186#discussion_r129573004)):

> I've mainly used two references for verification. I see a discrepancy of up to ~2 arcmin with the Carrington longitudes published in the Astronomical Almanac (2013 edition, at least, I haven't checked other years yet), but curiously I have an exact match to a Carrington longitude calculated in Astronomical Algorithms by Meeus, even though this code doesn't include any implementation of Meeus's equations. I'm not currently sure what to make of this discrepancy, but I wouldn't be surprised if there are multiple, inconsistent definitions of Carrington longitude.

> Actually, now that I think more about it, the issue is probably simply the exact start time of the first Carrington rotation. I use the same time that Meeus uses (JD 2398167.4), hence the exact match isn't surprising, while the Astronomical Almanac doesn't (explicitly) state what it uses.

Currently, `sunpy.coordinates.ephemeris.get_sun_L0()` effectively propagates from the first Carrington rotation in 1853 (a la Meeus).  In contrast, Thompson (2006) provides a definition referenced to J2000.0:

> Seidelmann et al. (2002) gives the angle of the prime meridian from the ascending node as 84.10 deg at J2000.0.

Switching to the latter would probably improve the agreement with non-Meeus sources, but at the expense of breaking agreement with Meeus.  With @dpshelio's [comment](https://github.com/sunpy/sunpy/pull/2186#discussion_r130982132) that we also have similar-magnitude discrepancies with HELIO (which presumably agrees better with the _Astronomical Almanac_), the agreement with Meeus may not be worth keeping.

Resolve this.
Carrington rotation number
The SunPy and SSWIDL Carrington rotation numbers differ, and the difference is significant.

In [2]: import sunpy.sun
In [3]: sunpy.sun.carrington_rotation_number('2015-12-25 11:57:00')
Out[3]: 2172.011791498784

In [4]: sunpy.sun.carrington_rotation_number('2015-12-25 04:13:54')
Out[4]: 2172.0000007128997

IDL> print, tim2carr('2015-12-25 11:57:00',/dc)
       2172.0001

For reference, 
http://alpo-astronomy.org/solar/rotn_nos.html
http://umtof.umd.edu/pm/crn/

The SunPy Carrington rotation numbers appear to be the minority opinion.



Discussion:

Awkward...

I'm taking a look at this. 

That's being also part of the #1070 - hopefully in the next weeks I can get that PR up to speed and end with it. 

Looks like we have our own UTC->Julian Date conversion which we really should replace with http://docs.astropy.org/en/stable/time/index.html

see #1647

Any updates on this?
@bla1089 have you verified this is still an issue? I would think that #1647 should've helped. If it's still a problem we can have a look into it.
See also #2224 for some of my frustrations with definitions of Carrington longitude (and correspondingly Carrington rotation numbers).

Resolving patch:
diff --git a/sunpy/coordinates/ephemeris.py b/sunpy/coordinates/ephemeris.py
--- a/sunpy/coordinates/ephemeris.py
+++ b/sunpy/coordinates/ephemeris.py
@@ -27,7 +27,7 @@
 from sunpy.time.time import _variables_for_parse_time_docstring
 
 from .frames import HeliographicStonyhurst as HGS
-from .transformations import _SUN_DETILT_MATRIX
+from .transformations import _SUN_DETILT_MATRIX, _SOLAR_NORTH_POLE_HCRS
 
 __author__ = "Albert Y. Shih"
 __email__ = "ayshih@gmail.com"
@@ -233,18 +233,27 @@ def _B0(time='now'):
     return Angle(get_earth(time).lat)
 
 
-# Ignore warnings that result from going back in time to the first Carrington rotation
-with warnings.catch_warnings():
-    warnings.simplefilter("ignore", ErfaWarning)
+# Function returns a SkyCoord's longitude in the de-tilted frame (HCRS rotated so that the Sun's
+# rotation axis is aligned with the Z axis)
+def _detilt_lon(coord):
+    coord_detilt = coord.hcrs.cartesian.transform(_SUN_DETILT_MATRIX)
+    return coord_detilt.represent_as(SphericalRepresentation).lon.to('deg')
 
-    # Carrington rotation 1 starts late in the day on 1853 Nov 9
-    # according to Astronomical Algorithms (Meeus 1998, p.191)
-    _TIME_FIRST_ROTATION = Time('1853-11-09 21:36')
 
-    # Longitude of Earth at Carrington rotation 1 in de-tilted HCRS (so that solar north pole is Z)
-    _LON_FIRST_ROTATION = \
-        get_earth(_TIME_FIRST_ROTATION).hcrs.cartesian.transform(_SUN_DETILT_MATRIX) \
-        .represent_as(SphericalRepresentation).lon.to('deg')
+# J2000.0 epoch
+_J2000 = Time('J2000.0', scale='tt')
+
+
+# One of the two nodes of intersection between the ICRF equator and Sun's equator in HCRS
+_NODE = SkyCoord(_SOLAR_NORTH_POLE_HCRS.lon + 90*u.deg, 0*u.deg, frame='hcrs')
+
+
+# The longitude in the de-tilted frame of the Sun's prime meridian.
+# Siedelmann et al. (2007) and earlier define the apparent longitude of the meridian as seen from
+# Earth as 84.10 degrees eastward from the above-defined node of intersection.
+# Siedelmann et al. (2007) and later also define the true longitude of the meridian (i.e., without
+# light travel time to Earth) as 84.176 degrees eastward, but the apparent longitude is needed.
+_DLON_MERIDIAN = Longitude(_detilt_lon(_NODE) + 84.10*u.deg)
 
 
 @add_common_docstring(**_variables_for_parse_time_docstring())
@@ -253,13 +262,6 @@ def _L0(time='now'):
     Return the L0 angle for the Sun at a specified time, which is the Carrington longitude of the
     Sun-disk center as seen from Earth.
 
-    .. warning::
-       Due to apparent disagreement between published references, the returned L0 may be inaccurate
-       by up to ~2 arcmin, which translates in the worst case to a shift of ~0.5 arcsec in
-       helioprojective coordinates for an observer at 1 AU.  Until this is resolved, be cautious
-       with analysis that depends critically on absolute (as opposed to relative) values of
-       Carrington longitude.
-
     Parameters
     ----------
     time : {parse_time_types}
@@ -267,20 +269,28 @@ def _L0(time='now'):
 
     Returns
     -------
-    out : `~astropy.coordinates.Longitude`
+    `~astropy.coordinates.Longitude`
         The Carrington longitude
+
+    Notes
+    -----
+    This longitude is calculated using the values from Siedelmann et al. (2007), with care taken to
+    use the longitude as seen from Earth (see that paper's Appendix).
+
+    References
+    ----------
+    * Siedelmann et al. (2007), "Report of the IAU/IAG Working Group on cartographic coordinates
+      and rotational elements: 2006" `(link) <http://dx.doi.org/10.1007/s10569-007-9072-y>`_
     """
     obstime = parse_time(time)
 
-    # Calculate the longitude due to the Sun's rotation relative to the stars
-    # A sidereal rotation is defined to be exactly 25.38 days
-    sidereal_lon = Longitude((obstime.jd - _TIME_FIRST_ROTATION.jd) / 25.38 * 360*u.deg)
+    # Calculate the de-tilt longitude of the meridian due to the Sun's sidereal rotation
+    dlon_meridian = Longitude(_DLON_MERIDIAN + (obstime - _J2000) * 14.1844*u.deg/u.day)
 
-    # Calculate the longitude of the Earth in de-tilted HCRS
-    lon_obstime = get_earth(obstime).hcrs.cartesian.transform(_SUN_DETILT_MATRIX) \
-                  .represent_as(SphericalRepresentation).lon.to('deg')
+    # Calculate the de-tilt longitude of the Earth
+    dlon_earth = _detilt_lon(get_earth(obstime))
 
-    return Longitude(lon_obstime - _LON_FIRST_ROTATION - sidereal_lon)
+    return Longitude(dlon_earth - dlon_meridian)
 
 
 @add_common_docstring(**_variables_for_parse_time_docstring())
diff --git a/sunpy/coordinates/sun.py b/sunpy/coordinates/sun.py
--- a/sunpy/coordinates/sun.py
+++ b/sunpy/coordinates/sun.py
@@ -18,6 +18,7 @@
 from astropy import _erfa as erfa
 from astropy.coordinates.builtin_frames.utils import get_jd12
 
+from sunpy import log
 from sunpy.sun import constants
 from sunpy.time import parse_time
 from sunpy.time.time import _variables_for_parse_time_docstring
@@ -75,19 +76,35 @@ def sky_position(t='now', equinox_of_date=True):
 @add_common_docstring(**_variables_for_parse_time_docstring())
 def carrington_rotation_number(t='now'):
     """
-    Return the Carrington Rotation number.
-
-    .. warning::
-        The accuracy of this function is under investigation.
+    Return the Carrington rotation number.  Each whole rotation number marks when the Sun's prime
+    meridian coincides with the central meridian as seen from Earth, with the first rotation
+    starting on 1853 November 9.
 
     Parameters
     ----------
     t : {parse_time_types}
         Time to use in a parse-time-compatible format
     """
-    jd = parse_time(t).jd
-    result = (1. / 27.2753) * (jd - 2398167.0) + 1.0
-    return result
+    time = parse_time(t)
+
+    # Estimate the Carrington rotation number by dividing the time that has elapsed since
+    # JD 2398167.4 (late in the day on 1853 Nov 9), see Astronomical Algorithms (Meeus 1998, p.191),
+    # by the mean synodic period (27.2753 days)
+    estimate = (time.tt.jd - 2398167.4) / 27.2753 + 1
+    estimate_int, estimate_frac = divmod(estimate, 1)
+
+    # The fractional rotation number from the above estimate is inaccurate, so calculate the actual
+    # fractional rotation number from the longitude of the central meridian (L0)
+    actual_frac = 1 - L0(time).to('deg').value / 360
+
+    # Calculate any adjustment to the integer rotation number due to wrapping
+    wrap_adjustment = np.around(estimate_frac - actual_frac)
+
+    actual = estimate_int + actual_frac + wrap_adjustment
+
+    log.debug(f"Carrington rotation number: estimate is {estimate}, actual is {actual}")
+
+    return actual
 
 
 @add_common_docstring(**_variables_for_parse_time_docstring())
@@ -384,7 +401,7 @@ def print_params(t='now'):
     print('Heliographic long. and lat of disk center = ({}, {})'.format(L0(t).to_string(),
                                                                         B0(t).to_string()))
     print('Position angle of north pole = {}'.format(P(t)))
-    print('Carrington Rotation Number = {}'.format(carrington_rotation_number(t)))
+    print('Carrington rotation number = {}'.format(carrington_rotation_number(t)))
 
 
 # The following functions belong to this module, but their code is still in the old location of


Test patch:
diff --git a/sunpy/coordinates/tests/test_sun.py b/sunpy/coordinates/tests/test_sun.py
--- a/sunpy/coordinates/tests/test_sun.py
+++ b/sunpy/coordinates/tests/test_sun.py
@@ -1,5 +1,7 @@
 import pytest
 
+from numpy.testing import assert_allclose
+
 from astropy.coordinates import Angle, EarthLocation, SkyCoord
 from astropy.time import Time
 import astropy.units as u
@@ -172,21 +174,21 @@ def test_B0():
 
 def test_B0_array_time():
     # Validate against published values from the Astronomical Almanac (2013)
-    sun_B0 = sun.B0(Time(['2013-04-01', '2013-12-01']))
+    sun_B0 = sun.B0(Time(['2013-04-01', '2013-12-01'], scale='tt'))
     assert_quantity_allclose(sun_B0[0], -6.54*u.deg, atol=5e-3*u.deg)
     assert_quantity_allclose(sun_B0[1], 0.88*u.deg, atol=5e-3*u.deg)
 
 
 def test_L0():
     # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191)
-    assert_quantity_allclose(sun.L0('1992-Oct-13'), 238.6317*u.deg, atol=5e-5*u.deg)
+    assert_quantity_allclose(sun.L0('1992-Oct-13'), 238.63*u.deg, atol=2e-2*u.deg)
 
 
 def test_L0_array_time():
     # Validate against published values from the Astronomical Almanac (2013)
-    sun_L0 = sun.L0(Time(['2013-04-01', '2013-12-01']))
-    assert_quantity_allclose(sun_L0[0], 221.44*u.deg, atol=3e-2*u.deg)
-    assert_quantity_allclose(sun_L0[1], 237.83*u.deg, atol=3e-2*u.deg)
+    sun_L0 = sun.L0(Time(['2013-04-01', '2013-12-01'], scale='tt'))
+    assert_quantity_allclose(sun_L0[0], 221.44*u.deg, atol=5e-3*u.deg)
+    assert_quantity_allclose(sun_L0[1], 237.83*u.deg, atol=5e-3*u.deg)
 
 
 def test_P():
@@ -196,7 +198,7 @@ def test_P():
 
 def test_P_array_time():
     # Validate against published values from the Astronomical Almanac (2013)
-    sun_P = sun.P(Time(['2013-04-01', '2013-12-01']))
+    sun_P = sun.P(Time(['2013-04-01', '2013-12-01'], scale='tt'))
     assert_quantity_allclose(sun_P[0], -26.15*u.deg, atol=1e-2*u.deg)
     assert_quantity_allclose(sun_P[1], 16.05*u.deg, atol=1e-2*u.deg)
 
@@ -208,9 +210,9 @@ def test_earth_distance():
 
 def test_earth_distance_array_time():
     # Validate against published values from the Astronomical Almanac (2013)
-    sunearth_distance = sun.earth_distance(Time(['2013-04-01', '2013-12-01']))
-    assert_quantity_allclose(sunearth_distance[0], 0.9992311*u.AU, atol=5e-7*u.AU)
-    assert_quantity_allclose(sunearth_distance[1], 0.9861362*u.AU, atol=5e-7*u.AU)
+    sunearth_distance = sun.earth_distance(Time(['2013-04-01', '2013-12-01'], scale='tt'))
+    assert_quantity_allclose(sunearth_distance[0], 0.9992311*u.AU, rtol=0, atol=5e-8*u.AU)
+    assert_quantity_allclose(sunearth_distance[1], 0.9861362*u.AU, rtol=0, atol=5e-8*u.AU)
 
 
 def test_orientation():
@@ -223,3 +225,26 @@ def test_orientation():
     # Check the Southern Hemisphere
     angle = sun.orientation(EarthLocation(lat=-40*u.deg, lon=-75*u.deg), '2017-02-18 13:00')
     assert_quantity_allclose(angle, -110.8*u.deg, atol=0.1*u.deg)
+
+
+# Validate against published values from the Astronomical Almanac (2013, C4)
+@pytest.mark.parametrize("date, day_fraction, rotation_number",
+                         [('2012-12-29', 0.49, 2132),
+                          ('2013-01-25', 0.83, 2133),
+                          ('2013-02-22', 0.17, 2134),
+                          ('2013-03-21', 0.49, 2135),
+                          ('2013-04-17', 0.78, 2136),
+                          ('2013-05-15', 0.02, 2137),
+                          ('2013-06-11', 0.22, 2138),
+                          ('2013-07-08', 0.42, 2139),
+                          ('2013-08-04', 0.63, 2140),
+                          ('2013-08-31', 0.87, 2141),
+                          ('2013-09-28', 0.14, 2142),
+                          ('2013-10-25', 0.43, 2143),
+                          ('2013-11-21', 0.73, 2144),
+                          ('2013-12-19', 0.05, 2145),
+                          ('2014-01-15', 0.38, 2146),
+                          ('2014-02-11', 0.73, 2147)])
+def test_carrington_rotation_number(date, day_fraction, rotation_number):
+    assert_allclose(sun.carrington_rotation_number(Time(date, scale='tt') + day_fraction*u.day),
+                    rotation_number, rtol=0, atol=2e-4)
diff --git a/sunpy/map/sources/tests/test_mdi_source.py b/sunpy/map/sources/tests/test_mdi_source.py
--- a/sunpy/map/sources/tests/test_mdi_source.py
+++ b/sunpy/map/sources/tests/test_mdi_source.py
@@ -53,5 +53,5 @@ def test_waveunit(mdi):
 def test_observer(mdi):
     assert isinstance(mdi.observer_coordinate.frame, frames.HeliographicStonyhurst)
     assert u.allclose(mdi.observer_coordinate.lat, -5.774028172878*u.deg)
-    assert u.allclose(mdi.observer_coordinate.lon, -0.1327673730752963*u.deg)
+    assert u.allclose(mdi.observer_coordinate.lon, -0.10181577*u.deg)
     assert u.allclose(mdi.observer_coordinate.radius, 0.9739569156244*u.AU)