Skip to content
15 changes: 6 additions & 9 deletions astroplan/observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1487,15 +1487,12 @@ def moon_altaz(self, time, ephemeris=None):
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])
altaz_frame = AltAz(location=self.location, obstime=time)
moon_coords = get_moon(time, ephemeris=ephemeris).transform_to(altaz_frame)
obstime = time
alts = moon_coords.alt
azs = moon_coords.az
dists = moon_coords.distance
return SkyCoord(AltAz(azs, alts, dists, obstime=obstime, location=self.location))

@u.quantity_input(horizon=u.deg)
Expand Down
84 changes: 56 additions & 28 deletions astroplan/scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from astropy.table import Table

from .utils import time_grid_from_range, stride_array
from .constraints import AltitudeConstraint

__all__ = ['ObservingBlock', 'TransitionBlock', 'Schedule', 'Slot', 'Scheduler',
'SequentialScheduler', 'PriorityScheduler', 'Transitioner']
Expand Down Expand Up @@ -106,10 +107,10 @@ def __init__(self, components, start_time=None):
start_time : `~astropy.units.Quantity`
Start time of observation
"""
self._components = None
self.duration = None
self.start_time = start_time
self.components = components
self._components = None

def __repr__(self):
orig_repr = object.__repr__(self)
Expand Down Expand Up @@ -143,8 +144,7 @@ def components(self, val):
def from_duration(cls, duration):
# for testing how to put transitions between observations during
# scheduling without considering the complexities of duration
tb = TransitionBlock({None: 0*u.second})
tb.duration = duration
tb = TransitionBlock({'duration': duration})
return tb


Expand Down Expand Up @@ -173,14 +173,12 @@ def __init__(self, start_time, end_time, constraints=None):
self.end_time = end_time
self.slots = [Slot(start_time, end_time)]
self.constraints = constraints
self.slew_duration = 4*u.min
# TODO: replace/overwrite slew_duration with Transitioner calls
self.observer = None

def __repr__(self):
return 'Schedule containing ' + str(len(self.observing_blocks)) + \
' observing blocks between ' + str(self.slots[0].start.iso) + \
' and ' + str(self.slots[-1].end.iso)
return ('Schedule containing ' + str(len(self.observing_blocks)) +
' observing blocks between ' + str(self.slots[0].start.iso) +
' and ' + str(self.slots[-1].end.iso))

def apply_constraints(self):
# this needs to be able to handle being passed constraints
Expand Down Expand Up @@ -243,23 +241,23 @@ def insert_slot(self, start_time, block):
# due to float representation, this will change block start time
# and duration by up to 1 second in order to fit in a slot
for j, slot in enumerate(self.slots):
if (slot.start < start_time or np.abs(slot.start-start_time) < 1*u.second) \
and (slot.end > start_time):
if ((slot.start < start_time or abs(slot.start-start_time) < 1*u.second)
and (slot.end > start_time + 1*u.second)):
slot_index = j
if (block.duration - self.slots[slot_index].duration) > 1*u.second:
print(self.slots[slot_index].duration.to(u.second), block.duration)
raise ValueError('longer block than slot')
elif self.slots[slot_index].end - block.duration < start_time:
start_time = self.slots[slot_index].end - block.duration

if np.abs((self.slots[slot_index].duration - block.duration) < 1 * u.second):
if abs((self.slots[slot_index].duration - block.duration) < 1 * u.second):
block.duration = self.slots[slot_index].duration
start_time = self.slots[slot_index].start
end_time = self.slots[slot_index].end
elif np.abs(self.slots[slot_index].start - start_time) < 1*u.second:
elif abs(self.slots[slot_index].start - start_time) < 1*u.second:
start_time = self.slots[slot_index].start
end_time = start_time + block.duration
elif np.abs(self.slots[slot_index].end - start_time - block.duration) < 1*u.second:
elif abs(self.slots[slot_index].end - start_time - block.duration) < 1*u.second:
end_time = self.slots[slot_index].end
else:
end_time = start_time + block.duration
Expand Down Expand Up @@ -464,6 +462,12 @@ def _make_schedule(self, blocks):
b._all_constraints = self.constraints
else:
b._all_constraints = self.constraints + b.constraints
# to make sure the scheduler has some constraint to work off of
# and to prevent scheduling of targets below the horizon
if b._all_constraints is None:
b._all_constraints = [AltitudeConstraint(min=0*u.deg)]
elif not any(isinstance(c, AltitudeConstraint) for c in b._all_constraints):
b._all_constraints.append(AltitudeConstraint(min=0*u.deg))
b._duration_offsets = u.Quantity([0*u.second, b.duration/2,
b.duration])
b.observer = self.observer
Expand Down Expand Up @@ -540,6 +544,12 @@ def _make_schedule(self, blocks):
b._all_constraints = self.constraints
else:
b._all_constraints = self.constraints + b.constraints
# to make sure the scheduler has some constraint to work off of
# and to prevent scheduling of targets below the horizon
if b._all_constraints is None:
b._all_constraints = [AltitudeConstraint(min=0*u.deg)]
elif not any(isinstance(c, AltitudeConstraint) for c in b._all_constraints):
b._all_constraints.append(AltitudeConstraint(min=0*u.deg))
b._duration_offsets = u.Quantity([0 * u.second, b.duration / 2, b.duration])
_block_priorities[i] = b.priority
_all_times.append(b.duration)
Expand Down Expand Up @@ -573,11 +583,21 @@ def _make_schedule(self, blocks):
# And then remove any times that are already scheduled
constraint_scores[is_open_time == False] = 0
# Select the most optimal time

# need to leave time around the Block for transitions
if self.transitioner.instrument_reconfig_times:
max_config_time = sum([max(value.values()) for value in
self.transitioner.instrument_reconfig_times.values()])
else:
max_config_time = 0*u.second
if self.transitioner.slew_rate:
buffer_time = (160*u.deg/self.transitioner.slew_rate + max_config_time)
else:
buffer_time = max_config_time
# TODO: make it so that this isn't required to prevent errors in slot creation
total_duration = b.duration + self.gap_time
total_duration = b.duration + buffer_time
# calculate the number of time slots needed for this exposure
_stride_by = np.int(np.ceil(total_duration / time_resolution))
_stride_by = np.int(np.ceil(float(total_duration / time_resolution)))

# Stride the score arrays by that number
_strided_scores = stride_array(constraint_scores, _stride_by)
Expand All @@ -602,10 +622,11 @@ def _make_schedule(self, blocks):

if _is_scheduled:
# set duration such that the Block will fit in the strided array
duration_indices = np.int(np.ceil(b.duration / time_resolution))
duration_indices = np.int(np.ceil(float(b.duration / time_resolution)))
b.duration = duration_indices * time_resolution
# add 1 second to the start time to allow for scheduling at the start of a slot
slot_index = [q for q, slot in enumerate(self.schedule.slots)
if slot.start < new_start_time < slot.end][0]
if slot.start < new_start_time + 1*u.second < slot.end][0]
slots_before = self.schedule.slots[:slot_index]
slots_after = self.schedule.slots[slot_index + 1:]
# this has to remake transitions between already existing ObservingBlocks
Expand All @@ -614,14 +635,14 @@ def _make_schedule(self, blocks):
# make a transition object after the previous ObservingBlock
tb = self.transitioner(self.schedule.slots[slot_index - 1].block, b,
self.schedule.slots[slot_index - 1].end, self.observer)
times_indices = np.int(np.ceil(tb.duration / time_resolution))
times_indices = np.int(np.ceil(float(tb.duration / time_resolution)))
tb.duration = times_indices * time_resolution
start_idx = self.schedule.slots[slot_index - 1].block.end_idx
end_idx = times_indices + start_idx
# this may make some OBs get sub-optimal scheduling, but it closes gaps
# TODO: determine a reasonable range inside which it gets shifted
if tb.duration > new_start_time - tb.start_time or \
np.abs(new_start_time - tb.end_time) < self.gap_time:
if (tb.duration > new_start_time - tb.start_time or
abs(new_start_time - tb.end_time) < self.gap_time):
new_start_time = tb.end_time
start_time_idx = end_idx
self.schedule.insert_slot(tb.start_time, tb)
Expand All @@ -632,13 +653,13 @@ def _make_schedule(self, blocks):
# change the existing TransitionBlock to what it needs to be now
tb = self.transitioner(self.schedule.slots[slot_index - 2].block, b,
self.schedule.slots[slot_index - 2].end, self.observer)
times_indices = np.int(np.ceil(tb.duration / time_resolution))
times_indices = np.int(np.ceil(float(tb.duration / time_resolution)))
tb.duration = times_indices * time_resolution
start_idx = self.schedule.slots[slot_index - 2].block.end_idx
end_idx = times_indices + start_idx
self.schedule.change_slot_block(slot_index - 1, new_block=tb)
if tb.duration > new_start_time - tb.start_time or \
np.abs(new_start_time - tb.end_time) < self.gap_time:
if (tb.duration > new_start_time - tb.start_time or
abs(new_start_time - tb.end_time) < self.gap_time):
new_start_time = tb.end_time
start_time_idx = end_idx
is_open_time[start_idx: end_idx] = False
Expand All @@ -649,7 +670,7 @@ def _make_schedule(self, blocks):
# make a transition object after the new ObservingBlock
tb = self.transitioner(b, self.schedule.slots[slot_index + 1].block,
new_start_time + b.duration, self.observer)
times_indices = np.int(np.ceil(tb.duration / time_resolution))
times_indices = np.int(np.ceil(float(tb.duration / time_resolution)))
tb.duration = times_indices * time_resolution
self.schedule.insert_slot(tb.start_time, tb)
start_idx = end_time_idx
Expand Down Expand Up @@ -687,7 +708,8 @@ def __init__(self, slew_rate=None, instrument_reconfig_times=None):
If not None, gives a mapping from property names to another
dictionary. The second dictionary maps 2-tuples of states to the
time it takes to transition between those states (as an
`~astropy.units.Quantity`).
`~astropy.units.Quantity`), can also take a 'default' key
mapped to a default transition time.
"""
self.slew_rate = slew_rate
self.instrument_reconfig_times = instrument_reconfig_times
Expand Down Expand Up @@ -734,19 +756,25 @@ def __call__(self, oldblock, newblock, start_time, observer):
if components:
return TransitionBlock(components, start_time)
else:
return None
return TransitionBlock.from_duration(0*u.second)

def compute_instrument_transitions(self, oldblock, newblock):
components = {}
for conf_name, old_conf in oldblock.configuration.items():
if conf_name in newblock:
if conf_name in newblock.configuration:
conf_times = self.instrument_reconfig_times.get(conf_name,
None)
if conf_times is not None:
new_conf = newblock[conf_name]
new_conf = newblock.configuration[conf_name]
ctime = conf_times.get((old_conf, new_conf), None)
def_time = conf_times.get('default', None)
if ctime is not None:
s = '{0}:{1} to {2}'.format(conf_name, old_conf,
new_conf)
components[s] = ctime
elif def_time and not old_conf == new_conf:
s = '{0}:{1} to {2}'.format(conf_name, old_conf,
new_conf)
components[s] = def_time

return components
Loading