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

1import os 

2import sqlite3 as db 

3import datetime 

4import socket 

5import numpy as np 

6import healpy as hp 

7import pandas as pd 

8import matplotlib.path as mplPath 

9from lsst.sims.utils import _hpid2RaDec, xyz_angular_radius, _buildTree, _xyz_from_ra_dec 

10from lsst.sims.featureScheduler import version 

11from lsst.sims.survey.fields import FieldsDatabase 

12 

13 

14 

15 

16def smallest_signed_angle(a1, a2): 

17 """ 

18 via https://stackoverflow.com/questions/1878907/the-smallest-difference-between-2-angles""" 

19 TwoPi = 2.*np.pi 

20 x = a1 % TwoPi 

21 y = a2 % TwoPi 

22 a = (x - y) % TwoPi 

23 b = (y - x) % TwoPi 

24 result = b+0 

25 alb = np.where(a < b)[0] 

26 result[alb] = -1.*a[alb] 

27 return result 

28 

29class int_rounded(object): 

30 """ 

31 Class to help force comparisons be made on scaled up integers, 

32 preventing machine precision issues cross-platforms 

33 

34 Parameters 

35 ---------- 

36 inval : number-like thing 

37 Some number that we want to compare 

38 scale : float (1e5) 

39 How much to scale inval before rounding and converting to an int. 

40 """ 

41 def __init__(self, inval, scale=1e5): 

42 self.initial = inval 

43 self.value = np.round(inval * scale).astype(int) 

44 self.scale = scale 

45 

46 def __eq__(self, other): 

47 return self.value == other.value 

48 

49 def __ne__(self, other): 

50 return self.value != other.value 

51 

52 def __lt__(self, other): 

53 return self.value < other.value 

54 

55 def __le__(self, other): 

56 return self.value <= other.value 

57 

58 def __gt__(self, other): 

59 return self.value > other.value 

60 

61 def __ge__(self, other): 

62 return self.value >= other.value 

63 

64 def __repr__(self): 

65 return str(self.initial) 

66 

67 def __add__(self, other): 

68 out_scale = np.min([self.scale, other.scale]) 

69 result = int_rounded(self.initial + other.initial, scale=out_scale) 

70 return result 

71 

72 def __sub__(self, other): 

73 out_scale = np.min([self.scale, other.scale]) 

74 result = int_rounded(self.initial - other.initial, scale=out_scale) 

75 return result 

76 

77 def __mul__(self, other): 

78 out_scale = np.min([self.scale, other.scale]) 

79 result = int_rounded(self.initial * other.initial, scale=out_scale) 

80 return result 

81 

82 def __div__(self, other): 

83 out_scale = np.min([self.scale, other.scale]) 

84 result = int_rounded(self.initial / other.initial, scale=out_scale) 

85 return result 

86 

87 

88def set_default_nside(nside=None): 

89 """ 

90 Utility function to set a default nside value across the scheduler. 

91 

92 XXX-there might be a better way to do this. 

93 

94 Parameters 

95 ---------- 

96 nside : int (None) 

97 A valid healpixel nside. 

98 """ 

99 if not hasattr(set_default_nside, 'nside'): 99 ↛ 103line 99 didn't jump to line 103, because the condition on line 99 was never false

100 if nside is None: 100 ↛ 102line 100 didn't jump to line 102, because the condition on line 100 was never false

101 nside = 32 

102 set_default_nside.nside = nside 

103 if nside is not None: 103 ↛ 105line 103 didn't jump to line 105, because the condition on line 103 was never false

104 set_default_nside.nside = nside 

105 return set_default_nside.nside 

106 

107 

108def restore_scheduler(observationId, scheduler, observatory, filename, filter_sched=None): 

109 """Put the scheduler and observatory in the state they were in. Handy for checking reward fucnction 

110 

111 Parameters 

112 ---------- 

113 observationId : int 

114 The ID of the last observation that should be completed 

115 scheduler : lsst.sims.featureScheduler.scheduler object 

116 Scheduler object. 

117 observatory : lsst.sims.featureSchedler.observatory.Model_observatory 

118 The observaotry object 

119 filename : str 

120 The output sqlite dayabase to use 

121 filter_sched : lsst.sims.featureScheduler.scheduler object 

122 The filter scheduler. Note that we don't look up the official end of the previous night, 

123 so there is potential for the loaded filters to not match. 

124 """ 

