Your message dated Sun, 19 Feb 2017 23:19:28 +0100
with message-id <20170219221926.ga2...@ugent.be>
and subject line Re: unblock: astroplan/0.2-5
has caused the Debian Bug report #855523,
regarding unblock: astroplan/0.2-5
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact ow...@bugs.debian.org
immediately.)


-- 
855523: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=855523
Debian Bug Tracking System
Contact ow...@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
User: release.debian....@packages.debian.org
Usertags: unblock

Dear release team,

please unblock astroplan in the current freeze.

It solves #855477 "Failure with broadcasts in schedulers", severity: important.

Changelog entry:

astroplan (0.2-5) unstable; urgency=medium

  * Fix broadcasts in schedulers (Closes: #855477)

 -- Vincent Prat <vincep...@free.fr>  Sat, 18 Feb 2017 16:37:34 +0100


The debdiff is attached. Requested commands:

unblock astroplan/0.2-5

diff -Nru astroplan-0.2/debian/changelog astroplan-0.2/debian/changelog
--- astroplan-0.2/debian/changelog      2017-01-27 20:57:06.000000000 +0100
+++ astroplan-0.2/debian/changelog      2017-02-18 16:37:34.000000000 +0100
@@ -1,3 +1,9 @@
+astroplan (0.2-5) unstable; urgency=medium
+
+  * Fix broadcasts in schedulers (Closes: #855477)
+
+ -- Vincent Prat <vincep...@free.fr>  Sat, 18 Feb 2017 16:37:34 +0100
+
 astroplan (0.2-4) unstable; urgency=medium
 
   * Github patches + failures marked as known (Closes: #851437)
diff -Nru astroplan-0.2/debian/patches/disable_failing_tests.patch 
astroplan-0.2/debian/patches/disable_failing_tests.patch
--- astroplan-0.2/debian/patches/disable_failing_tests.patch    2017-01-27 
20:57:06.000000000 +0100
+++ astroplan-0.2/debian/patches/disable_failing_tests.patch    1970-01-01 
01:00:00.000000000 +0100
@@ -1,40 +0,0 @@
-From: Ole Streicher <oleb...@debian.org>
-Subject: Mark known failures
---- a/astroplan/tests/test_scheduling.py
-+++ b/astroplan/tests/test_scheduling.py
-@@ -6,6 +6,7 @@
- from astropy.time import Time
- import astropy.units as u
- from astropy.coordinates import SkyCoord
-+from astropy.tests.helper import pytest
- 
- from ..utils import time_grid_from_range
- from ..observer import Observer
-@@ -103,7 +104,8 @@
-     assert np.abs(schedule.slots[0].end - new_duration - start) < 1*u.second
-     assert schedule.slots[1].start == schedule.slots[0].end
- 
--
-+# see https://github.com/astropy/astroplan/pull/282
-+@pytest.mark.xfail()
- def test_transitioner():
-     blocks = [ObservingBlock(t, 55 * u.minute, i) for i, t in 
enumerate(targets)]
-     slew_rate = 1 * u.deg / u.second
-@@ -132,6 +134,8 @@
- default_transitioner = Transitioner(slew_rate=1 * u.deg / u.second)
- 
- 
-+# see https://github.com/astropy/astroplan/pull/282
-+@pytest.mark.xfail()
- def test_priority_scheduler():
-     constraints = [AirmassConstraint(3, boolean_constraint=False)]
-     blocks = [ObservingBlock(t, 55*u.minute, i) for i, t in 
enumerate(targets)]
-@@ -157,6 +161,8 @@
-     scheduler(blocks, schedule)
- 
- 
-+# see https://github.com/astropy/astroplan/pull/282
-+@pytest.mark.xfail()
- def test_sequential_scheduler():
-     constraints = [AirmassConstraint(2.5, boolean_constraint=False)]
-     blocks = [ObservingBlock(t, 55 * u.minute, i) for i, t in 
enumerate(targets)]
diff -Nru astroplan-0.2/debian/patches/pull-285-Fix-broadcasting.patch 
astroplan-0.2/debian/patches/pull-285-Fix-broadcasting.patch
--- astroplan-0.2/debian/patches/pull-285-Fix-broadcasting.patch        
1970-01-01 01:00:00.000000000 +0100
+++ astroplan-0.2/debian/patches/pull-285-Fix-broadcasting.patch        
2017-02-18 16:37:34.000000000 +0100
@@ -0,0 +1,891 @@
+Author: Vincent Prat <vincep...@free.fr>
+Description: Fix the broadcasting issue, that caused tests to fail.
+ The patch comes from https://github.com/astropy/astroplan/pull/285.
+--- a/astroplan/constraints.py
++++ b/astroplan/constraints.py
+@@ -404,7 +404,7 @@
+                     observer.pressure = 0
+ 
+                 # find solar altitude at these times
+-                altaz = observer.altaz(times, get_sun(times))
++                altaz = observer.altaz(times, get_sun(times), grid=False)
+                 altitude = altaz.alt
+                 # cache the altitude
+                 observer._altaz_cache[aakey] = dict(times=times,
+@@ -447,7 +447,7 @@
+         self.max = max
+ 
+     def compute_constraint(self, times, observer, targets):
+-        sunaltaz = observer.altaz(times, get_sun(times))
++        sunaltaz = observer.altaz(times, get_sun(times), grid=False)
+         target_coos = [target.coord if hasattr(target, 'coord') else target
+                        for target in targets]
+         target_altazs = [observer.altaz(times, coo) for coo in target_coos]
+--- a/astroplan/observer.py
++++ b/astroplan/observer.py
+@@ -11,6 +11,8 @@
+                                  get_moon, Angle, Latitude, Longitude,
+                                  UnitSphericalRepresentation)
+ from astropy.extern.six import string_types
++from astropy.utils import isiterable
++from astropy.utils.compat.numpy import broadcast_to
+ import astropy.units as u
+ from astropy.time import Time
+ from astropy.utils import isiterable
+@@ -20,6 +22,8 @@
+ # Package
+ from .exceptions import TargetNeverUpWarning, TargetAlwaysUpWarning
+ from .moon import moon_illumination, moon_phase_angle
++from .target import get_skycoord
++
+ 
+ __all__ = ["Observer", "MAGIC_TIME"]
+ 
+@@ -64,6 +68,14 @@
+     else:
+         time_grid = np.linspace(start, end, N)*u.day
+ 
++    # broadcast so grid is first index, and remaining shape of t0
++    # falls in later indices. e.g. if t0 is shape (10), time_grid
++    # will be shape (N, 10). If t0 is shape (5, 2), time_grid is (N, 5, 2)
++    while time_grid.ndim <= t0.ndim:
++        time_grid = time_grid[:, np.newaxis]
++    # we want to avoid 1D grids since we always want to broadcast against 
targets
++    if time_grid.ndim == 1:
++        time_grid = time_grid[:, np.newaxis]
+     return t0 + time_grid
+ 
+ 
+@@ -110,20 +122,24 @@
+         """
+         Parameters
+         ----------
+-        name : str
+-            A short name for the telescope, observatory or location.
+-
+         location : `~astropy.coordinates.EarthLocation`
+             The location (latitude, longitude, elevation) of the observatory.
+ 
+-        longitude : float, str, `~astropy.units.Quantity` (optional)
+-            The longitude of the observing location. Should be valid input for
+-            initializing a `~astropy.coordinates.Longitude` object.
++        timezone : str or `datetime.tzinfo` (optional)
++            The local timezone to assume. If a string, it will be passed
++            through ``pytz.timezone()`` to produce the timezone object.
++
++        name : str
++            A short name for the telescope, observatory or location.
+ 
+         latitude : float, str, `~astropy.units.Quantity` (optional)
+             The latitude of the observing location. Should be valid input for
+             initializing a `~astropy.coordinates.Latitude` object.
+ 
++        longitude : float, str, `~astropy.units.Quantity` (optional)
++            The longitude of the observing location. Should be valid input for
++            initializing a `~astropy.coordinates.Longitude` object.
++
+         elevation : `~astropy.units.Quantity` (optional), default = 0 meters
+             The elevation of the observing location, with respect to sea
+             level. Defaults to sea level.
+@@ -137,10 +153,6 @@
+         temperature : `~astropy.units.Quantity` (optional)
+             The ambient temperature.
+ 
+-        timezone : str or `datetime.tzinfo` (optional)
+-            The local timezone to assume. If a string, it will be passed
+-            through ``pytz.timezone()`` to produce the timezone object.
+-
+         description : str (optional)
+             A short description of the telescope, observatory or observing
+             location.
+@@ -336,59 +348,64 @@
+ 
+         return Time(date_time, location=self.location)
+ 
+-    def _transform_target_list_to_altaz(self, times, targets):
++    def _is_broadcastable(self, shp1, shp2):
++        """Test if two shape tuples are broadcastable"""
++        if shp1 == shp2:
++            return True
++        for a, b in zip(shp1[::-1], shp2[::-1]):
++            if a == 1 or b == 1 or a == b:
++                pass
++            else:
++                return False
++        return True
++
++    def _preprocess_inputs(self, time, target=None, grid=True):
+         """
+-        Workaround for transforming a list of coordinates ``targets`` to
+-        altitudes and azimuths.
++        Preprocess time and target inputs
+ 
+-        Parameters
+-        ----------
+-        times : `~astropy.time.Time` or list of `~astropy.time.Time` objects
+-            Time of observation
++        This routine takes the inputs for time and target and attempts to
++        return a single `~astropy.time.Time` and 
`~astropy.coordinates.SkyCoord`
++        for each argument, which may be non-scalar if necessary.
+ 
+-        targets : `~astropy.coordinates.SkyCoord` or list of 
`~astropy.coordinates.SkyCoord` objects
+-            List of target coordinates
++        time : `~astropy.time.Time` or other (see below)
++            The time(s) to use in the calculation. It can be anything that
++            `~astropy.time.Time` will accept (including a 
`~astropy.time.Time` object)
+ 
+-        location : `~astropy.coordinates.EarthLocation`
+-            Location of observer
++        target : `~astroplan.FixedTarget`, `~astropy.coordinates.SkyCoord`, 
or list
++            The target(s) to use in the calculation.
+ 
+-        Returns
+-        -------
+-        altitudes : list
+-            List of altitudes for each target, at each time
++        grid: bool
++            If True, and the time and target objects cannot be broadcast,
++            the target object will have extra dimensions packed onto the end,
++            so that calculations with M targets and N times will return an 
(M, N)
++            shaped result. Useful for grid searches for rise/set times etc.
+         """
+-        if times.isscalar:
+-            times = Time([times])
+-
+-        if not isinstance(targets, list) and targets.isscalar:
+-            targets = [targets]
+-
+-        targets_is_unitsphericalrep = [x.data.__class__ is
+-                                       UnitSphericalRepresentation for x in 
targets]
+-        if all(targets_is_unitsphericalrep) or not 
any(targets_is_unitsphericalrep):
+-            repeated_times = np.tile(times, len(targets))
+-            ra_list = Longitude([x.icrs.ra for x in targets])
+-            dec_list = Latitude([x.icrs.dec for x in targets])
+-            repeated_ra = np.repeat(ra_list, len(times))
+-            repeated_dec = np.repeat(dec_list, len(times))
+-            inner_sc = SkyCoord(ra=repeated_ra, dec=repeated_dec)
+-            target_SkyCoord = 
SkyCoord(inner_sc.data.represent_as(UnitSphericalRepresentation),
+-                                       
representation=UnitSphericalRepresentation)
+-            transformed_coord = 
target_SkyCoord.transform_to(AltAz(location=self.location,
+-                                                                   
obstime=repeated_times))
+-        else:
+-            # TODO: This is super slow.
+-            repeated_times = np.tile(times, len(targets))
+-            repeated_targets = np.repeat(targets, len(times))
+-            target_SkyCoord = 
SkyCoord(SkyCoord(repeated_targets).data.represent_as(
+-                                       UnitSphericalRepresentation),
+-                                       
representation=UnitSphericalRepresentation)
+-
+-            transformed_coord = 
target_SkyCoord.transform_to(AltAz(location=self.location,
+-                                                                   
obstime=repeated_times))
+-        return transformed_coord
++        # make sure we have a non-scalar time
++        if not isinstance(time, Time):
++            time = Time(time)
+ 
+-    def altaz(self, time, target=None, obswl=None):
++        if target is None:
++            return time, None
++
++        # convert any kind of target argument to non-scalar SkyCoord
++        target = get_skycoord(target)
++
++        if grid:
++            # now we broadcast the targets array so that the first index
++            # iterates over targets, any other indices over times
++            if not target.isscalar:
++                if time.isscalar:
++                    target = target[:, np.newaxis]
++                while target.ndim <= time.ndim:
++                    target = target[:, np.newaxis]
++        if not self._is_broadcastable(target.shape, time.shape):
++            raise ValueError(
++                'Time and Target arguments cannot be broadcast against each 
other with shapes {} and {}'.format(
++                    time.shape, target.shape
++                ))
++        return time, target
++
++    def altaz(self, time, target=None, obswl=None, grid=True):
+         """
+         Get an `~astropy.coordinates.AltAz` frame or coordinate.
+ 
+@@ -411,6 +428,12 @@
+         obswl : `~astropy.units.Quantity` (optional)
+             Wavelength of the observation used in the calculation.
+ 
++        grid: bool
++            If True, and the time and target objects cannot be broadcast,
++            the target object will have extra dimensions packed onto the end,
++            so that calculations with M targets and N times will return an 
(M, N)
++            shaped result. Useful for grid searches for rise/set times etc.
++
+         Returns
+         -------
+         `~astropy.coordinates.AltAz`
+@@ -440,8 +463,8 @@
+ 
+         >>> target_altaz = apo.altaz(time, target) # doctest: +SKIP
+         """
+-        if not isinstance(time, Time):
+-            time = Time(time)
++        if target is not None:
++            time, target = self._preprocess_inputs(time, target, grid)
+ 
+         altaz_frame = AltAz(location=self.location, obstime=time,
+                             pressure=self.pressure, obswl=obswl,
+@@ -451,24 +474,7 @@
+             # Return just the frame
+             return altaz_frame
+         else:
+-            # If target is a list of targets:
+-            if isiterable(target) and not isinstance(target, SkyCoord):
+-                get_coord = lambda x: x.coord if hasattr(x, 'coord') else x
+-                transformed_coords = 
self._transform_target_list_to_altaz(time,
+-                                                                          
list(map(get_coord, target)))
+-                n_targets = len(target)
+-                new_shape = (n_targets, 
int(len(transformed_coords)/n_targets))
+-
+-                for comp in transformed_coords.data.components:
+-                    getattr(transformed_coords.data, comp).resize(new_shape)
+-                return transformed_coords
+-
+-            # If single target is a FixedTarget or a SkyCoord:
+-            if hasattr(target, 'coord'):
+-                coordinate = target.coord
+-            else:
+-                coordinate = target
+-            return coordinate.transform_to(altaz_frame)
++            return target.transform_to(altaz_frame)
+ 
+     def parallactic_angle(self, time, target):
+         """
+@@ -496,17 +502,7 @@
+         .. [1] https://en.wikipedia.org/wiki/Parallactic_angle
+ 
+         """
+-        if not isinstance(time, Time):
+-            time = Time(time)
+-
+-        if isiterable(target):
+-            get_coord = lambda x: x.coord if hasattr(x, 'coord') else x
+-            coordinate = SkyCoord(list(map(get_coord, target)))
+-        else:
+-            if hasattr(target, 'coord'):
+-                coordinate = target.coord
+-            else:
+-                coordinate = target
++        time, coordinate = self._preprocess_inputs(time, target)
+ 
+         # Eqn (14.1) of Meeus' Astronomical Algorithms
+         LST = time.sidereal_time('mean', longitude=self.location.longitude)
+@@ -530,9 +526,12 @@
+         Parameters
+         ----------
+         t : `~astropy.time.Time`
+-            Grid of times
++            Grid of N times, any shape. Search grid along first axis, e.g (N, 
...)
+         alt : `~astropy.units.Quantity`
+             Grid of altitudes
++            Depending on broadcasting we either have ndim >=3 and
++            M targets along first axis, e.g (M, N, ...), or
++            ndim = 2 and targets/times in last axis
+         rise_set : {"rising",  "setting"}
+             Calculate either rising or setting across the horizon
+         horizon : float
+@@ -543,61 +542,88 @@
+         Returns
+         -------
+         Returns the lower and upper limits on the time and altitudes
+-        of the horizon crossing.
+-        """
+-        alt = np.atleast_2d(Latitude(alt))
+-        n_targets = alt.shape[0]
++        of the horizon crossing. The altitude limits have shape (M, ...) and 
the
++        time limits have shape (...). These arrays aresuitable for 
interpolation
++        to find the horizon crossing time.
++        """
++        # handle different cases by enforcing standard shapes on
++        # the altitude grid
++        finesse_time_indexes = False
++        if alt.ndim == 1:
++            raise ValueError('Must supply more at least a 2D grid of 
altitudes')
++        elif alt.ndim == 2:
++            # TODO: this test for ndim=2 doesn't work. if times is e.g (2,5)
++            # then alt will have ndim=3, but shape (100, 2, 5) so grid
++            # is in first index...
++            ntargets = alt.shape[1]
++            ngrid = alt.shape[0]
++            unit = alt.unit
++            alt = broadcast_to(alt, (ntargets, ngrid, ntargets)).T
++            alt = alt*unit
++            extra_dimension_added = True
++            if t.shape[1] == 1:
++                finesse_time_indexes = True
++        else:
++            extra_dimension_added = False
++        output_shape = (alt.shape[0],) + alt.shape[2:]
+ 
+         if rise_set == 'rising':
+             # Find index where altitude goes from below to above horizon
+-            condition = (alt[:, :-1] < horizon) * (alt[:, 1:] > horizon)
++            condition = (alt[:, :-1, ...] < horizon) * (alt[:, 1:, ...] > 
horizon)
+         elif rise_set == 'setting':
+             # Find index where altitude goes from above to below horizon
+-            condition = (alt[:, :-1] > horizon) * (alt[:, 1:] < horizon)
+-
+-        target_inds, time_inds = np.nonzero(condition)
++            condition = (alt[:, :-1, ...] > horizon) * (alt[:, 1:, ...] < 
horizon)
+ 
+-        if np.count_nonzero(condition) < n_targets:
+-            target_inds, _ = np.nonzero(condition)
+-            noncrossing_target_ind = np.setdiff1d(np.arange(n_targets),
+-                                                  target_inds,
+-                                                  assume_unique=True)  # [0]
+-
+-            warnmsg = ('Target(s) index {} does not cross horizon={} within '
+-                       '24 hours'.format(noncrossing_target_ind, horizon))
+-
+-            if (alt[noncrossing_target_ind, :] > horizon).all():
+-                warnings.warn(warnmsg, TargetAlwaysUpWarning)
+-            else:
+-                warnings.warn(warnmsg, TargetNeverUpWarning)
+-
+-            # Fill in missing time with MAGIC_TIME
+-            target_inds = np.insert(target_inds, noncrossing_target_ind,
+-                                    noncrossing_target_ind)
+-            time_inds = np.insert(time_inds.astype(float),
+-                                  noncrossing_target_ind,
+-                                  np.nan)
+-        elif np.count_nonzero(condition) > n_targets:
+-            old_target_inds = np.copy(target_inds)
+-            old_time_inds = np.copy(time_inds)
+-
+-            time_inds = []
+-            target_inds = []
+-            for tgt, tm in zip(old_target_inds, old_time_inds):
+-                if tgt not in target_inds:
+-                    time_inds.append(tm)
+-                    target_inds.append(tgt)
+-            target_inds = np.array(target_inds, dtype=int)
+-            time_inds = np.array(time_inds)
+-
+-        times = [t[int(i):int(i)+2] if not np.isnan(i) else np.nan for i in 
time_inds]
+-        altitudes = [alt[int(i), int(j):int(j)+2] if not np.isnan(j) else 
np.nan
+-                     for i, j in zip(target_inds, time_inds)]
++        noncrossing_indices = np.sum(condition, axis=1, dtype=np.intp) < 1
++        alt_lims1 = u.Quantity(np.zeros(output_shape), unit=u.deg)
++        alt_lims2 = u.Quantity(np.zeros(output_shape), unit=u.deg)
++        jd_lims1 = np.zeros(output_shape)
++        jd_lims2 = np.zeros(output_shape)
++        if np.any(noncrossing_indices):
++            for target_index in set(np.where(noncrossing_indices)[0]):
++                warnmsg = ('Target with index {} does not cross horizon={} 
within '
++                           '24 hours'.format(target_index, horizon))
++                if (alt[target_index, ...] > horizon).all():
++                    warnings.warn(warnmsg, TargetAlwaysUpWarning)
++                else:
++                    warnings.warn(warnmsg, TargetNeverUpWarning)
+ 
+-        return times, altitudes
++            alt_lims1[np.nonzero(noncrossing_indices)] = np.nan
++            alt_lims2[np.nonzero(noncrossing_indices)] = np.nan
++            jd_lims1[np.nonzero(noncrossing_indices)] = np.nan
++            jd_lims2[np.nonzero(noncrossing_indices)] = np.nan
++
++        before_indices = np.array(np.nonzero(condition))
++        # we want to add an vector like (0, 1, ...) to get after indices
++        array_to_add = np.zeros(before_indices.shape[0])[:, 
np.newaxis].astype(int)
++        array_to_add[1] = 1
++        after_indices = before_indices + array_to_add
++
++        al1 = alt[tuple(before_indices)]
++        al2 = alt[tuple(after_indices)]
++        # slice the time in the same way, but delete the object index
++        before_time_index_tuple = np.delete(before_indices, 0, 0)
++        after_time_index_tuple = np.delete(after_indices, 0, 0)
++        if finesse_time_indexes:
++            before_time_index_tuple[1:] = 0
++            after_time_index_tuple[1:] = 0
++        tl1 = t[tuple(before_time_index_tuple)]
++        tl2 = t[tuple(after_time_index_tuple)]
++
++        alt_lims1[tuple(np.delete(before_indices, 1, 0))] = al1
++        alt_lims2[tuple(np.delete(before_indices, 1, 0))] = al2
++        jd_lims1[tuple(np.delete(before_indices, 1, 0))] = tl1.utc.jd
++        jd_lims2[tuple(np.delete(before_indices, 1, 0))] = tl2.utc.jd
++
++        if extra_dimension_added:
++            return (alt_lims1.diagonal(), alt_lims2.diagonal(),
++                    jd_lims1.diagonal(), jd_lims2.diagonal())
++        else:
++            return alt_lims1, alt_lims2, jd_lims1, jd_lims2
+ 
+     @u.quantity_input(horizon=u.deg)
+-    def _two_point_interp(self, times, altitudes, horizon=0*u.deg):
++    def _two_point_interp(self, jd_before, jd_after,
++                          alt_before, alt_after, horizon=0*u.deg):
+         """
+         Do linear interpolation between two ``altitudes`` at
+         two ``times`` to determine the time where the altitude
+@@ -605,11 +631,17 @@
+ 
+         Parameters
+         ----------
+-        times : `~astropy.time.Time`
+-            Two times for linear interpolation between
++        jd_before : `float`
++            JD(UTC) before crossing event
++
++        jd_after : `float`
++            JD(UTC) after crossing event
+ 
+-        altitudes : array of `~astropy.units.Quantity`
+-            Two altitudes for linear interpolation between
++        alt_before : `~astropy.units.Quantity`
++            altitude before crossing event
++
++        alt_after : `~astropy.units.Quantity`
++            altitude after crossing event
+ 
+         horizon : `~astropy.units.Quantity`
+             Solve for the time when the altitude is equal to
+@@ -621,12 +653,10 @@
+             Time when target crosses the horizon
+ 
+         """
+-        if not isinstance(times, Time):
+-            return MAGIC_TIME
+-        else:
+-            slope = (altitudes[1] - altitudes[0])/(times[1].jd - times[0].jd)
+-            return Time(times[1].jd - ((altitudes[1] - horizon)/slope).value,
+-                        format='jd')
++        slope = (alt_after-alt_before)/((jd_after - jd_before)*u.d)
++        crossing_jd = (jd_after*u.d - ((alt_after - horizon)/slope))
++        crossing_jd[np.isnan(crossing_jd)] = u.d*MAGIC_TIME.jd
++        return np.squeeze(Time(crossing_jd, format='jd'))
+ 
+     def _altitude_trig(self, LST, target):
+         """
+@@ -648,6 +678,7 @@
+         alt : `~astropy.unit.Quantity`
+             Array of altitudes
+         """
++        LST, target = self._preprocess_inputs(LST, target)
+         alt = np.arcsin(np.sin(self.location.latitude.radian) *
+                         np.sin(target.dec) +
+                         np.cos(self.location.latitude.radian) *
+@@ -677,9 +708,6 @@
+         rise_set : str - either 'rising' or 'setting'
+             Compute prev/next rise or prev/next set
+ 
+-        location : `~astropy.coordinates.EarthLocation`
+-            Location of observer
+-
+         horizon : `~astropy.units.Quantity`
+             Degrees above/below actual horizon to use
+             for calculating rise/set times (i.e.,
+@@ -694,30 +722,21 @@
+         ret1 : `~astropy.time.Time`
+             Time of rise/set
+         """
+-
+         if not isinstance(time, Time):
+             time = Time(time)
+ 
+-        target_is_vector = isiterable(target)
+-
+         if prev_next == 'next':
+             times = _generate_24hr_grid(time, 0, 1, N)
+         else:
+             times = _generate_24hr_grid(time, -1, 0, N)
+ 
+-        altaz = self.altaz(times, target)
++        altaz = self.altaz(times, target, grid=True)
+         altitudes = altaz.alt
+ 
+-        time_limits, altitude_limits = self._horiz_cross(times, altitudes, 
rise_set,
+-                                                         horizon)
+-        if not target_is_vector:
+-            return self._two_point_interp(time_limits[0], altitude_limits[0],
+-                                          horizon=horizon)
+-        else:
+-            return Time([self._two_point_interp(time_limit, altitude_limit,
+-                                                horizon=horizon)
+-                         for time_limit, altitude_limit in
+-                         zip(time_limits, altitude_limits)])
++        al1, al2, jd1, jd2 = self._horiz_cross(times, altitudes, rise_set,
++                                               horizon)
++        return self._two_point_interp(jd1, jd2, al1, al2,
++                                      horizon=horizon)
+ 
+     def _calc_transit(self, time, target, prev_next, antitransit=False, 
N=150):
+         """
+@@ -742,9 +761,6 @@
+             Toggle compute antitransit (below horizon, equivalent to midnight
+             for the Sun)
+ 
+-        location : `~astropy.coordinates.EarthLocation`
+-            Location of observer
+-
+         N : int
+             Number of altitudes to compute when searching for
+             rise or set.
+@@ -754,11 +770,10 @@
+         ret1 : `~astropy.time.Time`
+             Time of transit/antitransit
+         """
++        # TODO FIX BROADCASTING HERE
+         if not isinstance(time, Time):
+             time = Time(time)
+ 
+-        target_is_vector = isiterable(target)
+-
+         if prev_next == 'next':
+             times = _generate_24hr_grid(time, 0, 1, N, for_deriv=True)
+         else:
+@@ -771,26 +786,22 @@
+         else:
+             rise_set = 'setting'
+ 
+-        altaz = self.altaz(times, target)
+-        if target_is_vector:
+-            d_altitudes = [each_alt.diff() for each_alt in altaz.alt]
++        altaz = self.altaz(times, target, grid=True)
++        altitudes = altaz.alt
++        if altitudes.ndim > 2:
++            # shape is (M, N, ...) where M is targets and N is grid
++            d_altitudes = altitudes.diff(axis=1)
+         else:
+-            altitudes = altaz.alt
+-            d_altitudes = altitudes.diff()
++            # shape is (N, M) where M is targets and N is grid
++            d_altitudes = altitudes.diff(axis=0)
+ 
+         dt = Time((times.jd[1:] + times.jd[:-1])/2, format='jd')
+ 
+         horizon = 0*u.degree  # Find when derivative passes through zero
+-        time_limits, altitude_limits = self._horiz_cross(dt, d_altitudes,
+-                                                         rise_set, horizon)
+-        if not target_is_vector:
+-            return self._two_point_interp(time_limits[0], altitude_limits[0],
+-                                          horizon=horizon)
+-        else:
+-            return Time([self._two_point_interp(time_limit, altitude_limit,
+-                                                horizon=horizon)
+-                         for time_limit, altitude_limit in
+-                         zip(time_limits, altitude_limits)])
++        al1, al2, jd1, jd2 = self._horiz_cross(dt, d_altitudes,
++                                               rise_set, horizon)
++        return self._two_point_interp(jd1, jd2, al1, al2,
++                                      horizon=horizon)
+ 
+     def _determine_which_event(self, function, args_dict):
+         """
+@@ -828,19 +839,10 @@
+                 return previous_event
+ 
+         if which == 'nearest':
+-            if isiterable(target):
+-                return_times = []
+-                for next_e, prev_e in zip(next_event, previous_event):
+-                    if abs(time - prev_e) < abs(time - next_e):
+-                        return_times.append(prev_e)
+-                    else:
+-                        return_times.append(next_e)
+-                return Time(return_times)
+-            else:
+-                if abs(time - previous_event) < abs(time - next_event):
+-                    return previous_event
+-                else:
+-                    return next_event
++            mask = abs(time - previous_event) < abs(time - next_event)
++            return Time(np.where(mask, previous_event.utc.jd,
++                        next_event.utc.jd), format='jd')
++
+ 
+         raise ValueError('"which" kwarg must be "next", "previous" or '
+                          '"nearest".')
+@@ -1467,27 +1469,8 @@
+         if not isinstance(time, Time):
+             time = Time(time)
+ 
+-        # TODO: when astropy/astropy#5069 is resolved, replace this 
workaround which
+-        # handles scalar and non-scalar time inputs differently
+-
+-        if time.isscalar:
+-            altaz_frame = AltAz(location=self.location, obstime=time)
+-            sun = get_sun(time).transform_to(altaz_frame)
+-
+-            moon = get_moon(time, location=self.location, 
ephemeris=ephemeris).transform_to(altaz_frame)
+-            return moon
+-
+-        else:
+-            moon_coords = []
+-            for t in time:
+-                altaz_frame = AltAz(location=self.location, obstime=t)
+-                moon_coord = get_moon(t, location=self.location, 
ephemeris=ephemeris).transform_to(altaz_frame)
+-                moon_coords.append(moon_coord)
+-            obstime = [coord.obstime for coord in moon_coords]
+-            alts = u.Quantity([coord.alt for coord in moon_coords])
+-            azs = u.Quantity([coord.az for coord in moon_coords])
+-            dists = u.Quantity([coord.distance for coord in moon_coords])
+-            return SkyCoord(AltAz(azs, alts, dists, obstime=obstime, 
location=self.location))
++        moon = get_moon(time, location=self.location, ephemeris=ephemeris)
++        return self.altaz(time, moon, grid=False)
+ 
+     @u.quantity_input(horizon=u.deg)
+     def target_is_up(self, time, target, horizon=0*u.degree, 
return_altaz=False):
+@@ -1515,7 +1498,7 @@
+ 
+         Returns
+         -------
+-        observable : boolean
++        observable : boolean or np.ndarray(bool)
+             True if ``target`` is above ``horizon`` at ``time``, else False.
+ 
+         Examples
+@@ -1538,10 +1521,13 @@
+             time = Time(time)
+ 
+         altaz = self.altaz(time, target)
+-        if isiterable(target):
+-            observable = [bool(alt > horizon) for alt in altaz.alt]
++        observable = altaz.alt > horizon
++        if altaz.isscalar:
++            observable = bool(observable)
+         else:
+-            observable = bool(altaz.alt > horizon)
++            # TODO: simply return observable if we move to
++            # a fully broadcasted API
++            observable = [value for value in observable.flat]
+ 
+         if not return_altaz:
+             return observable
+@@ -1571,7 +1557,7 @@
+ 
+         Returns
+         -------
+-        sun_below_horizon : bool
++        sun_below_horizon : bool or np.ndarray(bool)
+             `True` if sun is below ``horizon`` at ``time``, else `False`.
+ 
+         Examples
+@@ -1590,7 +1576,12 @@
+             time = Time(time)
+ 
+         solar_altitude = self.altaz(time, target=get_sun(time), 
obswl=obswl).alt
+-        return bool(solar_altitude < horizon)
++        if solar_altitude.isscalar:
++            return bool(solar_altitude < horizon)
++        else:
++            # TODO: simply return solar_altitude < horizon if we move to
++            # a fully broadcasted API
++            return [val for val in (solar_altitude < horizon).flat]
+ 
+     def local_sidereal_time(self, time, kind='apparent', model=None):
+         """
+@@ -1646,21 +1637,8 @@
+         hour_angle : `~astropy.coordinates.Angle`
+             The hour angle(s) of the target(s) at ``time``
+         """
+-        if not isinstance(time, Time):
+-            time = Time(time)
+-
+-        if isiterable(target):
+-            coords = [t.coord if hasattr(t, 'coord') else t
+-                      for t in target]
+-
+-            hour_angle = Longitude([self.local_sidereal_time(time) - coord.ra
+-                                    for coord in coords])
+-
+-        else:
+-            coord = target.coord if hasattr(target, 'coord') else target
+-            hour_angle = Longitude(self.local_sidereal_time(time) - coord.ra)
+-
+-        return hour_angle
++        time, target = self._preprocess_inputs(time, target)
++        return Longitude(self.local_sidereal_time(time) - target.ra)
+ 
+     @u.quantity_input(horizon=u.degree)
+     def tonight(self, time=None, horizon=0 * u.degree, obswl=None):
+@@ -1689,11 +1667,16 @@
+             A tuple of times corresponding to the start and end of current 
night
+         """
+         current_time = Time.now() if time is None else time
+-        if self.is_night(current_time, horizon=horizon, obswl=obswl):
+-            start_time = current_time
++        night_mask = self.is_night(current_time, horizon=horizon, obswl=obswl)
++        sun_set_time = self.sun_set_time(current_time, which='next', 
horizon=horizon)
++        # workaround for NPY <= 1.8, otherwise np.where works even in scalar 
case
++        if current_time.isscalar:
++            start_time = current_time if night_mask else sun_set_time
+         else:
+-            start_time = self.sun_set_time(current_time, which='next', 
horizon=horizon)
+-
+-        end_time = self.sun_rise_time(current_time, which='next', 
horizon=horizon)
++            start_time = np.where(night_mask, current_time, sun_set_time)
++            # np.where gives us a list of start Times - convert to Time object
++            if not isinstance(start_time, Time):
++                start_time = Time(start_time)
++        end_time = self.sun_rise_time(start_time, which='next', 
horizon=horizon)
+ 
+         return start_time, end_time
+--- a/astroplan/tests/test_target.py
++++ b/astroplan/tests/test_target.py
+@@ -4,10 +4,13 @@
+ 
+ # Third-party
+ import astropy.units as u
+-from astropy.coordinates import SkyCoord
++from astropy.coordinates import SkyCoord, GCRS, ICRS
++from astropy.time import Time
++from astropy.tests.helper import pytest
+ 
+ # Package
+-from ..target import FixedTarget
++from ..target import FixedTarget, get_skycoord
++from ..observer import Observer
+ 
+ 
+ def test_FixedTarget_from_name():
+@@ -40,3 +43,39 @@
+                                                         'SkyCoord')
+     assert vega.coord.dec == vega_coords.dec == vega.dec, ('Retrieve Dec from 
'
+                                                            'SkyCoord')
++
++
++def test_get_skycoord():
++    m31 = SkyCoord(10.6847083*u.deg, 41.26875*u.deg)
++    m31_with_distance = SkyCoord(10.6847083*u.deg, 41.26875*u.deg, 780*u.kpc)
++    subaru = Observer.at_site('subaru')
++    time = Time("2016-01-22 12:00")
++    pos, vel = subaru.location.get_gcrs_posvel(time)
++    gcrs_frame = GCRS(obstime=Time("2016-01-22 12:00"), obsgeoloc=pos, 
obsgeovel=vel)
++    m31_gcrs = m31.transform_to(gcrs_frame)
++    m31_gcrs_with_distance = m31_with_distance.transform_to(gcrs_frame)
++
++    coo = get_skycoord(m31)
++    assert coo.is_equivalent_frame(ICRS())
++    with pytest.raises(TypeError) as exc_info:
++        len(coo)
++
++    coo = get_skycoord([m31])
++    assert coo.is_equivalent_frame(ICRS())
++    assert len(coo) == 1
++
++    coo = get_skycoord([m31, m31_gcrs])
++    assert coo.is_equivalent_frame(ICRS())
++    assert len(coo) == 2
++
++    coo = get_skycoord([m31_with_distance, m31_gcrs_with_distance])
++    assert coo.is_equivalent_frame(ICRS())
++    assert len(coo) == 2
++
++    coo = get_skycoord([m31, m31_gcrs, m31_gcrs_with_distance, 
m31_with_distance])
++    assert coo.is_equivalent_frame(ICRS())
++    assert len(coo) == 4
++
++    coo = get_skycoord([m31_gcrs, m31_gcrs_with_distance])
++    assert coo.is_equivalent_frame(m31_gcrs.frame)
++    assert len(coo) == 2
+--- a/astroplan/target.py
++++ b/astroplan/target.py
+@@ -7,7 +7,7 @@
+ 
+ # Third-party
+ import astropy.units as u
+-from astropy.coordinates import SkyCoord
++from astropy.coordinates import SkyCoord, ICRS, UnitSphericalRepresentation
+ 
+ __all__ = ["Target", "FixedTarget", "NonFixedTarget"]
+ 
+@@ -185,3 +185,77 @@
+     """
+     Placeholder for future function.
+     """
++
++def get_skycoord(targets):
++    """
++    Return an `~astropy.coordinates.SkyCoord` object.
++
++    When performing calculations it is usually most efficient to have
++    a single `~astropy.coordinates.SkyCoord` object, rather than a
++    list of `FixedTarget` or `~astropy.coordinates.SkyCoord` objects.
++
++    This is a convenience routine to do that.
++
++    Parameters
++    -----------
++    targets : list, `~astropy.coordinates.SkyCoord`, `Fixedtarget`
++        either a single target or a list of targets
++
++    Returns
++    --------
++    coord : `~astropy.coordinates.SkyCoord`
++        a single SkyCoord object, which may be non-scalar
++    """
++    if not isinstance(targets, list):
++        return getattr(targets, 'coord', targets)
++
++    # get the SkyCoord object itself
++    coords = [getattr(target, 'coord', target) for target in targets]
++
++    # are all SkyCoordinate's in equivalent frames? If not, convert to ICRS
++    convert_to_icrs = not 
all([coord.frame.is_equivalent_frame(coords[0].frame) for coord in coords[1:]])
++
++    # we also need to be careful about handling mixtures of 
UnitSphericalRepresentations and others
++    targets_is_unitsphericalrep = [x.data.__class__ is
++                                   UnitSphericalRepresentation for x in 
coords]
++
++    longitudes = []
++    latitudes = []
++    distances = []
++    get_distances = not all(targets_is_unitsphericalrep)
++    if convert_to_icrs:
++        # mixture of frames
++        for coordinate in coords:
++            icrs_coordinate = coordinate.icrs
++            longitudes.append(icrs_coordinate.ra)
++            latitudes.append(icrs_coordinate.dec)
++            if get_distances:
++                distances.append(icrs_coordinate.distance)
++        frame = ICRS()
++    else:
++        # all the same frame, get the longitude and latitude names
++        lon_name, lat_name = [mapping.framename for mapping in
++                              
coords[0].frame_specific_representation_info['spherical']]
++        frame = coords[0].frame
++        for coordinate in coords:
++            longitudes.append(getattr(coordinate, lon_name))
++            latitudes.append(getattr(coordinate, lat_name))
++            if get_distances:
++                distances.append(coordinate.distance)
++
++    # now let's deal with the fact that we may have a mixture of coords with 
distances and
++    # coords with UnitSphericalRepresentations
++    if all(targets_is_unitsphericalrep):
++        return SkyCoord(longitudes, latitudes, frame=frame)
++    elif not any(targets_is_unitsphericalrep):
++        return SkyCoord(longitudes, latitudes, distances, frame=frame)
++    else:
++        """
++        We have a mixture of coords with distances and without.
++        Since we don't know in advance the origin of the frame where further 
transformation
++        will take place, it's not safe to drop the distances from those 
coords with them set.
++
++        Instead, let's assign large distances to those objects with none.
++        """
++        distances = [distance if distance != 1 else 100*u.kpc for distance in 
distances]
++        return SkyCoord(longitudes, latitudes, distances, frame=frame)
+\ No newline at end of file
+--- a/setup.py
++++ b/setup.py
+@@ -102,7 +102,7 @@
+       version=VERSION,
+       description=DESCRIPTION,
+       scripts=scripts,
+-      install_requires=['numpy>=1.6', 'astropy>=1.2', 'pytz'],
++      install_requires=['numpy>=1.6', 'astropy>=1.3', 'pytz'],
+       extras_require=dict(
+           plotting=['matplotlib>=1.4'],
+           docs=['sphinx_rtd_theme']
diff -Nru astroplan-0.2/debian/patches/series 
astroplan-0.2/debian/patches/series
--- astroplan-0.2/debian/patches/series 2017-01-27 20:57:06.000000000 +0100
+++ astroplan-0.2/debian/patches/series 2017-02-18 16:27:41.000000000 +0100
@@ -4,4 +4,4 @@
 pull-274-Stop-recognizing-scalar-SkyCoord-objects-as-vect.patch
 pull-273-Change-to-use-new-SkyCoord-prints.patch
 issues-282-Fix-more-test-failures-in-astroplan.patch
-disable_failing_tests.patch
+pull-285-Fix-broadcasting.patch

--- End Message ---
--- Begin Message ---
Hi,

On Sun, Feb 19, 2017 at 07:35:54PM +0100, Vincent Prat wrote:
> please unblock astroplan in the current freeze.

Unblocked.

Cheers,

Ivo

--- End Message ---

Reply via email to