"""
Matplotlib provides sophisticated date plotting capabilities, standing on the
shoulders of python :mod:`datetime`, the add-on modules :mod:`pytz` and
:mod:`dateutil`.  :class:`datetime` objects are converted to floating point
numbers which represent time in days since 0001-01-01 UTC, plus 1.  For
example, 0001-01-01, 06:00 is 1.25, not 0.25.  The helper functions
:func:`date2num`, :func:`num2date` and :func:`drange` are used to facilitate
easy conversion to and from :mod:`datetime` and numeric ranges.

.. note::

   Like Python's datetime, mpl uses the Gregorian calendar for all
   conversions between dates and floating point numbers. This practice
   is not universal, and calendar differences can cause confusing
   differences between what Python and mpl give as the number of days
   since 0001-01-01 and what other software and databases yield.  For
   example, the US Naval Observatory uses a calendar that switches
   from Julian to Gregorian in October, 1582.  Hence, using their
   calculator, the number of days between 0001-01-01 and 2006-04-01 is
   732403, whereas using the Gregorian calendar via the datetime
   module we find::

     In [31]:date(2006,4,1).toordinal() - date(1,1,1).toordinal()
     Out[31]:732401

A wide range of specific and general purpose date tick locators and
formatters are provided in this module.  See
:mod:`matplotlib.ticker` for general information on tick locators
and formatters.  These are described below.

All the matplotlib date converters, tickers and formatters are
timezone aware, and the default timezone is given by the timezone
parameter in your :file:`matplotlibrc` file.  If you leave out a
:class:`tz` timezone instance, the default from your rc file will be
assumed.  If you want to use a custom time zone, pass a
:class:`pytz.timezone` instance with the tz keyword argument to
:func:`num2date`, :func:`plot_date`, and any custom date tickers or
locators you create.  See `pytz <http://pythonhosted.org/pytz/>`_ for
information on :mod:`pytz` and timezone handling.

The `dateutil module <https://dateutil.readthedocs.io/en/stable/>`_ provides
additional code to handle date ticking, making it easy to place ticks
on any kinds of dates.  See examples below.

Date tickers
------------

Most of the date tickers can locate single or multiple values.  For
example::

    # import constants for the days of the week
    from matplotlib.dates import MO, TU, WE, TH, FR, SA, SU

    # tick on mondays every week
    loc = WeekdayLocator(byweekday=MO, tz=tz)

    # tick on mondays and saturdays
    loc = WeekdayLocator(byweekday=(MO, SA))

In addition, most of the constructors take an interval argument::

    # tick on mondays every second week
    loc = WeekdayLocator(byweekday=MO, interval=2)

The rrule locator allows completely general date ticking::

    # tick every 5th easter
    rule = rrulewrapper(YEARLY, byeaster=1, interval=5)
    loc = RRuleLocator(rule)

Here are all the date tickers:

    * :class:`MinuteLocator`: locate minutes

    * :class:`HourLocator`: locate hours

    * :class:`DayLocator`: locate specifed days of the month

    * :class:`WeekdayLocator`: Locate days of the week, e.g., MO, TU

    * :class:`MonthLocator`: locate months, e.g., 7 for july

    * :class:`YearLocator`: locate years that are multiples of base

    * :class:`RRuleLocator`: locate using a
      :class:`matplotlib.dates.rrulewrapper`.  The
      :class:`rrulewrapper` is a simple wrapper around a
      :class:`dateutil.rrule` (`dateutil
      <https://dateutil.readthedocs.io/en/stable/>`_) which allow almost
      arbitrary date tick specifications.  See `rrule example
      <../examples/pylab_examples/date_demo_rrule.html>`_.

    * :class:`AutoDateLocator`: On autoscale, this class picks the best
      :class:`MultipleDateLocator` to set the view limits and the tick
      locations.

Date formatters
---------------

Here all all the date formatters:

    * :class:`AutoDateFormatter`: attempts to figure out the best format
      to use.  This is most useful when used with the :class:`AutoDateLocator`.

    * :class:`DateFormatter`: use :func:`strftime` format strings

    * :class:`IndexDateFormatter`: date plots with implicit *x*
      indexing.
"""
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import six
from six.moves import zip
from matplotlib import rcParams
import re
import time
import math
import datetime

import warnings

from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY,
                            MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
                            SECONDLY)
from dateutil.relativedelta import relativedelta
import dateutil.parser
import numpy as np

import matplotlib
import matplotlib.units as units
import matplotlib.cbook as cbook
import matplotlib.ticker as ticker

__all__ = ('date2num', 'num2date', 'drange', 'epoch2num',
           'num2epoch', 'mx2num', 'DateFormatter',
           'IndexDateFormatter', 'AutoDateFormatter', 'DateLocator',
           'RRuleLocator', 'AutoDateLocator', 'YearLocator',
           'MonthLocator', 'WeekdayLocator',
           'DayLocator', 'HourLocator', 'MinuteLocator',
           'SecondLocator', 'MicrosecondLocator',
           'rrule', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU',
           'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY',
           'HOURLY', 'MINUTELY', 'SECONDLY', 'MICROSECONDLY', 'relativedelta',
           'seconds', 'minutes', 'hours', 'weeks')

# Make a simple UTC instance so we don't always have to import
# pytz.  From the python datetime library docs:

class _UTC(datetime.tzinfo):
    """UTC"""

    def utcoffset(self, dt):
        return datetime.timedelta(0)

    def tzname(self, dt):
        return str("UTC")

    def dst(self, dt):
        return datetime.timedelta(0)

UTC = _UTC()

def _get_rc_timezone():
    """
    Retrieve the preferred timeszone from the rcParams dictionary.
    """
    s = matplotlib.rcParams['timezone']
    if s == 'UTC':
        return UTC
    import pytz
    return pytz.timezone(s)

"""
Time-related constants.
"""
EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal())
JULIAN_OFFSET = 1721424.5                         # Julian date at 0001-01-01
MICROSECONDLY = SECONDLY + 1
HOURS_PER_DAY = 24.
MIN_PER_HOUR = 60.
SEC_PER_MIN = 60.
MONTHS_PER_YEAR = 12.

DAYS_PER_WEEK = 7.
DAYS_PER_MONTH = 30.
DAYS_PER_YEAR = 365.0

MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY

SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR
SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY
SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK

MUSECONDS_PER_DAY = 1e6 * SEC_PER_DAY

MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = (
    MO, TU, WE, TH, FR, SA, SU)
WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY)

def _to_ordinalf(dt):
    """
    Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float
    days, preserving hours, minutes, seconds and microseconds.  Return value
    is a :func:`float`.
    """
    # Convert to UTC
    tzi = getattr(dt, 'tzinfo', None)
    if tzi is not None:
        dt = dt.astimezone(UTC)
        tzi = UTC

    base = float(dt.toordinal())

    # If it's sufficiently datetime-like, it will have a `date()` method
    cdate = getattr(dt, 'date', lambda: None)()
    if cdate is not None:
        # Get a datetime object at midnight UTC
        midnight_time = datetime.time(0, tzinfo=tzi)

        rdt = datetime.datetime.combine(cdate, midnight_time)

        # Append the seconds as a fraction of a day
        base += (dt - rdt).total_seconds() / SEC_PER_DAY

    return base

# a version of _to_ordinalf that can operate on numpy arrays
_to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf)

def _from_ordinalf(x, tz=None):
    """
    Convert Gregorian float of the date, preserving hours, minutes,
    seconds and microseconds.  Return value is a :class:`datetime`.

    The input date `x` is a float in ordinal days at UTC, and the output will
    be the specified :class:`datetime` object corresponding to that time in
    timezone `tz`, or if `tz` is `None`, in the timezone specified in
    `rcParams['timezone']`.
    """
    if tz is None:
        tz = _get_rc_timezone()

    ix = int(x)
    dt = datetime.datetime.fromordinal(ix).replace(tzinfo=UTC)

    remainder = float(x) - ix

    # Round down to the nearest microsecond.
    dt += datetime.timedelta(microseconds=int(remainder * MUSECONDS_PER_DAY))

    # Compensate for rounding errors
    if dt.microsecond < 10:
        dt = dt.replace(microsecond=0)
    elif dt.microsecond > 999990:
        dt += datetime.timedelta(microseconds=1e6 - dt.microsecond)

    return dt.astimezone(tz)

# a version of _from_ordinalf that can operate on numpy arrays
_from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf)

class strpdate2num(object):
    """
    Use this class to parse date strings to matplotlib datenums when
    you know the date format string of the date you are parsing.
    """
    def __init__(self, fmt):
        """ fmt: any valid strptime format is supported """
        self.fmt = fmt

    def __call__(self, s):
        """s : string to be converted
           return value: a date2num float
        """
        return date2num(datetime.datetime(*time.strptime(s, self.fmt)[:6]))

class bytespdate2num(strpdate2num):
    """
    Use this class to parse date strings to matplotlib datenums when
    you know the date format string of the date you are parsing.  See
    :file:`examples/pylab_examples/load_converter.py`.
    """
    def __init__(self, fmt, encoding='utf-8'):
        """
        Args:
            fmt: any valid strptime format is supported
            encoding: encoding to use on byte input (default: 'utf-8')
        """
        super(bytespdate2num, self).__init__(fmt)
        self.encoding = encoding

    def __call__(self, b):
        """
        Args:
            b: byte input to be converted
        Returns:
            A date2num float
        """
        s = b.decode(self.encoding)
        return super(bytespdate2num, self).__call__(s)

# a version of dateutil.parser.parse that can operate on nump0y arrays
_dateutil_parser_parse_np_vectorized = np.vectorize(dateutil.parser.parse)