125 sc = schema_converter() 

126 # load up the observations 

127 observations = sc.opsim2obs(filename) 

128 good_obs = np.where(observations['ID'] <= observationId)[0] 

129 observations = observations[good_obs] 

130 

131 # replay the observations back into the scheduler 

132 for obs in observations: 

133 scheduler.add_observation(obs) 

134 if filter_sched is not None: 

135 filter_sched.add_observation(obs) 

136 

137 if filter_sched is not None: 

138 # Make sure we have mounted the right filters for the night 

139 # XXX--note, this might not be exact, but should work most of the time. 

140 mjd_start_night = np.min(observations['mjd'][np.where(observations['night'] == obs['night'])]) 

141 observatory.mjd = mjd_start_night 

142 conditions = observatory.return_conditions() 

143 filters_needed = filter_sched(conditions) 

144 else: 

145 filters_needed = ['u', 'g', 'r', 'i', 'y'] 

146 

147 # update the observatory 

148 observatory.mjd = obs['mjd'] + observatory.observatory.visit_time(obs)/3600./24. 

149 observatory.observatory.parked = False 

150 observatory.observatory.current_RA_rad = obs['RA'] 

151 observatory.observatory.current_dec_rad = obs['dec'] 

152 observatory.observatory.current_rotSkyPos_rad = obs['rotSkyPos'] 

153 observatory.observatory.cumulative_azimuth_rad = obs['cummTelAz'] 

154 observatory.observatory.mounted_filters = filters_needed 

155 # Note that we haven't updated last_az_rad, etc, but those values should be ignored. 

156 

157 return scheduler, observatory 

158 

159 

160def int_binned_stat(ids, values, statistic=np.mean): 

161 """ 

162 Like scipy.binned_statistic, but for unique int ids 

163 """ 

164 

165 uids = np.unique(ids) 

166 order = np.argsort(ids) 

167 

168 ordered_ids = ids[order] 

169 ordered_values = values[order] 

170 

171 left = np.searchsorted(ordered_ids, uids, side='left') 

172 right = np.searchsorted(ordered_ids, uids, side='right') 

173 

174 stat_results = [] 

175 for le, ri in zip(left, right): 

176 stat_results.append(statistic(ordered_values[le:ri])) 

177 

178 return uids, np.array(stat_results) 

179 

180 

181def gnomonic_project_toxy(RA1, Dec1, RAcen, Deccen): 

182 """Calculate x/y projection of RA1/Dec1 in system with center at RAcen, Deccen. 

183 Input radians. Grabbed from sims_selfcal""" 

184 # also used in Global Telescope Network website 

185 cosc = np.sin(Deccen) * np.sin(Dec1) + np.cos(Deccen) * np.cos(Dec1) * np.cos(RA1-RAcen) 

186 x = np.cos(Dec1) * np.sin(RA1-RAcen) / cosc 

187 y = (np.cos(Deccen)*np.sin(Dec1) - np.sin(Deccen)*np.cos(Dec1)*np.cos(RA1-RAcen)) / cosc 

188 return x, y 

189 

190 

191def gnomonic_project_tosky(x, y, RAcen, Deccen): 

192 """Calculate RA/Dec on sky of object with x/y and RA/Cen of field of view. 

193 Returns Ra/Dec in radians.""" 

194 denom = np.cos(Deccen) - y * np.sin(Deccen) 

195 RA = RAcen + np.arctan2(x, denom) 

196 Dec = np.arctan2(np.sin(Deccen) + y * np.cos(Deccen), np.sqrt(x*x + denom*denom)) 

197 return RA, Dec 

198 

199 

200def match_hp_resolution(in_map, nside_out, UNSEEN2nan=True): 

201 """Utility to convert healpix map resolution if needed and change hp.UNSEEN values to 

202 np.nan. 

203 

204 Parameters 

205 ---------- 

206 in_map : np.array 

207 A valie healpix map 

208 nside_out : int 

209 The desired resolution to convert in_map to 

210 UNSEEN2nan : bool (True) 

211 If True, convert any hp.UNSEEN values to np.nan 

212 """ 

213 current_nside = hp.npix2nside(np.size(in_map)) 

214 if current_nside != nside_out: 

215 out_map = hp.ud_grade(in_map, nside_out=nside_out) 

216 else: 

217 out_map = in_map 

