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

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

import numpy as np 

from lsst.sims.featureScheduler.utils import (empty_observation, set_default_nside, 

hp_in_lsst_fov, read_fields, hp_in_comcam_fov, 

comcamTessellate) 

import healpy as hp 

from lsst.sims.featureScheduler.thomson import xyz2thetaphi, thetaphi2xyz 

from lsst.sims.featureScheduler.detailers import Zero_rot_detailer 

 

__all__ = ['BaseSurvey', 'BaseMarkovDF_survey'] 

 

 

class BaseSurvey(object): 

"""A baseclass for survey objects.  

 

Parameters 

---------- 

basis_functions : list 

List of basis_function objects 

extra_features : list XXX--should this be a dict for clarity? 

List of any additional features the survey may want to use 

e.g., for computing final dither positions. 

ignore_obs : list of str (None) 

If an incoming observation has this string in the note, ignore it. Handy if 

one wants to ignore DD fields or observations requested by self. Take note, 

if a survey is called 'mysurvey23', setting ignore_obs to 'mysurvey2' will 

ignore it because 'mysurvey2' is a substring of 'mysurvey23'. 

detailers : list of lsst.sims.featureScheduler.detailers objects 

The detailers to apply to the list of observations. 

""" 

def __init__(self, basis_functions, extra_features=None, 

ignore_obs=None, survey_name='', nside=None, detailers=None): 

if nside is None: 

nside = set_default_nside() 

if ignore_obs is None: 

ignore_obs = [] 

 

if isinstance(ignore_obs, str): 

ignore_obs = [ignore_obs] 

 

self.nside = nside 

self.survey_name = survey_name 

self.ignore_obs = ignore_obs 

 

self.reward = None 

self.sequence = False # Specifies the survey gives sequence of observations 

self.survey_index = None 

 

self.basis_functions = basis_functions 

 

if extra_features is None: 

self.extra_features = {} 

else: 

self.extra_features = extra_features 

self.reward_checked = False 

 

# Attribute to track if the reward function is up-to-date. 

self.reward_checked = False 

 

# If there's no detailers, add one to set rotation to near zero 

if detailers is None: 

self.detailers = [Zero_rot_detailer(nside=nside)] 

else: 

self.detailers = detailers 

 

def add_observation(self, observation, **kwargs): 

# Check each posible ignore string 

checks = [io not in str(observation['note']) for io in self.ignore_obs] 

# ugh, I think here I have to assume observation is an array and not a dict. 

if all(checks): 

for feature in self.extra_features: 

self.extra_features[feature].add_observation(observation, **kwargs) 

for bf in self.basis_functions: 

bf.add_observation(observation, **kwargs) 

for detailer in self.detailers: 

detailer.add_observation(observation, **kwargs) 

self.reward_checked = False 

 

def _check_feasibility(self, conditions): 

""" 

Check if the survey is feasable in the current conditions 

""" 

for bf in self.basis_functions: 

result = bf.check_feasibility(conditions) 

if not result: 

return result 

return result 

 

def calc_reward_function(self, conditions): 

""" 

Parameters 

---------- 

conditions : lsst.sims.featureScheduler.features.Conditions object 

 

Returns 

------- 

reward : float (or array) 

 

""" 

if self._check_feasability(): 

self.reward = 0 

else: 

# If we don't pass feasability 

self.reward = -np.inf 

 

self.reward_checked = True 

return self.reward 

 

def generate_observations_rough(self, conditions): 

""" 

Returns 

------- 

one of: 

1) None 

2) A list of observations 

""" 

# If the reward function hasn't been updated with the 

# latest info, calculate it 

if not self.reward_checked: 

self.reward = self.calc_reward_function(conditions) 

obs = empty_observation() 

return [obs] 

 

def generate_observations(self, conditions): 

observations = self.generate_observations_rough(conditions) 

for detailer in self.detailers: 

observations = detailer(observations, conditions) 

return observations 

 

def viz_config(self): 

# XXX--zomg, we should have a method that goes through all the objects and 

# makes plots/prints info so there can be a little notebook showing the config! 

pass 

 

 

def rotx(theta, x, y, z): 

"""rotate the x,y,z points theta radians about x axis""" 

sin_t = np.sin(theta) 

cos_t = np.cos(theta) 

xp = x 

yp = y*cos_t+z*sin_t 

zp = -y*sin_t+z*cos_t 

return xp, yp, zp 

 

 

class BaseMarkovDF_survey(BaseSurvey): 

""" A Markov Decision Function survey object. Uses Basis functions to compute a 

final reward function and decide what to observe based on the reward. Includes 

methods for dithering and defaults to dithering nightly. 

 

Parameters 

---------- 

basis_function : list of lsst.sims.featureSchuler.basis_function objects 

 

basis_weights : list of float 

Must be same length as basis_function 

seed : hashable 

Random number seed, used for randomly orienting sky tessellation. 

camera : str ('LSST') 

Should be 'LSST' or 'comcam' 

""" 

def __init__(self, basis_functions, basis_weights, extra_features=None, 

smoothing_kernel=None, 

ignore_obs=None, survey_name='', nside=None, seed=42, 

dither=True, detailers=None, camera='LSST'): 

 