def datestr2num(d, default=None):
    """
    Convert a date string to a datenum using
    :func:`dateutil.parser.parse`.

    Parameters
    ----------
    d : string or sequence of strings
        The dates to convert.

    default : datetime instance
        The default date to use when fields are missing in `d`.
    """
    if cbook.is_string_like(d):
        dt = dateutil.parser.parse(d, default=default)
        return date2num(dt)
    else:
        if default is not None:
            d = [dateutil.parser.parse(s, default=default) for s in d]
        d = np.asarray(d)
        if not d.size:
            return d
        return date2num(_dateutil_parser_parse_np_vectorized(d))

def date2num(d):
    """
    *d* is either a :class:`datetime` instance or a sequence of datetimes.

    Return value is a floating point number (or sequence of floats)
    which gives the number of days (fraction part represents hours,
    minutes, seconds) since 0001-01-01 00:00:00 UTC, *plus* *one*.
    The addition of one here is a historical artifact.  Also, note
    that the Gregorian calendar is assumed; this is not universal
    practice.  For details, see the module docstring.
    """
    if not cbook.iterable(d):
        return _to_ordinalf(d)
    else:
        d = np.asarray(d)
        if not d.size:
            return d
        return _to_ordinalf_np_vectorized(d)

def julian2num(j):
    """
    Convert a Julian date (or sequence) to a matplotlib date (or sequence).
    """
    if cbook.iterable(j):
        j = np.asarray(j)
    return j - JULIAN_OFFSET

def num2julian(n):
    """
    Convert a matplotlib date (or sequence) to a Julian date (or sequence).
    """
    if cbook.iterable(n):
        n = np.asarray(n)
    return n + JULIAN_OFFSET

def num2date(x, tz=None):
    """
    *x* is a float value which gives the number of days
    (fraction part represents hours, minutes, seconds) since
    0001-01-01 00:00:00 UTC *plus* *one*.
    The addition of one here is a historical artifact.  Also, note
    that the Gregorian calendar is assumed; this is not universal
    practice.  For details, see the module docstring.

    Return value is a :class:`datetime` instance in timezone *tz* (default to
    rcparams TZ value).

    If *x* is a sequence, a sequence of :class:`datetime` objects will
    be returned.
    """
    if tz is None:
        tz = _get_rc_timezone()
    if not cbook.iterable(x):
        return _from_ordinalf(x, tz)
    else:
        x = np.asarray(x)
        if not x.size:
            return x
        return _from_ordinalf_np_vectorized(x, tz).tolist()

def drange(dstart, dend, delta):
    """
    Return a date range as float Gregorian ordinals.  *dstart* and
    *dend* are :class:`datetime` instances.  *delta* is a
    :class:`datetime.timedelta` instance.
    """
    f1 = _to_ordinalf(dstart)
    f2 = _to_ordinalf(dend)
    step = delta.total_seconds() / SEC_PER_DAY

    # calculate the difference between dend and dstart in times of delta
    num = int(np.ceil((f2 - f1) / step))

    # calculate end of the interval which will be generated
    dinterval_end = dstart + num * delta

    # ensure, that an half open interval will be generated [dstart, dend)
    if dinterval_end >= dend:
        # if the endpoint is greated than dend, just subtract one delta
        dinterval_end -= delta
        num -= 1

    f2 = _to_ordinalf(dinterval_end)  # new float-endpoint
    return np.linspace(f1, f2, num + 1)

### date tickers and formatters ###

