Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Depth first #66

Merged
merged 31 commits into from
Oct 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
581b265
Implement simple rules for a depth-first survey
dkirkby Sep 26, 2017
1a7c8f2
Fix typo
dkirkby Sep 26, 2017
81ce057
movie updates
dkirkby Sep 27, 2017
dd97b06
movie debugging at nersc
dkirkby Sep 27, 2017
5d9782e
Add support for bytes input to utils.get_date()
dkirkby Sep 27, 2017
07dc539
debug movie updates
dkirkby Sep 27, 2017
98a4ce1
Backwards compatible for numpy < 1.12
dkirkby Sep 27, 2017
3715f12
bug fix
dkirkby Sep 27, 2017
f612eef
Fix more str/byte issues
dkirkby Sep 27, 2017
f57d87f
Bug fix in surveymovie
dkirkby Sep 28, 2017
a70b8dd
Fix docstring units
dkirkby Oct 2, 2017
ae225a1
Add E(B-V) column to default get_exposures() output
dkirkby Oct 4, 2017
1e47c6b
Add new progress columns: covered, available
dkirkby Oct 4, 2017
08d4654
Implement utils.day_number()
dkirkby Oct 4, 2017
28a6326
Use utils.day_number() in plan
dkirkby Oct 4, 2017
a6dd5bc
Update Progress.copy_range() to handle covered, available columns
dkirkby Oct 4, 2017
e463b03
Update progress covered,available in surveyplan
dkirkby Oct 4, 2017
7cc68e6
Rename night_number to day_number for consistency
dkirkby Oct 4, 2017
56e1ef9
Update progress covered,available in surveyplan
dkirkby Oct 4, 2017
8725a5f
Add docstrings
dkirkby Oct 4, 2017
45288e1
Add planned column to Progress
dkirkby Oct 5, 2017
73b04e9
Merge branch 'master' into depth_first
dkirkby Oct 5, 2017
84018f9
Update changelog
dkirkby Oct 5, 2017
a5aacdc
Update surveymovie to use new progress fields instead of nightly plans
dkirkby Oct 5, 2017
07809ac
Add fps option to surveymovie
dkirkby Oct 5, 2017
43740d2
Add --nightly option to surveymovie
dkirkby Oct 6, 2017
f6bd910
Implement and use config set_value()
dkirkby Oct 6, 2017
72eb46e
Simplify bookmark logic in surveyplan
dkirkby Oct 6, 2017
37071ad
Fix unit test
dkirkby Oct 6, 2017
ebaf5de
Fix some problems with surveymovie --nightly
dkirkby Oct 6, 2017
0d51d17
Cosmetic tweak
dkirkby Oct 7, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion doc/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ desisurvey change log
0.9.3 (unreleased)
------------------

* No changes yet.
* Fixes #18, #49, #54.
* Improvements to surveymovie script.
* Add progress columns to track fiber assignment and planning.
* Add support for optional depth-first survey strategy.
* Docs now auto-generated at http://desisurvey.readthedocs.io/en/latest/

0.9.2 (2017-09-29)
------------------
Expand Down
27 changes: 27 additions & 0 deletions py/desisurvey/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@
to astropy quantities. Strings of the form YYYY-MM-DD are converted to
datetime.date objects.

To change a value after the configuration has been loaded into memory use,
for example::

>>> config.full_moon_nights.set_value(5)

Assigned values must have the appropriate converted types, for example::

>>> import datetime
>>> config.last_day.set_value(datetime.date(2024, 1, 1))
>>> import astropy.units as u
>>> config.location.temperature.set_value(-5 * u.deg_C)

The configuration is implemented as a singleton so the YAML file is only
loaded and parsed the first time a Configuration() is built. Subsequent
calls to Configuration() always return the same object.
Expand Down Expand Up @@ -98,6 +110,21 @@ def __call__(self):
raise RuntimeError(
'{0} is a non-terminal config node.'.format(self.path))

def set_value(self, new_value):
"""Set a terminal node's value or raise a RuntimeError for
a non-terminal node.
"""
try:
old_value = self._value
if not isinstance(new_value, type(old_value)):
raise RuntimeError(
'new type ({}) does not match old type ({}).'
.format(type(new_value), type(old_value)))
self._value = new_value
except AttributeError:
raise RuntimeError(
'{0} is a non-terminal config node.'.format(self.path))


