Coverage for python/lsst/sims/featureScheduler/utils/utils.py : 17%

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
9import logging
10from lsst.sims.utils import _hpid2RaDec, xyz_angular_radius, _buildTree, _xyz_from_ra_dec
11from lsst.sims.featureScheduler import version
12from lsst.sims.survey.fields import FieldsDatabase
14log = logging.getLogger(__name__)
17class int_rounded(object):
18 """
19 Class to help force comparisons be made on scaled up integers,
20 preventing machine precision issues cross-platforms
22 Parameters
23 ----------
24 inval : number-like thing
25 Some number that we want to compare
26 scale : float (1e5)
27 How much to scale inval before rounding and converting to an int.
28 """
29 def __init__(self, inval, scale=1e5):
30 self.initial = inval
31 self.value = np.round(inval * scale).astype(int)
32 self.scale = scale
34 def __eq__(self, other):
35 return self.value == other.value
37 def __ne__(self, other):
38 return self.value != other.value
40 def __lt__(self, other):
41 return self.value < other.value
43 def __le__(self, other):
44 return self.value <= other.value
46 def __gt__(self, other):
47 return self.value > other.value
49 def __ge__(self, other):
50 return self.value >= other.value
52 def __repr__(self):
53 return str(self.initial)
55 def __add__(self, other):
56 out_scale = np.min([self.scale, other.scale])
57 result = int_rounded(self.initial + other.initial, scale=out_scale)
58 return result
60 def __sub__(self, other):
61 out_scale = np.min([self.scale, other.scale])
62 result = int_rounded(self.initial - other.initial, scale=out_scale)
63 return result
65 def __mul__(self, other):
66 out_scale = np.min([self.scale, other.scale])
67 result = int_rounded(self.initial * other.initial, scale=out_scale)
68 return result
70 def __div__(self, other):
71 out_scale = np.min([self.scale, other.scale])
72 result = int_rounded(self.initial / other.initial, scale=out_scale)
73 return result
76def set_default_nside(nside=None):
77 """
78 Utility function to set a default nside value across the scheduler.
80 XXX-there might be a better way to do this.
82 Parameters
83 ----------
84 nside : int (None)
85 A valid healpixel nside.
86 """
87 if not hasattr(set_default_nside, 'nside'): 87 ↛ 91line 87 didn't jump to line 91, because the condition on line 87 was never false
88 if nside is None: 88 ↛ 90line 88 didn't jump to line 90, because the condition on line 88 was never false
89 nside = 32
90 set_default_nside.nside = nside
91 if nside is not None: 91 ↛ 93line 91 didn't jump to line 93, because the condition on line 91 was never false
92 set_default_nside.nside = nside
93 return set_default_nside.nside
96def approx_altaz2pa(alt_rad, az_rad, latitude_rad):
97 """
98 A fast calculation of parallactic angle
99 XXX--could move this to lsst.sims.utils.approxCoordTransforms.py
100 Parameters
101 ----------
102 alt_rad : float
103 Altitude (radians)
104 az_rad : float
105 Azimuth (radians)
106 latitude_rad : float
107 The latitude of the observatory (radians)
108 """
110 y = np.sin(-az_rad)*np.cos(latitude_rad)
111 x = np.cos(alt_rad)*np.sin(latitude_rad) - np.sin(alt_rad)*np.cos(latitude_rad)*np.cos(-az_rad)
112 pa = np.arctan2(y, x)
113 # Make it run from 0-360 deg instead of of -180 to 180
114 pa = pa % (2.*np.pi)
115 return pa
118def int_binned_stat(ids, values, statistic=np.mean):
119 """
120 Like scipy.binned_statistic, but for unique int ids
121 """
123 uids = np.unique(ids)
124 order = np.argsort(ids)
126 ordered_ids = ids[order]
127 ordered_values = values[order]
129 left = np.searchsorted(ordered_ids, uids, side='left')
130 right = np.searchsorted(ordered_ids, uids, side='right')
132 stat_results = []
133 for le, ri in zip(left, right):
134 stat_results.append(statistic(ordered_values[le:ri]))
136 return uids, np.array(stat_results)
139def gnomonic_project_toxy(RA1, Dec1, RAcen, Deccen):
140 """Calculate x/y projection of RA1/Dec1 in system with center at RAcen, Deccen.
141 Input radians. Grabbed from sims_selfcal"""
142 # also used in Global Telescope Network website
143 cosc = np.sin(Deccen) * np.sin(Dec1) + np.cos(Deccen) * np.cos(Dec1) * np.cos(RA1-RAcen)
144 x = np.cos(Dec1) * np.sin(RA1-RAcen) / cosc
145 y = (np.cos(Deccen)*np.sin(Dec1) - np.sin(Deccen)*np.cos(Dec1)*np.cos(RA1-RAcen)) / cosc
146 return x, y
149def gnomonic_project_tosky(x, y, RAcen, Deccen):
150 """Calculate RA/Dec on sky of object with x/y and RA/Cen of field of view.
151 Returns Ra/Dec in radians."""
152 denom = np.cos(Deccen) - y * np.sin(Deccen)
153 RA = RAcen + np.arctan2(x, denom)
154 Dec = np.arctan2(np.sin(Deccen) + y * np.cos(Deccen), np.sqrt(x*x + denom*denom))
155 return RA, Dec
158def match_hp_resolution(in_map, nside_out, UNSEEN2nan=True):
159 """Utility to convert healpix map resolution if needed and change hp.UNSEEN values to
160 np.nan.
162 Parameters
163 ----------
164 in_map : np.array
165 A valie healpix map
166 nside_out : int
167 The desired resolution to convert in_map to
168 UNSEEN2nan : bool (True)
169 If True, convert any hp.UNSEEN values to np.nan
170 """
171 current_nside = hp.npix2nside(np.size(in_map))
172 if current_nside != nside_out:
173 out_map = hp.ud_grade(in_map, nside_out=nside_out)
174 else:
175 out_map = in_map
176 if UNSEEN2nan:
177 out_map[np.where(out_map == hp.UNSEEN)] = np.nan
178 return out_map
181def raster_sort(x0, order=['x', 'y'], xbin=1.):
182 """XXXX--depriciated, use tsp instead.
184 Do a sort to scan a grid up and down. Simple starting guess to traveling salesman.
186 Parameters
187 ----------
188 x0 : array
189 order : list
190 Keys for the order x0 should be sorted in.
191 xbin : float (1.)
192 The binsize to round off the first coordinate into
194 returns
195 -------
196 array sorted so that it rasters up and down.
197 """
198 coords = x0.copy()
199 bins = np.arange(coords[order[0]].min()-xbin/2., coords[order[0]].max()+3.*xbin/2., xbin)
200 # digitize my bins
201 coords[order[0]] = np.digitize(coords[order[0]], bins)
202 order1 = np.argsort(coords, order=order)
203 coords = coords[order1]
204 places_to_invert = np.where(np.diff(coords[order[-1]]) < 0)[0]
205 if np.size(places_to_invert) > 0:
206 places_to_invert += 1
207 indx = np.arange(coords.size)
208 index_sorted = np.zeros(indx.size, dtype=int)
209 index_sorted[0:places_to_invert[0]] = indx[0:places_to_invert[0]]
211 for i, inv_pt in enumerate(places_to_invert[:-1]):
212 if i % 2 == 0:
213 index_sorted[inv_pt:places_to_invert[i+1]] = indx[inv_pt:places_to_invert[i+1]][::-1]
214 else:
215 index_sorted[inv_pt:places_to_invert[i+1]] = indx[inv_pt:places_to_invert[i+1]]
217 if np.size(places_to_invert) % 2 != 0:
218 index_sorted[places_to_invert[-1]:] = indx[places_to_invert[-1]:][::-1]
219 else:
220 index_sorted[places_to_invert[-1]:] = indx[places_to_invert[-1]:]
221 return order1[index_sorted]
222 else:
223 return order1
226class schema_converter(object):
227 """
228 Record how to convert an observation array to the standard opsim schema
229 """
230 def __init__(self):
231 # Conversion dictionary, keys are opsim schema, values are observation dtype names
232 self.convert_dict = {'observationId': 'ID', 'night': 'night',
233 'observationStartMJD': 'mjd',
234 'observationStartLST': 'lmst', 'numExposures': 'nexp',
235 'visitTime': 'visittime', 'visitExposureTime': 'exptime',
236 'proposalId': 'survey_id', 'fieldId': 'field_id',
237 'fieldRA': 'RA', 'fieldDec': 'dec', 'altitude': 'alt', 'azimuth': 'az',
238 'filter': 'filter', 'airmass': 'airmass', 'skyBrightness': 'skybrightness',
239 'cloud': 'clouds', 'seeingFwhm500': 'FWHM_500',
240 'seeingFwhmGeom': 'FWHM_geometric', 'seeingFwhmEff': 'FWHMeff',
241 'fiveSigmaDepth': 'fivesigmadepth', 'slewTime': 'slewtime',
242 'slewDistance': 'slewdist', 'paraAngle': 'pa', 'rotTelPos': 'rotTelPos',
243 'rotSkyPos': 'rotSkyPos', 'moonRA': 'moonRA',
244 'moonDec': 'moonDec', 'moonAlt': 'moonAlt', 'moonAz': 'moonAz',
245 'moonDistance': 'moonDist', 'moonPhase': 'moonPhase',
246 'sunAlt': 'sunAlt', 'sunAz': 'sunAz', 'solarElong': 'solarElong', 'note':'note'}
247 # Column(s) not bothering to remap: 'observationStartTime': None,
248 self.inv_map = {v: k for k, v in self.convert_dict.items()}
249 # angles to converts
250 self.angles_rad2deg = ['fieldRA', 'fieldDec', 'altitude', 'azimuth', 'slewDistance',
251 'paraAngle', 'rotTelPos', 'rotSkyPos', 'moonRA', 'moonDec',
252 'moonAlt', 'moonAz', 'moonDistance', 'sunAlt', 'sunAz', 'solarElong']
253 # Put LMST into degrees too
254 self.angles_hours2deg = ['observationStartLST']
256 def obs2opsim(self, obs_array, filename=None, info=None, delete_past=False):
257 """convert an array of observations into a pandas dataframe with Opsim schema
258 """
259 if delete_past:
260 try:
261 os.remove(filename)
262 except OSError:
263 pass
265 df = pd.DataFrame(obs_array)
266 df = df.rename(index=str, columns=self.inv_map)
267 for colname in self.angles_rad2deg:
268 df[colname] = np.degrees(df[colname])
269 for colname in self.angles_hours2deg:
270 df[colname] = df[colname] * 360./24.
272 if filename is not None:
273 con = db.connect(filename)
274 df.to_sql('SummaryAllProps', con, index=False)
275 if info is not None:
276 df = pd.DataFrame(info)
277 df.to_sql('info', con)
279 def opsim2obs(self, filename):
280 """convert an opsim schema dataframe into an observation array.
281 """
283 con = db.connect(filename)
284 df = pd.read_sql('select * from SummaryAllProps;', con)
285 for key in self.angles_rad2deg:
286 df[key] = np.radians(df[key])
287 for key in self.angles_hours2deg:
288 df[key] = df[key] * 24./360.
290 df = df.rename(index=str, columns=self.convert_dict)
292 blank = empty_observation()
293 final_result = np.empty(df.shape[0], dtype=blank.dtype)
294 # XXX-ugh, there has to be a better way.
295 for i, key in enumerate(df.columns):
296 if key in self.inv_map.keys():
297 final_result[key] = df[key].values
299 return final_result
302def empty_observation():
303 """
304 Return a numpy array that could be a handy observation record
306 XXX: Should this really be "empty visit"? Should we have "visits" made
307 up of multple "observations" to support multi-exposure time visits?
309 XXX-Could add a bool flag for "observed". Then easy to track all proposed
310 observations. Could also add an mjd_min, mjd_max for when an observation should be observed.
311 That way we could drop things into the queue for DD fields.
313 XXX--might be nice to add a generic "sched_note" str field, to record any metadata that
314 would be useful to the scheduler once it's observed. and/or observationID.
316 Returns
317 -------
318 numpy array
320 Notes
321 -----
322 The numpy fields have the following structure
323 RA : float
324 The Right Acension of the observation (center of the field) (Radians)
325 dec : float
326 Declination of the observation (Radians)
327 mjd : float
328 Modified Julian Date at the start of the observation (time shutter opens)
329 exptime : float
330 Total exposure time of the visit (seconds)
331 filter : str
332 The filter used. Should be one of u, g, r, i, z, y.
333 rotSkyPos : float
334 The rotation angle of the camera relative to the sky E of N (Radians)
335 nexp : int
336 Number of exposures in the visit.
337 airmass : float
338 Airmass at the center of the field
339 FWHMeff : float
340 The effective seeing FWHM at the center of the field. (arcsec)
341 skybrightness : float
342 The surface brightness of the sky background at the center of the
343 field. (mag/sq arcsec)
344 night : int
345 The night number of the observation (days)
346 flush_by_mjd : float
347 If we hit this MJD, we should flush the queue and refill it.
348 """
350 names = ['ID', 'RA', 'dec', 'mjd', 'flush_by_mjd', 'exptime', 'filter', 'rotSkyPos', 'nexp',
351 'airmass', 'FWHM_500', 'FWHMeff', 'FWHM_geometric', 'skybrightness', 'night',
352 'slewtime', 'visittime', 'slewdist', 'fivesigmadepth',
353 'alt', 'az', 'pa', 'clouds', 'moonAlt', 'sunAlt', 'note',
354 'field_id', 'survey_id', 'block_id',
355 'lmst', 'rotTelPos', 'moonAz', 'sunAz', 'sunRA', 'sunDec', 'moonRA', 'moonDec',
356 'moonDist', 'solarElong', 'moonPhase']
358 types = [int, float, float, float, float, float, 'U1', float, int,
359 float, float, float, float, float, int,
360 float, float, float, float,
361 float, float, float, float, float, float, 'U40',
362 int, int, int,
363 float, float, float, float, float, float, float, float,
364 float, float, float]
365 result = np.zeros(1, dtype=list(zip(names, types)))
366 return result
369def obs_to_fbsobs(obs):
370 """
371 converts an Observation from the Driver (which is a normal python class)
372 to an observation for the feature based scheduler (a numpy ndarray).
373 """
375 fbsobs = empty_observation()
376 fbsobs['RA'] = obs.ra_rad
377 fbsobs['dec'] = obs.dec_rad
378 log.debug('Observation MJD: %.4f', obs.observation_start_mjd)
379 fbsobs['mjd'] = obs.observation_start_mjd
380 fbsobs['exptime'] = obs.exp_time
381 fbsobs['filter'] = obs.filter
382 fbsobs['rotSkyPos'] = obs.ang_rad
383 fbsobs['nexp'] = obs.num_exp
384 fbsobs['airmass'] = obs.airmass
385 fbsobs['FWHMeff'] = obs.seeing_fwhm_eff
386 fbsobs['FWHM_geometric'] = obs.seeing_fwhm_geom
387 fbsobs['skybrightness'] = obs.sky_brightness
388 fbsobs['night'] = obs.night
389 fbsobs['slewtime'] = obs.slewtime
390 fbsobs['fivesigmadepth'] = obs.five_sigma_depth
391 fbsobs['alt'] = obs.alt_rad
392 fbsobs['az'] = obs.az_rad
393 fbsobs['clouds'] = obs.cloud
394 fbsobs['moonAlt'] = obs.moon_alt
395 fbsobs['sunAlt'] = obs.sun_alt
396 fbsobs['note'] = obs.note
397 fbsobs['field_id'] = obs.fieldid
398 fbsobs['survey_id'] = obs.propid_list[0]
400 return fbsobs
403def empty_scheduled_observation():
404 """
405 Same as empty observation, but with mjd_min, mjd_max columns
406 """
407 start = empty_observation()
408 names = start.dtype.names
409 types = start.dtype.types
410 names.extend(['mjd_min', 'mjd_max'])
411 types.extend([float, float])
413 result = np.zeros(1, dtype=list(zip(names, types)))
414 return result
417def read_fields():
418 """
419 Read in the Field coordinates
420 Returns
421 -------
422 numpy.array
423 With RA and dec in radians.
424 """
425 query = 'select fieldId, fieldRA, fieldDEC from Field;'
426 fd = FieldsDatabase()
427 fields = np.array(list(fd.get_field_set(query)))
428 # order by field ID
429 fields = fields[fields[:,0].argsort()]
431 names = ['RA', 'dec']
432 types = [float, float]
433 result = np.zeros(np.size(fields[:, 1]), dtype=list(zip(names, types)))
434 result['RA'] = np.radians(fields[:, 1])
435 result['dec'] = np.radians(fields[:, 2])
437 return result
440def hp_kd_tree(nside=None, leafsize=100, scale=1e5):
441 """
442 Generate a KD-tree of healpixel locations
444 Parameters
445 ----------
446 nside : int
447 A valid healpix nside
448 leafsize : int (100)
449 Leafsize of the kdtree
451 Returns
452 -------
453 tree : scipy kdtree
454 """
455 if nside is None:
456 nside = set_default_nside()
458 hpid = np.arange(hp.nside2npix(nside))
459 ra, dec = _hpid2RaDec(nside, hpid)
460 return _buildTree(ra, dec, leafsize, scale=scale)
463class hp_in_lsst_fov(object):
464 """
465 Return the healpixels within a pointing. A very simple LSST camera model with
466 no chip/raft gaps.
467 """
468 def __init__(self, nside=None, fov_radius=1.75, scale=1e5):
469 """
470 Parameters
471 ----------
472 fov_radius : float (1.75)
473 Radius of the filed of view in degrees
474 """
475 if nside is None:
476 nside = set_default_nside()
478 self.tree = hp_kd_tree(nside=nside, scale=scale)
479 self.radius = np.round(xyz_angular_radius(fov_radius)*scale).astype(int)
480 self.scale = scale
482 def __call__(self, ra, dec, **kwargs):
483 """
484 Parameters
485 ----------
486 ra : float
487 RA in radians
488 dec : float
489 Dec in radians
491 Returns
492 -------
493 indx : numpy array
494 The healpixels that are within the FoV
495 """
497 x, y, z = _xyz_from_ra_dec(np.max(ra), np.max(dec))
498 x = np.round(x * self.scale).astype(int)
499 y = np.round(y * self.scale).astype(int)
500 z = np.round(z * self.scale).astype(int)
502 indices = self.tree.query_ball_point((x, y, z), self.radius)
503 return np.array(indices)
506class hp_in_comcam_fov(object):
507 """
508 Return the healpixels within a ComCam pointing. Simple camera model
509 with no chip gaps.
510 """
511 def __init__(self, nside=None, side_length=0.7):
512 """
513 Parameters
514 ----------
515 side_length : float (0.7)
516 The length of one side of the square field of view (degrees).
517 """
518 if nside is None:
519 nside = set_default_nside()
520 self.nside = nside
521 self.tree = hp_kd_tree(nside=nside)
522 self.side_length = np.radians(side_length)
523 self.inner_radius = xyz_angular_radius(side_length/2.)
524 self.outter_radius = xyz_angular_radius(side_length/2.*np.sqrt(2.))
525 # The positions of the raft corners, unrotated
526 self.corners_x = np.array([-self.side_length/2., -self.side_length/2., self.side_length/2.,
527 self.side_length/2.])
528 self.corners_y = np.array([self.side_length/2., -self.side_length/2., -self.side_length/2.,
529 self.side_length/2.])
531 def __call__(self, ra, dec, rotSkyPos=0.):
532 """
533 Parameters
534 ----------
535 ra : float
536 RA in radians
537 dec : float
538 Dec in radians
539 rotSkyPos : float
540 The rotation angle of the camera in radians
541 Returns
542 -------
543 indx : numpy array
544 The healpixels that are within the FoV
545 """
546 x, y, z = _xyz_from_ra_dec(np.max(ra), np.max(dec))
547 # Healpixels within the inner circle
548 indices = self.tree.query_ball_point((x, y, z), self.inner_radius)
549 # Healpixels withing the outer circle
550 indices_all = np.array(self.tree.query_ball_point((x, y, z), self.outter_radius))
551 indices_to_check = indices_all[np.in1d(indices_all, indices, invert=True)]
553 cos_rot = np.cos(rotSkyPos)
554 sin_rot = np.sin(rotSkyPos)
555 x_rotated = self.corners_x*cos_rot - self.corners_y*sin_rot
556 y_rotated = self.corners_x*sin_rot + self.corners_y*cos_rot
558 # Draw the square that we want to check if points are in.
559 bbPath = mplPath.Path(np.array([[x_rotated[0], y_rotated[0]],
560 [x_rotated[1], y_rotated[1]],
561 [x_rotated[2], y_rotated[2]],
562 [x_rotated[3], y_rotated[3]],
563 [x_rotated[0], y_rotated[0]]]))
565 ra_to_check, dec_to_check = _hpid2RaDec(self.nside, indices_to_check)
567 # Project the indices to check to the tangent plane, see if they fall inside the polygon
568 x, y = gnomonic_project_toxy(ra_to_check, dec_to_check, ra, dec)
569 for i, xcheck in enumerate(x):
570 # I wonder if I can do this all at once rather than a loop?
571 if bbPath.contains_point((x[i], y[i])):
572 indices.append(indices_to_check[i])
574 return np.array(indices)
577def run_info_table(observatory, extra_info=None):
578 """
579 Make a little table for recording the information about a run
580 """
582 observatory_info = observatory.get_info()
583 for key in extra_info:
584 observatory_info.append([key, extra_info[key]])
585 observatory_info = np.array(observatory_info)
587 n_feature_entries = 4
589 names = ['Parameter', 'Value']
590 dtypes = ['|U200', '|U200']
591 result = np.zeros(observatory_info[:, 0].size + n_feature_entries,
592 dtype=list(zip(names, dtypes)))
594 # Fill in info about the run
595 result[0]['Parameter'] = 'Date, ymd'
596 now = datetime.datetime.now()
597 result[0]['Value'] = '%i, %i, %i' % (now.year, now.month, now.day)
599 result[1]['Parameter'] = 'hostname'
600 result[1]['Value'] = socket.gethostname()
602 result[2]['Parameter'] = 'featureScheduler version'
603 result[2]['Value'] = version.__version__
605 result[3]['Parameter'] = 'featureScheduler fingerprint'
606 result[3]['Value'] = version.__fingerprint__
608 result[4:]['Parameter'] = observatory_info[:, 0]
609 result[4:]['Value'] = observatory_info[:, 1]
611 return result
614def inrange(inval, minimum=-1., maximum=1.):
615 """
616 Make sure values are within min/max
617 """
618 inval = np.array(inval)
619 below = np.where(inval < minimum)
620 inval[below] = minimum
621 above = np.where(inval > maximum)
622 inval[above] = maximum
623 return inval
626def warm_start(scheduler, observations, mjd_key='mjd'):
627 """Replay a list of observations into the scheduler
629 Parameters
630 ----------
631 scheduler : scheduler object
633 observations : np.array
634 An array of observation (e.g., from sqlite2observations)
635 """
637 # Check that observations are in order
638 observations.sort(order=mjd_key)
639 for observation in observations:
640 scheduler.add_observation(observation)
642 return scheduler
645def season_calc(night, offset=0, modulo=None, max_season=None, season_length=365.25, floor=True):
646 """
647 Compute what season a night is in with possible offset and modulo
649 using convention that night -365 to 0 is season -1.
651 Parameters
652 ----------
653 night : int or array
654 The night we want to convert to a season
655 offset : float or array (0)
656 Offset to be applied to night (days)
657 modulo : int (None)
658 If the season should be modulated (i.e., so we can get all even years)
659 (seasons, years w/default season_length)
660 max_season : int (None)
661 For any season above this value (before modulo), set to -1
662 season_length : float (365.25)
663 How long to consider one season (nights)
664 floor : bool (True)
665 If true, take the floor of the season. Otherwise, returns season as a float
666 """
667 if np.size(night) == 1:
668 night = np.ravel(np.array([night]))
669 result = night + offset
670 result = result/season_length
671 if floor:
672 result = np.floor(result)
673 if max_season is not None:
674 over_indx = np.where(int_rounded(result) >= int_rounded(max_season))
676 if modulo is not None:
677 neg = np.where(int_rounded(result) < int_rounded(0))
678 result = result % modulo
679 result[neg] = -1
680 if max_season is not None:
681 result[over_indx] = -1
682 if floor:
683 result = result.astype(int)
684 return result
687def create_season_offset(nside, sun_RA_rad):
688 """
689 Make an offset map so seasons roll properly
690 """
691 hpindx = np.arange(hp.nside2npix(nside))
692 ra, dec = _hpid2RaDec(nside, hpindx)
693 offset = ra - sun_RA_rad + 2.*np.pi
694 offset = offset % (np.pi*2)
695 offset = offset * 365.25/(np.pi*2)
696 offset = -offset - 365.25
697 return offset
700class TargetoO(object):
701 """Class to hold information about a target of opportunity object
703 Parameters
704 ----------
705 tooid : int
706 Unique ID for the ToO.
707 footprints : np.array
708 np.array healpix maps. 1 for areas to observe, 0 for no observe.
709 mjd_start : float
710 The MJD the ToO starts
711 duration : float
712 Duration of the ToO (days).
713 """
714 def __init__(self, tooid, footprint, mjd_start, duration):
715 self.footprint = footprint
716 self.duration = duration
717 self.id = tooid
718 self.mjd_start = mjd_start
721class Sim_targetoO_server(object):
722 """Wrapper to deliver a targetoO object at the right time
723 """
725 def __init__(self, targetoO_list):
726 self.targetoO_list = targetoO_list
727 self.mjd_starts = np.array([too.mjd_start for too in self.targetoO_list])
728 durations = np.array([too.duration for too in self.targetoO_list])
729 self.mjd_ends = self.mjd_starts + durations
731 def __call__(self, mjd):
732 in_range = np.where((mjd > self.mjd_starts) & (mjd < self.mjd_ends))[0]
733 result = None
734 if in_range.size > 0:
735 result = [self.targetoO_list[i] for i in in_range]
736 return result