class DateFormatter(ticker.Formatter):
    """
    Tick location is seconds since the epoch.  Use a :func:`strftime`
    format string.

    Python only supports :mod:`datetime` :func:`strftime` formatting
    for years greater than 1900.  Thanks to Andrew Dalke, Dalke
    Scientific Software who contributed the :func:`strftime` code
    below to include dates earlier than this year.
    """

    illegal_s = re.compile(r"((^|[^%])(%%)*%s)")

    def __init__(self, fmt, tz=None):
        """
        *fmt* is a :func:`strftime` format string; *tz* is the
         :class:`tzinfo` instance.
        """
        if tz is None:
            tz = _get_rc_timezone()
        self.fmt = fmt
        self.tz = tz

    def __call__(self, x, pos=0):
        if x == 0:
            raise ValueError('DateFormatter found a value of x=0, which is '
                             'an illegal date.  This usually occurs because '
                             'you have not informed the axis that it is '
                             'plotting dates, e.g., with ax.xaxis_date()')
        dt = num2date(x, self.tz)
        return self.strftime(dt, self.fmt)

    def set_tzinfo(self, tz):
        self.tz = tz

    def _replace_common_substr(self, s1, s2, sub1, sub2, replacement):
        """Helper function for replacing substrings sub1 and sub2
        located at the same indexes in strings s1 and s2 respectively,
        with the string replacement.  It is expected that sub1 and sub2
        have the same length.  Returns the pair s1, s2 after the
        substitutions.
        """
        # Find common indexes of substrings sub1 in s1 and sub2 in s2
        # and make substitutions inplace. Because this is inplace,
        # it is okay if len(replacement) != len(sub1), len(sub2).
        i = 0
        while True:
            j = s1.find(sub1, i)
            if j == -1:
                break

            i = j + 1
            if s2[j:j + len(sub2)] != sub2:
                continue

            s1 = s1[:j] + replacement + s1[j + len(sub1):]
            s2 = s2[:j] + replacement + s2[j + len(sub2):]

        return s1, s2

    def strftime_pre_1900(self, dt, fmt=None):
        """Call time.strftime for years before 1900 by rolling
        forward a multiple of 28 years.

        *fmt* is a :func:`strftime` format string.

        Dalke: I hope I did this math right.  Every 28 years the
        calendar repeats, except through century leap years excepting
        the 400 year leap years.  But only if you're using the Gregorian
        calendar.
        """
        if fmt is None:
            fmt = self.fmt

        # Since python's time module's strftime implementation does not
        # support %f microsecond (but the datetime module does), use a
        # regular expression substitution to replace instances of %f.
        # Note that this can be useful since python's floating-point
        # precision representation for datetime causes precision to be
        # more accurate closer to year 0 (around the year 2000, precision
        # can be at 10s of microseconds).
        fmt = re.sub(r'((^|[^%])(%%)*)%f',
                     r'\g<1>{0:06d}'.format(dt.microsecond), fmt)

        year = dt.year
        # For every non-leap year century, advance by
        # 6 years to get into the 28-year repeat cycle
        delta = 2000 - year
        off = 6 * (delta // 100 + delta // 400)
        year = year + off

        # Move to between the years 1973 and 2000
        year1 = year + ((2000 - year) // 28) * 28
        year2 = year1 + 28
        timetuple = dt.timetuple()
        # Generate timestamp string for year and year+28
        s1 = time.strftime(fmt, (year1,) + timetuple[1:])
        s2 = time.strftime(fmt, (year2,) + timetuple[1:])

        # Replace instances of respective years (both 2-digit and 4-digit)
        # that are located at the same indexes of s1, s2 with dt's year.
        # Note that C++'s strftime implementation does not use padded
        # zeros or padded whitespace for %y or %Y for years before 100, but
        # uses padded zeros for %x. (For example, try the runnable examples
        # with .tm_year in the interval [-1900, -1800] on
        # http://en.cppreference.com/w/c/chrono/strftime.) For ease of
        # implementation, we always use padded zeros for %y, %Y, and %x.
        s1, s2 = self._replace_common_substr(s1, s2,
                                             "{0:04d}".format(year1),
                                             "{0:04d}".format(year2),
                                             "{0:04d}".format(dt.year))
        s1, s2 = self._replace_common_substr(s1, s2,
                                             "{0:02d}".format(year1 % 100),
                                             "{0:02d}".format(year2 % 100),
                                             "{0:02d}".format(dt.year % 100))
        return cbook.unicode_safe(s1)

    def strftime(self, dt, fmt=None):
        """Refer to documentation for datetime.strftime.

        *fmt* is a :func:`strftime` format string.

        Warning: For years before 1900, depending upon the current
        locale it is possible that the year displayed with %x might
        be incorrect. For years before 100, %y and %Y will yield
        zero-padded strings.
        """
        if fmt is None:
            fmt = self.fmt
        fmt = self.illegal_s.sub(r"\1", fmt)
        fmt = fmt.replace("%s", "s")
        if dt.year >= 1900:
            # Note: in python 3.3 this is okay for years >= 1000,
            # refer to http://bugs.python.org/issue177742
            return cbook.unicode_safe(dt.strftime(fmt))

        return self.strftime_pre_1900(dt, fmt)

class IndexDateFormatter(ticker.Formatter):
    """
    Use with :class:`~matplotlib.ticker.IndexLocator` to cycle format
    strings by index.
    """
    def __init__(self, t, fmt, tz=None):
        """
        *t* is a sequence of dates (floating point days).  *fmt* is a
        :func:`strftime` format string.
        """
        if tz is None:
            tz = _get_rc_timezone()
        self.t = t
        self.fmt = fmt
        self.tz = tz

    def __call__(self, x, pos=0):
        'Return the label for time *x* at position *pos*'
        ind = int(np.round(x))
        if ind >= len(self.t) or ind <= 0:
            return ''

        dt = num2date(self.t[ind], self.tz)

        return cbook.unicode_safe(dt.strftime(self.fmt))

class AutoDateFormatter(ticker.Formatter):
    """
    This class attempts to figure out the best format to use.  This is
    most useful when used with the :class:`AutoDateLocator`.

    The AutoDateFormatter has a scale dictionary that maps the scale
    of the tick (the distance in days between one major tick) and a
    format string.  The default looks like this::

        self.scaled = {
            DAYS_PER_YEAR: rcParams['date.autoformat.year'],
            DAYS_PER_MONTH: rcParams['date.autoformat.month'],
            1.0: rcParams['date.autoformat.day'],
            1. / HOURS_PER_DAY: rcParams['date.autoformat.hour'],
            1. / (MINUTES_PER_DAY): rcParams['date.autoformat.minute'],
            1. / (SEC_PER_DAY): rcParams['date.autoformat.second'],
            1. / (MUSECONDS_PER_DAY): rcParams['date.autoformat.microsecond'],
            }

    The algorithm picks the key in the dictionary that is >= the
    current scale and uses that format string.  You can customize this
    dictionary by doing::

    >>> locator = AutoDateLocator()
    >>> formatter = AutoDateFormatter(locator)
    >>> formatter.scaled[1/(24.*60.)] = '%M:%S' # only show min and sec

    A custom :class:`~matplotlib.ticker.FuncFormatter` can also be used.
    The following example shows how to use a custom format function to strip
    trailing zeros from decimal seconds and adds the date to the first
    ticklabel::

        >>> def my_format_function(x, pos=None):
        ...     x = matplotlib.dates.num2date(x)
        ...     if pos == 0:
        ...         fmt = '%D %H:%M:%S.%f'
        ...     else:
        ...         fmt = '%H:%M:%S.%f'
        ...     label = x.strftime(fmt)
        ...     label = label.rstrip("0")
        ...     label = label.rstrip(".")
        ...     return label
        >>> from matplotlib.ticker import FuncFormatter
        >>> formatter.scaled[1/(24.*60.)] = FuncFormatter(my_format_function)
    """

    # This can be improved by providing some user-level direction on
    # how to choose the best format (precedence, etc...)

    # Perhaps a 'struct' that has a field for each time-type where a
    # zero would indicate "don't show" and a number would indicate
    # "show" with some sort of priority.  Same priorities could mean
    # show all with the same priority.

    # Or more simply, perhaps just a format string for each
    # possibility...

    def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
        """
        Autoformat the date labels.  The default format is the one to use
        if none of the values in ``self.scaled`` are greater than the unit
        returned by ``locator._get_unit()``.
        """
        self._locator = locator
        self._tz = tz
        self.defaultfmt = defaultfmt
        self._formatter = DateFormatter(self.defaultfmt, tz)
        self.scaled = {DAYS_PER_YEAR: rcParams['date.autoformatter.year'],
                       DAYS_PER_MONTH: rcParams['date.autoformatter.month'],
                       1.0: rcParams['date.autoformatter.day'],
                       1. / HOURS_PER_DAY: rcParams['date.autoformatter.hour'],
                       1. / (MINUTES_PER_DAY):
                           rcParams['date.autoformatter.minute'],
                       1. / (SEC_PER_DAY):
                           rcParams['date.autoformatter.second'],
                       1. / (MUSECONDS_PER_DAY):
                           rcParams['date.autoformatter.microsecond']}

    def __call__(self, x, pos=None):
        locator_unit_scale = float(self._locator._get_unit())
        fmt = self.defaultfmt

        # Pick the first scale which is greater than the locator unit.
        for possible_scale in sorted(self.scaled):
            if possible_scale >= locator_unit_scale:
                fmt = self.scaled[possible_scale]
                break

        if isinstance(fmt, six.string_types):
            self._formatter = DateFormatter(fmt, self._tz)
            result = self._formatter(x, pos)
        elif six.callable(fmt):
            result = fmt(x, pos)
        else:
            raise TypeError('Unexpected type passed to {0!r}.'.format(self))

        return result

class rrulewrapper(object):

    def __init__(self, freq, **kwargs):
        self._construct = kwargs.copy()
        self._construct["freq"] = freq
        self._rrule = rrule(**self._construct)

    def set(self, **kwargs):
        self._construct.update(kwargs)
        self._rrule = rrule(**self._construct)

    def __getattr__(self, name):
        if name in self.__dict__:
            return self.__dict__[name]
        return getattr(self._rrule, name)

class DateLocator(ticker.Locator):
    """
    Determines the tick locations when plotting dates.
    """
    hms0d = {'byhour': 0, 'byminute': 0, 'bysecond': 0}

    def __init__(self, tz=None):
        """
        *tz* is a :class:`tzinfo` instance.
        """
        if tz is None:
            tz = _get_rc_timezone()
        self.tz = tz

    def set_tzinfo(self, tz):
        """
        Set time zone info.
        """
        self.tz = tz

    def datalim_to_dt(self):
        """
        Convert axis data interval to datetime objects.
        """
        dmin, dmax = self.axis.get_data_interval()
        if dmin > dmax:
            dmin, dmax = dmax, dmin

        return num2date(dmin, self.tz), num2date(dmax, self.tz)

    def viewlim_to_dt(self):
        """
        Converts the view interval to datetime objects.
        """
        vmin, vmax = self.axis.get_view_interval()
        if vmin > vmax:
            vmin, vmax = vmax, vmin

        return num2date(vmin, self.tz), num2date(vmax, self.tz)

    def _get_unit(self):
        """
        Return how many days a unit of the locator is; used for
        intelligent autoscaling.
        """
        return 1

    def _get_interval(self):
        """
        Return the number of units for each tick.
        """
        return 1

    def nonsingular(self, vmin, vmax):
        """
        Given the proposed upper and lower extent, adjust the range
        if it is too close to being singular (i.e. a range of ~0).

        """
        unit = self._get_unit()
        interval = self._get_interval()
        if abs(vmax - vmin) < 1e-6:
            vmin -= 2 * unit * interval
            vmax += 2 * unit * interval
        return vmin, vmax

class RRuleLocator(DateLocator):
    # use the dateutil rrule instance

    def __init__(self, o, tz=None):
        DateLocator.__init__(self, tz)
        self.rule = o

    def __call__(self):
        # if no data have been set, this will tank with a ValueError
        try:
            dmin, dmax = self.viewlim_to_dt()
        except ValueError:
            return []

        return self.tick_values(dmin, dmax)

    def tick_values(self, vmin, vmax):
        delta = relativedelta(vmax, vmin)

        # We need to cap at the endpoints of valid datetime
        try:
            start = vmin - delta
        except ValueError:
            start = _from_ordinalf(1.0)

        try:
            stop = vmax + delta
        except ValueError:
            # The magic number!
            stop = _from_ordinalf(3652059.9999999)

        self.rule.set(dtstart=start, until=stop)

        dates = self.rule.between(vmin, vmax, True)
        if len(dates) == 0:
            return date2num([vmin, vmax])
        return self.raise_if_exceeds(date2num(dates))

    def _get_unit(self):
        """
        Return how many days a unit of the locator is; used for
        intelligent autoscaling.
        """
        freq = self.rule._rrule._freq
        return self.get_unit_generic(freq)

    @staticmethod
    def get_unit_generic(freq):
        if freq == YEARLY:
            return DAYS_PER_YEAR
        elif freq == MONTHLY:
            return DAYS_PER_MONTH
        elif freq == WEEKLY:
            return DAYS_PER_WEEK
        elif freq == DAILY:
            return 1.0
        elif freq == HOURLY:
            return 1.0 / HOURS_PER_DAY
        elif freq == MINUTELY:
            return 1.0 / MINUTES_PER_DAY
        elif freq == SECONDLY:
            return 1.0 / SEC_PER_DAY
        else:
            # error
            return -1   # or should this just return '1'?

    def _get_interval(self):
        return self.rule._rrule._interval

    def autoscale(self):
        """
        Set the view limits to include the data range.
        """
        dmin, dmax = self.datalim_to_dt()
        delta = relativedelta(dmax, dmin)

        # We need to cap at the endpoints of valid datetime
        try:
            start = dmin - delta
        except ValueError:
            start = _from_ordinalf(1.0)

        try:
            stop = dmax + delta
        except ValueError:
            # The magic number!
            stop = _from_ordinalf(3652059.9999999)

        self.rule.set(dtstart=start, until=stop)
        dmin, dmax = self.datalim_to_dt()

        vmin = self.rule.before(dmin, True)
        if not vmin:
            vmin = dmin

        vmax = self.rule.after(dmax, True)
        if not vmax:
            vmax = dmax

        vmin = date2num(vmin)
        vmax = date2num(vmax)

        return self.nonsingular(vmin, vmax)

class AutoDateLocator(DateLocator):
    """
    On autoscale, this class picks the best
    :class:`DateLocator` to set the view limits and the tick
    locations.
    """
    def __init__(self, tz=None, minticks=5, maxticks=None,
                 interval_multiples=False):
        """
        *minticks* is the minimum number of ticks desired, which is used to
        select the type of ticking (yearly, monthly, etc.).

        *maxticks* is the maximum number of ticks desired, which controls
        any interval between ticks (ticking every other, every 3, etc.).
        For really fine-grained control, this can be a dictionary mapping
        individual rrule frequency constants (YEARLY, MONTHLY, etc.)
        to their own maximum number of ticks.  This can be used to keep
        the number of ticks appropriate to the format chosen in
        :class:`AutoDateFormatter`. Any frequency not specified in this
        dictionary is given a default value.

        *tz* is a :class:`tzinfo` instance.

        *interval_multiples* is a boolean that indicates whether ticks
        should be chosen to be multiple of the interval. This will lock
        ticks to 'nicer' locations. For example, this will force the
        ticks to be at hours 0,6,12,18 when hourly ticking is done at
        6 hour intervals.

        The AutoDateLocator has an interval dictionary that maps the
        frequency of the tick (a constant from dateutil.rrule) and a
        multiple allowed for that ticking.  The default looks like this::

          self.intervald = {
            YEARLY  : [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
                      1000, 2000, 4000, 5000, 10000],
            MONTHLY : [1, 2, 3, 4, 6],
            DAILY   : [1, 2, 3, 7, 14],
            HOURLY  : [1, 2, 3, 4, 6, 12],
            MINUTELY: [1, 5, 10, 15, 30],
            SECONDLY: [1, 5, 10, 15, 30],
            MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000,
                           5000, 10000, 20000, 50000, 100000, 200000, 500000,
                           1000000],
            }

        The interval is used to specify multiples that are appropriate for
        the frequency of ticking. For instance, every 7 days is sensible
        for daily ticks, but for minutes/seconds, 15 or 30 make sense.
        You can customize this dictionary by doing::

          locator = AutoDateLocator()
          locator.intervald[HOURLY] = [3] # only show every 3 hours
        """
        DateLocator.__init__(self, tz)
        self._locator = YearLocator()
        self._freq = YEARLY
        self._freqs = [YEARLY, MONTHLY, DAILY, HOURLY, MINUTELY,
                       SECONDLY, MICROSECONDLY]
        self.minticks = minticks

        self.maxticks = {YEARLY: 11, MONTHLY: 12, DAILY: 11, HOURLY: 12,
                         MINUTELY: 11, SECONDLY: 11, MICROSECONDLY: 8}
        if maxticks is not None:
            try:
                self.maxticks.update(maxticks)
            except TypeError:
                # Assume we were given an integer. Use this as the maximum
                # number of ticks for every frequency and create a
                # dictionary for this
                self.maxticks = dict(zip(self._freqs,
                                         [maxticks] * len(self._freqs)))
        self.interval_multiples = interval_multiples
        self.intervald = {
            YEARLY:   [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
                       1000, 2000, 4000, 5000, 10000],
            MONTHLY:  [1, 2, 3, 4, 6],
            DAILY:    [1, 2, 3, 7, 14, 21],
            HOURLY:   [1, 2, 3, 4, 6, 12],
            MINUTELY: [1, 5, 10, 15, 30],
            SECONDLY: [1, 5, 10, 15, 30],
            MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000,
                            5000, 10000, 20000, 50000, 100000, 200000, 500000,
                            1000000]}
        self._byranges = [None, range(1, 13), range(1, 32),
                          range(0, 24), range(0, 60), range(0, 60), None]

    def __call__(self):
        'Return the locations of the ticks'
        self.refresh()
        return self._locator()

    def tick_values(self, vmin, vmax):
        return self.get_locator(vmin, vmax).tick_values(vmin, vmax)

    def nonsingular(self, vmin, vmax):
        # whatever is thrown at us, we can scale the unit.
        # But default nonsingular date plots at an ~4 year period.
        if vmin == vmax:
            vmin = vmin - DAYS_PER_YEAR * 2
            vmax = vmax + DAYS_PER_YEAR * 2
        return vmin, vmax

    def set_axis(self, axis):
        DateLocator.set_axis(self, axis)
        self._locator.set_axis(axis)

    def refresh(self):
        'Refresh internal information based on current limits.'
        dmin, dmax = self.viewlim_to_dt()
        self._locator = self.get_locator(dmin, dmax)

    def _get_unit(self):
        if self._freq in [MICROSECONDLY]:
            return 1. / MUSECONDS_PER_DAY
        else:
            return RRuleLocator.get_unit_generic(self._freq)

    def autoscale(self):
        'Try to choose the view limits intelligently.'
        dmin, dmax = self.datalim_to_dt()
        self._locator = self.get_locator(dmin, dmax)
        return self._locator.autoscale()

    def get_locator(self, dmin, dmax):
        'Pick the best locator based on a distance.'
        delta = relativedelta(dmax, dmin)
        tdelta = dmax - dmin

        # take absolute difference
        if dmin > dmax:
            delta = -delta
            tdelta = -tdelta

        # The following uses a mix of calls to relativedelta and timedelta
        # methods because there is incomplete overlap in the functionality of
        # these similar functions, and it's best to avoid doing our own math
        # whenever possible.
        numYears = float(delta.years)
        numMonths = (numYears * MONTHS_PER_YEAR) + delta.months
        numDays = tdelta.days   # Avoids estimates of days/month, days/year
        numHours = (numDays * HOURS_PER_DAY) + delta.hours
        numMinutes = (numHours * MIN_PER_HOUR) + delta.minutes
        numSeconds = np.floor(tdelta.total_seconds())
        numMicroseconds = np.floor(tdelta.total_seconds() * 1e6)

        nums = [numYears, numMonths, numDays, numHours, numMinutes,
                numSeconds, numMicroseconds]

        use_rrule_locator = [True] * 6 + [False]

        # Default setting of bymonth, etc. to pass to rrule
        # [unused (for year), bymonth, bymonthday, byhour, byminute,
        #  bysecond, unused (for microseconds)]
        byranges = [None, 1, 1, 0, 0, 0, None]

        # Loop over all the frequencies and try to find one that gives at
        # least a minticks tick positions.  Once this is found, look for
        # an interval from an list specific to that frequency that gives no
        # more than maxticks tick positions. Also, set up some ranges
        # (bymonth, etc.) as appropriate to be passed to rrulewrapper.
        for i, (freq, num) in enumerate(zip(self._freqs, nums)):
            # If this particular frequency doesn't give enough ticks, continue
            if num < self.minticks:
                # Since we're not using this particular frequency, set
                # the corresponding by_ to None so the rrule can act as
                # appropriate
                byranges[i] = None
                continue

            # Find the first available interval that doesn't give too many
            # ticks
            for interval in self.intervald[freq]:
                if num <= interval * (self.maxticks[freq] - 1):
                    break
            else:
                # We went through the whole loop without breaking, default to
                # the last interval in the list and raise a warning
                warnings.warn('AutoDateLocator was unable to pick an '
                              'appropriate interval for this date range. '
                              'It may be necessary to add an interval value '
                              "to the AutoDateLocator's intervald dictionary."
                              ' Defaulting to {0}.'.format(interval))

            # Set some parameters as appropriate
            self._freq = freq

            if self._byranges[i] and self.interval_multiples:
                byranges[i] = self._byranges[i][::interval]
                interval = 1
            else:
                byranges[i] = self._byranges[i]

            # We found what frequency to use
            break
        else:
            raise ValueError('No sensible date limit could be found in the '
                             'AutoDateLocator.')

        if use_rrule_locator[i]:
            _, bymonth, bymonthday, byhour, byminute, bysecond, _ = byranges

            rrule = rrulewrapper(self._freq, interval=interval,
                                 dtstart=dmin, until=dmax,
                                 bymonth=bymonth, bymonthday=bymonthday,
                                 byhour=byhour, byminute=byminute,
                                 bysecond=bysecond)

            locator = RRuleLocator(rrule, self.tz)
        else:
            locator = MicrosecondLocator(interval, tz=self.tz)

        locator.set_axis(self.axis)

        locator.set_view_interval(*self.axis.get_view_interval())
        locator.set_data_interval(*self.axis.get_data_interval())
        return locator

class YearLocator(DateLocator):
    """
    Make ticks on a given day of each year that is a multiple of base.

    Examples::

      # Tick every year on Jan 1st
      locator = YearLocator()

      # Tick every 5 years on July 4th
      locator = YearLocator(5, month=7, day=4)
    """
    def __init__(self, base=1, month=1, day=1, tz=None):
        """
        Mark years that are multiple of base on a given month and day
        (default jan 1).
        """
        DateLocator.__init__(self, tz)
        self.base = ticker.Base(base)
        self.replaced = {'month':  month,
                         'day':    day,
                         'hour':   0,
                         'minute': 0,
                         'second': 0,
                         'tzinfo': tz
                         }

    def __call__(self):
        # if no data have been set, this will tank with a ValueError
        try:
            dmin, dmax = self.viewlim_to_dt()
        except ValueError:
            return []

        return self.tick_values(dmin, dmax)

    def tick_values(self, vmin, vmax):
        ymin = self.base.le(vmin.year)
        ymax = self.base.ge(vmax.year)

        ticks = [vmin.replace(year=ymin, **self.replaced)]
        while 1:
            dt = ticks[-1]
            if dt.year >= ymax:
                return date2num(ticks)
            year = dt.year + self.base.get_base()
            ticks.append(dt.replace(year=year, **self.replaced))

    def autoscale(self):
        """
        Set the view limits to include the data range.
        """
        dmin, dmax = self.datalim_to_dt()

        ymin = self.base.le(dmin.year)
        ymax = self.base.ge(dmax.year)
        vmin = dmin.replace(year=ymin, **self.replaced)
        vmax = dmax.replace(year=ymax, **self.replaced)

        vmin = date2num(vmin)
        vmax = date2num(vmax)
        return self.nonsingular(vmin, vmax)

class MonthLocator(RRuleLocator):
    """
    Make ticks on occurances of each month month, e.g., 1, 3, 12.
    """
    def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None):
        """
        Mark every month in *bymonth*; *bymonth* can be an int or
        sequence.  Default is ``range(1,13)``, i.e. every month.

        *interval* is the interval between each iteration.  For
        example, if ``interval=2``, mark every second occurance.
        """
        if bymonth is None:
            bymonth = range(1, 13)
        elif isinstance(bymonth, np.ndarray):
            # This fixes a bug in dateutil <= 2.3 which prevents the use of
            # numpy arrays in (among other things) the bymonthday, byweekday
            # and bymonth parameters.
            bymonth = [x.item() for x in bymonth.astype(int)]

        rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday,
                         interval=interval, **self.hms0d)
        RRuleLocator.__init__(self, rule, tz)