class Configuration(Node):
"""Top-level configuration data node.
Expand Down
72 changes: 72 additions & 0 deletions py/desisurvey/data/rules-depth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#-------------------------------------------------------------------
# Definition of tile groups that are separately scheduled
# and prioritized. See doc/rules.rst for an explanation for the format.
#-------------------------------------------------------------------

# Implement a simple depth-first survey strategy that expands out from
# DEC=18deg (2deg) in the NGC (SGC), with all tiles included in the
# schedule as soon as they have fibers assigned.

NHI:
# Expand upwards from DEC=18deg in all passes.
cap: N
passes: 0,1,2,3,4,5,6,7
dec_min: 18
dec_order: +0.2
rules:
NHI(0): { START: 1.0 }
NHI(1): { START: 1.0 }
NHI(2): { START: 1.0 }
NHI(3): { START: 1.0 }
NHI(4): { START: 1.0 }
NHI(5): { START: 1.0 }
NHI(6): { START: 1.0 }
NHI(7): { START: 1.0 }

NLO:
# Expand downwards from DEC=18deg in all passes.
cap: N
passes: 0,1,2,3,4,5,6,7
dec_max: 18
dec_order: -0.2
rules:
NLO(0): { START: 1.0 }
NLO(1): { START: 1.0 }
NLO(2): { START: 1.0 }
NLO(3): { START: 1.0 }
NLO(4): { START: 1.0 }
NLO(5): { START: 1.0 }
NLO(6): { START: 1.0 }
NLO(7): { START: 1.0 }

SHI:
# Expand upwards from DEC=2deg in all passes.
cap: S
passes: 0,1,2,3,4,5,6,7
dec_min: 2
dec_order: +0.2
rules:
SHI(0): { START: 1.0 }
SHI(1): { START: 1.0 }
SHI(2): { START: 1.0 }
SHI(3): { START: 1.0 }
SHI(4): { START: 1.0 }
SHI(5): { START: 1.0 }
SHI(6): { START: 1.0 }
SHI(7): { START: 1.0 }

SLO:
# Expand upwards from DEC=2deg in all passes.
cap: S
passes: 0,1,2,3,4,5,6,7
dec_max: 2
dec_order: -0.2
rules:
SLO(0): { START: 1.0 }
SLO(1): { START: 1.0 }
SLO(2): { START: 1.0 }
SLO(3): { START: 1.0 }
SLO(4): { START: 1.0 }
SLO(5): { START: 1.0 }
SLO(6): { START: 1.0 }
SLO(7): { START: 1.0 }
2 changes: 1 addition & 1 deletion py/desisurvey/optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def get_exptime(self, ha, subset=None):
-------
tuple
Tuple (exptime, subset) where exptime is an array of estimated
exposure times in seconds and subset is the input subset or
exposure times in degrees and subset is the input subset or
else a slice initialized for all tiles.
"""
# Calculate for all tiles if no subset is specified.
Expand Down
14 changes: 7 additions & 7 deletions py/desisurvey/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def create(hourangles, priorities):
# covered by any tiles in passes that must be completed before
# fiber assignment can be run.
config = desisurvey.config.Configuration()
num_nights = (config.last_day() - config.first_day()).days
num_nights = desisurvey.utils.day_number(config.last_day())
plan['covered'] = np.full(ntiles, num_nights, int)

# Any passes with no fiber-assignment dependency are initially available.
Expand Down Expand Up @@ -107,13 +107,13 @@ def update_available(plan, progress, night, ephem, fa_delay, fa_delay_type):
config = desisurvey.config.Configuration()
tile_radius = config.tile_radius().to(u.deg).value
# Look up the current night number.
night_number = (night - config.first_day()).days
day_number = desisurvey.utils.day_number(night)
# Find complete tiles.
complete = (progress._table['status'] == 2)
# Average length of synodic month in days.
synodic = 29.53
# Run monthly / quarterly fiber assignment?
month_number = int(np.floor(night_number / synodic))
month_number = int(np.floor(day_number / synodic))
full_moon = ephem.is_full_moon(night, num_nights=1)
do_monthly = do_quarterly = False
if full_moon:
Expand Down Expand Up @@ -146,7 +146,7 @@ def update_available(plan, progress, night, ephem, fa_delay, fa_delay_type):
overlapping = desisurvey.utils.separation_matrix(
ra[under], dec[under], ra[over], dec[over], 2 * tile_radius)
covered = np.all(~overlapping | complete[over], axis=1)
new_covered = covered & (plan['covered'][under] > night_number)
new_covered = covered & (plan['covered'][under] > day_number)
if np.any(new_covered):
new_tiles = plan['tileid'][under][new_covered]
log.info(
Expand All @@ -155,17 +155,17 @@ def update_available(plan, progress, night, ephem, fa_delay, fa_delay_type):
# Record the night number when these tiles were first covered.
new = under.copy()
new[under] = new_covered
plan['covered'][new] = night_number
plan['covered'][new] = day_number
# Check if any tiles are newly available now.
if fa_delay_type == 'd':
avail = plan['available'][under] | (
plan['covered'][under] + fa_delay <= night_number)
plan['covered'][under] + fa_delay <= day_number)
elif do_monthly or do_quarterly:
# Calculate delay since each tile was covered in units of
# lunar cycles ("months") or 3 lunar cycles ("quarters").
period = 3 * synodic if do_quarterly else synodic
delay = np.floor(
(night_number - plan['covered'][under]) / period)
(day_number - plan['covered'][under]) / period)
avail = plan['available'][under] | (delay >= fa_delay)
else:
# No new available tiles.
Expand Down
38 changes: 35 additions & 3 deletions py/desisurvey/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

