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

Get ACA penalty and planning limits from chandra_models #356

Merged
merged 2 commits into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 22 additions & 8 deletions proseco/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def get_aca_catalog(obsid=0, **kwargs):
:param man_angle: maneuver angle (deg)
:param t_ccd_acq: ACA CCD temperature for acquisition (degC)
:param t_ccd_guide: ACA CCD temperature for guide (degC)
:param t_ccd_penalty_limit: ACA CCD penalty limit for planning (degC). If not
provided this defaults to value from the ACA xija thermal model.
:param date: date of acquisition (any DateTime-compatible format)
:param dither_acq: acq dither size (2-element sequence (y, z), arcsec)
:param dither_guide: guide dither size (2-element sequence (y, z), arcsec)
Expand Down Expand Up @@ -110,11 +112,14 @@ def _get_aca_catalog(**kwargs):
aca.call_args = kwargs.copy()
aca.set_attrs_from_kwargs(**kwargs)

# Remove kwargs that are specific to AcaTable
for kwarg in ('t_ccd', 't_ccd_penalty_limit'):
if kwarg in kwargs:
del kwargs[kwarg]

# 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

Expand Down Expand Up @@ -168,16 +173,22 @@ def _get_aca_catalog(**kwargs):
return aca


def get_effective_t_ccd(t_ccd):
def get_effective_t_ccd(t_ccd, t_ccd_penalty_limit=None):
"""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:
:param t_ccd: float
Actual (predicted) CCD temperature
:param t_ccd_penalty_limit: float, None
ACA penalty limit to use (degC). Default = aca_t_ccd_penalty_limit from
proseco.characteristics.
:returns: t_ccd_eff
Effective CCD temperature (degC) for use in star selection
"""
t_limit = ACA.aca_t_ccd_penalty_limit
t_limit = (ACA.aca_t_ccd_penalty_limit if t_ccd_penalty_limit is None
else t_ccd_penalty_limit)
if t_ccd > t_limit:
return t_ccd + 1 + (t_ccd - t_limit)
else:
Expand Down Expand Up @@ -205,6 +216,7 @@ class ACATable(ACACatalogTable):
# method below).
t_ccd_eff_acq = MetaAttribute(is_kwarg=False)
t_ccd_eff_guide = MetaAttribute(is_kwarg=False)
t_ccd_penalty_limit = MetaAttribute()

def __copy__(self):
# Astropy Table now does a light key-only copy of the `meta` dict, so
Expand Down Expand Up @@ -252,8 +264,10 @@ def set_attrs_from_kwargs(self, **kwargs):
"""
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)
if self.t_ccd_penalty_limit is None:
self.t_ccd_penalty_limit = ACA.aca_t_ccd_penalty_limit
self.t_ccd_eff_acq = get_effective_t_ccd(self.t_ccd_acq, self.t_ccd_penalty_limit)
self.t_ccd_eff_guide = get_effective_t_ccd(self.t_ccd_guide, self.t_ccd_penalty_limit)
self.version = VERSION

def get_review_table(self):
Expand Down
58 changes: 54 additions & 4 deletions proseco/characteristics.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@
col_spoiler_mag_diff = 4.5
col_spoiler_pix_sep = 10 # pixels

# ACA T_ccd penalty limit (degC).
# Above this limit a temperature "penalty" is applied via get_effective_t_ccd()
aca_t_ccd_penalty_limit = -7.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 All @@ -49,3 +45,57 @@ def _load_bad_star_set():


bad_star_set = LazyDict(_load_bad_star_set)

# Version of chandra_models to use for the ACA xija model from which the
# planning and penalty limits are extracted. Default of None means use the
# current flight-approved version. For testing or other rare circumstances this
# can be overridden prior to calling get_aca_catalog(). This module variable
# gets set to the actual chandra_models version when the ACA attributes are
# first accessed.
chandra_models_version = None

# The next two characteristics are lazily defined to ensure import succeeds.

# aca_t_ccd_penalty_limit : ACA T_ccd penalty limit (degC).
# Above this limit a temperature "penalty" is applied via get_effective_t_ccd()

# aca_t_ccd_planning_limit : ACA T_ccd planning limit (degC).
# Predicted ACA CCD temperatures must be below this limit.


def __getattr__(name):
"""Lazily define module attributes for the ACA planning and penalty limits"""
if name in ('aca_t_ccd_penalty_limit', 'aca_t_ccd_planning_limit'):
_set_aca_limits()
return globals()[name]
else:
raise AttributeError(f"module {__name__} has no attribute {name}")


def _set_aca_limits():
"""Set global variables for ACA thermal planning and penalty limits"""
global chandra_models_version
from xija.get_model_spec import get_xija_model_spec
spec, chandra_models_version = get_xija_model_spec('aca', version=chandra_models_version)

names = ('aca_t_ccd_penalty_limit', 'aca_t_ccd_planning_limit')
spec_names = ('planning.penalty.high', 'planning.warning.high')
for name, spec_name in zip(names, spec_names):
try:
limit = spec['limits']['aacccdpt'][spec_name]
except KeyError:
raise KeyError(f"unable to find ['limits']['aacccdpt']['{spec_name}'] "
"in the ACA xija model in "
f"chandra_models version {chandra_models_version}.")
else:
globals()[name] = limit


# Make sure module-level `dir()` includes the lazy attributes.

# Grab the module attributes before defining __dir__
_attrs = dir()


def __dir__():
return sorted(_attrs + ['aca_t_ccd_penalty_limit', 'aca_t_ccd_planning_limit'])
35 changes: 34 additions & 1 deletion proseco/tests/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
def test_allowed_kwargs():
"""Test #332 where allowed_kwargs class attribute is unique for each subclass"""
new_kwargs = ACATable.allowed_kwargs - ACACatalogTable.allowed_kwargs
assert new_kwargs == {'call_args', 'version'}
assert new_kwargs == {'call_args', 'version', 't_ccd_penalty_limit'}

new_kwargs = FidTable.allowed_kwargs - ACACatalogTable.allowed_kwargs
assert new_kwargs == {'acqs', 'include_ids', 'exclude_ids'}
Expand Down Expand Up @@ -404,6 +404,39 @@ def test_t_ccd_effective_acq_guide(t_ccd_case):

kwargs = mod_std_info(stars=stars, t_ccd_acq=t_ccd_acq, t_ccd_guide=t_ccd_guide)
aca = get_aca_catalog(**kwargs)
assert aca.t_ccd_penalty_limit == t_limit

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)


@pytest.mark.parametrize('t_ccd_case', t_ccd_cases)
def test_t_ccd_effective_acq_guide_via_kwarg(t_ccd_case):
"""Test setting of effective T_ccd temperatures for cases above and
below a manually specified limit
"""
stars = StarsTable.empty()
stars.add_fake_constellation(mag=8.0, n_stars=8)

t_limit = -np.pi # some number different from ACA.aca_t_ccd_penalty_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)
kwargs['t_ccd_penalty_limit'] = t_limit
aca = get_aca_catalog(**kwargs)

assert aca.t_ccd_penalty_limit == t_limit

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