class WeekdayLocator(RRuleLocator):
    """
    Make ticks on occurances of each weekday.
    """

    def __init__(self, byweekday=1, interval=1, tz=None):
        """
        Mark every weekday in *byweekday*; *byweekday* can be a number or
        sequence.

        Elements of *byweekday* must be one of MO, TU, WE, TH, FR, SA,
        SU, the constants from :mod:`dateutil.rrule`, which have been
        imported into the :mod:`matplotlib.dates` namespace.

        *interval* specifies the number of weeks to skip.  For example,
        ``interval=2`` plots every second week.
        """
        if isinstance(byweekday, np.ndarray):
            # This fixes a bug in dateutil <= 2.3 which prevents the use of
            # numpy arrays in (among other things) the bymonthday, byweekday
            # and bymonth parameters.
            [x.item() for x in byweekday.astype(int)]

        rule = rrulewrapper(DAILY, byweekday=byweekday,
                            interval=interval, **self.hms0d)
        RRuleLocator.__init__(self, rule, tz)

class DayLocator(RRuleLocator):
    """
    Make ticks on occurances of each day of the month.  For example,
    1, 15, 30.
    """
    def __init__(self, bymonthday=None, interval=1, tz=None):
        """
        Mark every day in *bymonthday*; *bymonthday* can be an int or
        sequence.

        Default is to tick every day of the month: ``bymonthday=range(1,32)``
        """
        if not interval == int(interval) or interval < 1:
            raise ValueError("interval must be an integer greater than 0")
        if bymonthday is None:
            bymonthday = range(1, 32)
        elif isinstance(bymonthday, np.ndarray):
            # This fixes a bug in dateutil <= 2.3 which prevents the use of
            # numpy arrays in (among other things) the bymonthday, byweekday
            # and bymonth parameters.
            bymonthday = [x.item() for x in bymonthday.astype(int)]

        rule = rrulewrapper(DAILY, bymonthday=bymonthday,
                            interval=interval, **self.hms0d)
        RRuleLocator.__init__(self, rule, tz)