# Increment this value whenever a non-backwards compatible change to the
# table schema is introduced.
_version = 3
_version = 4

class Progress(object):
"""Initialize a progress tracking object.
Expand Down Expand Up @@ -77,6 +77,15 @@ def __init__(self, restore=None, max_exposures=32):
table['status'] = astropy.table.Column(
length=num_tiles, dtype=np.int32,
description='Observing status: 0=none, 1=partial, 2=done')
table['covered'] = astropy.table.Column(
length=num_tiles, dtype=np.int32,
description='Tile covered on this day number >=0 (or -1)')
table['available'] = astropy.table.Column(
length=num_tiles, dtype=np.int32,
description='Tile available on this day number >=0 (or -1)')
table['planned'] = astropy.table.Column(
length=num_tiles, dtype=np.int32,
description='Tile first planned on this day number >=0 (or -1)')
# Add per-exposure columns.
table['mjd'] = astropy.table.Column(
length=num_tiles, shape=(max_exposures,), format='%.5f',
Expand Down Expand Up @@ -113,6 +122,9 @@ def __init__(self, restore=None, max_exposures=32):
table['dec'] = tiles['DEC']
# Initialize other columns.
table['status'] = 0
table['covered'] = -1
table['available'] = -1
table['planned'] = -1
table['mjd'] = 0.
table['exptime'] = 0.
table['snr2frac'] = 0.
Expand Down Expand Up @@ -319,7 +331,9 @@ def get_summary(self, include='observed'):

# Start a new summary table with the selected rows.
sel = self._table['status'] >= min_status[include]
summary = self._table[sel][['tileid', 'pass', 'ra', 'dec', 'status']]
summary = self._table[sel][[
'tileid', 'pass', 'ra', 'dec', 'status',
'covered', 'available', 'planned']]

# Summarize exposure start times.
col = self._table['mjd']
Expand Down Expand Up @@ -390,6 +404,13 @@ def copy_range(self, mjd_min=None, mjd_max=None):
table['status'] = 1
table['status'][snr2sum == 0] = 0
table['status'][snr2sum >= self.min_snr2] = 2
if mjd_max is not None:
# Rewind the covered and available columns.
config = desisurvey.config.Configuration()
max_day_number = desisurvey.utils.day_number(mjd_max)
table['covered'][table['covered'] > max_day_number] = -1
table['available'][table['available'] > max_day_number] = -1
table['planned'][table['planned'] > max_day_number] = -1
# Return a new progress object with this table.
return Progress(restore=table)

Expand Down Expand Up @@ -462,7 +483,7 @@ def add_exposure(self, tile_id, start, exptime, snr2frac, airmass, seeing,
row['status'] = 1 if row['snr2frac'].sum() < self.min_snr2 else 2

def get_exposures(self, start=None, stop=None,
tile_fields='tileid,pass,ra,dec',
tile_fields='tileid,pass,ra,dec,ebmv',
exp_fields='night,mjd,exptime,seeing,transparency,'
+'airmass,moonfrac,moonalt,moonsep'):
"""Create a table listing exposures in time order.
Expand All @@ -478,6 +499,8 @@ def get_exposures(self, start=None, stop=None,
tile_fields : str
Comma-separated list of per-tile field names to include. The
special name 'index' denotes the index into the visible tile array.
The special name 'ebmv' adds median E(B-V) values for each tile
from the tile design file.
exp_fields : str
Comma-separated list of per-exposure field names to include. The
special name 'snr2cum' denotes the cummulative snr2frac on each
Expand Down Expand Up @@ -524,10 +547,19 @@ def get_exposures(self, start=None, stop=None,
assert np.all(expid[order] >= 0)

# Create the output table.
tileinfo = None
output = astropy.table.Table()
for name in tile_fields.split(','):
if name == 'index':
output[name] = tile_index
elif name == 'ebmv':
if tileinfo is None:
config = desisurvey.config.Configuration()
tileinfo = astropy.table.Table(
desimodel.io.load_tiles(onlydesi=True, extra=False,
tilesfile=config.tiles_file()))
assert np.all(tileinfo['TILEID'] == table['tileid'])
output[name] = tileinfo['EBV_MED'][tile_index]
else:
if name not in table.colnames or len(table[name].shape) != 1:
raise ValueError(
Expand Down
Loading