def test_rad(self): nside = 64 hpids = np.arange(hp.nside2npix(nside)) ra, dec = utils._hpid2RaDec(nside, hpids) mjd = 59852. obs = utils.ObservationMetaData(mjd=mjd) alt1, az1, pa1 = utils._altAzPaFromRaDec(ra, dec, obs) alt2, az2 = utils._approx_RaDec2AltAz(ra, dec, obs.site.latitude_rad, obs.site.longitude_rad, mjd) # Check that the fast is similar to the more precice transform tol = np.radians(2) tol_mean = np.radians(1.) separations = utils._angularSeparation(az1, alt1, az2, alt2) self.assertLess(np.max(separations), tol) self.assertLess(np.mean(separations), tol_mean) # Check that the fast can nearly round-trip ra_back, dec_back = utils._approx_altAz2RaDec(alt2, az2, obs.site.latitude_rad, obs.site.longitude_rad, mjd) separations = utils._angularSeparation(ra, dec, ra_back, dec_back) self.assertLess(np.max(separations), tol) self.assertLess(np.mean(separations), tol_mean)
def test_rad(self): nside = 64 hpids = np.arange(hp.nside2npix(nside)) ra, dec = utils._hpid2RaDec(nside, hpids) mjd = 59852. obs = utils.ObservationMetaData(mjd=mjd) alt1, az1, pa1 = utils._altAzPaFromRaDec(ra, dec, obs) alt2, az2 = utils._approx_RaDec2AltAz(ra, dec, obs.site.latitude_rad, obs.site.longitude_rad, mjd) # Check that the fast is similar to the more precice transform tol = np.radians(2) tol_mean = np.radians(1.) separations = utils._angularSeparation(az1, alt1, az2, alt2) self.assertLess(np.max(separations), tol) self.assertLess(np.mean(separations), tol_mean) # Check that the fast can nearly round-trip ra_back, dec_back = utils._approx_altAz2RaDec(alt2, az2, obs.site.latitude_rad, obs.site.longitude_rad, mjd) separations = utils._angularSeparation(ra, dec, ra_back, dec_back) self.assertLess(np.max(separations), tol) self.assertLess(np.mean(separations), tol_mean)
def __call__(self, ra, dec, mjd): alt, az, pa = _approx_RaDec2AltAz(ra, dec, self.location.lat_rad, self.location.lon_rad, mjd, return_pa=True) return alt, az, pa
def _update_rotSkyPos(self, observation): """If we have an undefined rotSkyPos, try to fill it out. """ alt, az = _approx_RaDec2AltAz(observation['RA'], observation['dec'], self.site.latitude_rad, self.site.longitude_rad, self.mjd) obs_pa = approx_altaz2pa(alt, az, self.site.latitude_rad) observation['rotSkyPos'] = (obs_pa + observation['rotTelPos']) % (2 * np.pi) observation['rotTelPos'] = 0. return observation
def __call__(self, observation_list, conditions): # XXX--should I convert the list into an array and get rid of this loop? for obs in observation_list: alt, az = _approx_RaDec2AltAz(obs['RA'], obs['dec'], conditions.site.latitude_rad, conditions.site.longitude_rad, conditions.mjd) obs_pa = _approx_altaz2pa(alt, az, conditions.site.latitude_rad) obs['rotSkyPos'] = obs_pa return observation_list
def slewtime_map(self): """ Return a map of how long it would take to slew to lots of positions """ if self.ra is None: return 0. alt, az = _approx_RaDec2AltAz(self.ra_all_sky, self.dec_all_sky, self.obs.lat, self.obs.lon, self.mjd) good = np.where(alt >= self.alt_limit) result = np.empty(self.ra_all_sky.size, dtype=float) result.fill(hp.UNSEEN) result[good] = self.slew_time(alt[good], az[good]) return result
def request_observation(self, mjd=None): """ Ask the scheduler what it wants to observe next Paramters --------- mjd : float (None) The Modified Julian Date. If None, it uses the MJD from the conditions from the last conditions update. Returns ------- observation object (ra,dec,filter,rotangle) Returns None if the queue fails to fill """ if mjd is None: mjd = self.conditions.mjd 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 (int_rounded(mjd) > int_rounded(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 we are limiting the camera rotator if self.rotator_limits is not None: alt, az = _approx_RaDec2AltAz( observation['RA'], observation['dec'], self.conditions.site.latitude_rad, self.conditions.site.longitude_rad, mjd) obs_pa = approx_altaz2pa(alt, az, self.conditions.site.latitude_rad) rotTelPos_expected = (obs_pa - observation['rotSkyPos']) % (2. * np.pi) if (int_rounded(rotTelPos_expected) > int_rounded( self.rotator_limits[0])) & ( int_rounded(rotTelPos_expected) < int_rounded( self.rotator_limits[1])): diff = np.abs(self.rotator_limits - rotTelPos_expected) limit_indx = np.min(np.where(diff == np.min(diff))[0]) observation['rotSkyPos'] = ( obs_pa - self.rotator_limits[limit_indx]) % (2. * np.pi) return observation
def _check_alts(self, observation, conditions): result = False # Just do a fast ra,dec to alt,az conversion. Can use LMST from a feature. alt, az = _approx_RaDec2AltAz(observation['RA'], observation['dec'], self.lat, None, conditions.mjd, lmst=conditions.lmst) in_range = np.where((alt < self.max_alt) & (alt > self.min_alt))[0] if np.size(in_range) > 0: result = True return result
def check_feasibility(self, conditions): result =True alt, az = _approx_RaDec2AltAz(self.ra, self.dec, conditions.site.latitude_rad, conditions.site.longitude_rad, conditions.mjd) if az > np.pi: az = az - 2*np.pi val = np.abs(60. - 30.*(np.abs(np.degrees(az))/60.)**2-np.degrees(alt)) if val < 10.: result = False return result
def __call__(self, observation_list, conditions): # Generate offsets in RA and Dec offsets = self._generate_offsets(len(observation_list), conditions.night) for i, obs in enumerate(observation_list): alt, az = _approx_RaDec2AltAz(obs['RA'], obs['dec'], conditions.site.latitude_rad, conditions.site.longitude_rad, conditions.mjd) obs_pa = approx_altaz2pa(alt, az, conditions.site.latitude_rad) obs['rotSkyPos'] = (obs_pa + offsets[i]) % (2. * np.pi) return observation_list
def slew_time(self, alt, az, mintime=2.): """ Compute slew time to new ra, dec position """ current_alt, current_az = _approx_RaDec2AltAz(np.array([self.ra]), np.array([self.dec]), self.obs.lat, self.obs.lon, self.mjd) # Interpolation can be off by ~.1 seconds if there's no slew. if (np.max(current_alt) == np.max(alt)) & (np.max(current_az) == np.max(az)): time = mintime else: time = self.slew_interp(current_alt, current_az, alt, az) return time
def __call__(self, observation_list, conditions): obs_array = np.concatenate(observation_list) alt, az = _approx_RaDec2AltAz(obs_array['RA'], obs_array['dec'], conditions.site.latitude_rad, conditions.site.longitude_rad, conditions.mjd) alt_diff = np.abs(alt - conditions.telAlt) in_band = np.where(int_rounded(alt_diff) <= self.alt_band)[0] if in_band.size == 0: in_band = np.arange(alt.size) # Find the closest in angular distance of the points that are in band ang_dist = _angularSeparation(az[in_band], alt[in_band], conditions.telAz, conditions.telAlt) good = np.min(np.where(ang_dist == ang_dist.min())[0]) indx = in_band[good] result = observation_list[indx:] + observation_list[:indx] return result
def __call__(self, observation_list, conditions): favored_rotSkyPos = np.radians([0., 90., 180., 270., 360.]).reshape(5, 1) obs_array = np.concatenate(observation_list) alt, az = _approx_RaDec2AltAz(obs_array['RA'], obs_array['dec'], conditions.site.latitude_rad, conditions.site.longitude_rad, conditions.mjd) parallactic_angle = _approx_altaz2pa(alt, az, conditions.site.latitude_rad) # If we set rotSkyPos to parallactic angle, rotTelPos will be zero. So, find the # favored rotSkyPos that is closest to PA to keep rotTelPos as close as possible to zero. ang_diff = np.abs(parallactic_angle - favored_rotSkyPos) min_indxs = np.argmin(ang_diff, axis=0) # can swap 360 and zero if needed? final_rotSkyPos = favored_rotSkyPos[min_indxs] # Set all the observations to the proper rotSkyPos for rsp, obs in zip(final_rotSkyPos, observation_list): obs['rotSkyPos'] = rsp return observation_list
def _check_alts_HA(self, observation, conditions): """Given scheduled observations, check which ones can be done in current conditions. Parameters ---------- observation : np.array An array of scheduled observations. Probably generated with lsst.sims.featureScheduler.utils.scheduled_observation """ # Just do a fast ra,dec to alt,az conversion. alt, az = _approx_RaDec2AltAz(observation['RA'], observation['dec'], conditions.site.latitude_rad, None, conditions.mjd, lmst=conditions.lmst) HA = conditions.lmst - observation['RA'] * 12. / np.pi HA[np.where(HA > 24)] -= 24 HA[np.where(HA < 0)] += 24 in_range = np.where((alt < observation['alt_max']) & (alt > observation['alt_min']) & ((HA > observation['HA_max']) | (HA < observation['HA_min'])))[0] return in_range
def calc_altAz(self): self._alt, self._az = _approx_RaDec2AltAz(self.ra, self.dec, self.site.latitude_rad, self.site.longitude_rad, self._mjd)
def generate_observations_rough(self, conditions): """ Find a good block of observations. """ 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() if self.grow_blob: # Note, returns highest first ordered_hp = hp_grow_argsort(self.reward) ordered_fields = self.hp2fields[ordered_hp] orig_order = np.arange(ordered_fields.size) # Remove duplicate field pointings _u_of, u_indx = np.unique(ordered_fields, return_index=True) new_order = np.argsort(orig_order[u_indx]) best_fields = ordered_fields[u_indx[new_order]] if np.size(best_fields) < self.nvisit_block: # Let's fall back to the simple sort self.simple_order_sort() else: self.best_fields = best_fields[0:self.nvisit_block] else: self.simple_order_sort() if len(self.best_fields) == 0: # everything was nans, or self.nvisit_block was zero return [] # Let's find the alt, az coords of the points (right now, hopefully doesn't change much in time block) pointing_alt, pointing_az = _approx_RaDec2AltAz( self.fields['RA'][self.best_fields], self.fields['dec'][self.best_fields], conditions.site.latitude_rad, conditions.site.longitude_rad, conditions.mjd, lmst=conditions.lmst) # Let's find a good spot to project the points to a plane mid_alt = (np.max(pointing_alt) - np.min(pointing_alt)) / 2. # Code snippet from MAF for computing mean of angle accounting for wrap around # XXX-TODO: Maybe move this to sims_utils as a generally useful snippet. x = np.cos(pointing_az) y = np.sin(pointing_az) meanx = np.mean(x) meany = np.mean(y) angle = np.arctan2(meany, meanx) radius = np.sqrt(meanx**2 + meany**2) mid_az = angle % (2. * np.pi) if radius < 0.1: mid_az = np.pi # Project the alt,az coordinates to a plane. Could consider scaling things to represent # time between points rather than angular distance. pointing_x, pointing_y = gnomonic_project_toxy(pointing_az, pointing_alt, mid_az, mid_alt) # Round off positions so that we ensure identical cross-platform performance scale = 1e6 pointing_x = np.round(pointing_x * scale).astype(int) pointing_y = np.round(pointing_y * scale).astype(int) # Now I have a bunch of x,y pointings. Drop into TSP solver to get an effiencent route towns = np.vstack((pointing_x, pointing_y)).T # Leaving optimize=False for speed. The optimization step doesn't usually improve much. better_order = tsp_convex(towns, optimize=False) # XXX-TODO: Could try to roll better_order to start at the nearest/fastest slew from current position. observations = [] counter2 = 0 approx_end_time = np.size(better_order) * ( self.slew_approx + self.exptime + self.read_approx * (self.nexp - 1)) flush_time = conditions.mjd + approx_end_time / 3600. / 24. + self.flush_time for i, indx in enumerate(better_order): field = self.best_fields[indx] obs = empty_observation() obs['RA'] = self.fields['RA'][field] obs['dec'] = self.fields['dec'][field] obs['rotSkyPos'] = 0. obs['filter'] = self.filtername1 if self.nexp_dict is None: obs['nexp'] = self.nexp else: obs['nexp'] = self.nexp_dict[self.filtername1] obs['exptime'] = self.exptime obs['field_id'] = -1 obs['note'] = '%s' % (self.survey_note) obs['block_id'] = self.counter obs['flush_by_mjd'] = flush_time # Add the mjd for debugging # obs['mjd'] = conditions.mjd # XXX temp debugging line obs['survey_id'] = i observations.append(obs) counter2 += 1 result = observations return result
def attempt_observe(self, observation_in, indx=None): """ Check an observation, if there is enough time, execute it and return it, otherwise, return None. """ # If we were in a parked position, assume no time lost to slew, settle, filter change observation = observation_in.copy() alt, az = _approx_RaDec2AltAz(np.array([observation['RA']]), np.array([observation['dec']]), self.obs.lat, self.obs.lon, self.mjd) if self.ra is not None: if self.filtername != observation['filter']: ft = self.f_change_time st = 0. else: ft = 0. st = self.slew_time(alt, az) else: st = 0. ft = 0. # Assume we can slew while reading the last exposure (note that slewtime calc gives 2 as a minimum. So this # will not fail for DD fields, etc.) # So, filter change time, slew to target time, expose time, read time rt = (observation['nexp']-1.)*self.readtime shutter_time = self.shutter_time*observation['nexp'] total_time = (ft + st + observation['exptime'] + rt + shutter_time)*sec2days check_result, jump_mjd = self.check_mjd(self.mjd + total_time) if check_result: # XXX--major decision here, should the status be updated after every observation? Or just assume # airmass, seeing, and skybrightness do not change significantly? if self.ra is None: update_status = True else: update_status = False # This should be the start of the exposure. observation['mjd'] = self.mjd + (ft + st)*sec2days self.set_mjd(self.mjd + (ft + st)*sec2days) self.ra = observation['RA'] self.dec = observation['dec'] if update_status: # What's the name for temp variables? status = self.return_status() observation['night'] = self.night # XXX I REALLY HATE THIS! READTIME SHOULD NOT BE LUMPED IN WITH SLEWTIME! # XXX--removing that so I may not be using the same convention as opsim. observation['slewtime'] = ft+st self.filtername = observation['filter'][0] hpid = _raDec2Hpid(self.sky_nside, self.ra, self.dec) observation['skybrightness'] = self.sky.returnMags(observation['mjd'], indx=[hpid], extrapolate=True)[self.filtername] observation['FWHMeff'] = self.status['FWHMeff_%s' % self.filtername][hpid] observation['FWHM_geometric'] = self.status['FWHM_geometric_%s' % self.filtername][hpid] observation['airmass'] = self.status['airmass'][hpid] observation['fivesigmadepth'] = m5_flat_sed(observation['filter'][0], observation['skybrightness'], observation['FWHMeff'], observation['exptime'], observation['airmass']) observation['alt'] = alt observation['az'] = az observation['clouds'] = self.status['clouds'] observation['sunAlt'] = self.status['sunAlt'] observation['moonAlt'] = self.status['moonAlt'] # We had advanced the slew and filter change, so subtract that off and add the total visit time. self.set_mjd(self.mjd + total_time - (ft + st)*sec2days) return observation else: self.mjd = jump_mjd self.night = self.mjd2night(self.mjd) self.ra = None self.dec = None self.status = None self.filtername = None return None
def setRaDecMjd(self, lon, lat, mjd, degrees=False, azAlt=False, solarFlux=130., filterNames=['u', 'g', 'r', 'i', 'z', 'y']): """ Set the sky parameters by computing the sky conditions on a given MJD and sky location. lon: Longitude-like (RA or Azimuth). Can be single number, list, or numpy array lat: Latitude-like (Dec or Altitude) mjd: Modified Julian Date for the calculation. Must be single number. degrees: (False) Assumes lon and lat are radians unless degrees=True azAlt: (False) Assume lon, lat are RA, Dec unless azAlt=True solarFlux: solar flux in SFU Between 50 and 310. Default=130. 1 SFU=10^4 Jy. filterNames: list of fitlers to return magnitudes for (if initialized with mags=True). """ self.filterNames = filterNames if self.mags: self.npix = len(self.filterNames) # Wrap in array just in case single points were passed if np.size(lon) == 1: lon = np.array([lon]).ravel() lat = np.array([lat]).ravel() else: lon = np.array(lon) lat = np.array(lat) if degrees: self.ra = np.radians(lon) self.dec = np.radians(lat) else: self.ra = lon self.dec = lat if np.size(mjd) > 1: raise ValueError('mjd must be single value.') self.mjd = mjd if azAlt: self.azs = self.ra.copy() self.alts = self.dec.copy() if self.preciseAltAz: self.ra, self.dec = _raDecFromAltAz(self.alts, self.azs, ObservationMetaData(mjd=self.mjd, site=self.telescope)) else: self.ra, self.dec = _approx_altAz2RaDec(self.alts, self.azs, self.telescope.latitude_rad, self.telescope.longitude_rad, mjd) else: if self.preciseAltAz: self.alts, self.azs, pa = _altAzPaFromRaDec(self.ra, self.dec, ObservationMetaData(mjd=self.mjd, site=self.telescope)) else: self.alts, self.azs = _approx_RaDec2AltAz(self.ra, self.dec, self.telescope.latitude_rad, self.telescope.longitude_rad, mjd) self.npts = self.ra.size self._initPoints() self.solarFlux = solarFlux self.points['solarFlux'] = self.solarFlux self._setupPointGrid() self.paramsSet = True # Assume large airmasses are the same as airmass=2.5 to_fudge = np.where((self.points['airmass'] > 2.5) & (self.points['airmass'] < self.airmassLimit)) self.points['airmass'][to_fudge] = 2.499 self.points['alt'][to_fudge] = np.pi/2-np.arccos(1./self.airmassLimit) # Interpolate the templates to the set paramters self.goodPix = np.where((self.airmass <= self.airmassLimit) & (self.airmass >= 1.))[0] if self.goodPix.size > 0: self._interpSky() else: warnings.warn('No valid points to interpolate')
def obs2sqlite(observations_in, location='LSST', outfile='observations.sqlite', slewtime_limit=5., full_sky=False, radians=True): """ Utility to take an array of observations and dump it to a sqlite file, filling in useful columns along the way. observations_in: numpy array with at least columns of ra : RA in degrees dec : dec in degrees mjd : MJD in day filter : string with the filter name exptime : the exposure time in seconds slewtime_limit : float Consider all slewtimes larger than this to be closed-dome time not part of a slew. """ # Set the location to be LSST if location == 'LSST': telescope = Site('LSST') # Check that we have the columns we need needed_cols = ['ra', 'dec', 'mjd', 'filter'] in_cols = observations_in.dtype.names for col in needed_cols: if needed_cols not in in_cols: ValueError('%s column not found in observtion array' % col) n_obs = observations_in.size sm = None # make sure they are in order by MJD observations_in.sort(order='mjd') # Take all the columns that are in the input and add any missing names = ['filter', 'ra', 'dec', 'mjd', 'exptime', 'alt', 'az', 'skybrightness', 'seeing', 'night', 'slewtime', 'fivesigmadepth', 'airmass', 'sunAlt', 'moonAlt'] types = ['|S1'] types.extend([float]*(len(names)-1)) observations = np.zeros(n_obs, dtype=list(zip(names, types))) # copy over the ones we have for col in in_cols: observations[col] = observations_in[col] # convert output to be in degrees like expected if radians: observations['ra'] = np.degrees(observations['ra']) observations['dec'] = np.degrees(observations['dec']) if 'exptime' not in in_cols: observations['exptime'] = 30. # Fill in the slewtime. Note that filterchange time gets included in slewtimes if 'slewtime' not in in_cols: # Assume MJD is midpoint of exposures mjd_sec = observations_in['mjd']*24.*3600. observations['slewtime'][1:] = mjd_sec[1:]-mjd_sec[0:-1] - observations['exptime'][0:-1]*0.5 - observations['exptime'][1:]*0.5 closed = np.where(observations['slewtime'] > slewtime_limit*60.) observations['slewtime'][closed] = 0. # Let's just use the stupid-fast to get alt-az if 'alt' not in in_cols: alt, az = _approx_RaDec2AltAz(np.radians(observations['ra']), np.radians(observations['dec']), telescope.latitude_rad, telescope.longitude_rad, observations['mjd']) observations['alt'] = np.degrees(alt) observations['az'] = np.degrees(az) # Fill in the airmass if 'airmass' not in in_cols: observations['airmass'] = 1./np.cos(np.pi/2. - np.radians(observations['alt'])) # Fill in the seeing if 'seeing' not in in_cols: # XXX just fill in a dummy val observations['seeing'] = 0.8 if 'night' not in in_cols: m2n = mjd2night() observations['night'] = m2n(observations['mjd']) # Sky Brightness if 'skybrightness' not in in_cols: if full_sky: sm = SkyModel(mags=True) for i, obs in enumerate(observations): sm.setRaDecMjd(obs['ra'], obs['dec'], obs['mjd'], degrees=True) observations['skybrightness'][i] = sm.returnMags()[obs['filter']] else: # Let's try using the pre-computed sky brighntesses sm = sb.SkyModelPre(preload=False) full = sm.returnMags(observations['mjd'][0]) nside = hp.npix2nside(full['r'].size) imax = float(np.size(observations)) for i, obs in enumerate(observations): indx = raDec2Hpid(nside, obs['ra'], obs['dec']) observations['skybrightness'][i] = sm.returnMags(obs['mjd'], indx=[indx])[obs['filter']] sunMoon = sm.returnSunMoon(obs['mjd']) observations['sunAlt'][i] = sunMoon['sunAlt'] observations['moonAlt'][i] = sunMoon['moonAlt'] progress = i/imax*100 text = "\rprogress = %.2f%%"%progress sys.stdout.write(text) sys.stdout.flush() observations['sunAlt'] = np.degrees(observations['sunAlt']) observations['moonAlt'] = np.degrees(observations['moonAlt']) # 5-sigma depth for fn in np.unique(observations['filter']): good = np.where(observations['filter'] == fn) observations['fivesigmadepth'][good] = m5_flat_sed(fn, observations['skybrightness'][good], observations['seeing'][good], observations['exptime'][good], observations['airmass'][good]) conn = sqlite3.connect(outfile) df = pd.DataFrame(observations) df.to_sql('observations', conn)