class HourLocator(RRuleLocator):
    """
    Make ticks on occurances of each hour.
    """
    def __init__(self, byhour=None, interval=1, tz=None):
        """
        Mark every hour in *byhour*; *byhour* can be an int or sequence.
        Default is to tick every hour: ``byhour=range(24)``

        *interval* is the interval between each iteration.  For
        example, if ``interval=2``, mark every second occurrence.
        """
        if byhour is None:
            byhour = range(24)

        rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval,
                            byminute=0, bysecond=0)
        RRuleLocator.__init__(self, rule, tz)

class MinuteLocator(RRuleLocator):
    """
    Make ticks on occurances of each minute.
    """
    def __init__(self, byminute=None, interval=1, tz=None):
        """
        Mark every minute in *byminute*; *byminute* can be an int or
        sequence.  Default is to tick every minute: ``byminute=range(60)``

        *interval* is the interval between each iteration.  For
        example, if ``interval=2``, mark every second occurrence.
        """
        if byminute is None:
            byminute = range(60)

        rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval,
                            bysecond=0)
        RRuleLocator.__init__(self, rule, tz)

class SecondLocator(RRuleLocator):
    """
    Make ticks on occurances of each second.
    """
    def __init__(self, bysecond=None, interval=1, tz=None):
        """
        Mark every second in *bysecond*; *bysecond* can be an int or
        sequence.  Default is to tick every second: ``bysecond = range(60)``

        *interval* is the interval between each iteration.  For
        example, if ``interval=2``, mark every second occurrence.

        """
        if bysecond is None:
            bysecond = range(60)

        rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval)
        RRuleLocator.__init__(self, rule, tz)

class MicrosecondLocator(DateLocator):
    """
    Make ticks on occurances of each microsecond.

    """
    def __init__(self, interval=1, tz=None):
        """
        *interval* is the interval between each iteration.  For
        example, if ``interval=2``, mark every second microsecond.

        """
        self._interval = interval
        self._wrapped_locator = ticker.MultipleLocator(interval)
        self.tz = tz

    def set_axis(self, axis):
        self._wrapped_locator.set_axis(axis)
        return DateLocator.set_axis(self, axis)

    def set_view_interval(self, vmin, vmax):
        self._wrapped_locator.set_view_interval(vmin, vmax)
        return DateLocator.set_view_interval(self, vmin, vmax)

    def set_data_interval(self, vmin, vmax):
        self._wrapped_locator.set_data_interval(vmin, vmax)
        return DateLocator.set_data_interval(self, vmin, vmax)

    def __call__(self):
        # if no data have been set, this will tank with a ValueError
        try:
            dmin, dmax = self.viewlim_to_dt()
        except ValueError:
            return []

        return self.tick_values(dmin, dmax)

    def tick_values(self, vmin, vmax):
        nmin, nmax = date2num((vmin, vmax))
        nmin *= MUSECONDS_PER_DAY
        nmax *= MUSECONDS_PER_DAY
        ticks = self._wrapped_locator.tick_values(nmin, nmax)
        ticks = [tick / MUSECONDS_PER_DAY for tick in ticks]
        return ticks

    def _get_unit(self):
        """
        Return how many days a unit of the locator is; used for
        intelligent autoscaling.
        """
        return 1. / MUSECONDS_PER_DAY

    def _get_interval(self):
        """
        Return the number of units for each tick.
        """
        return self._interval

def _close_to_dt(d1, d2, epsilon=5):
    """
    Assert that datetimes *d1* and *d2* are within *epsilon* microseconds.
    """
    delta = d2 - d1
    mus = abs(delta.total_seconds() * 1e6)
    assert mus < epsilon

def _close_to_num(o1, o2, epsilon=5):
    """
    Assert that float ordinals *o1* and *o2* are within *epsilon*
    microseconds.
    """
    delta = abs((o2 - o1) * MUSECONDS_PER_DAY)
    assert delta < epsilon

def epoch2num(e):
    """
    Convert an epoch or sequence of epochs to the new date format,
    that is days since 0001.
    """
    return EPOCH_OFFSET + np.asarray(e) / SEC_PER_DAY

def num2epoch(d):
    """
    Convert days since 0001 to epoch.  *d* can be a number or sequence.
    """
    return (np.asarray(d) - EPOCH_OFFSET) * SEC_PER_DAY

def mx2num(mxdates):
    """
    Convert mx :class:`datetime` instance (or sequence of mx
    instances) to the new date format.
    """
    scalar = False
    if not cbook.iterable(mxdates):
        scalar = True
        mxdates = [mxdates]
    ret = epoch2num([m.ticks() for m in mxdates])
    if scalar:
        return ret[0]
    else:
        return ret

def date_ticker_factory(span, tz=None, numticks=5):
    """
    Create a date locator with *numticks* (approx) and a date formatter
    for *span* in days.  Return value is (locator, formatter).
    """

    if span == 0:
        span = 1 / HOURS_PER_DAY

    mins = span * MINUTES_PER_DAY
    hrs = span * HOURS_PER_DAY
    days = span
    wks = span / DAYS_PER_WEEK
    months = span / DAYS_PER_MONTH      # Approx
    years = span / DAYS_PER_YEAR        # Approx

    if years > numticks:
        locator = YearLocator(int(years / numticks), tz=tz)  # define
        fmt = '%Y'
    elif months > numticks:
        locator = MonthLocator(tz=tz)
        fmt = '%b %Y'
    elif wks > numticks:
        locator = WeekdayLocator(tz=tz)
        fmt = '%a, %b %d'
    elif days > numticks:
        locator = DayLocator(interval=int(math.ceil(days / numticks)), tz=tz)
        fmt = '%b %d'
    elif hrs > numticks:
        locator = HourLocator(interval=int(math.ceil(hrs / numticks)), tz=tz)
        fmt = '%H:%M\n%b %d'
    elif mins > numticks:
        locator = MinuteLocator(interval=int(math.ceil(mins / numticks)),
                                tz=tz)
        fmt = '%H:%M:%S'
    else:
        locator = MinuteLocator(tz=tz)
        fmt = '%H:%M:%S'

    formatter = DateFormatter(fmt, tz=tz)
    return locator, formatter

def seconds(s):
    """
    Return seconds as days.
    """
    return float(s) / SEC_PER_DAY

def minutes(m):
    """
    Return minutes as days.
    """
    return float(m) / MINUTES_PER_DAY

def hours(h):
    """
    Return hours as days.
    """
    return h / HOURS_PER_DAY

def weeks(w):
    """
    Return weeks as days.
    """
    return w * DAYS_PER_WEEK

class DateConverter(units.ConversionInterface):
    """
    Converter for datetime.date and datetime.datetime data,
    or for date/time data represented as it would be converted
    by :func:`date2num`.

    The 'unit' tag for such data is None or a tzinfo instance.
    """

    @staticmethod
    def axisinfo(unit, axis):
        """
        Return the :class:`~matplotlib.units.AxisInfo` for *unit*.

        *unit* is a tzinfo instance or None.
        The *axis* argument is required but not used.
        """
        tz = unit

        majloc = AutoDateLocator(tz=tz)
        majfmt = AutoDateFormatter(majloc, tz=tz)
        datemin = datetime.date(2000, 1, 1)
        datemax = datetime.date(2010, 1, 1)

        return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
                              default_limits=(datemin, datemax))

    @staticmethod
    def convert(value, unit, axis):
        """
        If *value* is not already a number or sequence of numbers,
        convert it with :func:`date2num`.

        The *unit* and *axis* arguments are not used.
        """
        if units.ConversionInterface.is_numlike(value):
            return value
        return date2num(value)

    @staticmethod
    def default_units(x, axis):
        """
        Return the tzinfo instance of *x* or of its first element, or None
        """
        if isinstance(x, np.ndarray):
            x = x.ravel()

        try:
            x = cbook.safe_first_element(x)
        except (TypeError, StopIteration):
            pass

        try:
            return x.tzinfo
        except AttributeError:
            pass
        return None