218 if UNSEEN2nan: 

219 out_map[np.where(out_map == hp.UNSEEN)] = np.nan 

220 return out_map 

221 

222 

223def raster_sort(x0, order=['x', 'y'], xbin=1.): 

224 """XXXX--depriciated, use tsp instead. 

225 

226 Do a sort to scan a grid up and down. Simple starting guess to traveling salesman. 

227 

228 Parameters 

229 ---------- 

230 x0 : array 

231 order : list 

232 Keys for the order x0 should be sorted in. 

233 xbin : float (1.) 

234 The binsize to round off the first coordinate into 

235 

236 returns 

237 ------- 

238 array sorted so that it rasters up and down. 

239 """ 

240 coords = x0.copy() 

241 bins = np.arange(coords[order[0]].min()-xbin/2., coords[order[0]].max()+3.*xbin/2., xbin) 

242 # digitize my bins 

243 coords[order[0]] = np.digitize(coords[order[0]], bins) 

244 order1 = np.argsort(coords, order=order) 

245 coords = coords[order1] 

246 places_to_invert = np.where(np.diff(coords[order[-1]]) < 0)[0] 

247 if np.size(places_to_invert) > 0: 

248 places_to_invert += 1 

249 indx = np.arange(coords.size) 

250 index_sorted = np.zeros(indx.size, dtype=int) 

251 index_sorted[0:places_to_invert[0]] = indx[0:places_to_invert[0]] 

252 

253 for i, inv_pt in enumerate(places_to_invert[:-1]): 

254 if i % 2 == 0: 

255 index_sorted[inv_pt:places_to_invert[i+1]] = indx[inv_pt:places_to_invert[i+1]][::-1] 

256 else: 

257 index_sorted[inv_pt:places_to_invert[i+1]] = indx[inv_pt:places_to_invert[i+1]] 

258 

259 if np.size(places_to_invert) % 2 != 0: 

260 index_sorted[places_to_invert[-1]:] = indx[places_to_invert[-1]:][::-1] 

261 else: 

262 index_sorted[places_to_invert[-1]:] = indx[places_to_invert[-1]:] 

263 return order1[index_sorted] 

264 else: 

265 return order1 

266 

267 

268class schema_converter(object): 

269 """ 

270 Record how to convert an observation array to the standard opsim schema 

271 """ 

272 def __init__(self): 

273 # Conversion dictionary, keys are opsim schema, values are observation dtype names 

274 self.convert_dict = {'observationId': 'ID', 'night': 'night', 

275 'observationStartMJD': 'mjd', 

276 'observationStartLST': 'lmst', 'numExposures': 'nexp', 

277 'visitTime': 'visittime', 'visitExposureTime': 'exptime', 

278 'proposalId': 'survey_id', 'fieldId': 'field_id', 

279 'fieldRA': 'RA', 'fieldDec': 'dec', 'altitude': 'alt', 'azimuth': 'az', 

280 'filter': 'filter', 'airmass': 'airmass', 'skyBrightness': 'skybrightness', 

281 'cloud': 'clouds', 'seeingFwhm500': 'FWHM_500', 

282 'seeingFwhmGeom': 'FWHM_geometric', 'seeingFwhmEff': 'FWHMeff', 

283 'fiveSigmaDepth': 'fivesigmadepth', 'slewTime': 'slewtime', 

284 'slewDistance': 'slewdist', 'paraAngle': 'pa', 'rotTelPos': 'rotTelPos', 

285 'rotSkyPos': 'rotSkyPos', 'moonRA': 'moonRA', 

286 'moonDec': 'moonDec', 'moonAlt': 'moonAlt', 'moonAz': 'moonAz', 

287 'moonDistance': 'moonDist', 'moonPhase': 'moonPhase', 

288 'sunAlt': 'sunAlt', 'sunAz': 'sunAz', 'solarElong': 'solarElong', 'note':'note'} 

289 # Column(s) not bothering to remap: 'observationStartTime': None, 

290 self.inv_map = {v: k for k, v in self.convert_dict.items()} 

291 # angles to converts 

292 self.angles_rad2deg = ['fieldRA', 'fieldDec', 'altitude', 'azimuth', 'slewDistance', 

293 'paraAngle', 'rotTelPos', 'rotSkyPos', 'moonRA', 'moonDec', 

294 'moonAlt', 'moonAz', 'moonDistance', 'sunAlt', 'sunAz', 'solarElong', 

295 'cummTelAz'] 

