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

Add support for dynamic limits via effective t_ccd #271

Merged
merged 7 commits into from
Feb 19, 2019
Merged
11 changes: 6 additions & 5 deletions proseco/acq.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ class AcqTable(ACACatalogTable):
# Required attributes
required_attrs = ('att', 'man_angle', 't_ccd_acq', 'date', 'dither_acq')

t_ccd = AliasAttribute() # Maps t_ccd to t_ccd_acq base attribute
dither = AliasAttribute() # .. and likewise.
include_ids = AliasAttribute()
include_halfws = AliasAttribute()
exclude_ids = AliasAttribute()

p_man_errs = MetaAttribute(is_kwarg=False)
cand_acqs = MetaAttribute(is_kwarg=False)
p_safe = MetaAttribute(is_kwarg=False)
Expand Down Expand Up @@ -174,11 +180,6 @@ def empty(cls):
out['halfw'] = np.full(fill_value=0, shape=(0,), dtype=np.int64)
return out

t_ccd = AliasAttribute()
dither = AliasAttribute()
include_ids = AliasAttribute()
include_halfws = AliasAttribute()
exclude_ids = AliasAttribute()

@property
def fid_set(self):
Expand Down
70 changes: 70 additions & 0 deletions proseco/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
from .acq import get_acq_catalog, AcqTable
from .fid import get_fid_catalog, FidTable
from . import characteristics_acq as ACQ
from . import characteristics as ACA
from . import test as test_from_init

VERSION = test_from_init(get_version=True)


def get_aca_catalog(obsid=0, **kwargs):
Expand Down Expand Up @@ -96,6 +100,14 @@ def _get_aca_catalog(**kwargs):
aca.call_args = kwargs.copy()
aca.set_attrs_from_kwargs(**kwargs)

# Override t_ccd related inputs with effective temperatures for downstream
# action by AcqTable, GuideTable, FidTable. See set_attrs_from_kwargs()
# method for more details.
if 't_ccd' in kwargs:
del kwargs['t_ccd']
kwargs['t_ccd_acq'] = aca.t_ccd_eff_acq
kwargs['t_ccd_guide'] = aca.t_ccd_eff_guide

# Get stars (typically from AGASC) and do not filter for stars near
# the ACA FOV. This leaves the full radial selection available for
# later roll optimization. Use aca.stars or aca.acqs.stars from here.
Expand Down Expand Up @@ -144,18 +156,51 @@ def _get_aca_catalog(**kwargs):
return aca


def get_effective_t_ccd(t_ccd):
"""Return the effective T_ccd used for selection and catalog evaluation.

For details see Dynamic ACA limits in baby steps section in:
https://occweb.cfa.harvard.edu/twiki/bin/view/Aspect/StarWorkingGroupMeeting2019x02x13

:param t_ccd:
:return:
"""
t_limit = ACA.aca_t_ccd_planning_limit
if t_ccd > t_limit:
return t_ccd + 1 + (t_ccd - t_limit)
else:
return t_ccd


class ACATable(ACACatalogTable):
"""Container ACACatalogTable class that has merged guide / acq / fid catalogs
as attributes and other methods relevant to the merged catalog.

"""
optimize = MetaAttribute(default=True)
call_args = MetaAttribute(default={})
version = MetaAttribute()

# For validation with get_aca_catalog(obsid), store the starcheck
# catalog in the ACATable meta.
starcheck_catalog = MetaAttribute(is_kwarg=False)

# Effective T_ccd used for dynamic ACA limits (see updates_for_t_ccd_effective()
# method below).
t_ccd_eff_acq = MetaAttribute(is_kwarg=False)
t_ccd_eff_guide = MetaAttribute(is_kwarg=False)

@property
def t_ccd(self):
# For top-level ACATable object use the guide temperature, which is always
# greater than or equal to the acq temperature.
return self.t_ccd_guide

@t_ccd.setter
def t_ccd(self, value):
self.t_ccd_guide = value
self.t_ccd_acq = value

@classmethod
def empty(cls):
out = super().empty()
Expand All @@ -170,6 +215,31 @@ def thumbs_up(self):
self.fids.thumbs_up &
self.guides.thumbs_up)