units.registry[datetime.date] = DateConverter()
units.registry[datetime.datetime] = DateConverter()


"""
================================
Gradient Boosting regularization
================================

Illustration of the effect of different regularization strategies
for Gradient Boosting. The example is taken from Hastie et al 2009.

The loss function used is binomial deviance. Regularization via
shrinkage (``learning_rate < 1.0``) improves performance considerably.
In combination with shrinkage, stochastic gradient boosting
(``subsample < 1.0``) can produce more accurate models by reducing the
variance via bagging.
Subsampling without shrinkage usually does poorly.
Another strategy to reduce the variance is by subsampling the features
analogous to the random splits in Random Forests
(via the ``max_features`` parameter).

.. [1] T. Hastie, R. Tibshirani and J. Friedman, "Elements of Statistical
    Learning Ed. 2", Springer, 2009.
"""
print(__doc__)

# Author: Peter Prettenhofer <peter.prettenhofer@gmail.com>
#
# License: BSD 3 clause

import numpy as np
import matplotlib.pyplot as plt

from sklearn import ensemble
from sklearn import datasets

X, y = datasets.make_hastie_10_2(n_samples=12000, random_state=1)
X = X.astype(np.float32)

# map labels from {-1, 1} to {0, 1}
labels, y = np.unique(y, return_inverse=True)

X_train, X_test = X[:2000], X[2000:]
y_train, y_test = y[:2000], y[2000:]

original_params = {'n_estimators': 1000, 'max_leaf_nodes': 4, 'max_depth': None, 'random_state': 2,
                   'min_samples_split': 5}

plt.figure()

for label, color, setting in [('No shrinkage', 'orange',
                               {'learning_rate': 1.0, 'subsample': 1.0}),
                              ('learning_rate=0.1', 'turquoise',
                               {'learning_rate': 0.1, 'subsample': 1.0}),
                              ('subsample=0.5', 'blue',
                               {'learning_rate': 1.0, 'subsample': 0.5}),
                              ('learning_rate=0.1, subsample=0.5', 'gray',
                               {'learning_rate': 0.1, 'subsample': 0.5}),
                              ('learning_rate=0.1, max_features=2', 'magenta',
                               {'learning_rate': 0.1, 'max_features': 2})]:
    params = dict(original_params)
    params.update(setting)

    clf = ensemble.GradientBoostingClassifier(**params)
    clf.fit(X_train, y_train)

    # compute test set deviance
    test_deviance = np.zeros((params['n_estimators'],), dtype=np.float64)

    for i, y_pred in enumerate(clf.staged_decision_function(X_test)):
        # clf.loss_ assumes that y_test[i] in {0, 1}
        test_deviance[i] = clf.loss_(y_test, y_pred)

    plt.plot((np.arange(test_deviance.shape[0]) + 1)[::5], test_deviance[::5],
            '-', color=color, label=label)

plt.legend(loc='upper left')
plt.xlabel('Boosting Iterations')
plt.ylabel('Test Set Deviance')

plt.show()


# -*- coding: utf-8 -*-
#
# scikit-learn documentation build configuration file, created by
# sphinx-quickstart on Fri Jan  8 09:13:42 2010.
#
# This file is execfile()d with the current directory set to its containing
# dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

from __future__ import print_function
import sys
import os
from sklearn.externals.six import u

# If extensions (or modules to document with autodoc) are in another
# directory, add these directories to sys.path here. If the directory
# is relative to the documentation root, use os.path.abspath to make it
# absolute, like shown here.
sys.path.insert(0, os.path.abspath('sphinxext'))

from github_link import make_linkcode_resolve

# -- General configuration ---------------------------------------------------

# Try to override the matplotlib configuration as early as possible
try:
    import gen_rst
except:
    pass

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['gen_rst',
              'sphinx.ext.autodoc', 'sphinx.ext.autosummary',
              'sphinx.ext.pngmath', 'numpy_ext.numpydoc',
              'sphinx.ext.linkcode',
              ]

autosummary_generate = True

autodoc_default_flags = ['members', 'inherited-members']

# Add any paths that contain templates here, relative to this directory.
templates_path = ['templates']

# generate autosummary even if no references
autosummary_generate = True

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8'

# Generate the plots for the gallery
plot_gallery = True

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u('scikit-learn')
copyright = u('2010 - 2015, scikit-learn developers (BSD License)')

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
import sklearn
version = sklearn.__version__
# The full version, including alpha/beta/rc tags.
release = sklearn.__version__

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of documents that shouldn't be included in the build.
#unused_docs = []

# List of directories, relative to source directory, that shouldn't be
# searched for source files.
exclude_trees = ['_build', 'templates', 'includes']

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = False

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages.  Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'scikit-learn'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
html_theme_options = {'oldversion': False, 'collapsiblesidebar': True,
                      'google_analytics': True, 'surveybanner': False,
                      'sprintbanner': True}

# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['themes']

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
html_short_title = 'scikit-learn'

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
html_logo = 'logos/scikit-learn-logo-small.png'

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = 'logos/favicon.ico'

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['images']

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
html_domain_indices = False

# If false, no index is generated.
html_use_index = False

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''

# Output file base name for HTML help builder.
htmlhelp_basename = 'scikit-learndoc'

# -- Options for LaTeX output ------------------------------------------------

# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'

# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [('index', 'user_guide.tex', u('scikit-learn user guide'),
                    u('scikit-learn developers'), 'manual'), ]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
latex_logo = "logos/scikit-learn-logo.png"

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# Additional stuff for the LaTeX preamble.
latex_preamble = r"""
\usepackage{amsmath}\usepackage{amsfonts}\usepackage{bm}\usepackage{morefloats}
\usepackage{enumitem} \setlistdepth{10}
"""

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
latex_domain_indices = False

trim_doctests_flags = True

def generate_example_rst(app, what, name, obj, options, lines):
    # generate empty examples files, so that we don't get
    # inclusion errors if there are no examples for a class / module
    examples_path = os.path.join(app.srcdir, "modules", "generated",
                                 "%s.examples" % name)
    if not os.path.exists(examples_path):
        # touch file
        open(examples_path, 'w').close()

def setup(app):
    # to hide/show the prompt in code examples:
    app.add_javascript('js/copybutton.js')
    app.connect('autodoc-process-docstring', generate_example_rst)

# The following is used by sphinx.ext.linkcode to provide links to github
linkcode_resolve = make_linkcode_resolve('sklearn',
                                         u'https://github.com/scikit-learn/'
                                         'scikit-learn/blob/{revision}/'
                                         '{package}/{path}#L{lineno}')


#------------------------------------------------------------------------------
# Name:        01_es_lu_cc.py
# Purpose:     Processing for the CREW project on ES, LUC and CC.
#
# Author:      James Sample
#
# Created:     14/01/2015
# Copyright:   (c) James Sample and JHI, 2015
# License:     https://github.com/JamesSample/ecosystem_services_impacts/blob/master/LICENSE
#------------------------------------------------------------------------------
""" Processes the Future Flows (FF) climate data and estimate climate and land
    use change effects on Ecosystem Services (ES). Reads workshop outputs and
    performs the following steps:

        1. For each ES, reads monthly rainfall and ET grids for the months
           specified for both baseline and future periods. For the seasons of
           interest, calculates the % change in rainfall and ET between
           baseline and future.

        2. Combines rainfall and runoff percentage changes into a qualitative
           grid of change in runoff.

        3. Estimates impacts grids for each ES for CC only, LUC only and CC &
           LUC combined.

    Inputs grids are supplied in HDF5 file format.
"""

import pandas as pd, h5py, numpy as np, matplotlib, matplotlib.pyplot as plt
import os, sys
from mpl_toolkits.axes_grid1 import ImageGrid
from osgeo import gdal, gdalconst, osr

def read_array_from_h5(h5, variable, model, year, month):
    """ Read an array from a specified location in an H5 file.

    Args:
        h5:       The open HDF5 file object
        variable: The variable of interest ('rainfall' or 'pet')
        model:    The code for the climate model of interest (string)
        year:     Year (integer)
        month:    Month (integer)

    Returns:
        array
    """
    dset_path = r'/ff_data/%s/%s/%s_%s' % (model, variable, variable, year)
    data = h5.get(dset_path)[:,:,month-1].astype(float)

    # Set NoData to NaN
    data[data==-99] = np.nan

    # Convert units
    data = data/100

    return data

def avg_rain_et(h5, st_yr, end_yr, months):
    """ Calculate average rainfall and ET grids for the specified years and
        months.

    Args:
        h5:     The open HDF5 file object
        st_yr:  Start year for period of interest (integer)
        end_yr: End year for period of interest (integer)
        months: List of months of interest (integers)

    Returns:
        Tuple of arrays (average rainfall, average PET)
    """
    # Empty arrays to store rainfall and ET totals
    rn_tot = np.zeros((715, 485))
    et_tot = np.zeros((715, 485))

    # Total number of years to average over
    years = end_yr + 1 - st_yr

    # Loop over rainfall and ET
    for year in range(st_yr, end_yr+1):
        for month in months:
            # Read rainfall and ET grids
            rn = read_array_from_h5(h5, 'rainfall', model, year, month)
            et = read_array_from_h5(h5, 'pet', model, year, month)

            # Add to totals
            rn_tot += rn
            et_tot += et

    # Average
    rn_av = rn_tot/years
    et_av = et_tot/years

    return (rn_av, et_av)