296 # Put LMST into degrees too 

297 self.angles_hours2deg = ['observationStartLST'] 

298 

299 def obs2opsim(self, obs_array, filename=None, info=None, delete_past=False): 

300 """convert an array of observations into a pandas dataframe with Opsim schema 

301 """ 

302 if delete_past: 

303 try: 

304 os.remove(filename) 

305 except OSError: 

306 pass 

307 

308 df = pd.DataFrame(obs_array) 

309 df = df.rename(index=str, columns=self.inv_map) 

310 for colname in self.angles_rad2deg: 

311 df[colname] = np.degrees(df[colname]) 

312 for colname in self.angles_hours2deg: 

313 df[colname] = df[colname] * 360./24. 

314 

315 if filename is not None: 

316 con = db.connect(filename) 

317 df.to_sql('SummaryAllProps', con, index=False) 

318 if info is not None: 

319 df = pd.DataFrame(info) 

320 df.to_sql('info', con) 

321 

322 def opsim2obs(self, filename): 

323 """convert an opsim schema dataframe into an observation array. 

324 """ 

325 

326 con = db.connect(filename) 

327 df = pd.read_sql('select * from SummaryAllProps;', con) 

328 for key in self.angles_rad2deg: 

329 df[key] = np.radians(df[key]) 

330 for key in self.angles_hours2deg: 

331 df[key] = df[key] * 24./360. 

332 

333 df = df.rename(index=str, columns=self.convert_dict) 

334 

335 blank = empty_observation() 

336 final_result = np.empty(df.shape[0], dtype=blank.dtype) 

337 # XXX-ugh, there has to be a better way. 

338 for i, key in enumerate(df.columns): 

339 if key in self.inv_map.keys(): 

340 final_result[key] = df[key].values 

341 

342 return final_result 

343 

344 

345def empty_observation(): 

346 """ 

347 Return a numpy array that could be a handy observation record 

348 

349 XXX: Should this really be "empty visit"? Should we have "visits" made 

350 up of multple "observations" to support multi-exposure time visits? 

351 

352 XXX-Could add a bool flag for "observed". Then easy to track all proposed 

353 observations. Could also add an mjd_min, mjd_max for when an observation should be observed. 

354 That way we could drop things into the queue for DD fields. 

355 

356 XXX--might be nice to add a generic "sched_note" str field, to record any metadata that 

357 would be useful to the scheduler once it's observed. and/or observationID. 

358 

359 Returns 

360 ------- 

361 numpy array 

362 

363 Notes 

364 ----- 

365 The numpy fields have the following structure 

366 RA : float 

367 The Right Acension of the observation (center of the field) (Radians) 

368 dec : float 

369 Declination of the observation (Radians) 

370 mjd : float 

371 Modified Julian Date at the start of the observation (time shutter opens) 

372 exptime : float 

373 Total exposure time of the visit (seconds) 

374 filter : str 

375 The filter used. Should be one of u, g, r, i, z, y. 

376 rotSkyPos : float 

377 The rotation angle of the camera relative to the sky E of N (Radians) 

378 nexp : int 

379 Number of exposures in the visit. 

380 airmass : float 

381 Airmass at the center of the field 

382 FWHMeff : float 

383 The effective seeing FWHM at the center of the field. (arcsec) 

384 skybrightness : float 

385 The surface brightness of the sky background at the center of the 

386 field. (mag/sq arcsec) 

387 night : int 

388 The night number of the observation (days) 

389 flush_by_mjd : float 

390 If we hit this MJD, we should flush the queue and refill it. 

391 cummTelAz : float 

392 The cummulative telescope rotation in azimuth 

393 """ 

394 

395 names = ['ID', 'RA', 'dec', 'mjd', 'flush_by_mjd', 'exptime', 'filter', 'rotSkyPos', 'nexp', 

396 'airmass', 'FWHM_500', 'FWHMeff', 'FWHM_geometric', 'skybrightness', 'night', 

397 'slewtime', 'visittime', 'slewdist', 'fivesigmadepth', 

398 'alt', 'az', 'pa', 'clouds', 'moonAlt', 'sunAlt', 'note', 

399 'field_id', 'survey_id', 'block_id', 

400 'lmst', 'rotTelPos', 'moonAz', 'sunAz', 'sunRA', 'sunDec', 'moonRA', 'moonDec', 

401 'moonDist', 'solarElong', 'moonPhase', 'cummTelAz'] 