def set_attrs_from_kwargs(self, **kwargs):
"""Set object attributes from kwargs.

After calling the base class method which does all the real work, then
compute the effective T_ccd temperatures.

In this ACATable object:
- t_ccd_eff_{acq,guide} are the effective T_ccd values which are adjusted
if the actual t_ccd{acq,guide} values are above ACA.aca_t_ccd_planning_limit.
- t_ccd_{acq,guide} are the actual (or predicted) values from the call

The downstream AcqTable, GuideTable, and FidTable are initialized with the
*effective* values as t_ccd. Those classes do not have the concept of effective
temperature.

:param kwargs: dict of input kwargs
:return: dict
"""
super().set_attrs_from_kwargs(**kwargs)

self.t_ccd_eff_acq = get_effective_t_ccd(self.t_ccd_acq)
self.t_ccd_eff_guide = get_effective_t_ccd(self.t_ccd_guide)
self.version = VERSION


def get_review_table(self):
"""Get ACAReviewTable object based on self.

Expand Down
3 changes: 3 additions & 0 deletions proseco/characteristics.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
col_spoiler_mag_diff = 4.5
col_spoiler_pix_sep = 10 # pixels

# ACA T_ccd planning limit (degC)
aca_t_ccd_planning_limit = -9.5

# Dark current that corresponds to a 5.0 mag star in a single pixel. Apply
# this value to the region specified by bad_pixels.
bad_pixel_dark_current = 700_000
Expand Down
5 changes: 3 additions & 2 deletions proseco/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,11 +692,12 @@ def dither(self, value):

@property
def t_ccd(self):
return self.t_ccd_guide
# Subclasses must implement.
raise NotImplementedError()

@t_ccd.setter
def t_ccd(self, value):
self.t_ccd_guide = value
raise NotImplementedError()

@classmethod
def empty(cls):
Expand Down
9 changes: 9 additions & 0 deletions proseco/fid.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ def acqs(self, val):
"""
self._acqs = weakref.ref(val)

@property
def t_ccd(self):
# For fids use the guide CCD temperature
return self.t_ccd_guide

@t_ccd.setter
def t_ccd(self, value):
self.t_ccd_guide = value

@property
def thumbs_up(self):
if self.n_fid == 0:
Expand Down
4 changes: 2 additions & 2 deletions proseco/guide.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ def reject(self, reject):
reject_info = self.reject_info
reject_info.append(reject)

t_ccd = AliasAttribute()
dither = AliasAttribute()
t_ccd = AliasAttribute() # Maps t_ccd to t_ccd_guide attribute from base class
dither = AliasAttribute() # .. and likewise.
include_ids = AliasAttribute()
exclude_ids = AliasAttribute()

Expand Down
94 changes: 88 additions & 6 deletions proseco/tests/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import copy

import matplotlib

matplotlib.use('agg')

import pickle
Expand All @@ -12,12 +13,11 @@
import mica.starcheck

from .test_common import STD_INFO, mod_std_info, DARK40, OBS_INFO
from ..core import StarsTable, ACACatalogTable
from ..catalog import get_aca_catalog
from ..core import StarsTable
from ..catalog import get_aca_catalog, ACATable
from ..fid import get_fid_catalog
from .. import characteristics as ACA


HAS_SC_ARCHIVE = Path(mica.starcheck.starcheck.FILES['data_root']).exists()
TEST_COLS = 'slot idx id type sz yang zang dim res halfw'.split()

Expand Down Expand Up @@ -57,7 +57,6 @@ def test_get_aca_catalog_20603():
' 7 9 116791744 ACQ 6x6 985.38 -1210.19 20 1 140',
' 0 10 40108048 ACQ 6x6 2.21 1619.17 20 1 140']


repr(aca) # Apply default formats
assert aca[TEST_COLS].pformat(max_width=-1) == exp

Expand Down Expand Up @@ -168,7 +167,7 @@ def test_big_dither_from_mica_starcheck():
Test code that infers dither_acq and dither_guide for a big-dither
observation like 20168.
"""
aca = ACACatalogTable()
aca = ACATable()
aca.set_attrs_from_kwargs(obsid=20168)

assert aca.detector == 'HRC-S'
Expand Down Expand Up @@ -229,6 +228,7 @@ def test_pickle():
atol=0, rtol=1e-6)
assert aca.acqs.fid_set == aca2.acqs.fid_set


def test_copy_deepcopy_pickle():
"""
Test that copy, deepcopy and pickle all return the expected object which
Expand Down Expand Up @@ -280,6 +280,88 @@ def test_big_sim_offset():
assert all(name in aca.fids.colnames for name in names)