def plot_avg_grids(base_rn_av, base_et_av, fut_rn_av, fut_et_av):
    """ Plot the average rainfall and ET grids. Used for testing.

    Args:
        base_rn_av: Average rainfall grid for baseline period.
        base_et_av: Average PET grid for baseline period.
        fut_rn_av:  Average rainfall grid for future period.
        fut_et_av:  Average PET grid for future period.

    Returns:
        None. Displays maps of each grid using same colour scale.
    """
    # Get min and max values from grids
    rnmin = min(np.nanmin(base_rn_av), np.nanmin(fut_rn_av))
    rnmax = max(np.nanmax(base_rn_av), np.nanmax(fut_rn_av))

    etmin = min(np.nanmin(base_et_av), np.nanmin(fut_et_av))
    etmax = max(np.nanmax(base_et_av), np.nanmax(fut_et_av))

    # Plot
    fig = plt.figure()
    grid = ImageGrid(fig, 111,
                     nrows_ncols = (1, 4),
                     axes_pad=0.5,
                     cbar_mode='each')

    im0 = grid[0].imshow(base_rn_av, vmin=rnmin, vmax=rnmax,
                         interpolation='nearest')
    grid.cbar_axes[0].colorbar(im0)

    im1 = grid[1].imshow(fut_rn_av, vmin=rnmin, vmax=rnmax,
                         interpolation='nearest')
    grid.cbar_axes[1].colorbar(im1)

    im2 = grid[2].imshow(base_et_av, vmin=etmin, vmax=etmax,
                         interpolation='nearest')
    grid.cbar_axes[2].colorbar(im2)

    im3 = grid[3].imshow(fut_et_av, vmin=etmin, vmax=etmax,
                         interpolation='nearest')
    grid.cbar_axes[3].colorbar(im3)

    plt.show()

def plot_reclassified_grid(array, out_path, sup_title='Main title',
                           title='Sub-title'):
    """ Plot and save the reclassified grid.

    Args:
        array:     Grid of integers in range -2 to +2
        out_path:  Output file path (PNG or PDF)
        sup_title: Main title for plot (string)
        title:     Sub-title for plot (string)

    Returns:
        None. Saves a plot to the specified path.
    """
    # Make a color map of fixed colors
    cmap = matplotlib.colors.ListedColormap(['Red', 'Orange', 'LimeGreen',
                                             'DeepSkyBlue', 'Blue'])

    bounds=[-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]
    norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N)

    # Create axes for plot (A4 size)
    fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(8.3,11.7))

    # Plot the array, using the colours specified
    img = axes.imshow(array, interpolation='nearest', origin='upper',
                      cmap=cmap, norm=norm)

    # Add labels to plot
    plt.title(title)
    plt.suptitle(sup_title, fontsize=16, y=0.95)
    plt.ylabel('Northing')
    plt.xlabel('Easting')
    plt.grid(True)

    # Reformat the axis labels (mainly change the Y values into northings)
    axes.set_yticks([35, 135, 235, 335, 435, 535, 635, 735])
    axes.set_yticklabels([1200, 1100, 1000, 900, 800, 700, 600, 500])
    axes.set_xticks([100, 200, 300, 400])

    # Add axes for the color bar
    cax = fig.add_axes([0.2, 0.785, 0.02, 0.10])

    # Add the colour bar and set labels
    cbar = fig.colorbar(img, cax=cax, cmap=cmap, norm=norm, boundaries=bounds,
                        ticks=[-2.2,-1.2,-0.2,0.8,1.8])

    cbar.set_ticklabels(['Large decrease',
                         'Small decrease',
                         'Neutral',
                         'Small increase',
                         'Large increase'], update_ticks=True)

    # Make the cbar ticks invisible
    ticks = cbar.ax.get_yticklines()
    for tick in ticks:
        plt.setp(tick, alpha=0)

    cbar_labels = plt.getp(cbar.ax.axes, 'yticklabels')
    plt.setp(cbar_labels, fontsize=10)

    # Save fig
    plt.savefig(out_path, dpi=300)
##    plt.show()
    plt.clf()
    plt.close()

def reclass_rn_et_grid(array):
    """ Take an array of percentage changes and reclassify it according to:

            % change  | Class
             x<=-15   |  -2
            -15<x<=-5 |  -1
            -5<x<=5   |   0
             5<x<=15  |  +1
             15<x     |  +2

    Args:
        array: Array of percentage changes to be reclassified.

    Returns:
        Reclassified array
    """
    # Create copy of array for reclass values
    rc = array.copy()
    rc[array<=-15] = -2
    rc[(-15<array) & (array<=-5)] = -1
    rc[(-5<array) & (array<=5)] = 0
    rc[(5<array) & (array<=15)] = 1
    rc[15<array] = 2

    return rc

def reclass_ro(matrix_path, rn, et):
    """ Generate reclassification matrix for runoff based on reclassified
        change grids for rainfall and PET and the runoff reclassification
        matrix from the workshop.

    Args:
        matrix_path: Path to CSV file representing runoff matrix.
        rn:          Reclassified rainfall grid from reclass_rn_et_grid
        et:          Reclassified PET grid from reclass_rn_et_grid

    Returns:
        Array (grid of integers representing change in runoff)
    """
    # Read matrix
    df = pd.read_csv(matrix_path, index_col=0)

    # Grid of NaNs wih correct shape
    ro = rn.copy()*np.nan

    # Loop over inidces
    for x, y in np.ndindex(ro.shape):
        # Get values for change in rainfall and ET
        et_ch = et[x, y]
        rn_ch = rn[x, y]

        # If both are not nan, reclassify
        if (np.isfinite(et_ch) and np.isfinite(rn_ch)):
            rc_val = df.ix[int(et_ch), str(int(rn_ch))]
            ro[x, y] = rc_val

    return ro

def reclass_es_ro(es_idx, ro):
    """ Reclassify the runoff grid to estimate effects of runoff change on each
        ES.

    Args:
        es_idx: The ID of the ES of interest in data frame ro_df
        ro:     The runoff change grid from reclass_ro

    Returns:
        Array (grid of integers representing change in ES)
    """
    # Make a copy of the ro grid to update
    es = ro.copy()

    # Reclassify
    for chng in [-2, -1, 0, 1, 2]:
        es[ro==chng] = ro_df.ix[es_idx, 'RO_%d' % chng]

    return es

def read_ascii(ascii_path,
               xmin=0,
               xmax=485000,
               ymin=520000,
               ymax=1235000,
               exptd_rows=715,
               exptd_cols=485,
               exptd_px_wd=1000,
               exptd_px_ht=-1000,
               exptd_ndv=-9999):
    """ Read an ASCII grid file, clip it to the specified bounding box and
        return a numpy array.

    Args:
        xmin:         Minimum Easting in OSGB1936 metres.
        xmax:         Maximum Easting in OSGB1936 metres.
        ymin:         Minimum Northing in OSGB1936 metres.
        ymax:         Maximum Northing in OSGB1936 metres.
        exptd_rows:   No. of rows expected in file.
        exptd_cols:   No. of columns expected in file.
        exptd_px_wd:  Cell width.
        exptd_px_ht:  Cell height.
        exptd_ndv:    No data value.

    Returns:
        Array (floats).
    """
    # Register drivers
    gdal.AllRegister()

    # Process the file with GDAL
    ds = gdal.Open(ascii_path, gdalconst.GA_ReadOnly)
    if ds is None:
    	print 'Could not open ' + ascii_path
    	sys.exit(1)

    # In order to select the first cell correctly, choose a point just within
    # the top left corner of the specified bounding box.
    x = xmin + 10
    y = ymax - 10

    # Dataset properties
    geotransform = ds.GetGeoTransform()
    originX = geotransform[0]
    originY = geotransform[3]
    pixelWidth = geotransform[1]
    pixelHeight = geotransform[5]

    # Calculate number of rows and cols to return
    rows = abs(int((ymax-ymin)/pixelHeight))
    cols = int((xmax-xmin)/pixelWidth)

    # Select starting pixel
    xOffset = int((x - originX) / pixelWidth)
    yOffset = int((y - originY) / pixelHeight)

    band = ds.GetRasterBand(1)
    no_data_val = band.GetNoDataValue()

    # Simple checking
    assert rows == exptd_rows
    assert cols == exptd_cols
    assert pixelWidth == exptd_px_wd
    assert pixelHeight == exptd_px_ht
    assert no_data_val == exptd_ndv

    # Read the data to an array
    data = band.ReadAsArray(xOffset, yOffset, cols, rows)

    # Close the dataset
    ds = None

    return data.astype(float)

def process_land_use_change(lu_mat_path, base, fut, esid, codes_df):
    """ Estimate land use change (LUC) only effects for the specified ES.

    Args:
        lu_mat_path: Excel file containing land use matrices from the workshop.
        base:        Baseline land luse grid.
        fut:         Future land luse grid.
        esid:        ES ID from land use matrices Excel file
        codes_df:    Land use code look-up table (as data frame)

    Returns:
        Array (grid of integers representing change in ES)
    """
    # Read matrix for this ES
    lu_mat = pd.read_excel(lu_mat_path, sheetname='Land Use')

    # Get row for start of matrix
    st_row = (lu_mat['ES_ID']==esid).nonzero()[0][0] + 2

    # Read matrix of interest
    lu_mat = pd.read_excel(lu_mat_path, sheetname='Land Use', skiprows=st_row,
                           skip_footer=(120-6-st_row), parse_cols='C:I',
                           index_col=0)

    # Perform reclassification
    # Grid of NaNs wih correct shape
    rc = base.copy()*np.nan

    # Loop over inidces
    for x, y in np.ndindex(base.shape):
        # Get values for baseline and future LU
        base_lu = base[x, y]
        fut_lu = fut[x, y]

        # If both are not nan, reclassify
        if (np.isfinite(base_lu) and np.isfinite(fut_lu)):
            # Get the base and fut LU as a string
            base_str = codes_df.ix[int(base_lu)]['LU_Class']
            fut_str = codes_df.ix[int(fut_lu)]['LU_Class']
            rc_val = lu_mat.ix[base_str, fut_str]
            rc[x, y] = rc_val

    return rc

