Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

from __future__ import absolute_import 

from builtins import object 

import numpy as np 

import healpy as hp 

from lsst.sims.utils import _hpid2RaDec 

from lsst.sims.featureScheduler.utils import hp_in_lsst_fov, set_default_nside, hp_in_comcam_fov 

import logging 

 

 

__all__ = ['Core_scheduler'] 

 

 

class Core_scheduler(object): 

"""Core scheduler that takes completed observations and observatory status and requests observations 

 

Parameters 

---------- 

surveys : list (or list of lists) of lsst.sims.featureScheduler.survey objects 

A list of surveys to consider. If multiple surveys return the same highest 

reward value, the survey at the earliest position in the list will be selected. 

Can also be a list of lists to make heirarchical priorities. 

nside : int 

A HEALpix nside value. 

camera : str ('LSST') 

Which camera to use for computing overlapping HEALpixels for an observation. 

Can be 'LSST' or 'comcam' 

conditions : a lsst.sims.featureScheduler.features.Conditions object (None) 

An object that hold the current conditions and derived values (e.g., 5-sigma depth). Will 

generate a default if set to None. 

""" 

 

def __init__(self, surveys, nside=None, camera='LSST'): 

""" 

Parameters 

---------- 

surveys : list (or list of lists) of lsst.sims.featureScheduler.survey objects 

A list of surveys to consider. If multiple surveys return the same highest 

reward value, the survey at the earliest position in the list will be selected. 

Can also be a list of lists to make heirarchical priorities. 

nside : int 

A HEALpix nside value. 

camera : str ('LSST') 

Which camera to use for computing overlapping HEALpixels for an observation. 

Can be 'LSST' or 'comcam' 

""" 

if nside is None: 

nside = set_default_nside() 

 

self.log = logging.getLogger("Core_scheduler") 

# initialize a queue of observations to request 

self.queue = [] 

# Are the observations in the queue part of a sequence. 

self.queue_is_sequence = False 

# The indices of self.survey_lists that provided the last addition(s) to the queue 

self.survey_index = [None, None] 

 

# If we have a list of survey objects, convert to list-of-lists 

if isinstance(surveys[0], list): 

self.survey_lists = surveys 

else: 

self.survey_lists = [surveys] 

self.nside = nside 

hpid = np.arange(hp.nside2npix(nside)) 

self.ra_grid_rad, self.dec_grid_rad = _hpid2RaDec(nside, hpid) 

# Should just make camera a class that takes a pointing and returns healpix indices 

if camera == 'LSST': 

self.pointing2hpindx = hp_in_lsst_fov(nside=nside) 

elif camera == 'comcam': 

self.pointing2hpindx = hp_in_comcam_fov(nside=nside) 

else: 

raise ValueError('camera %s not implamented' % camera) 

 

# keep track of how many observations get flushed from the queue 

self.flushed = 0 

 

def flush_queue(self): 

"""" 

Like it sounds, clear any currently queued desired observations. 

""" 

self.queue = [] 

self.queue_is_sequence = False 

self.survey_index = [None, None] 

 

def add_observation(self, observation): 

""" 

Record a completed observation and update features accordingly. 

 

Parameters 

---------- 

observation : dict-like 

An object that contains the relevant information about a 

completed observation (e.g., mjd, ra, dec, filter, rotation angle, etc) 

""" 

 

# Find the healpixel centers that are included in an observation 

indx = self.pointing2hpindx(observation['RA'], observation['dec'], 

rotSkyPos=observation['rotSkyPos']) 

for surveys in self.survey_lists: 

for survey in surveys: 

survey.add_observation(observation, indx=indx) 

 

def update_conditions(self, conditions_in): 

""" 

Parameters 

---------- 

conditions : dict-like 

The current conditions of the telescope (pointing position, loaded filters, cloud-mask, etc) 

""" 

# Add the current queue and scheduled queue to the conditions 

self.conditions = conditions_in 

# put the local queue in the conditions 

self.conditions.queue = self.queue 

 

# XXX---TODO: Could potentially put more complicated info from all 

# the surveys in the conditions object here. e.g., when a DDF plans to next request 

# observations. 

 

def _check_queue_mjd_only(self, mjd): 

""" 

Check if there are things in the queue that can be executed using only MJD and not full conditions. 

This is primarly used by sim_runner to reduce calls calculating updated conditions when they are not 

needed. 

""" 

result = False 

if len(self.queue) > 0: 

if (mjd < self.queue[0]['flush_by_mjd']) | (self.queue[0]['flush_by_mjd'] == 0): 

result = True 

return result 

 

def request_observation(self): 

""" 

Ask the scheduler what it wants to observe next 

 

Returns 

------- 

observation object (ra,dec,filter,rotangle) 

""" 

if len(self.queue) == 0: 

self._fill_queue() 

 

if len(self.queue) == 0: 

return None 

else: 

# If the queue has gone stale, flush and refill. Zero means no flush_by was set. 

if (self.conditions.mjd > self.queue[0]['flush_by_mjd']) & (self.queue[0]['flush_by_mjd'] != 0): 

self.flushed += len(self.queue) 

self.flush_queue() 

self._fill_queue() 

if len(self.queue) == 0: 

return None 

observation = self.queue.pop(0) 

if self.queue_is_sequence: 

if self.survey_lists[self.survey_index[0]][self.survey_index[1]].check_continue(observation, self.conditions): 

return observation 

else: 

self.log.warning('Sequence interrupted! Cleaning queue!') 

self.flush_queue() 

self._fill_queue() 

if len(self.queue) == 0: 

return None 

else: 

observation = self.queue.pop(0) 

return observation 

else: 

return observation 

 

def _fill_queue(self): 

""" 

Compute reward function for each survey and fill the observing queue with the 

observations from the highest reward survey. 

""" 

 

rewards = None 

for ns, surveys in enumerate(self.survey_lists): 

rewards = np.zeros(len(surveys)) 

for i, survey in enumerate(surveys): 

rewards[i] = np.nanmax(survey.calc_reward_function(self.conditions)) 

# If we have a good reward, break out of the loop 

if np.nanmax(rewards) > -np.inf: 

self.survey_index[0] = ns 

break 

if (np.nanmax(rewards) == -np.inf) | (np.isnan(np.nanmax(rewards))): 

self.flush_queue() 

else: 

to_fix = np.where(np.isnan(rewards) == True) 

rewards[to_fix] = -np.inf 

# Take a min here, so the surveys will be executed in the order they are 

# entered if there is a tie. 

self.survey_index[1] = np.min(np.where(rewards == np.nanmax(rewards))) 

 

# Survey return list of observations 

result = self.survey_lists[self.survey_index[0]][self.survey_index[1]].generate_observations(self.conditions) 

self.queue = result 

self.queue_is_sequence = self.survey_lists[self.survey_index[0]][self.survey_index[1]].sequence 

 

if len(self.queue) == 0: 

self.log.warning('Failed to fill queue')