402 

403 types = [int, float, float, float, float, float, 'U1', float, int, 

404 float, float, float, float, float, int, 

405 float, float, float, float, 

406 float, float, float, float, float, float, 'U40', 

407 int, int, int, 

408 float, float, float, float, float, float, float, float, 

409 float, float, float, float] 

410 result = np.zeros(1, dtype=list(zip(names, types))) 

411 return result 

412 

413 

414def scheduled_observation(): 

415 """Make an array for pre-scheduling observations 

416 

417 mjd_tol : float 

418 The tolerance on how early an observation can execute (days). 

419 

420 """ 

421 

422 # Standard things from the usual observations 

423 names = ['ID', 'RA', 'dec', 'mjd', 'flush_by_mjd', 'exptime', 'filter', 'rotSkyPos', 'nexp', 

424 'note'] 

425 types = [int, float, float, float, float, float, 'U1', float, float, 'U40'] 

426 names += ['mjd_tol', 'dist_tol', 'alt_min', 'alt_max', 'HA_max', 'HA_min', 'observed'] 

427 types += [float, float, float, float, float, float, bool] 

428 result = np.zeros(1, dtype=list(zip(names, types))) 

429 return result 

430 

431 

432def read_fields(): 

433 """ 

434 Read in the Field coordinates 

435 Returns 

436 ------- 

437 numpy.array 

438 With RA and dec in radians. 

439 """ 

440 query = 'select fieldId, fieldRA, fieldDEC from Field;' 

441 fd = FieldsDatabase() 

442 fields = np.array(list(fd.get_field_set(query))) 

443 # order by field ID 

444 fields = fields[fields[:,0].argsort()] 

445 

446 names = ['RA', 'dec'] 

447 types = [float, float] 

448 result = np.zeros(np.size(fields[:, 1]), dtype=list(zip(names, types))) 

449 result['RA'] = np.radians(fields[:, 1]) 

450 result['dec'] = np.radians(fields[:, 2]) 

451 

452 return result 

453 

454 

455def hp_kd_tree(nside=None, leafsize=100, scale=1e5): 

456 """ 

457 Generate a KD-tree of healpixel locations 

458 

459 Parameters 

460 ---------- 

461 nside : int 

462 A valid healpix nside 

463 leafsize : int (100) 

464 Leafsize of the kdtree 

465 

466 Returns 

467 ------- 

468 tree : scipy kdtree 

469 """ 

470 if nside is None: 

471 nside = set_default_nside() 

472 

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

474 ra, dec = _hpid2RaDec(nside, hpid) 

475 return _buildTree(ra, dec, leafsize, scale=scale) 

476 

477 

478class hp_in_lsst_fov(object): 

479 """ 

480 Return the healpixels within a pointing. A very simple LSST camera model with 

481 no chip/raft gaps. 

482 """ 

483 def __init__(self, nside=None, fov_radius=1.75, scale=1e5): 

484 """ 

485 Parameters 

486 ---------- 

487 fov_radius : float (1.75) 

488 Radius of the filed of view in degrees 

489 """ 

490 if nside is None: 

491 nside = set_default_nside() 

492 

493 self.tree = hp_kd_tree(nside=nside, scale=scale) 

494 self.radius = np.round(xyz_angular_radius(fov_radius)*scale).astype(int) 

495 self.scale = scale 

496 

497 def __call__(self, ra, dec, **kwargs): 

498 """ 

499 Parameters 

500 ---------- 

501 ra : float 

502 RA in radians 

503 dec : float 

504 Dec in radians 

505 

506 Returns 

507 ------- 

508 indx : numpy array 

509 The healpixels that are within the FoV 

510 """ 

511 

512 x, y, z = _xyz_from_ra_dec(np.max(ra), np.max(dec)) 

513 x = np.round(x * self.scale).astype(int) 

514 y = np.round(y * self.scale).astype(int) 

515 z = np.round(z * self.scale).astype(int) 

516 

517 indices = self.tree.query_ball_point((x, y, z), self.radius) 

518 return np.array(indices) 

519 

520 

521class hp_in_comcam_fov(object): 