def process_land_use_and_climate_change(lucc_mat_path, lugrid, ccgrid, esid):
    """ Estimate combined land use and climate change effects for the specified
        ES.

    Args:
        lucc_mat_path: Excel file containing matrices from the workshop.
        lugrid:        The grid of land use change effects.
        ccgrid:        The grid of climate change effects.
        esid:          ES ID from workshop matrices Excel file.

    Returns:
        Array (grid of integers representing change in ES)
    """
    # Read matrix for this ES
    lucc_mat = pd.read_excel(lucc_mat_path, sheetname='CC_LU')

    # Get row for start of matrix
    st_row = (lucc_mat['ES_ID']==esid).nonzero()[0][0] + 2

    # Read matrix of interest
    lucc_mat = pd.read_excel(lucc_mat_path, sheetname='CC_LU', skiprows=st_row,
                             skip_footer=(108-5-st_row), parse_cols='C:I',
                             index_col=0)

    # Perform reclassification
    # Grid of NaNs wih correct shape
    rc = lugrid.copy()*np.nan

    # Loop over inidces
    for x, y in np.ndindex(lugrid.shape):
        # Get values for baseline and future LU
        lu = lugrid[x, y]
        cc = ccgrid[x, y]

        # If both are not nan, reclassify
        if (np.isfinite(lu) and np.isfinite(cc)):
            # Get the base and fut LU as a string
            rc_val = lucc_mat.ix[int(lu), int(cc)]
            rc[x, y] = rc_val

    return rc

def array_to_gtiff(out_path, data_array, ndv=-9999, xmin=0, ymax=1235000,
                   cell_size=1000):
    """ Convert numpy array to 16-bit integer GeoTiff.

    Args:
        out_path:   The .tif file to be created.
        data_array: The (integer) data array to save.
        ndv:        No data value.
        xmin:       Minimum x (Easting) co-ordinate, in OSGB1936 metres
        ymax:       Maximim y (Northing) co-ordinate, in OSGB1936 metres
        cell_size:  Cell size (metres)

    Returns:
        None. Array is saved to specified path.
    """
    # Copy data_array so that it is not modified
    data = data_array.copy()

    # Convert NaNs to NDV
    data[np.isnan(data)] = ndv

    # Get array shape
    cols = data.shape[1]
    rows = data.shape[0]

    # Get driver
    driver = gdal.GetDriverByName('GTiff') # NB can't directly create ArcInfo ASCII grids in this way

    # Create a new raster data source
    out_ds = driver.Create(out_path, cols, rows, 1, gdal.GDT_Int16)

    # Get spatial ref details
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(27700) # From EPSG for OSGB36 grid

    # Write metadata
    out_ds.SetGeoTransform((xmin, cell_size, 0.0, ymax, 0.0, -1*cell_size)) #(xmin, cellsize, 0, ymax, 0, -cellsize)
    out_ds.SetProjection(srs.ExportToWkt())
    out_band = out_ds.GetRasterBand(1)
    out_band.SetNoDataValue(ndv)
    out_band.WriteArray(data)

    # Tidy up
    del out_ds, out_band

# #############################################################################
# User input
# Climate data
ff_h5_path = r'D:\WBM_Development_2014\WBM_2014_Monthly_Input_File.h5'

# Runoff matrices
ro_path = r'D:\Eco_Services_Impacts\Matrices_Development\03_Group_1_Matrices\Runoff_Impacts_Grp1.csv'
ro_matrix_15 = r'D:\Eco_Services_Impacts\Matrices_Development\02_Common_Matrices\Runoff_Matrix_15pct.csv'

# Land use data
base_path = r'D:\Eco_Services_Impacts\Land_Use\baseline_lu_lcm07.txt'
fut_path = r'D:\Eco_Services_Impacts\Land_Use\future_lu_2050.txt'

# Land use matrices
lu_classes_path = r'D:\Eco_Services_Impacts\Land_Use\Land_Use_Classes.csv'
lu_matrices_path = r'D:\Eco_Services_Impacts\Matrices_Development\03_Group_1_Matrices\Land_Use_Matrices_Grp1.xlsx'

# Land use and climate combined matrices
lucc_matrices_path = r'D:\Eco_Services_Impacts\Matrices_Development\03_Group_1_Matrices\Climate_And_Land_Use_Matrices_Grp1.xlsx'

# Output folders
out_pdf_fold = r'D:\Eco_Services_Impacts\Model_Output\02_Group_1_Output\PDF'
out_array_fold = r'D:\Eco_Services_Impacts\Model_Output\02_Group_1_Output\GeoTiffs'

# Time periods to compare
base_st_yr, base_end_yr = 1961, 1990
fut_st_yr, fut_end_yr = 2041, 2070

# Future Flows models of interest
models = ['afixa', 'afixc', 'afixl', 'afixm', 'afixo', 'afixh',
          'afixi', 'afixj', 'afixk', 'afgcx', 'afixq']
# #############################################################################

# Read LU grids
base = read_ascii(base_path)
base[base==-9999] = np.nan
fut = read_ascii(fut_path)
fut[fut==-9999] = np.nan

# Read LU class codes
codes_df = pd.read_csv(lu_classes_path, index_col=0)

# Read the runoff matrices
ro_df = pd.read_csv(ro_path, index_col=0)

# Open H5 file
h5 = h5py.File(ff_h5_path, 'r')

# Iterate over each ES
for idx in ro_df.index:
    print '\nProcessing land use change impacts for %s.' % ro_df.ix[idx, 'ES']

    # 1. Process land use change only
    luc = process_land_use_change(lu_matrices_path, base, fut, idx, codes_df)

    # Prepare to save
    out_name = 'ES%02d_LUC' % idx

    # Save array
    out_array = os.path.join(out_array_fold, '%s.tif' % out_name)
    array_to_gtiff(out_array, luc)

    # Save PDF
    out_pdf = os.path.join(out_pdf_fold, '%s.pdf' % out_name)
    plot_reclassified_grid(luc, out_pdf,
                           sup_title='Change in %s' % ro_df.ix[idx, 'ES'],
                           title='(land use change only)' )

    # 2. Process climate change only
    # Get the relevant months for this ES
    months = [int(i) for i in ro_df.ix[idx, 'Key_Months'].split(',')]

    # Loop over climate models of interest
    for model in models:
        print ('Processing climate change impacts for '
               '%s (model %s).' % (ro_df.ix[idx, 'ES'], model))

        # 2.1. Baseline
        base_rn_av, base_et_av = avg_rain_et(h5, base_st_yr, base_end_yr,
                                             months)

        # 2.2. Future
        fut_rn_av, fut_et_av = avg_rain_et(h5, fut_st_yr, fut_end_yr,
                                           months)

        # Plot
#        plot_avg_grids(base_rn_av, base_et_av, fut_rn_av, fut_et_av)

        # Calculate % change
        rn_pct = 100*(fut_rn_av - base_rn_av)/base_rn_av
        et_pct = 100*(fut_et_av - base_et_av)/base_et_av

        # Reclassify
        rn_rc = reclass_rn_et_grid(rn_pct)
        et_rc = reclass_rn_et_grid(et_pct)

#        plot_reclassified_grid(rn_rc)
#        plot_reclassified_grid(et_rc)

        # Generate runoff grid
        ro = reclass_ro(ro_matrix_15, rn_rc, et_rc)

#        # Plot runoff grid
#        plot_reclassified_grid(ro,
#                               sup_title='Change in runoff',
#                               title='(Model %s; %s)' % (model, months))

        # Reclass ro grid to estimate ES impact
        es = reclass_es_ro(idx, ro)

        # Prepare to save
        out_name = 'ES%02d_%s' % (idx, model)

        # Save array
        out_array = os.path.join(out_array_fold, '%s.tif' % out_name)
        array_to_gtiff(out_array, es)

        # Save PDF
        out_pdf = os.path.join(out_pdf_fold, '%s.pdf' % out_name)
        plot_reclassified_grid(es, out_pdf,
                               sup_title='Change in %s' % ro_df.ix[idx, 'ES'],
                               title='(climate model %s only)' % model)

        # 3. Process combined land use and climate effects
        print ('Processing climate and land use change impacts for '
               '%s (model %s).' % (ro_df.ix[idx, 'ES'], model))

        # Reclassify to get CC and LUC effects
        cc_lu = process_land_use_and_climate_change(lucc_matrices_path, luc,
                                                    es, idx)

        # Prepare to save
        out_name = 'ES%02d_LUC_%s' % (idx, model)

        # Save array
        out_array = os.path.join(out_array_fold, '%s.tif' % out_name)
        array_to_gtiff(out_array, cc_lu)

        # Save PDF
        out_pdf = os.path.join(out_pdf_fold, '%s.pdf' % out_name)
        plot_reclassified_grid(cc_lu, out_pdf,
                               sup_title='Change in %s' % ro_df.ix[idx, 'ES'],
                               title='(climate and land use change together)')

# Close H5 file
h5.close()

print '\nFinished.'