super(BaseMarkovDF_survey, self).__init__(basis_functions=basis_functions, 

extra_features=extra_features, 

ignore_obs=ignore_obs, survey_name=survey_name, 

nside=nside, detailers=detailers) 

 

self.basis_weights = basis_weights 

# Check that weights and basis functions are same length 

if len(basis_functions) != np.size(basis_weights): 

raise ValueError('basis_functions and basis_weights must be same length.') 

 

self.camera = camera 

# Load the OpSim field tesselation and map healpix to fields 

if self.camera == 'LSST': 

self.fields_init = read_fields() 

elif self.camera == 'comcam': 

self.fields_init = comcamTessellate() 

else: 

ValueError('camera %s unknown, should be "LSST" or "comcam"' %camera) 

self.fields = self.fields_init.copy() 

self.hp2fields = np.array([]) 

self._hp2fieldsetup(self.fields['RA'], self.fields['dec']) 

 

if smoothing_kernel is not None: 

self.smoothing_kernel = np.radians(smoothing_kernel) 

else: 

self.smoothing_kernel = None 

 

# Start tracking the night 

self.night = -1 

 

# Set the seed 

np.random.seed(seed) 

self.dither = dither 

 

def _hp2fieldsetup(self, ra, dec, leafsize=100): 

"""Map each healpixel to nearest field. This will only work if healpix 

resolution is higher than field resolution. 

""" 

if self.camera == 'LSST': 

pointing2hpindx = hp_in_lsst_fov(nside=self.nside) 

elif self.camera == 'comcam': 

pointing2hpindx = hp_in_comcam_fov(nside=self.nside) 

 

self.hp2fields = np.zeros(hp.nside2npix(self.nside), dtype=np.int) 

for i in range(len(ra)): 

hpindx = pointing2hpindx(ra[i], dec[i], rotSkyPos=0.) 

self.hp2fields[hpindx] = i 

 

def _spin_fields(self, lon=None, lat=None, lon2=None): 

"""Spin the field tessellation to generate a random orientation 

 

The default field tesselation is rotated randomly in longitude, and then the 

pole is rotated to a random point on the sphere. 

 

Parameters 

---------- 

lon : float (None) 

The amount to initially rotate in longitude (radians). Will use a random value 

between 0 and 2 pi if None (default). 

lat : float (None) 

The amount to rotate in latitude (radians). 

lon2 : float (None) 

The amount to rotate the pole in longitude (radians). 

""" 

if lon is None: 

lon = np.random.rand()*np.pi*2 

if lat is None: 

# Make sure latitude points spread correctly 

# http://mathworld.wolfram.com/SpherePointPicking.html 

lat = np.arccos(2.*np.random.rand() - 1.) 

if lon2 is None: 

lon2 = np.random.rand()*np.pi*2 

# rotate longitude 

ra = (self.fields_init['RA'] + lon) % (2.*np.pi) 

dec = self.fields_init['dec'] + 0 

 

# Now to rotate ra and dec about the x-axis 

x, y, z = thetaphi2xyz(ra, dec+np.pi/2.) 

xp, yp, zp = rotx(lat, x, y, z) 

theta, phi = xyz2thetaphi(xp, yp, zp) 

dec = phi - np.pi/2 

ra = theta + np.pi 

 

# One more RA rotation 

ra = (ra + lon2) % (2.*np.pi) 

 

self.fields['RA'] = ra 

self.fields['dec'] = dec 

# Rebuild the kdtree with the new positions 

# XXX-may be doing some ra,dec to conversions xyz more than needed. 

self._hp2fieldsetup(ra, dec) 

 

def smooth_reward(self): 

"""If we want to smooth the reward function. 

""" 

if hp.isnpixok(self.reward.size): 

self.reward_smooth = hp.sphtfunc.smoothing(self.reward.filled(), 

fwhm=self.smoothing_kernel, 

verbose=False) 

good = ~np.isnan(self.reward_smooth) 

# Round off to prevent strange behavior early on 

self.reward_smooth[good] = np.round(self.reward_smooth[good], decimals=4) 

 

def calc_reward_function(self, conditions): 

self.reward_checked = True 

if self._check_feasibility(conditions): 

self.reward = 0 

indx = np.arange(hp.nside2npix(self.nside)) 

for bf, weight in zip(self.basis_functions, self.basis_weights): 

basis_value = bf(conditions, indx=indx) 

self.reward += basis_value*weight 

 

if np.any(np.isinf(self.reward)): 

self.reward = np.inf 

else: 

# If not feasable, negative infinity reward 

self.reward = -np.inf 

if self.smoothing_kernel is not None: 

self.smooth_reward() 

return self.reward_smooth 

else: 

return self.reward 

 

def generate_observations_rough(self, conditions): 

 

self.reward = self.calc_reward_function(conditions) 

 

# Check if we need to spin the tesselation 

if self.dither & (conditions.night != self.night): 

self._spin_fields() 

self.night = conditions.night.copy() 

 

# XXX Use self.reward to decide what to observe. 

return None