@@ -169,6 +169,16 @@ def __init__(self, start_date=None, stop_date=None, num_obj_steps=25,
169
169
# tabulating the (ra,dec) of objects to avoid.
170
170
t_obj = np .linspace (0. , 1. , num_obj_steps )
171
171
172
+ # Lookup sun altitude thresholds.
173
+ bright_max_alt = (
174
+ config .programs .BRIGHT .max_sun_altitude ().to (u .rad ).value )
175
+ dark_max_alt = (
176
+ config .programs .DARK .max_sun_altitude ().to (u .rad ).value )
177
+
178
+ rng = np .rad2deg (bright_max_alt - dark_max_alt )
179
+ print ('limits' , rng )
180
+ max_err = 0.
181
+
172
182
# Calculate ephmerides for each night.
173
183
for day_offset in range (num_nights ):
174
184
day = self .start + day_offset * u .day
@@ -177,19 +187,34 @@ def __init__(self, start_date=None, stop_date=None, num_obj_steps=25,
177
187
# Store local noon for this day.
178
188
row ['noon' ] = day .mjd
179
189
# Calculate bright twilight.
180
- mayall .horizon = (
181
- config .programs .BRIGHT .max_sun_altitude ().to (u .rad ).value )
190
+ mayall .horizon = bright_max_alt
182
191
row ['brightdusk' ] = mayall .next_setting (
183
192
ephem .Sun (), use_center = True ) + mjd0
184
193
row ['brightdawn' ] = mayall .next_rising (
185
194
ephem .Sun (), use_center = True ) + mjd0
186
195
# Calculate dark / gray twilight.
187
- mayall .horizon = (
188
- config .programs .DARK .max_sun_altitude ().to (u .rad ).value )
196
+ mayall .horizon = dark_max_alt
189
197
row ['dusk' ] = mayall .next_setting (
190
198
ephem .Sun (), use_center = True ) + mjd0
191
199
row ['dawn' ] = mayall .next_rising (
192
200
ephem .Sun (), use_center = True ) + mjd0
201
+ # Calculate the midpoint between BRIGHT/DARK twilight.
202
+ mayall .horizon = 0.5 * (
203
+ config .programs .BRIGHT .max_sun_altitude ().to (u .rad ).value +
204
+ config .programs .DARK .max_sun_altitude ().to (u .rad ).value )
205
+ middusk = mayall .next_setting (
206
+ ephem .Sun (), use_center = True ) + mjd0
207
+ middawn = mayall .next_rising (
208
+ ephem .Sun (), use_center = True ) + mjd0
209
+ # Compare with linear extrapolations.
210
+ middusk_pred = 0.5 * (row ['brightdusk' ] + row ['dusk' ])
211
+ middawn_pred = 0.5 * (row ['brightdawn' ] + row ['dawn' ])
212
+ dawn = (row ['brightdawn' ] - row ['dawn' ])
213
+ dusk = (row ['dusk' ] - row ['brightdusk' ])
214
+ dusk_err = (middusk_pred - middusk ) / dusk * rng
215
+ dawn_err = (middusk_pred - middusk ) / dawn * rng
216
+ #print('dawn/dusk (arcsec)', dusk_err * 3600, dawn_err * 360)
217
+ max_err = max (max_err , abs (dusk_err ), abs (dawn_err ))
193
218
# Calculate the moonrise/set for any moon visible tonight.
194
219
m0 = ephem .Moon ()
195
220
mayall .horizon = '-0:34' # the value that the USNO uses.
@@ -212,6 +237,8 @@ def __init__(self, start_date=None, stop_date=None, num_obj_steps=25,
212
237
row [name + '_ra' ][i ] = math .degrees (float (model .ra ))
213
238
row [name + '_dec' ][i ] = math .degrees (float (model .dec ))
214
239
240
+ print ('max_err' , max_err * 3600 , 'arcsec' )
241
+
215
242
# Initialize a grid covering each night with 15sec resolution
216
243
# for tabulating the night program.
217
244
t_grid = get_grid (step_size = 15 * u .s )
@@ -291,8 +318,7 @@ def get_moon_illuminated_fraction(self, mjd):
291
318
Parameters
292
319
----------
293
320
mjd : float or array
294
- MJD values during a single night where the program should be
295
- tabulated.
321
+ MJD values where the illuminated fraction should be tabulated.
296
322
297
323
Returns
298
324
-------
@@ -423,6 +449,60 @@ def is_full_moon(self, night, num_nights=None):
423
449
return index - lo in sort_order [- num_nights :]
424
450
425
451
452
+ def get_sun_altitude (row , mjd ):
453
+ """Return the sun altitude at the specified time for twilight modeling.
454
+
455
+ This function only returns values in the range [-10, -20] deg
456
+ since values outside this range are either never used for observing
457
+ or else considered completely dark.
458
+
459
+ The calculation uses linear interpolation of the tabulated times when the
460
+ sun passes through the bright and dark maximum altitudes, nominally -13
461
+ and -15 deg, which provides an altitude accurate to better than 30"
462
+ between these limits.
463
+
464
+ Parameters
465
+ ----------
466
+ row : astropy.table.Row
467
+ A single row from the ephemerides astropy Table corresponding to the
468
+ night in question.
469
+ mjd : float
470
+ MJD value(s) during a single night where the sun altitude should be
471
+ calculated.
472
+
473
+ Returns
474
+ -------
475
+ astropy.units.Quantity
476
+ Sun altitude(s) in degrees at each input mjd value, clipped to
477
+ the range [-10, -20].
478
+ """
479
+ # Lookup the bright, dark threshold altitudes.
480
+ config = desisurvey .config .Configuration ()
481
+ bright_max_alt = config .programs .BRIGHT .max_sun_altitude ()
482
+ dark_max_alt = config .programs .DARK .max_sun_altitude ()
483
+
484
+ # Validate input timestamp(s)
485
+ mjd = np .asarray (mjd )
486
+ scalar = mjd .ndim == 0
487
+ mjd = np .atleast1d (mjd )
488
+ midnight = 0.5 * (row ['dusk' ] + row ['dawn' ])
489
+ if np .any (np .abs (mjd - midnight ) > 0.5 ):
490
+ raise ValueError ('mjd is not associated with the specified night' )
491
+
492
+ # Use linear interpolation on the dusk timestamps for times before midnight.
493
+ sun_alt = np .empty_like (mjd )
494
+ at_dusk = mjd < midnight
495
+ sun_alt [at_dusk ] = bright_max_alt + (bright_max_alt - dark_max_alt ) * (
496
+ mjd [at_dusk ] - row ['brightdusk' ]) / (row ['dusk' ] - row ['brightdusk' ])
497
+
498
+ # Use linear interpolation on the dawn timestamps after midnight.
499
+ at_dawn = ~ at_dusk
500
+ sun_alt [at_dawn ] = bright_max_alt + (bright_max_alt - dark_max_alt ) * (
501
+ mjd [at_dawn ] - row ['brightdawn' ]) / (row ['dusk' ] - row ['brightdusk' ])
502
+
503
+ return sun_alt
504
+
505
+
426
506
def get_object_interpolator (row , object_name , altaz = False ):
427
507
"""Build an interpolator for object location during one night.
428
508
0 commit comments