522 """ 

523 Return the healpixels within a ComCam pointing. Simple camera model 

524 with no chip gaps. 

525 """ 

526 def __init__(self, nside=None, side_length=0.7): 

527 """ 

528 Parameters 

529 ---------- 

530 side_length : float (0.7) 

531 The length of one side of the square field of view (degrees). 

532 """ 

533 if nside is None: 

534 nside = set_default_nside() 

535 self.nside = nside 

536 self.tree = hp_kd_tree(nside=nside) 

537 self.side_length = np.radians(side_length) 

538 self.inner_radius = xyz_angular_radius(side_length/2.) 

539 self.outter_radius = xyz_angular_radius(side_length/2.*np.sqrt(2.)) 

540 # The positions of the raft corners, unrotated 

541 self.corners_x = np.array([-self.side_length/2., -self.side_length/2., self.side_length/2., 

542 self.side_length/2.]) 

543 self.corners_y = np.array([self.side_length/2., -self.side_length/2., -self.side_length/2., 

544 self.side_length/2.]) 

545 

546 def __call__(self, ra, dec, rotSkyPos=0.): 

547 """ 

548 Parameters 

549 ---------- 

550 ra : float 

551 RA in radians 

552 dec : float 

553 Dec in radians 

554 rotSkyPos : float 

555 The rotation angle of the camera in radians 

556 Returns 

557 ------- 

558 indx : numpy array 

559 The healpixels that are within the FoV 

560 """ 

561 x, y, z = _xyz_from_ra_dec(np.max(ra), np.max(dec)) 

562 # Healpixels within the inner circle 

563 indices = self.tree.query_ball_point((x, y, z), self.inner_radius) 

564 # Healpixels withing the outer circle 

565 indices_all = np.array(self.tree.query_ball_point((x, y, z), self.outter_radius)) 

566 indices_to_check = indices_all[np.in1d(indices_all, indices, invert=True)] 

567 

568 cos_rot = np.cos(rotSkyPos) 

569 sin_rot = np.sin(rotSkyPos) 

570 x_rotated = self.corners_x*cos_rot - self.corners_y*sin_rot 

571 y_rotated = self.corners_x*sin_rot + self.corners_y*cos_rot 

572 

573 # Draw the square that we want to check if points are in. 

574 bbPath = mplPath.Path(np.array([[x_rotated[0], y_rotated[0]], 

575 [x_rotated[1], y_rotated[1]], 

576 [x_rotated[2], y_rotated[2]], 

577 [x_rotated[3], y_rotated[3]], 

578 [x_rotated[0], y_rotated[0]]])) 

579 

580 ra_to_check, dec_to_check = _hpid2RaDec(self.nside, indices_to_check) 

581 

582 # Project the indices to check to the tangent plane, see if they fall inside the polygon 

583 x, y = gnomonic_project_toxy(ra_to_check, dec_to_check, ra, dec) 

584 for i, xcheck in enumerate(x): 

585 # I wonder if I can do this all at once rather than a loop? 

586 if bbPath.contains_point((x[i], y[i])): 

587 indices.append(indices_to_check[i]) 

588 

589 return np.array(indices) 

590 

591 

592def run_info_table(observatory, extra_info=None): 

593 """ 

594 Make a little table for recording the information about a run 

595 """ 

596 

597 observatory_info = observatory.get_info() 

598 for key in extra_info: 

599 observatory_info.append([key, extra_info[key]]) 

600 observatory_info = np.array(observatory_info) 

601 

602 n_feature_entries = 4 

603 

604 names = ['Parameter', 'Value'] 

605 dtypes = ['|U200', '|U200'] 

606 result = np.zeros(observatory_info[:, 0].size + n_feature_entries, 

607 dtype=list(zip(names, dtypes))) 

608 

609 # Fill in info about the run 

610 result[0]['Parameter'] = 'Date, ymd' 

611 now = datetime.datetime.now() 

612 result[0]['Value'] = '%i, %i, %i' % (now.year, now.month, now.day) 

613 

614 result[1]['Parameter'] = 'hostname' 

615 result[1]['Value'] = socket.gethostname() 

616 

617 result[2]['Parameter'] = 'featureScheduler version' 

618 result[2]['Value'] = version.__version__ 

619 

620 result[3]['Parameter'] = 'featureScheduler fingerprint' 

621 result[3]['Value'] = version.__fingerprint__ 

622 

