From bdc099a04b653c405d3dda39b97cc9feb8611af5 Mon Sep 17 00:00:00 2001 From: tygger7 <91161158+tygger7@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:44:35 -0400 Subject: [PATCH 1/6] added observable_interval func added a generic function of `months_observable` called `observable_interval` that takes 'days','weeks',or 'months' and returns the same results. --- astroplan/constraints.py | 82 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index 59be070c..b99fdc28 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -1092,6 +1092,88 @@ def is_event_observable(constraints, observer, target, times=None, np.logical_and.reduce(applied_constraints_egr)) return constraint_arr + +def observable_interval(constraints, observer, targets, + time_range=_current_year_time_range, + time_grid_resolution=0.5*u.hour, + interval='months'): + """ + Determines which weeks the specified ``targets`` are observable for a + specific ``observer``, given the supplied ``constraints``. Generic version of + `months_observable` that includes functionality for days and weeks. + + Parameters + ---------- + constraints : list or `~astroplan.constraints.Constraint` + Observational constraint(s) + + observer : `~astroplan.Observer` + The observer who has constraints ``constraints`` + + targets : {list, `~astropy.coordinates.SkyCoord`, `~astroplan.FixedTarget`} + Target or list of targets + + time_range : `~astropy.time.Time` (optional) + Lower and upper bounds on time sequence + If ``time_range`` is not specified, defaults to current year (localtime) + + time_grid_resolution : `~astropy.units.Quantity` (optional) + If ``time_range`` is specified, determine whether constraints are met + between test times in ``time_range`` by checking constraint at + linearly-spaced times separated by ``time_resolution``. Default is 0.5 + hours. + + interval : str (optional) + Defines the interval of what dates to calculate ``constraints`` on. + + + Returns + ------- + observability : list + List of sets of unique integers representing each interval that a target is + observable, one set per target. These integers are 1-based so that + January maps to 1, Feburary maps to 2, etc., and it is a similar structure + for day and week intervals. + + """ + # TODO: This method could be sped up a lot by dropping to the trigonometric + # altitude calculations. + if not hasattr(constraints, '__len__'): + constraints = [constraints] + if interval not in ['days','weeks','months']: + raise ValueError('interval is of an incorrect type. Please choose days, weeks, or months') + + times = time_grid_from_range(time_range, time_grid_resolution) + + # If the constraints don't include AltitudeConstraint or its subclasses, + # warn the user that they may get months when the target is below the horizon + altitude_constraint_supplied = any( + [isinstance(constraint, AltitudeConstraint) for constraint in constraints] + ) + if not altitude_constraint_supplied: + message = ("observable_interval usually expects an AltitudeConstraint or " + "AirmassConstraint to ensure targets are above horizon.") + warnings.warn(message, MissingConstraintWarning) + + applied_constraints = [constraint(observer, targets, + times=times, + grid_times_targets=True) + for constraint in constraints] + constraint_arr = np.logical_and.reduce(applied_constraints) + + + observability = [] + + method_dic = {'days':lambda t: t.datetime.timetuple().tm_yday, + 'weeks':lambda t: t.datetime.isocalendar()[1], + 'months':lambda t: t.datetime.month} + + + for target, observable in zip(targets, constraint_arr): + s = set([method_dic[interval](t) for t in times[observable]]) + observability.append(s) + + return observability def months_observable(constraints, observer, targets, time_range=_current_year_time_range, From 64c98d9a91e6e975273f0c2a16401a4456bb82ff Mon Sep 17 00:00:00 2001 From: tygger7 <91161158+tygger7@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:47:10 -0400 Subject: [PATCH 2/6] `_current_year_time_range` fix old version was not including the 31st of December in its calculations for `months_observable`. This was the only function to use this, so it is an easy update, and with `observable_interval` added is needed. --- astroplan/constraints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index b99fdc28..dcc99d1d 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -40,7 +40,7 @@ _current_year = time.localtime().tm_year # needed for backward compatibility _current_year_time_range = Time( # needed for backward compatibility [str(_current_year) + '-01-01', - str(_current_year) + '-12-31'] + str(_current_year + 1) + '-01-01'] ) From 7f57b045dddbd440a0a4341efadee49bf3e3b46a Mon Sep 17 00:00:00 2001 From: tygger7 <91161158+tygger7@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:08:07 -0400 Subject: [PATCH 3/6] `observable_interval` aliases + output readability update Made `months_observable` an alias of `observable_interval` to maintain backwards compatibility. Also added `weeks_observable` and `days_observable` aliases. I also made a small change to `observable_interval` to enhance output readability in the case a target is never visible given the constraints. (would have been "set()" now is "{}") --- astroplan/constraints.py | 89 ++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 59 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index dcc99d1d..7c46c10a 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -1170,7 +1170,10 @@ def observable_interval(constraints, observer, targets, for target, observable in zip(targets, constraint_arr): - s = set([method_dic[interval](t) for t in times[observable]]) + if observable.any() == True: + s = set([method_dic[interval](t) for t in times[observable]]) + else: + s = {} observability.append(s) return observability @@ -1179,69 +1182,37 @@ def months_observable(constraints, observer, targets, time_range=_current_year_time_range, time_grid_resolution=0.5*u.hour): """ - Determines which month the specified ``targets`` are observable for a + Determines which months in ``time_range`` the specified ``targets`` are observable for a specific ``observer``, given the supplied ``constraints``. - - Parameters - ---------- - constraints : list or `~astroplan.constraints.Constraint` - Observational constraint(s) - - observer : `~astroplan.Observer` - The observer who has constraints ``constraints`` - - targets : {list, `~astropy.coordinates.SkyCoord`, `~astroplan.FixedTarget`} - Target or list of targets - - time_range : `~astropy.time.Time` (optional) - Lower and upper bounds on time sequence - If ``time_range`` is not specified, defaults to current year (localtime) - - time_grid_resolution : `~astropy.units.Quantity` (optional) - If ``time_range`` is specified, determine whether constraints are met - between test times in ``time_range`` by checking constraint at - linearly-spaced times separated by ``time_resolution``. Default is 0.5 - hours. - - Returns - ------- - observable_months : list - List of sets of unique integers representing each month that a target is - observable, one set per target. These integers are 1-based so that - January maps to 1, February maps to 2, etc. - + + This is an alias of observable_interval(..., interval = 'months') """ - # TODO: This method could be sped up a lot by dropping to the trigonometric - # altitude calculations. - if not hasattr(constraints, '__len__'): - constraints = [constraints] - times = time_grid_from_range(time_range, time_grid_resolution) - - # If the constraints don't include AltitudeConstraint or its subclasses, - # warn the user that they may get months when the target is below the horizon - altitude_constraint_supplied = any( - [isinstance(constraint, AltitudeConstraint) for constraint in constraints] - ) - if not altitude_constraint_supplied: - message = ("months_observable usually expects an AltitudeConstraint or " - "AirmassConstraint to ensure targets are above horizon.") - warnings.warn(message, MissingConstraintWarning) - - # TODO: This method could be sped up a lot by dropping to the trigonometric - # altitude calculations. - applied_constraints = [constraint(observer, targets, - times=times, - grid_times_targets=True) - for constraint in constraints] - constraint_arr = np.logical_and.reduce(applied_constraints) + return observable_interval(constraints, observer, targets, time_range, time_grid_resolution, interval = 'months') + +def weeks_observable(constraints, observer, targets, + time_range=_current_year_time_range, + time_grid_resolution=0.5*u.hour): + """ + Determines which weeks in ``time_range`` the specified ``targets`` are observable for a + specific ``observer``, given the supplied ``constraints``. + + This is an alias of observable_interval(..., interval = 'weeks') + """ - months_observable = [] - for target, observable in zip(targets, constraint_arr): - s = set([t.datetime.month for t in times[observable]]) - months_observable.append(s) + return observable_interval(constraints, observer, targets, time_range, time_grid_resolution, interval = 'weeks') + +def days_observable(constraints, observer, targets, + time_range=_current_year_time_range, + time_grid_resolution=0.5*u.hour): + """ + Determines which days in ``time_range`` the specified ``targets`` are observable for a + specific ``observer``, given the supplied ``constraints``. + + This is an alias of observable_interval(..., interval = 'days') + """ - return months_observable + return observable_interval(constraints, observer, targets, time_range, time_grid_resolution, interval = 'days') def observability_table(constraints, observer, targets, times=None, From 482780b1a15de6e965fc4d6a21ba21205d8f4cb2 Mon Sep 17 00:00:00 2001 From: tygger7 <91161158+tygger7@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:26:48 -0400 Subject: [PATCH 4/6] docstring clean-up --- astroplan/constraints.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index 7c46c10a..22759a66 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -1098,9 +1098,8 @@ def observable_interval(constraints, observer, targets, time_grid_resolution=0.5*u.hour, interval='months'): """ - Determines which weeks the specified ``targets`` are observable for a - specific ``observer``, given the supplied ``constraints``. Generic version of - `months_observable` that includes functionality for days and weeks. + Determines which ``intervals`` in ``time_range`` the specified ``targets`` are observable for a + specific ``observer``, given the supplied ``constraints``. Parameters ---------- @@ -1123,8 +1122,9 @@ def observable_interval(constraints, observer, targets, linearly-spaced times separated by ``time_resolution``. Default is 0.5 hours. - interval : str (optional) - Defines the interval of what dates to calculate ``constraints`` on. + interval : str; ('days','weeks','months') (optional) + Defines the interval of what dates to calculate ``constraints`` on. + Default is 'months'. Returns From 7d42c9dae13546825c94887a31811f28458003b4 Mon Sep 17 00:00:00 2001 From: tygger7 <91161158+tygger7@users.noreply.github.com> Date: Fri, 4 Nov 2022 16:55:35 -0400 Subject: [PATCH 5/6] code style changes for tox checks fixed the issues that were noted with the tox check --- astroplan/constraints.py | 78 ++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index 22759a66..eb488cd7 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -1092,14 +1092,15 @@ def is_event_observable(constraints, observer, target, times=None, np.logical_and.reduce(applied_constraints_egr)) return constraint_arr - + def observable_interval(constraints, observer, targets, - time_range=_current_year_time_range, - time_grid_resolution=0.5*u.hour, - interval='months'): + time_range=_current_year_time_range, + time_grid_resolution=0.5*u.hour, + interval='months'): """ - Determines which ``intervals`` in ``time_range`` the specified ``targets`` are observable for a - specific ``observer``, given the supplied ``constraints``. + Determines which ``intervals`` in ``time_range`` the specified ``targets`` + are observable for a specific ``observer``, + given the supplied ``constraints``. Parameters ---------- @@ -1121,11 +1122,11 @@ def observable_interval(constraints, observer, targets, between test times in ``time_range`` by checking constraint at linearly-spaced times separated by ``time_resolution``. Default is 0.5 hours. - + interval : str; ('days','weeks','months') (optional) Defines the interval of what dates to calculate ``constraints`` on. Default is 'months'. - + Returns ------- @@ -1140,11 +1141,11 @@ def observable_interval(constraints, observer, targets, # altitude calculations. if not hasattr(constraints, '__len__'): constraints = [constraints] - if interval not in ['days','weeks','months']: - raise ValueError('interval is of an incorrect type. Please choose days, weeks, or months') - + if interval not in ['days', 'weeks', 'months']: + raise ValueError('Interval is of an incorrect type. Please choose days, weeks, or months') + times = time_grid_from_range(time_range, time_grid_resolution) - + # If the constraints don't include AltitudeConstraint or its subclasses, # warn the user that they may get months when the target is below the horizon altitude_constraint_supplied = any( @@ -1160,17 +1161,15 @@ def observable_interval(constraints, observer, targets, grid_times_targets=True) for constraint in constraints] constraint_arr = np.logical_and.reduce(applied_constraints) - - + observability = [] - - method_dic = {'days':lambda t: t.datetime.timetuple().tm_yday, - 'weeks':lambda t: t.datetime.isocalendar()[1], - 'months':lambda t: t.datetime.month} - - + + method_dic = {'days': lambda t: t.datetime.timetuple().tm_yday, + 'weeks': lambda t: t.datetime.isocalendar()[1], + 'months': lambda t: t.datetime.month} + for target, observable in zip(targets, constraint_arr): - if observable.any() == True: + if observable.any(): s = set([method_dic[interval](t) for t in times[observable]]) else: s = {} @@ -1178,41 +1177,50 @@ def observable_interval(constraints, observer, targets, return observability + def months_observable(constraints, observer, targets, time_range=_current_year_time_range, time_grid_resolution=0.5*u.hour): """ - Determines which months in ``time_range`` the specified ``targets`` are observable for a - specific ``observer``, given the supplied ``constraints``. - + Determines which months in ``time_range`` the specified ``targets`` + are observable for a specific ``observer``, + given the supplied ``constraints``. + This is an alias of observable_interval(..., interval = 'months') """ - return observable_interval(constraints, observer, targets, time_range, time_grid_resolution, interval = 'months') + return observable_interval(constraints, observer, targets, time_range, + time_grid_resolution, interval='months') + def weeks_observable(constraints, observer, targets, - time_range=_current_year_time_range, - time_grid_resolution=0.5*u.hour): + time_range=_current_year_time_range, + time_grid_resolution=0.5*u.hour): """ - Determines which weeks in ``time_range`` the specified ``targets`` are observable for a - specific ``observer``, given the supplied ``constraints``. + Determines which weeks in ``time_range`` the specified ``targets`` + are observable for a specific ``observer``, + given the supplied ``constraints``. This is an alias of observable_interval(..., interval = 'weeks') """ - return observable_interval(constraints, observer, targets, time_range, time_grid_resolution, interval = 'weeks') + return observable_interval(constraints, observer, targets, time_range, + time_grid_resolution, interval='weeks') + def days_observable(constraints, observer, targets, - time_range=_current_year_time_range, - time_grid_resolution=0.5*u.hour): + time_range=_current_year_time_range, + time_grid_resolution=0.5*u.hour): """ - Determines which days in ``time_range`` the specified ``targets`` are observable for a - specific ``observer``, given the supplied ``constraints``. + Determines which days in ``time_range`` the specified ``targets`` + are observable for a specific ``observer``, + given the supplied ``constraints``. This is an alias of observable_interval(..., interval = 'days') """ - return observable_interval(constraints, observer, targets, time_range, time_grid_resolution, interval = 'days') + return observable_interval(constraints, observer, targets, time_range, + time_grid_resolution, interval='days') def observability_table(constraints, observer, targets, times=None, From dd6d9da08873cc011d6547844a14c6c697fca324 Mon Sep 17 00:00:00 2001 From: tygger7 <91161158+tygger7@users.noreply.github.com> Date: Mon, 7 Nov 2022 10:34:06 -0500 Subject: [PATCH 6/6] code style updates minor white space errors removed in aliased functions and between aliased function --- astroplan/constraints.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/astroplan/constraints.py b/astroplan/constraints.py index eb488cd7..0b57dd84 100644 --- a/astroplan/constraints.py +++ b/astroplan/constraints.py @@ -1182,8 +1182,8 @@ def months_observable(constraints, observer, targets, time_range=_current_year_time_range, time_grid_resolution=0.5*u.hour): """ - Determines which months in ``time_range`` the specified ``targets`` - are observable for a specific ``observer``, + Determines which months in ``time_range`` the specified ``targets`` + are observable for a specific ``observer``, given the supplied ``constraints``. This is an alias of observable_interval(..., interval = 'months') @@ -1191,31 +1191,31 @@ def months_observable(constraints, observer, targets, return observable_interval(constraints, observer, targets, time_range, time_grid_resolution, interval='months') - + def weeks_observable(constraints, observer, targets, time_range=_current_year_time_range, time_grid_resolution=0.5*u.hour): """ - Determines which weeks in ``time_range`` the specified ``targets`` + Determines which weeks in ``time_range`` the specified ``targets`` are observable for a specific ``observer``, given the supplied ``constraints``. - + This is an alias of observable_interval(..., interval = 'weeks') """ return observable_interval(constraints, observer, targets, time_range, time_grid_resolution, interval='weeks') - + def days_observable(constraints, observer, targets, time_range=_current_year_time_range, time_grid_resolution=0.5*u.hour): """ - Determines which days in ``time_range`` the specified ``targets`` - are observable for a specific ``observer``, + Determines which days in ``time_range`` the specified ``targets`` + are observable for a specific ``observer``, given the supplied ``constraints``. - + This is an alias of observable_interval(..., interval = 'days') """