@pytest.mark.parametrize('call_t_ccd', [True, False])
def test_calling_with_t_ccd_acq_guide(call_t_ccd):
"""Test that calling get_aca_catalog with t_ccd or t_ccd_acq/guide args sets all
CCD attributes correctly in the nominal case of a temperature
below the planning limit.

"""
dark = DARK40.copy()
stars = StarsTable.empty()
stars.add_fake_constellation(mag=8.0, n_stars=8)

t_ccd = np.trunc(ACA.aca_t_ccd_planning_limit - 1.0)

if call_t_ccd:
# Call with just t_ccd=t_ccd
t_ccd_guide = t_ccd
t_ccd_acq = t_ccd
ccd_kwargs = {'t_ccd': t_ccd}
else:
# Call with separate values
t_ccd_guide = t_ccd
t_ccd_acq = t_ccd - 1
ccd_kwargs = {'t_ccd_acq': t_ccd_acq, 't_ccd_guide': t_ccd_guide}

kwargs = mod_std_info(stars=stars, dark=dark, **ccd_kwargs)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jeanconn - look here and at the tests below Hopefully at least the API now makes sense. The implementation is not that tricky either but there are some obfuscations that must be traversed...

aca = get_aca_catalog(**kwargs)

assert aca.t_ccd == t_ccd_guide
assert aca.t_ccd_acq == t_ccd_acq
assert aca.t_ccd_guide == t_ccd_guide

assert aca.t_ccd_eff_acq == t_ccd_acq
assert aca.t_ccd_eff_guide == t_ccd_guide

assert aca.acqs.t_ccd == t_ccd_acq
assert aca.acqs.t_ccd_acq == t_ccd_acq
assert aca.acqs.t_ccd_guide == t_ccd_guide

assert aca.guides.t_ccd == t_ccd_guide
assert aca.guides.t_ccd_guide == t_ccd_guide
assert aca.guides.t_ccd_acq == t_ccd_acq

assert aca.fids.t_ccd == t_ccd_guide
assert aca.fids.t_ccd_guide == t_ccd_guide
assert aca.fids.t_ccd_acq == t_ccd_acq


t_ccd_cases = [(-0.5, 0, 0),
(0, 0, 0),
(0.5, 1.5, 1.4)]


@pytest.mark.parametrize('t_ccd_case', t_ccd_cases)
def test_t_ccd_effective_acq_guide(t_ccd_case):
"""Test setting of effective T_ccd temperatures for cases above and
below the planning limit.

"""
stars = StarsTable.empty()
stars.add_fake_constellation(mag=8.0, n_stars=8)

t_limit = ACA.aca_t_ccd_planning_limit

t_offset, t_penalty_acq, t_penalty_guide = t_ccd_case
# Set acq and guide temperatures different
t_ccd_acq = t_limit + t_offset
t_ccd_guide = t_ccd_acq - 0.1

kwargs = mod_std_info(stars=stars, t_ccd_acq=t_ccd_acq, t_ccd_guide=t_ccd_guide)
aca = get_aca_catalog(**kwargs)

assert np.isclose(aca.t_ccd_acq, t_ccd_acq)
assert np.isclose(aca.t_ccd_guide, t_ccd_guide)

# t_ccd + 1 + (t_ccd - t_limit) from proseco.catalog.get_effective_t_ccd()
assert np.isclose(aca.t_ccd_eff_acq, t_ccd_acq + t_penalty_acq)
assert np.isclose(aca.t_ccd_eff_guide, t_ccd_guide + t_penalty_guide)

assert np.isclose(aca.t_ccd_eff_acq, aca.acqs.t_ccd)
assert np.isclose(aca.t_ccd_eff_guide, aca.guides.t_ccd)


def test_call_args_attr():
aca = get_aca_catalog(**mod_std_info(optimize=False, n_guide=0, n_acq=0, n_fid=0))
assert aca.call_args == {'att': (0, 0, 0),
Expand Down Expand Up @@ -540,7 +622,7 @@ def test_report_from_objects(tmpdir):
obsdir = rootdir / f'obs{obsid:05}'
for subdir in 'acq', 'guide':
outdir = obsdir / subdir
assert(outdir / 'index.html').exists()
assert (outdir / 'index.html').exists()
assert len(list(outdir.glob('*.png'))) > 0


Expand Down