623 result[4:]['Parameter'] = observatory_info[:, 0] 

624 result[4:]['Value'] = observatory_info[:, 1] 

625 

626 return result 

627 

628 

629def inrange(inval, minimum=-1., maximum=1.): 

630 """ 

631 Make sure values are within min/max 

632 """ 

633 inval = np.array(inval) 

634 below = np.where(inval < minimum) 

635 inval[below] = minimum 

636 above = np.where(inval > maximum) 

637 inval[above] = maximum 

638 return inval 

639 

640 

641def warm_start(scheduler, observations, mjd_key='mjd'): 

642 """Replay a list of observations into the scheduler 

643 

644 Parameters 

645 ---------- 

646 scheduler : scheduler object 

647 

648 observations : np.array 

649 An array of observation (e.g., from sqlite2observations) 

650 """ 

651 

652 # Check that observations are in order 

653 observations.sort(order=mjd_key) 

654 for observation in observations: 

655 scheduler.add_observation(observation) 

656 

657 return scheduler 

658 

659 

660def season_calc(night, offset=0, modulo=None, max_season=None, season_length=365.25, floor=True): 

661 """ 

662 Compute what season a night is in with possible offset and modulo 

663 

664 using convention that night -365 to 0 is season -1. 

665 

666 Parameters 

667 ---------- 

668 night : int or array 

669 The night we want to convert to a season 

670 offset : float or array (0) 

671 Offset to be applied to night (days) 

672 modulo : int (None) 

673 If the season should be modulated (i.e., so we can get all even years) 

674 (seasons, years w/default season_length) 

675 max_season : int (None) 

676 For any season above this value (before modulo), set to -1 

677 season_length : float (365.25) 

678 How long to consider one season (nights) 

679 floor : bool (True) 

680 If true, take the floor of the season. Otherwise, returns season as a float 

681 """ 

682 if np.size(night) == 1: 

683 night = np.ravel(np.array([night])) 

684 result = night + offset 

685 result = result/season_length 

686 if floor: 

687 result = np.floor(result) 

688 if max_season is not None: 

689 over_indx = np.where(int_rounded(result) >= int_rounded(max_season)) 

690 

691 if modulo is not None: 

692 neg = np.where(int_rounded(result) < int_rounded(0)) 

693 result = result % modulo 

694 result[neg] = -1 

695 if max_season is not None: 

696 result[over_indx] = -1 

697 if floor: 

698 result = result.astype(int) 

699 return result 

700 

701 

702def create_season_offset(nside, sun_RA_rad): 

703 """ 

704 Make an offset map so seasons roll properly 

705 """ 

706 hpindx = np.arange(hp.nside2npix(nside)) 

707 ra, dec = _hpid2RaDec(nside, hpindx) 

708 offset = ra - sun_RA_rad + 2.*np.pi 

709 offset = offset % (np.pi*2) 

710 offset = offset * 365.25/(np.pi*2) 

711 offset = -offset - 365.25 

712 return offset 

713 

714 

715class TargetoO(object): 

716 """Class to hold information about a target of opportunity object 

717 

718 Parameters 

719 ---------- 

720 tooid : int 

721 Unique ID for the ToO. 

722 footprints : np.array 

723 np.array healpix maps. 1 for areas to observe, 0 for no observe. 

724 mjd_start : float 

725 The MJD the ToO starts 

726 duration : float 

727 Duration of the ToO (days). 

728 """ 

729 def __init__(self, tooid, footprint, mjd_start, duration): 

730 self.footprint = footprint 

731 self.duration = duration 

732 self.id = tooid 

733 self.mjd_start = mjd_start 

734 

735 

736class Sim_targetoO_server(object): 

737 """Wrapper to deliver a targetoO object at the right time 

738 """ 

739 

740 def __init__(self, targetoO_list): 

741 self.targetoO_list = targetoO_list 

742 self.mjd_starts = np.array([too.mjd_start for too in self.targetoO_list]) 

743 durations = np.array([too.duration for too in self.targetoO_list]) 

744 self.mjd_ends = self.mjd_starts + durations 

745 

746 def __call__(self, mjd): 

747 in_range = np.where((mjd > self.mjd_starts) & (mjd < self.mjd_ends))[0] 

748 result = None 

749 if in_range.size > 0: 

750 result = [self.targetoO_list[i] for i in in_range] 

751 return result