diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..9c39a860 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: ".github/workflows" # Location of package manifests + schedule: + interval: "monthly" + groups: + actions: + patterns: + - "*" + diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index ce3fd144..fcf497ae 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -63,11 +63,11 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - name: Set up python ${{ matrix.python }} on ${{ matrix.os }} - uses: actions/setup-python@v4 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.python }} - name: Install base dependencies diff --git a/CHANGES.rst b/CHANGES.rst index 6ffafe58..64f25761 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,23 @@ -0.9 (unreleased) +0.11 (unreleased) +----------------- + +0.10.1 (2024-08-13) +------------------- + +- Compatibility with numpy 2.0. [#587] + +0.10 (2024-04-04) +----------------- + +- Fix compatibility with astropy v6.0. + +0.9.1 (2023-09-20) +------------------ + +- Fix bug when ``FixedTarget`` objects are passed to methods that calculate + lunar coordinates. [#568] + +0.9 (2023-07-27) ---------------- - Fix time range in ``months_observable`` to not be only in 2014. Function now @@ -7,6 +26,8 @@ - Fix ``Observer`` not having longtitude, latitude, and elevation parameters as class attributes. They are now properties calculated from the ``location``. +- Documentation revisions and theme update [#563] + 0.8 (2021-01-26) ---------------- diff --git a/README.rst b/README.rst index 000bf7a5..e90f94d4 100644 --- a/README.rst +++ b/README.rst @@ -7,13 +7,10 @@ Observation planning package for astronomers * Docs: https://astroplan.readthedocs.io/ * License: BSD-3 -.. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat - :target: http://www.astropy.org/ +.. image:: https://readthedocs.org/projects/astroplan/badge/?version=latest + :target: https://astroplan.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status -.. image:: http://img.shields.io/pypi/v/astroplan.svg?text=version - :target: https://pypi.python.org/pypi/astroplan/ - :alt: Latest release - .. image:: http://img.shields.io/badge/arXiv-1709.03913-red.svg?style=flat :target: https://arxiv.org/abs/1712.09631 :alt: arXiv paper @@ -21,6 +18,13 @@ Observation planning package for astronomers .. image:: https://github.com/astropy/astroplan/workflows/CI%20Tests/badge.svg :target: https://github.com/astropy/astroplan/actions +.. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat + :target: http://www.astropy.org/ + +.. image:: http://img.shields.io/pypi/v/astroplan.svg?text=version + :target: https://pypi.python.org/pypi/astroplan/ + :alt: Latest release + Attribution +++++++++++ diff --git a/astroplan/__init__.py b/astroplan/__init__.py index ceb480f5..c5ce9258 100644 --- a/astroplan/__init__.py +++ b/astroplan/__init__.py @@ -12,21 +12,18 @@ * Docs: https://astroplan.readthedocs.io/ """ -# Affiliated packages may add whatever they like to this file, but -# should keep this content at the top. -# ---------------------------------------------------------------------------- -from ._astropy_init import * -# ---------------------------------------------------------------------------- +try: + from .version import version as __version__ +except ImportError: + __version__ = '' -# For egg_info test builds to pass, put package imports here. -if not _ASTROPY_SETUP_: - from .utils import * - from .observer import * - from .target import * - from .exceptions import * - from .moon import * - from .constraints import * - from .scheduling import * - from .periodic import * +from .utils import * +from .observer import * +from .target import * +from .exceptions import * +from .moon import * +from .constraints import * +from .scheduling import * +from .periodic import * - download_IERS_A() +download_IERS_A() diff --git a/astroplan/_astropy_init.py b/astroplan/_astropy_init.py deleted file mode 100644 index fa37a637..00000000 --- a/astroplan/_astropy_init.py +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed under a 3-clause BSD style license - see LICENSE.rst - -__all__ = ['__version__'] - -# this indicates whether or not we are in the package's setup.py -try: - _ASTROPY_SETUP_ -except NameError: - import builtins - builtins._ASTROPY_SETUP_ = False - -try: - from .version import version as __version__ -except ImportError: - __version__ = '' - - -if not _ASTROPY_SETUP_: # noqa - import os - - # Create the test function for self test - from astropy.tests.runner import TestRunner - test = TestRunner.make_test_runner_in(os.path.dirname(__file__)) - test.__test__ = False - __all__ += ['test'] diff --git a/astroplan/constraints.py b/astroplan/constraints.py index f44f64d3..5e95f431 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -4,9 +4,6 @@ an observer. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - # Standard library from abc import ABCMeta, abstractmethod import datetime @@ -16,7 +13,7 @@ # Third-party from astropy.time import Time import astropy.units as u -from astropy.coordinates import get_body, get_sun, get_moon, Galactic, SkyCoord +from astropy.coordinates import get_body, get_sun, Galactic, SkyCoord from astropy import table import numpy as np @@ -354,7 +351,7 @@ def compute_constraint(self, times, observer, targets): uppermask = alt <= self.max return lowermask & uppermask else: - return max_best_rescale(alt, self.min, self.max) + return max_best_rescale(alt, self.min, self.max, greater_than_max=0) class AirmassConstraint(AltitudeConstraint): @@ -550,6 +547,7 @@ def compute_constraint(self, times, observer, targets): # by the observer. # 'get_sun' returns ICRS coords. sun = get_body('sun', times, location=observer.location) + targets = get_skycoord(targets) solar_separation = sun.separation(targets) if self.min is None and self.max is not None: @@ -590,11 +588,12 @@ def __init__(self, min=None, max=None, ephemeris=None): self.ephemeris = ephemeris def compute_constraint(self, times, observer, targets): - moon = get_moon(times, location=observer.location, ephemeris=self.ephemeris) + moon = get_body("moon", times, location=observer.location, ephemeris=self.ephemeris) # note to future editors - the order matters here # moon.separation(targets) is NOT the same as targets.separation(moon) # the former calculates the separation in the frame of the moon coord # which is GCRS, and that is what we want. + targets = get_skycoord(targets) moon_separation = moon.separation(targets) if self.min is None and self.max is not None: diff --git a/astroplan/exceptions.py b/astroplan/exceptions.py index 46710f5c..5221b367 100644 --- a/astroplan/exceptions.py +++ b/astroplan/exceptions.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) from astropy.utils.exceptions import AstropyWarning diff --git a/astroplan/moon.py b/astroplan/moon.py index 23146bbf..3b2bd467 100644 --- a/astroplan/moon.py +++ b/astroplan/moon.py @@ -3,12 +3,9 @@ This version of the `moon` module calculates lunar phase angle for a geocentric """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - # Third-party import numpy as np -from astropy.coordinates import get_moon, get_sun +from astropy.coordinates import get_sun, get_body __all__ = ["moon_phase_angle", "moon_illumination"] @@ -35,7 +32,7 @@ def moon_phase_angle(time, ephemeris=None): # TODO: cache these sun/moon SkyCoord objects sun = get_sun(time) - moon = get_moon(time, ephemeris=ephemeris) + moon = get_body("moon", time, ephemeris=ephemeris) elongation = sun.separation(moon) return np.arctan2(sun.distance*np.sin(elongation), moon.distance - sun.distance*np.cos(elongation)) diff --git a/astroplan/observer.py b/astroplan/observer.py index efa2f4cf..0217ea6c 100644 --- a/astroplan/observer.py +++ b/astroplan/observer.py @@ -1,7 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) -from six import string_types # Standard library import sys @@ -9,7 +6,7 @@ import warnings # Third-party from astropy.coordinates import (EarthLocation, SkyCoord, AltAz, get_sun, - get_moon, Angle, Longitude) + get_body, Angle, Longitude) import astropy.units as u from astropy.time import Time from astropy.utils.exceptions import AstropyDeprecationWarning @@ -216,7 +213,7 @@ def __init__(self, location=None, timezone='UTC', name=None, latitude=None, # Accept various timezone inputs, default to UTC if isinstance(timezone, datetime.tzinfo): self.timezone = timezone - elif isinstance(timezone, string_types): + elif isinstance(timezone, str): self.timezone = pytz.timezone(timezone) else: raise TypeError('timezone keyword should be a string, or an ' @@ -273,6 +270,85 @@ def __repr__(self): attributes_strings.append("{}={}".format(name, value)) return "<{}: {}>".format(class_name, ",\n ".join(attributes_strings)) + def _key(self): + """ + Generate a tuple of the attributes that determine uniqueness of + `~astroplan.Observer` objects. + + Returns + ------- + key : tuple + + Examples + -------- + + >>> from astroplan import Observer + >>> keck = Observer.at_site("Keck", timezone="US/Hawaii") + >>> keck._key() + ('Keck', None, None, None, , + , , + ) + """ + + return (self.name, + self.pressure, + self.temperature, + self.relative_humidity, + self.longitude, + self.latitude, + self.elevation, + self.timezone,) + + def __hash__(self): + """ + Hash the `~astroplan.Observer` object. + + Examples + -------- + + >>> from astroplan import Observer + >>> keck = Observer.at_site("Keck", timezone="US/Hawaii") + >>> hash(keck) + -3872382927731250571 + """ + + return hash(self._key()) + + def __eq__(self, other): + """ + Equality check for `~astroplan.Observer` objects. + + Examples + -------- + + >>> from astroplan import Observer + >>> keck = Observer.at_site("Keck", timezone="US/Hawaii") + >>> keck2 = Observer.at_site("Keck", timezone="US/Hawaii") + >>> keck == keck2 + True + """ + + if isinstance(other, Observer): + return self._key() == other._key() + else: + return NotImplemented + + def __ne__(self, other): + """ + Inequality check for `~astroplan.Observer` objects. + + Examples + -------- + + >>> from astroplan import Observer + >>> keck = Observer.at_site("Keck", timezone="US/Hawaii") + >>> kpno = Observer.at_site("KPNO", timezone="US/Arizona") + >>> keck != kpno + True + """ + + return not self.__eq__(other) + @classmethod def at_site(cls, site_name, **kwargs): """ @@ -513,7 +589,7 @@ def altaz(self, time, target=None, obswl=None, grid_times_targets=False): """ if target is not None: if target is MoonFlag: - target = get_moon(time, location=self.location) + target = get_body("moon", time, location=self.location) elif target is SunFlag: target = get_sun(time) @@ -1734,7 +1810,7 @@ def moon_altaz(self, time, ephemeris=None): if not isinstance(time, Time): time = Time(time) - moon = get_moon(time, location=self.location, ephemeris=ephemeris) + moon = get_body("moon", time, location=self.location, ephemeris=ephemeris) return self.altaz(time, moon) def sun_altaz(self, time): diff --git a/astroplan/periodic.py b/astroplan/periodic.py index 44038b97..74fb5105 100644 --- a/astroplan/periodic.py +++ b/astroplan/periodic.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) import numpy as np import astropy.units as u from astropy.time import Time diff --git a/astroplan/plots/finder.py b/astroplan/plots/finder.py index 14a86cb8..58ca4034 100644 --- a/astroplan/plots/finder.py +++ b/astroplan/plots/finder.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) import numpy as np diff --git a/astroplan/plots/mplstyles.py b/astroplan/plots/mplstyles.py index 5d93f42e..cc8f7e50 100644 --- a/astroplan/plots/mplstyles.py +++ b/astroplan/plots/mplstyles.py @@ -1,7 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import copy from astropy.visualization import astropy_mpl_style diff --git a/astroplan/plots/sky.py b/astroplan/plots/sky.py index 8858deff..8c0d3fa3 100644 --- a/astroplan/plots/sky.py +++ b/astroplan/plots/sky.py @@ -1,7 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import numpy as np import astropy.units as u from astropy.time import Time @@ -120,7 +117,9 @@ def plot_sky(target, observer, time, ax=None, style_kwargs=None, if isinstance(plt.gca(), plt.PolarAxes): ax = plt.gca() else: - ax = plt.axes(polar=True) + plt.close() + fig = plt.gcf() + ax = fig.add_subplot(projection='polar') if style_kwargs is None: style_kwargs = {} diff --git a/astroplan/plots/tests/test_sky.py b/astroplan/plots/tests/test_sky.py index 7ad41bab..d7123e1e 100644 --- a/astroplan/plots/tests/test_sky.py +++ b/astroplan/plots/tests/test_sky.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) import pytest try: diff --git a/astroplan/plots/time_dependent.py b/astroplan/plots/time_dependent.py index fc4ec4f4..e30e9874 100644 --- a/astroplan/plots/time_dependent.py +++ b/astroplan/plots/time_dependent.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) import copy import numpy as np import operator @@ -152,8 +150,11 @@ def plot_airmass(targets, observer, time, ax=None, style_kwargs=None, if style_kwargs is None: style_kwargs = {} style_kwargs = dict(style_kwargs) - style_kwargs.setdefault('linewidth', 1.5) - style_kwargs.setdefault('fmt', '-') + + if 'lw' not in style_kwargs: + style_kwargs.setdefault('linewidth', 1.5) + if 'ls' not in style_kwargs and 'linestyle' not in style_kwargs: + style_kwargs.setdefault('fmt', '-') if hasattr(time, 'utcoffset') and use_local_tz: tzoffset = time.utcoffset() @@ -366,9 +367,10 @@ def plot_altitude(targets, observer, time, ax=None, style_kwargs=None, if style_kwargs is None: style_kwargs = {} style_kwargs = dict(style_kwargs) - style_kwargs.setdefault('linestyle', '-') - style_kwargs.setdefault('linewidth', 1.5) - style_kwargs.setdefault('fmt', '-') + if 'ls' not in style_kwargs and 'fmt' not in style_kwargs: + style_kwargs.setdefault('linestyle', '-') + if 'lw' not in style_kwargs: + style_kwargs.setdefault('linewidth', 1.5) # Populate time window if needed. time = Time(time) @@ -591,8 +593,9 @@ def plot_parallactic(target, observer, time, ax=None, style_kwargs=None, if style_kwargs is None: style_kwargs = {} style_kwargs = dict(style_kwargs) - style_kwargs.setdefault('linestyle', '-') style_kwargs.setdefault('fmt', '-') + if 'ls' not in style_kwargs and 'fmt' not in style_kwargs: + style_kwargs.setdefault('linestyle', '-') # Populate time window if needed. time = Time(time) diff --git a/astroplan/scheduling.py b/astroplan/scheduling.py index 98ca45b7..8fda4a2b 100644 --- a/astroplan/scheduling.py +++ b/astroplan/scheduling.py @@ -3,9 +3,6 @@ Tools for scheduling observations. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import copy from abc import ABCMeta, abstractmethod @@ -24,7 +21,7 @@ 'Transitioner', 'Scorer'] -class ObservingBlock(object): +class ObservingBlock: """ An observation to be scheduled, consisting of a target and associated constraints on observations. @@ -98,7 +95,7 @@ def from_exposures(cls, target, priority, time_per_exposure, return ob -class Scorer(object): +class Scorer: """ Returns scores and score arrays from the evaluation of constraints on observing blocks @@ -166,7 +163,7 @@ def from_start_end(cls, blocks, observer, start_time, end_time, return sc -class TransitionBlock(object): +class TransitionBlock: """ Parameterizes the "dead time", e.g. between observations, while the telescope is slewing, instrument is reconfiguring, etc. @@ -224,7 +221,7 @@ def from_duration(cls, duration): return tb -class Schedule(object): +class Schedule: """ An object that represents a schedule, consisting of a list of `~astroplan.scheduling.Slot` objects. @@ -423,7 +420,7 @@ def change_slot_block(self, slot_index, new_block=None): return slot_index - 1 -class Slot(object): +class Slot: """ A time slot consisting of a start and end time """ @@ -481,7 +478,7 @@ def split_slot(self, early_time, later_time): return [new_slot] -class Scheduler(object): +class Scheduler: """ Schedule a set of `~astroplan.scheduling.ObservingBlock` objects """ @@ -748,7 +745,7 @@ def _make_schedule(self, blocks): score_array = scorer.create_score_array(time_resolution) # Sort the list of blocks by priority - sorted_indices = np.argsort(_block_priorities) + sorted_indices = np.argsort(_block_priorities, kind='mergesort') unscheduled_blocks = [] # Compute the optimal observation time in priority order @@ -786,7 +783,7 @@ def _make_schedule(self, blocks): # does not prevent us from fitting it in. # loop over valid times and see if it fits # TODO: speed up by searching multiples of time resolution? - for idx in np.argsort(sum_scores)[::-1]: + for idx in np.argsort(-sum_scores, kind='mergesort'): if sum_scores[idx] <= 0.0: # we've run through all optimal blocks _is_scheduled = False @@ -948,7 +945,7 @@ def attempt_insert_block(self, b, new_start_time, start_time_idx): return True -class Transitioner(object): +class Transitioner: """ A class that defines how to compute transition times from one block to another. diff --git a/astroplan/target.py b/astroplan/target.py index 8a05dfca..e42fedb9 100644 --- a/astroplan/target.py +++ b/astroplan/target.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) # Standard library from abc import ABCMeta diff --git a/astroplan/tests/test_constraints.py b/astroplan/tests/test_constraints.py index 800471fb..0084c159 100644 --- a/astroplan/tests/test_constraints.py +++ b/astroplan/tests/test_constraints.py @@ -1,11 +1,9 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) import datetime as dt import numpy as np import astropy.units as u from astropy.time import Time -from astropy.coordinates import Galactic, SkyCoord, get_sun, get_moon +from astropy.coordinates import Galactic, SkyCoord, get_sun, get_body from astropy.utils import minversion import pytest @@ -98,6 +96,17 @@ def test_observability_table(): assert 'time observable' in stab.colnames +def test_altitude_constraint(): + subaru = Observer.at_site("Subaru") + time = Time('2001-02-03 15:35:00') + time_range = Time([time, time+3*u.hour]) + + constraint = AltitudeConstraint(min=40*u.deg, max=50*u.deg, boolean_constraint=False) + results = constraint(subaru, vega, times=time_grid_from_range(time_range)) + # Check if below min and above max values are 0 + assert np.all([results != 0][0] == [False, False, True, True, False, False]) + + def test_compare_altitude_constraint_and_observer(): time = Time('2001-02-03 04:05:06') time_ranges = [Time([time, time+1*u.hour]) + offset @@ -193,7 +202,7 @@ def test_moon_separation(): time = Time('2003-04-05 06:07:08') apo = Observer.at_site("APO") altaz_frame = apo.altaz(time) - moon = get_moon(time, apo.location).transform_to(altaz_frame) + moon = get_body("moon", time, apo.location).transform_to(altaz_frame) one_deg_away = SkyCoord(az=moon.az, alt=moon.alt+1*u.deg, frame=altaz_frame) five_deg_away = SkyCoord(az=moon.az+5*u.deg, alt=moon.alt, frame=altaz_frame) diff --git a/astroplan/tests/test_moon.py b/astroplan/tests/test_moon.py index 9e105740..a71457cb 100644 --- a/astroplan/tests/test_moon.py +++ b/astroplan/tests/test_moon.py @@ -1,7 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) - from ..observer import Observer from astropy.time import Time from astropy.coordinates import EarthLocation diff --git a/astroplan/tests/test_observer.py b/astroplan/tests/test_observer.py index 4bc514fc..3c2e87a7 100644 --- a/astroplan/tests/test_observer.py +++ b/astroplan/tests/test_observer.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) # Standard library import datetime @@ -159,22 +157,27 @@ def test_rise_set_transit_nearest_vector(): time = Time('2022-06-21 00:00:00') obs = Observer(location=location) - rise_vector = obs.target_rise_time(time, sc_list) + + with pytest.warns(TargetAlwaysUpWarning): + rise_vector = obs.target_rise_time(time, sc_list) + polaris_rise = obs.target_rise_time(time, polaris) + vega_rise = obs.target_rise_time(time, vega) mira_rise = obs.target_rise_time(time, mira) sirius_rise = obs.target_rise_time(time, sirius) - polaris_rise = obs.target_rise_time(time, polaris) assert rise_vector[0] == vega_rise assert rise_vector[1] == mira_rise assert rise_vector[2] == sirius_rise assert rise_vector[3].value.mask and polaris_rise.value.mask - set_vector = obs.target_set_time(time, sc_list) + with pytest.warns(TargetAlwaysUpWarning): + set_vector = obs.target_set_time(time, sc_list) + polaris_set = obs.target_set_time(time, polaris) + vega_set = obs.target_set_time(time, vega) mira_set = obs.target_set_time(time, mira) sirius_set = obs.target_set_time(time, sirius) - polaris_set = obs.target_set_time(time, polaris) assert set_vector[0] == vega_set assert set_vector[1] == mira_set @@ -1353,3 +1356,32 @@ def test_observer_lon_lat_el(): obs = Observer.at_site('Subaru') for attr in ['longitude', 'latitude', 'elevation']: assert hasattr(obs, attr) + + +def test_hash_observer(): + """Test that Observer objects are hashable.""" + obs1 = Observer.at_site('Subaru') + obs2 = Observer.at_site('Subaru') + assert hash(obs1) == hash(obs2) + + obs3 = Observer.at_site('Keck', timezone='US/Hawaii') + assert hash(obs1) != hash(obs3) + + obs4 = Observer.at_site('Keck', timezone='US/Hawaii') + assert hash(obs3) == hash(obs4) + + +def test_eq_observer(): + """Test that Observer objects are comparable.""" + obs1 = Observer.at_site('Subaru') + obs2 = Observer.at_site('Subaru') + assert obs1 == obs2 + + obs3 = Observer.at_site('Keck') + assert obs1 != obs3 + + obs4 = Observer.at_site('Subaru', timezone='US/Hawaii') + assert obs1 != obs4 + + obs5 = Observer.at_site('Subaru', timezone='US/Hawaii') + assert obs4 == obs5 diff --git a/astroplan/tests/test_periodic.py b/astroplan/tests/test_periodic.py index 5cd2f867..2c3ded0b 100644 --- a/astroplan/tests/test_periodic.py +++ b/astroplan/tests/test_periodic.py @@ -1,6 +1,3 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import numpy as np from astropy.time import Time import astropy.units as u diff --git a/astroplan/tests/test_scheduling.py b/astroplan/tests/test_scheduling.py index 76a35ba7..ff09cb7a 100644 --- a/astroplan/tests/test_scheduling.py +++ b/astroplan/tests/test_scheduling.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) import numpy as np from astropy.time import Time @@ -158,6 +156,30 @@ def test_priority_scheduler(): scheduler(blocks, schedule) scheduler(blocks, schedule) + # Check if the order of equal values is respected: + # 1. For equal priorities in blocks + # 2. For equal scores (boolean_constraint) + constraints = [AirmassConstraint(3, boolean_constraint=True)] + scheduler.constraints = constraints + # Any resolution coarser than this may result in gaps between the blocks + scheduler.time_resolution = 20*u.second + # This only happens with more than 16 items or so + targets_18 = [polaris, polaris, polaris, polaris, polaris, polaris, + polaris, polaris, polaris, polaris, polaris, polaris, + polaris, polaris, polaris, polaris, polaris, polaris] + blocks = [ObservingBlock(t, 4*u.minute, 5, name=i) for i, t in enumerate(targets_18)] + + schedule = Schedule(start_time, end_time) + scheduler(blocks, schedule) + + for i, t in enumerate(targets_18): + # Order of blocks + block = schedule.observing_blocks[i] + assert block.name == i + if i < len(targets_18) - 1: + # Same score for all timeslots, should be filled from the start without gaps + assert block.end_time.isclose(schedule.observing_blocks[i+1].start_time) + def test_sequential_scheduler(): constraints = [AirmassConstraint(2.5, boolean_constraint=False)] diff --git a/astroplan/tests/test_target.py b/astroplan/tests/test_target.py index 208a0a12..582edebd 100644 --- a/astroplan/tests/test_target.py +++ b/astroplan/tests/test_target.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) import pytest diff --git a/astroplan/tests/test_utils.py b/astroplan/tests/test_utils.py index 97f1fc18..9eff73a5 100644 --- a/astroplan/tests/test_utils.py +++ b/astroplan/tests/test_utils.py @@ -1,7 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import numpy as np import pytest diff --git a/astroplan/utils.py b/astroplan/utils.py index 0db34468..4d4524cf 100644 --- a/astroplan/utils.py +++ b/astroplan/utils.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import (absolute_import, division, print_function, - unicode_literals) # Standard library import warnings diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 00000000..78aeb6c6 --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,25 @@ +{# This extension of the 'layout.html' prevents documentation for previous + versions of Astropy to be indexed by bots, e.g. googlebot or bing bot, + by inserting a robots meta tag into pages that are not in the stable or + latest branch. + + It assumes that the documentation is built by and hosted on readthedocs.org: + 1. Readthedocs.org has a global robots.txt and no option for a custom one. + 2. The readthedocs app passes additional variables to the template context, + one of them being `version_slug`. This variable is a string computed from + the tags of the branches that are selected to be built. It can be 'latest', + 'stable' or even a unique stringified version number. + + For more information, please refer to: + https://github.com/astropy/astropy/pull/7874 + http://www.robotstxt.org/meta.html + https://github.com/rtfd/readthedocs.org/blob/master/readthedocs/builds/version_slug.py +#} + +{% extends "!layout.html" %} +{%- block extrahead %} + {% if not version_slug in to_be_indexed %} + + {% endif %} + {{ super() }} +{% endblock %} diff --git a/docs/conf.py b/docs/conf.py index f2732f55..a2817fbb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,12 +12,12 @@ # See astropy.sphinx.conf for which values are set there. from configparser import ConfigParser -import os import sys import datetime +from importlib import metadata try: - from sphinx_astropy.conf.v1 import * # noqa + from sphinx_astropy.conf.v2 import * # noqa except ImportError: print('ERROR: the documentation requires the sphinx-astropy package to ' 'be installed') @@ -34,7 +34,7 @@ highlight_language = 'python3' # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.7' +# needs_sphinx = '1.7' # Extend astropy intersphinx_mapping with packages we use here intersphinx_mapping['astroquery'] = ('http://astroquery.readthedocs.io/en/latest/', None) @@ -70,10 +70,17 @@ __import__(project) package = sys.modules[project] -# The short X.Y version. -version = package.__version__.split('-', 1)[0] +# 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 full version, including alpha/beta/rc tags. -release = package.__version__ +release = metadata.version(project) +# The short X.Y version. +version = ".".join(release.split(".")[:2]) + +# Only include dev docs in dev version. +dev = "dev" in release # -- Options for HTML output --------------------------------------------------- @@ -85,12 +92,6 @@ # variables set in the global configuration. The variables set in the # global configuration are listed below, commented out. -html_theme_options = { - 'logotext1': 'astro', # white, semi-bold - 'logotext2': 'plan', # orange, light - 'logotext3': ':docs' # white, light - } - # Add any paths that contain custom themes here, relative to this directory. # To use a different custom theme, add the directory containing the theme. #html_theme_path = [] @@ -179,6 +180,33 @@ # -- Turn on nitpicky mode for sphinx (to warn about references not found) ---- nitpicky = True +html_copy_source = False + +html_theme_options.update( # noqa: F405 + { + "github_url": "https://github.com/astropy/astroplan", + "external_links": [ + {"name": "astropy docs", "url": "https://docs.astropy.org/en/stable/"}, + ], + "use_edit_page_button": True, + } +) + +html_context = { + "default_mode": "light", + "to_be_indexed": ["stable", "latest"], + "is_development": dev, + "github_user": "astropy", + "github_repo": "astroplan", + "github_version": "main", + "doc_path": "docs", +} + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +html_extra_path = ["robots.txt"] + # # Some warnings are impossible to suppress, and you can list specific references # that should be ignored in a nitpick-exceptions file which should be inside @@ -199,4 +227,4 @@ # continue # dtype, target = line.split(None, 1) # target = target.strip() -# nitpick_ignore.append((dtype, six.u(target))) +# nitpick_ignore.append((dtype, str(target))) diff --git a/docs/faq/iers.rst b/docs/faq/iers.rst index 3a1c0654..ecce8613 100644 --- a/docs/faq/iers.rst +++ b/docs/faq/iers.rst @@ -78,7 +78,7 @@ astropy's IERS machinery: import numpy as np import astropy.units as u from astropy.time import Time - from six.moves.urllib.error import HTTPError + from urllib.error import HTTPError # Download and cache the IERS Bulletins A and B using astropy's machinery # (reminder: astroplan has its own function for this: `download_IERS_A`) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index c1a640b8..ee1fec04 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -7,7 +7,7 @@ Getting Started General Guidelines ================== -`astroplan` is based on `Astropy `__ and was built around the creation of Python +`astroplan` is based on `astropy `__ and was built around the creation of Python objects that contain all the information needed to perform certain tasks. You, the user, will create and manipulate these objects to plan your observation. For instance, an `~astroplan.Target` object contains information associated with @@ -57,7 +57,7 @@ Or you can specify your own location parameters:: timezone=timezone('US/Hawaii'), description="Subaru Telescope on Maunakea, Hawaii") -`astroplan` makes heavy use of certain `Astropy `__ machinery, including the +`astroplan` makes heavy use of certain `astropy `__ machinery, including the `~astropy.coordinates` objects and transformations and `~astropy.units`. Most importantly for basic use of `astroplan` is the representation of dates/times as `~astropy.time.Time` objects (note that @@ -66,10 +66,6 @@ these are in the UTC timezone by default):: from astropy.time import Time time = Time(['2015-06-16 06:00:00']) -Since `astroplan` objects are Python objects, manipulating them or accessing -attributes follows Python syntax and conventions. See Python documentation on -`objects `_ -for more information. Doing More ========== diff --git a/docs/index.rst b/docs/index.rst index 2b05f959..4977c01f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,13 +2,6 @@ .. _astroplan: -********************************** -Observation Planning (`astroplan`) -********************************** - -What is astroplan? -================== - **astroplan** is an open source Python package to help astronomers plan observations. @@ -17,7 +10,6 @@ scheduling. When complete, the goal is to be easy for Python beginners and new observers to to pick up, but powerful enough for observatories preparing nightly and long-term schedules. - Features: * Calculate rise/set/meridian transit times, alt/az positions for targets at @@ -26,7 +18,7 @@ Features: plots (airmass, parallactic angle, sky maps). * Determining observability of sets of targets given an arbitrary set of constraints (i.e., altitude, airmass, moon separation/illumination, etc.). -* `Astropy `__ powered! +* `astropy `__ powered! Links ===== @@ -39,8 +31,8 @@ License: BSD-3 .. _astroplan_docs: -General Documentation -===================== +Documentation +============= .. toctree:: :maxdepth: 2 @@ -52,22 +44,32 @@ General Documentation api changelog -.. _astroplan_authors: - -Authors -======= - Maintainers ------------ ++++++++++++ * `Brett Morris, including contributions from Google Summer of Code 2015 `_ -Contributors ------------- -* `Jazmin Berlanga Medina, including contributions from Google Summer of Code 2015 `_ -* Christoph Deil -* Stephanie Douglas -* Eric Jeschke -* Adrian Price-Whelan -* Erik Tollerud -* Brigitta Sipocz -* Karl Vyhmeister +Attribution ++++++++++++ + +If you use astroplan in your work, please cite `Morris et al. 2018 `_: + +.. code :: bibtex + + @ARTICLE{2018AJ....155..128M, + author = {{Morris}, Brett M. and {Tollerud}, Erik and {Sip{\H{o}}cz}, Brigitta and {Deil}, Christoph and {Douglas}, Stephanie T. and {Berlanga Medina}, Jazmin and {Vyhmeister}, Karl and {Smith}, Toby R. and {Littlefair}, Stuart and {Price-Whelan}, Adrian M. and {Gee}, Wilfred T. and {Jeschke}, Eric}, + title = "{astroplan: An Open Source Observation Planning Package in Python}", + journal = {\aj}, + keywords = {methods: numerical, methods: observational, Astrophysics - Instrumentation and Methods for Astrophysics}, + year = 2018, + month = mar, + volume = {155}, + number = {3}, + eid = {128}, + pages = {128}, + doi = {10.3847/1538-3881/aaa47e}, + archivePrefix = {arXiv}, + eprint = {1712.09631}, + primaryClass = {astro-ph.IM}, + adsurl = {https://ui.adsabs.harvard.edu/abs/2018AJ....155..128M}, + adsnote = {Provided by the SAO/NASA Astrophysics Data System} + } \ No newline at end of file diff --git a/docs/installation.rst b/docs/installation.rst index 502166be..43fffb67 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -10,21 +10,9 @@ Requirements ============ **astroplan** works on Linux, Mac OS X and Windows. -It requires Python 2.7 or 3.5+ (2.6 and 3.2 or earlier are not -supported, 3.3 and 3.4 may work) as well as the following packages: - -* `Numpy`_ (1.10 or later) -* `Astropy `__ (v1.3 or later) -* `pytz`_ - -Optional packages: - -* `Matplotlib`_ -* `astroquery`_ - -For testing: - -* `pytest-astropy`_ +It requires Python 3.7+ as well as numpy, astropy, and pytz. +Additional features are available when you install `Matplotlib`_ +and `astroquery`_. First-time Python users may want to consider an all-in-one Python installation package, such as the `Anaconda Python Distribution @@ -49,29 +37,28 @@ cloning the git repository:: ...then installing the package with:: cd astroplan - python setup.py install + pip install . Testing ======= If you want to check that all the tests are running correctly with your Python -configuration, start up python, and type:: +configuration, run the following from the command line:: - import astroplan - astroplan.test() + tox -e test If there are no errors, you are good to go! .. note:: If you want to run the tests that access the internet, you'll need to - replace the last line above with ``astroplan.test(remote_data=True)`` and + replace the last line above with ``tox -e test -- --remote-data`` and have an active connection to the internet. Also, if you want the tests that check plotting to work, you need `Matplotlib`_ and `pytest-mpl`_. More ==== -astroplan follows `Astropy `__'s guidelines for affiliated packages--installation -and testing for the two are quite similar! Please see Astropy's +astroplan follows `astropy `__'s guidelines for affiliated packages--installation +and testing for the two are quite similar! Please see astropy's `installation page `_ for more information. diff --git a/docs/robots.txt b/docs/robots.txt new file mode 100644 index 00000000..df810cf3 --- /dev/null +++ b/docs/robots.txt @@ -0,0 +1,6 @@ +User-agent: * +Allow: /*/latest/ +Allow: /en/latest/ # Fallback for bots that don't understand wildcards +Allow: /*/stable/ +Allow: /en/stable/ # Fallback for bots that don't understand wildcards +Disallow: / diff --git a/docs/tutorials/constraints.rst b/docs/tutorials/constraints.rst index ceb0ef3f..3e2c259b 100644 --- a/docs/tutorials/constraints.rst +++ b/docs/tutorials/constraints.rst @@ -259,9 +259,6 @@ satisfied. .. plot:: - from __future__ import (absolute_import, division, print_function, - unicode_literals) - from astroplan import (FixedTarget, Observer, AltitudeConstraint, AtNightConstraint, MoonSeparationConstraint) from astropy.time import Time diff --git a/pyproject.toml b/pyproject.toml index 0889579c..254fa99b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,4 @@ [build-system] requires = ["setuptools", - "setuptools_scm", - "wheel"] + "setuptools_scm"] build-backend = 'setuptools.build_meta' diff --git a/setup.cfg b/setup.cfg index 95629166..2fec85ff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,7 +20,6 @@ install_requires = numpy>=1.17 astropy>=4 pytz - six [options.extras_require] all = @@ -30,7 +29,7 @@ test = pytest-astropy pytest-mpl docs = - sphinx-astropy + sphinx-astropy[confv2] sphinx-rtd-theme matplotlib>=1.4 astroquery diff --git a/tox.ini b/tox.ini index 5a218f55..dcff8165 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ [tox] envlist = - py{37,38,39}-test{,-alldeps,-devdeps}{,-cov} - py{37,38,39}-test-numpy{117,118,119} - py{37,38,39}-test-astropy{lts,41,42} + py{310,311,312}-test{,-alldeps,-devdeps}{,-cov} + py{310,311,312}-test-numpy{123,124,125} + py{310,311,312}-test-astropy{lts,53,60} build_docs linkcheck codestyle @@ -11,7 +11,7 @@ requires = pip >= 19.3.1 isolated_build = true indexserver = - NIGHTLY = https://pypi.anaconda.org/scipy-wheels-nightly/simple + NIGHTLY = https://pypi.anaconda.org/scientific-python-nightly-wheels/simple [testenv] # Suppress display of matplotlib plots generated during docs build