|
| 1 | +from __future__ import print_function, division, absolute_import |
| 2 | + |
| 3 | +__all__ = ['ObservationPotential'] |
| 4 | +import time |
| 5 | +import numpy as np |
| 6 | +import pandas as pd |
| 7 | + |
| 8 | + |
| 9 | +import lsst.sims.skybrightness as sb |
| 10 | +from lsst.sims.utils import (Site, approx_RaDec2AltAz) |
| 11 | +import ephem |
| 12 | +from obscond import SkyCalculations as sm |
| 13 | + |
| 14 | + |
| 15 | +from lsst.sims.utils import angularSeparation |
| 16 | +from scipy.interpolate import interp1d |
| 17 | + |
| 18 | +class ObservationPotential(object): |
| 19 | + """ |
| 20 | + Class to define the potential for observations |
| 21 | +
|
| 22 | +
|
| 23 | + Parameters |
| 24 | + ---------- |
| 25 | + fieldRA : float, in radians |
| 26 | + RA of the field |
| 27 | + fieldDec : float, in radians |
| 28 | + Dec of the field |
| 29 | + observatory : string, defaults to `LSST` |
| 30 | + string specifying observatory to `ephem` |
| 31 | + """ |
| 32 | + def __init__(self, fieldRA, fieldDec, |
| 33 | + observatory='LSST'): |
| 34 | + self.ra = np.degrees(fieldRA) |
| 35 | + self.dec = np.degrees(fieldDec) |
| 36 | + self.site = Site(observatory) |
| 37 | + self.sun = ephem.Sun() |
| 38 | + self.moon = ephem.Moon() |
| 39 | + self.obs = ephem.Observer() |
| 40 | + self.obs.lat = np.radians(self.site.latitude) |
| 41 | + self.obs.long = np.radians(self.site.longitude) |
| 42 | + self.obs.elevation = self.site.height |
| 43 | + self.doff = ephem.Date(0) - ephem.Date('1858/11/17') |
| 44 | + self._available_times = None |
| 45 | + self.sec = 1.0 / 24.0 / 60.0/ 60.0 |
| 46 | + |
| 47 | + def moonCoords_singleTime(self, mjd): |
| 48 | + """ |
| 49 | + returns the moon ra, dec, and alt in radians |
| 50 | + |
| 51 | + Parameters |
| 52 | + ---------- |
| 53 | + mjd : `np.float`, unit day |
| 54 | + time of observation |
| 55 | +
|
| 56 | + Returns |
| 57 | + ------- |
| 58 | + moon ra: radians |
| 59 | + moon dec: radians |
| 60 | + moon alt: radians |
| 61 | + """ |
| 62 | + self.obs.date = mjd - self.doff |
| 63 | + self.moon.compute(self.obs) |
| 64 | + return self.moon.ra, self.moon.dec, self.moon.alt |
| 65 | + |
| 66 | + |
| 67 | + def sunAlt_singleTime(self, tt): |
| 68 | + """return the sun Altitude at a time tt in radians |
| 69 | + |
| 70 | + Parameters |
| 71 | + ---------- |
| 72 | + tt : `np.float`, unit day |
| 73 | + time at which altitude is desired in mjd |
| 74 | + |
| 75 | + Returns |
| 76 | + ------- |
| 77 | + sun alt: radians |
| 78 | + """ |
| 79 | + self.obs.date = tt - self.doff |
| 80 | + self.sun.compute(self.obs) |
| 81 | + |
| 82 | + return self.sun.alt |
| 83 | + |
| 84 | + def sunAlt(self, mjd): |
| 85 | + """return the sun Altitude at an array of times in degrees |
| 86 | +
|
| 87 | + Parameters |
| 88 | + ---------- |
| 89 | + mjd : array-like, MJD |
| 90 | + sequence of times of observation in MJD |
| 91 | + """ |
| 92 | + sunAlt = np.degrees(list(self.sunAlt_singleTime(tt) for tt in mjd)) |
| 93 | + return sunAlt |
| 94 | + |
| 95 | + def moonCoords(self, mjd): |
| 96 | + """return the moon coordinates (ra, dec, Alt) at an array of times in |
| 97 | + degrees |
| 98 | +
|
| 99 | +
|
| 100 | + Parameters |
| 101 | + ---------- |
| 102 | + mjd : sequence of floats |
| 103 | + Times in Modified Julian Date |
| 104 | +
|
| 105 | + Returns |
| 106 | + ------- |
| 107 | + moon ra: degrees |
| 108 | + moon dec: degrees |
| 109 | + moon alt: degrees |
| 110 | + """ |
| 111 | + moonRA, moonDec, moonAlt = list(zip(*(self.moonCoords_singleTime(tt) for tt in mjd))) |
| 112 | + return np.degrees(moonRA), np.degrees(moonDec), np.degrees(moonAlt) |
| 113 | + |
| 114 | + |
| 115 | + def field_coords(self, fieldRA, fieldDec, mjd): |
| 116 | + """Return the field Coordinates in degrees. |
| 117 | + |
| 118 | + Parameters |
| 119 | + ---------- |
| 120 | + fieldRA : float, or array, degrees |
| 121 | + RA of the field |
| 122 | + fiedlDec : float or array, degrees |
| 123 | + Dec of the field |
| 124 | + mjd : float or array |
| 125 | + Returns |
| 126 | + ------- |
| 127 | + alt : degrees |
| 128 | + az : degrees |
| 129 | + """ |
| 130 | + ra = np.ravel(fieldRA) |
| 131 | + dec = np.ravel(fieldDec) |
| 132 | + mjd = np.ravel(mjd) |
| 133 | + |
| 134 | + # note that all angle arguments are in degrees |
| 135 | + alt, az = approx_RaDec2AltAz(ra=ra, dec=dec, |
| 136 | + lat=self.site.latitude, |
| 137 | + lon=self.site.longitude, |
| 138 | + mjd=mjd, |
| 139 | + lmst=None) |
| 140 | + return alt, az |
| 141 | + |
| 142 | + def potential_obscond(self, t, fieldRA, fieldDec, |
| 143 | + nightOffset=59579.6): |
| 144 | + """ |
| 145 | + Calculate the observing Conditions at a sequence of mjd values |
| 146 | + `t` for a location with ra and dec `fieldRA` and `fieldDec` in |
| 147 | + `degrees`. |
| 148 | + Parameters |
| 149 | + ---------- |
| 150 | + t : times at which observations are being made |
| 151 | +
|
| 152 | + fieldRA : RA, degree |
| 153 | +
|
| 154 | + fieldDec : Dec, degree |
| 155 | +
|
| 156 | + nightOffset : mjd value, defaults to 59579.6 |
| 157 | + mjd value for night = 0 of the survey. |
| 158 | + """ |
| 159 | + alt, az = self.field_coords(fieldRA, fieldDec, mjd=t) |
| 160 | + moonra, moondec, moonalt = self.moonCoords(mjd=t) |
| 161 | + sunAlt = self.sunAlt(mjd=t) |
| 162 | + |
| 163 | + df = pd.DataFrame(dict(mjd=t, |
| 164 | + alt=alt, |
| 165 | + az=az, |
| 166 | + sunAlt=sunAlt, |
| 167 | + moonRA=moonra, |
| 168 | + moonDec=moondec, |
| 169 | + moonAlt=moonalt, |
| 170 | + night=np.floor(t-59579.6).astype(np.int))) |
| 171 | + |
| 172 | + df['moonDist'] = angularSeparation(moonra, moondec, |
| 173 | + np.degrees(self.ra), |
| 174 | + np.degrees(self.dec)) |
| 175 | + |
| 176 | + return df |
| 177 | + |
| 178 | + def available_times(self, potential_times, constraints): |
| 179 | + """returns available times |
| 180 | + """ |
| 181 | + self._available_times = potential_times.query(constraints) |
| 182 | + return self._available_times |
| 183 | + |
| 184 | + @staticmethod |
| 185 | + def dc2_sequence(start_time, year_block, delta=1): |
| 186 | + """ |
| 187 | +
|
| 188 | + Parameters |
| 189 | + ---------- |
| 190 | + start_time : |
| 191 | +
|
| 192 | + year_block : |
| 193 | +
|
| 194 | + delta : , defaults to 1. |
| 195 | + """ |
| 196 | + sec = 1.0 / 24.0/60./60. |
| 197 | + |
| 198 | + if year_block == 1: |
| 199 | + fraction = 0.75 |
| 200 | + |
| 201 | + if year_block == 2: |
| 202 | + fraction = 0.5 |
| 203 | + delta = 0. |
| 204 | + |
| 205 | + standard_sequence = np.array(list((20, 10, 20, 26, 20))) |
| 206 | + time = start_time |
| 207 | + l = [] |
| 208 | + visitlist = [] |
| 209 | + seq = standard_sequence |
| 210 | + extravisits = np.array([0, 1, 0, 1, 0]) * delta |
| 211 | + #print(seq) |
| 212 | + bandlist = [] |
| 213 | + |
| 214 | + # Iterate through the standard list of band, standard visit sequences |
| 215 | + # For on and off years |
| 216 | + for band, visits, morevisits in zip(list('rgizy'), seq, extravisits): |
| 217 | + numVisits = np.floor(visits * fraction) + morevisits |
| 218 | + times = np.linspace(time, time + (numVisits - 1) * 38 * sec, |
| 219 | + numVisits) |
| 220 | + l.append(times) |
| 221 | + visitlist.append(numVisits) |
| 222 | + time = times[-1] + 150.0 * sec |
| 223 | + bandlist.append(np.repeat(band, numVisits)) |
| 224 | + df = pd.DataFrame(dict(expMJD=np.concatenate(l), |
| 225 | + filter=np.concatenate(bandlist))) |
| 226 | + return df, visitlist |
| 227 | + |
| 228 | + @staticmethod |
| 229 | + def dc2_visits(start_times, year_block, delta=1, pointings=None): |
| 230 | + |
| 231 | + vs = [] |
| 232 | + for st in start_times.values: |
| 233 | + df, vl = ObservationPotential.dc2_sequence(st, year_block, delta) |
| 234 | + vs.append(df) |
| 235 | + df = pd.concat(vs) |
| 236 | + df['night'] = np.floor(df.expMJD - 59579.6) |
| 237 | + |
| 238 | + if pointings is not None: |
| 239 | + rawSeeing = interp1d(pointings.expMJD.values, |
| 240 | + pointings.rawSeeing.values, |
| 241 | + kind='nearest') |
| 242 | + |
| 243 | + df['rawSeeing'] = rawSeeing(df.expMJD.values) |
| 244 | + alt, az = approx_RaDec2AltAz(ra=ra, dec=dec, |
| 245 | + lat=self.site.latitude, |
| 246 | + lon=self.site.longitude, |
| 247 | + mjd=df.expMJD.values, |
| 248 | + lmst=None) |
| 249 | + df['alt'] = alt |
| 250 | + df['az'] = az |
| 251 | + |
| 252 | + return df |
| 253 | + |
| 254 | + @staticmethod |
| 255 | + def nightStats(availabletimes): |
| 256 | + df = availabletimes.groupby('night').agg(dict(mjd=(min, max))).reset_index() |
| 257 | + df = pd.DataFrame(df.values, columns=('night', 'minmjd', 'maxmjd')) |
| 258 | + df['availTime'] = (df.maxmjd - df.minmjd) * 24.0 |
| 259 | + return df.set_index('night') |
| 260 | + |
| 261 | + @staticmethod |
| 262 | + def start_times(nightStats, chosen_nights, rng): |
| 263 | + zz = nightStats |
| 264 | + df = zz.loc[chosen_nights]#.tolist()) |
| 265 | + df['maxtime'] = df.maxmjd - 1.25 / 24. |
| 266 | + random = rng.uniform(size=len(chosen_nights)) |
| 267 | + start_time = df.minmjd + (df.maxtime - df.minmjd) * random |
| 268 | + return start_time |
| 269 | + |
| 270 | + @staticmethod |
| 271 | + def timerange(series): |
| 272 | + return (max(series) - min(series))*24.0 |
0 commit comments