Verify FOT Matlab tools 2016_210 dynamic offsets test products

This notebook performs functional testing of test load products created starting with existing OFLS products and processing with Matlab tools 2016_210. This release introduces dynamical aimpoint offsets that compensate for temperature-dependent alignment changes of the ACA. The Matlab tools code in turn depends a new version 0.7 of the chandra_aca package which provides the core function to compute the temperature-dependent dynamical aimpoint offsets.

Overall test requirements are defined in the Aimpoint Transition Plan for Cycle 18. The functional testing in this notebook consists of the following:

For post-NOV0215 products with a nominal zero-offsets aimpoint table

  • Check internal consistency of dynamical_offsets table
  • Check dynamical offsets ACA attitude matches TEST maneuver summary
  • Check dynamical offsets ACA attitude matches FLIGHT maneuver summary for ACIS observations within ~10 arcsec.

    • This is expected because the new dynamic offsets should roughly replace the OFLS CHARACTERISTICS update method of aimpoint adjustment.
    • This constitutes a rough functional test of Python code to generate dynamic ACA offsets.
    • It also provides a very simple demonstration that the attitude changes are not large.
    • Differences will occur due to:
      • Change from the cycle 17 default aimpoint to the cycle 18 default aimpoint. These are less than 5 arcsec.
      • Difference in per-obsid predicted ACA temperature from the mean 3-month value used in the CHARACTERISTICS update.
  • Check that predicted CHIPX / CHIPY matches expectation to within 10 arcsec.

    • Use the test ACA attitude and observed SIM DY/DZ (alignment from fids).
    • Generate a "fake" observation with adjusted RA_NOM, DEC_NOM, ROLL_NOM ("HRMA attitude").
    • Adjustment based on the delta between TEST and FLIGHT pointing attitudes.
    • Use the CIAO tool dmcoords to compute predicted CHIPX / CHIPY.

For pre-NOV1615 products with an empty zero-offsets aimpoint table

  • Check that TEST and FLIGHT attitudes from maneuver summary match to within 0.1 arcsec.

ALL TESTS PASS


In [1]:
# Required imports

from __future__ import division, print_function
import glob
import shelve
from astropy.table import Table
import numpy as np
from Quaternion import Quat
import chandra_aca
from chandra_aca import calc_aca_from_targ, calc_targ_from_aca
import parse_cm
from Ska.engarchive import fetch_sci
from Chandra.Time import DateTime
import Ska.Shell
import functools
import mica.archive.obspar
import Ska.arc5gl
import os
import parse_cm.maneuver

%matplotlib inline

"Post-NOV0215" verification testing using the MAR0516O products

These are nominal flight-like products created using the existing MAR0516 loads.

One expected difference is that Matlab uses the OFLS CHARACTERISTICS file CHARACTERIS_07JUL16 to back out the OFLS ODB_SI_ALIGN transform. MAR0516 was planned using CHARACTERIS_28FEB16. There is a (1.77, 0.33) arcsec difference for both ACIS-S and ACIS-I in the ODB_SI_ALIGN offsets. This 1.8 arcsec offset is seen in comparisons while in newly generated products there will be no such offset.


In [2]:
PRODUCTS = 'MAR0516O'

TEST_DIR = '/proj/sot/ska/ops/SFE'
FLIGHT_DIR = '/data/mpcrit1/mplogs/2016'  # Must match PRODUCTS

In [3]:
# SI_ALIGN from Matlab code
SI_ALIGN = chandra_aca.ODB_SI_ALIGN
SI_ALIGN


Out[3]:
array([[  9.99999906e-01,  -3.37419984e-04,  -2.73439987e-04],
       [  3.37419984e-04,   9.99999943e-01,  -4.61320600e-08],
       [  2.73439987e-04,  -4.61320600e-08,   9.99999963e-01]])

In [4]:
def print_dq(q1, q2):
    """
    Print the difference between two quaternions in a nice formatted way.
    """
    dq = q1.inv() * q2
    dr, dp, dy, _ = np.degrees(dq.q) * 2 * 3600
    print('droll={:6.2f}, dpitch={:6.2f}, dyaw={:6.2f} arcsec'.format(dr, dp, dy))

In [5]:
def check_obs(obs):
    """
    Check `obs` (which is a row out of the dynamic offsets table) for consistency
    between target and aca coordinates given the target and aca offsets and the
    SI_ALIGN alignment matrix.
    """
    y_off = (obs['target_offset_y'] + obs['aca_offset_y']) / 3600
    z_off = (obs['target_offset_z'] + obs['aca_offset_z']) / 3600
    
    q_targ = Quat([obs['target_ra'], obs['target_dec'], obs['target_roll']])
    q_aca = Quat([obs['aca_ra'], obs['aca_dec'], obs['aca_roll']])
    
    q_aca_out = calc_aca_from_targ(q_targ, y_off, z_off, SI_ALIGN)
    print('{} {:6s} '.format(obs['obsid'], obs['detector']), end='')
    print_dq(q_aca, q_aca_out)

In [6]:
def check_obs_vs_manvr(obs, manvr):
    """
    Check against attitude from actual flight products (from maneuver summary file)
    """
    mf = manvr['final']
    q_flight = Quat([mf['q1'], mf['q2'], mf['q3'], mf['q4']])
    q_aca = Quat([obs['aca_ra'], obs['aca_dec'], obs['aca_roll']])
    print('{} {:6s} chipx={:8.2f} chipy={:8.2f} '
          .format(obs['obsid'], obs['detector'], obs['chipx'], obs['chipy']), end='')
    print_dq(q_aca, q_flight)

In [7]:
filename = os.path.join(TEST_DIR, PRODUCTS, 'ofls', 'output', '{}_dynamical_offsets.txt'.format(PRODUCTS))
dat = Table.read(filename, format='ascii')

In [8]:
dat[:5]


Out[8]:
<Table masked=False length=5>
obsiddetectorchipxchipychip_idtarget_offset_ytarget_offset_ztarget_ratarget_dectarget_rollaca_offset_yaca_offset_zaca_raaca_decaca_rollmean_datemean_t_ccd
int64string48float64float64int64float64float64float64float64float64float64float64float64float64float64string168float64
18168ACIS-S200.7476.970.0-18.035.88791745.821194305.04846610.632.6135.85667545.832545305.0712422016:065:07:11:35.816-14.41
18091ACIS-S200.7476.970.0-18.019.49791745.605944320.6725089.722.1719.46396545.610948320.6972152016:065:10:33:51.816-14.18
18157ACIS-S200.7476.970.0-18.0281.12833322.07444464.9999518.541.59281.1294322.05055864.9995892016:065:14:17:59.816-13.88
18725ACIS-S210.0520.070.0-18.0168.6376525.710603165.0002383.08-20.04168.66020925.71034164.9902152016:065:19:04:59.816-13.65
18276ACIS-I930.21009.63-18.0-18.0194.64958328.074806124.84737710.332.03194.67076528.067452124.837222016:066:10:09:43.816-14.72

Check internal consistency of dynamical_offsets table

This checks that applying the sum of target and ACA offsets along with the nominal SI_ALIGN to the target attitude produces the ACA attitude.

All the ACIS observations have a similar offset due to the CHARACTERISTICS mismatch noted earlier, while the HRC observations show the expected 0.0 offset.


In [9]:
for obs in dat:
    check_obs(obs)


18168 ACIS-S droll= -1.32, dpitch=  0.33, dyaw= -1.76 arcsec
18091 ACIS-S droll= -1.60, dpitch=  0.33, dyaw= -1.75 arcsec
18157 ACIS-S droll= -0.18, dpitch=  0.33, dyaw= -1.75 arcsec
18725 ACIS-S droll=  0.86, dpitch=  0.33, dyaw= -1.76 arcsec
18276 ACIS-I droll=  0.68, dpitch=  0.33, dyaw= -1.76 arcsec
18201 ACIS-S droll=  0.80, dpitch=  0.34, dyaw= -1.76 arcsec
18803 ACIS-S droll=  0.04, dpitch=  0.33, dyaw= -1.75 arcsec
17720 ACIS-S droll=  0.02, dpitch=  0.33, dyaw= -1.75 arcsec
18304 HRC-I  droll= -0.00, dpitch=  0.00, dyaw= -0.00 arcsec
18212 ACIS-S droll=  1.04, dpitch=  0.34, dyaw= -1.76 arcsec
18272 ACIS-I droll=  0.79, dpitch=  0.33, dyaw= -1.76 arcsec
18790 ACIS-S droll=  0.09, dpitch=  0.33, dyaw= -1.75 arcsec
18791 ACIS-I droll=  0.79, dpitch=  0.34, dyaw= -1.76 arcsec
18800 ACIS-S droll=  0.09, dpitch=  0.34, dyaw= -1.76 arcsec
18792 ACIS-I droll=  0.79, dpitch=  0.34, dyaw= -1.76 arcsec
18469 ACIS-I droll=  0.20, dpitch=  0.34, dyaw= -1.76 arcsec
18194 ACIS-S droll=  0.24, dpitch=  0.34, dyaw= -1.75 arcsec
18801 ACIS-S droll=  0.12, dpitch=  0.33, dyaw= -1.76 arcsec
18793 ACIS-I droll=  0.79, dpitch=  0.33, dyaw= -1.76 arcsec
17213 ACIS-I droll= -1.99, dpitch=  0.33, dyaw= -1.76 arcsec
18417 ACIS-I droll= -4.45, dpitch=  0.34, dyaw= -1.76 arcsec
17721 ACIS-S droll=  0.02, dpitch=  0.33, dyaw= -1.76 arcsec
16747 ACIS-S droll=  0.12, dpitch=  0.33, dyaw= -1.76 arcsec
18628 ACIS-I droll=  0.00, dpitch=  0.34, dyaw= -1.75 arcsec
18761 ACIS-I droll=  0.79, dpitch=  0.34, dyaw= -1.75 arcsec
17323 HRC-S  droll=  0.00, dpitch=  0.00, dyaw= -0.00 arcsec
18420 ACIS-S droll= -4.58, dpitch=  0.34, dyaw= -1.76 arcsec
18794 ACIS-I droll=  0.79, dpitch=  0.33, dyaw= -1.76 arcsec
18418 ACIS-S droll= -4.65, dpitch=  0.34, dyaw= -1.76 arcsec
17722 ACIS-S droll=  0.02, dpitch=  0.33, dyaw= -1.75 arcsec
18275 ACIS-I droll=  0.89, dpitch=  0.33, dyaw= -1.76 arcsec
18619 ACIS-S droll= -4.94, dpitch=  0.34, dyaw= -1.76 arcsec
18795 ACIS-I droll=  0.89, dpitch=  0.34, dyaw= -1.76 arcsec
18144 ACIS-S droll=  1.72, dpitch=  0.34, dyaw= -1.75 arcsec
18796 ACIS-I droll=  0.89, dpitch=  0.33, dyaw= -1.76 arcsec
18145 ACIS-S droll=  1.73, dpitch=  0.34, dyaw= -1.75 arcsec
18797 ACIS-I droll=  0.89, dpitch=  0.33, dyaw= -1.75 arcsec
18798 ACIS-I droll=  0.89, dpitch=  0.34, dyaw= -1.76 arcsec
18799 ACIS-S droll=  1.72, dpitch=  0.34, dyaw= -1.76 arcsec

Check dynamical offsets ACA attitude matches TEST maneuver summary

It is assumed that the maneuver summary matches the load product attitudes.


In [10]:
filename = glob.glob(os.path.join(TEST_DIR, PRODUCTS, 'ofls', 'mps', 'mm*.sum'))[0]
print('Reading', filename)
mm = parse_cm.maneuver.read_maneuver_summary(filename, structured=True)
mm = {m['final']['id']: m for m in mm}  # Turn into a dict


Reading /proj/sot/ska/ops/SFE/MAR0516O/ofls/mps/mm065_0409.sum

In [11]:
for obs in dat:
    check_obs_vs_manvr(obs, mm[obs['obsid'] * 100])


18168 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18091 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
18157 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
18725 ACIS-S chipx=  210.00 chipy=  520.00 droll=  0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18276 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18201 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch=  0.00, dyaw= -0.00 arcsec
18803 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch= -0.00, dyaw= -0.00 arcsec
17720 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
18304 HRC-I  chipx= 7591.00 chipy= 7936.10 droll=  0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18212 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
18272 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.00, dpitch= -0.00, dyaw=  0.00 arcsec
18790 ACIS-S chipx=  210.00 chipy=  520.00 droll= -0.00, dpitch= -0.00, dyaw=  0.00 arcsec
18791 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18800 ACIS-S chipx=  210.00 chipy=  520.00 droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
18792 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.00, dpitch= -0.00, dyaw=  0.00 arcsec
18469 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
18194 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch=  0.00, dyaw= -0.00 arcsec
18801 ACIS-S chipx=  205.00 chipy=  480.00 droll=  0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18793 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
17213 ACIS-I chipx=  932.00 chipy= 1009.00 droll= -0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18417 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch=  0.00, dyaw= -0.00 arcsec
17721 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch=  0.00, dyaw= -0.00 arcsec
16747 ACIS-S chipx=  205.00 chipy=  480.00 droll=  0.00, dpitch=  0.00, dyaw= -0.00 arcsec
18628 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
18761 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
17323 HRC-S  chipx= 2050.00 chipy= 9023.00 droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
18420 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch= -0.00, dyaw=  0.00 arcsec
18794 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18418 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch=  0.00, dyaw= -0.00 arcsec
17722 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18275 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18619 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch=  0.00, dyaw= -0.00 arcsec
18795 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch=  0.00, dyaw= -0.00 arcsec
18144 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch= -0.00, dyaw= -0.00 arcsec
18796 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
18145 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
18797 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.00, dpitch= -0.00, dyaw=  0.00 arcsec
18798 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.00, dpitch= -0.00, dyaw=  0.00 arcsec
18799 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch= -0.00, dyaw=  0.00 arcsec

Check dynamical offsets ACA attitude matches FLIGHT maneuver summary to ~10 arcsec

It is assumed that the maneuver summary matches the load product attitudes.

There are three discrepancies below: obsids 18725, 18790, and 18800. All of these are DDT observations that are configured in the OR list to use Cycle 18 aimpoint values, but without changing the target offsets from cycle 17 values. This is an artifact of testing and would not occur in flight planning.


In [12]:
os.path.join(FLIGHT_DIR, PRODUCTS[:-1], 'ofls', 'mps', 'mm*.sum')


Out[12]:
'/data/mpcrit1/mplogs/2016/MAR0516/ofls/mps/mm*.sum'

In [13]:
filename = glob.glob(os.path.join(FLIGHT_DIR, PRODUCTS[:-1], 'ofls', 'mps', 'mm*.sum'))[0]
print('Reading', filename)
mm = parse_cm.maneuver.read_maneuver_summary(filename, structured=True)
mm = {m['final']['id']: m for m in mm}  # Turn into a dict


Reading /data/mpcrit1/mplogs/2016/MAR0516/ofls/mps/mm065_0409.sum

In [14]:
for obs in dat:
    check_obs_vs_manvr(obs, mm[obs['obsid'] * 100])


18168 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.01, dpitch=  0.75, dyaw= -3.32 arcsec
18091 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch=  1.20, dyaw= -4.22 arcsec
18157 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.01, dpitch=  1.78, dyaw= -5.40 arcsec
18725 ACIS-S chipx=  210.00 chipy=  520.00 droll= -0.01, dpitch= 23.40, dyaw=-10.86 arcsec
18276 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch=  0.65, dyaw= -1.13 arcsec
18201 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.01, dpitch= -1.20, dyaw=  0.68 arcsec
18803 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch= -1.32, dyaw=  0.94 arcsec
17720 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch= -2.23, dyaw=  2.81 arcsec
18304 HRC-I  chipx= 7591.00 chipy= 7936.10 droll=  0.01, dpitch= -2.32, dyaw= 17.68 arcsec
18212 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.01, dpitch= -1.50, dyaw=  1.30 arcsec
18272 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch= -0.80, dyaw=  1.85 arcsec
18790 ACIS-S chipx=  210.00 chipy=  520.00 droll= -0.01, dpitch= 20.06, dyaw= -4.01 arcsec
18791 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch=  0.31, dyaw= -0.42 arcsec
18800 ACIS-S chipx=  210.00 chipy=  520.00 droll= -0.01, dpitch= 21.07, dyaw= -6.08 arcsec
18792 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch=  1.13, dyaw= -2.10 arcsec
18469 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch= -0.86, dyaw=  1.98 arcsec
18194 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch= -1.50, dyaw=  1.30 arcsec
18801 ACIS-S chipx=  205.00 chipy=  480.00 droll=  0.00, dpitch=  0.58, dyaw= -1.94 arcsec
18793 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.00, dpitch=  1.41, dyaw= -2.67 arcsec
17213 ACIS-I chipx=  932.00 chipy= 1009.00 droll= -0.00, dpitch=  1.52, dyaw= -1.39 arcsec
18417 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.01, dpitch=  0.92, dyaw= -1.67 arcsec
17721 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch= -0.16, dyaw= -1.42 arcsec
16747 ACIS-S chipx=  205.00 chipy=  480.00 droll=  0.00, dpitch=  1.65, dyaw= -4.14 arcsec
18628 ACIS-I chipx=  930.20 chipy= 1009.60 droll=  0.00, dpitch=  1.82, dyaw= -3.51 arcsec
18761 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch=  2.36, dyaw= -4.61 arcsec
17323 HRC-S  chipx= 2050.00 chipy= 9023.00 droll= -0.00, dpitch= -0.92, dyaw=  9.25 arcsec
18420 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch=  0.27, dyaw= -2.32 arcsec
18794 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch=  1.11, dyaw= -2.05 arcsec
18418 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch= -1.45, dyaw=  1.21 arcsec
17722 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.00, dpitch= -1.10, dyaw=  0.49 arcsec
18275 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.00, dpitch=  0.14, dyaw= -0.07 arcsec
18619 ACIS-S chipx=  200.70 chipy=  476.90 droll=  0.01, dpitch= -0.56, dyaw= -0.61 arcsec
18795 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch=  1.01, dyaw= -1.84 arcsec
18144 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch= -0.94, dyaw=  0.17 arcsec
18796 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch=  2.11, dyaw= -4.09 arcsec
18145 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch=  1.16, dyaw= -4.12 arcsec
18797 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch=  2.21, dyaw= -4.29 arcsec
18798 ACIS-I chipx=  930.20 chipy= 1009.60 droll= -0.01, dpitch=  0.34, dyaw= -0.47 arcsec
18799 ACIS-S chipx=  200.70 chipy=  476.90 droll= -0.00, dpitch= -0.18, dyaw= -1.38 arcsec

Check that predicted CHIPX / CHIPY matches expectation to within 10 arcsec.

  • Use the test ACA attitude and observed SIM DY/DZ (alignment from fids).
  • Generate a "fake" observation with adjusted RA_NOM, DEC_NOM, ROLL_NOM ("HRMA attitude").
  • Adjustment based on the delta between TEST and FLIGHT pointing attitudes.
  • Use the CIAO tool dmcoords to compute predicted CHIPX / CHIPY.

In the results below there are two discrepancies, obsids 18168 and 18091. These are very hot observations coming right after safe mode recovery. In this case the thermal model is inaccurate and the commanded pointing would have been offset by up to 15 arcsec. Future improvements in thermal modeling could reduce this offset, but it should be understood that pointing accuracy will be degraded in such a situation.

CIAO dmcoords tool setup


In [15]:
ciaoenv = Ska.Shell.getenv('source /soft/ciao/bin/ciao.sh')
ciaorun = functools.partial(Ska.Shell.bash, env=ciaoenv)

In [16]:
dmcoords_cmd = ['dmcoords', 'none',
                'asolfile=none',
                'detector="{detector}"',
                'fpsys="{fpsys}"',
                'opt=cel',
                'ra={ra_targ}', 
                'dec={dec_targ}',
                'celfmt=deg', 
                'ra_nom={ra_nom}',
                'dec_nom={dec_nom}',
                'roll_nom={roll_nom}',  
                'ra_asp=")ra_nom"',
                'dec_asp=")dec_nom"',
                'roll_asp=")roll_nom"',      
                'sim="{sim_x} 0 {sim_z}"',
                'displace="0 {dy} {dz} 0 0 0"',       
                'verbose=0']
dmcoords_cmd = ' '.join(dmcoords_cmd)

In [17]:
def dmcoords_chipx_chipy(keys, verbose=False):
    """
    Get the dmcoords-computed chipx and chipy for given event file 
    header keyword params.  NOTE: the ``dy`` and ``dz`` inputs
    to dmcoords are flipped in sign from the ASOL values.  Generally the
    ASOL DY/DZ are positive and dmcoord input values are negative.  This
    sign flip is handled *here*, so input to this is ASOL DY/DZ.
    
    :param keys: dict of event file keywords
    """
    # See the absolute_pointing_uncertainty notebook in this repo for the
    # detailed derivation of this -15.5, 6.0 arcsec offset factor.  See the
    # cell below for the summary version.
    ciaorun('punlearn dmcoords')
    fpsys_map = {'HRC-I': 'HI1',
                'HRC-S': 'HS2',
                'ACIS': 'ACIS'}
    keys = {key.lower(): val for key, val in keys.items()}
    det = keys['detnam']
    keys['detector'] = (det if det.startswith('HRC') else 'ACIS')
    keys['dy'] = -keys['dy_avg']
    keys['dz'] = -keys['dz_avg']
    keys['fpsys'] = fpsys_map[keys['detector']]
    
    cmd = dmcoords_cmd.format(**keys)
    ciaorun(cmd)
    
    if verbose:
        print(cmd)
    return [float(x) for x in ciaorun('pget dmcoords chipx chipy chip_id')]

In [18]:
def get_evt_meta(obsid, detector):
    """
    Get event file metadata (FITS keywords) for ``obsid`` and ``detector`` and cache for later use.
    
    Returns a dict of key=value pairs.
    """
    evts = shelve.open('event_meta.shelf')
    sobsid = str(obsid)
    if sobsid not in evts:
        det = 'hrc' if detector.startswith('HRC') else 'acis'
        arc5gl = Ska.arc5gl.Arc5gl()
        arc5gl.sendline('obsid={}'.format(obsid))
        arc5gl.sendline('get {}2'.format(det) + '{evt2}')
        del arc5gl

        files = glob.glob('{}f{}*_evt2.fits.gz'.format(det, obsid))
        if len(files) != 1:
            raise ValueError('Wrong number of files {}'.format(files))
        evt2 = Table.read(files[0])
        os.unlink(files[0])

        evts[sobsid] = {k.lower(): v for k, v in evt2.meta.items()}

    out = evts[sobsid]
    evts.close()
    
    return out

In [19]:
def check_predicted_chipxy(obs):
    """
    Compare the predicted CHIPX/Y values with planned using observed event file
    data on actual ACA alignment.
    """
    obsid = obs['obsid']
    detector = obs['detector']
    try:
        evt = get_evt_meta(obsid, detector)
    except ValueError as err:
        print('Obsid={} detector={}: fail {}'.format(obsid, detector, err))
        return
    f_chipx, f_chipy, f_chip_id = dmcoords_chipx_chipy(evt)
    
    q_nom_flight = Quat([evt['ra_nom'], evt['dec_nom'], evt['roll_nom']])
    q_aca = Quat([obs['aca_ra'], obs['aca_dec'], obs['aca_roll']])
    mf = mm[obsid * 100]['final']
    q_flight = Quat([mf['q1'], mf['q2'], mf['q3'], mf['q4']])
    dq = q_flight.dq(q_aca)
    q_nom_test = q_nom_flight * dq
    evt_test = dict(evt)
    evt_test['ra_nom'] = q_nom_test.ra
    evt_test['dec_nom'] = q_nom_test.dec
    evt_test['roll_nom'] = q_nom_test.roll
    
    scale = 0.13175 if detector.startswith('HRC') else 0.492
    aim_chipx = obs['chipx']
    aim_chipy = obs['chipy']
    if detector == 'ACIS-S':
        aim_chipx += -obs['target_offset_y'] / scale
        aim_chipy += -obs['target_offset_z'] / scale + 20.5 / 0.492 * (-190.14 - evt['sim_z'])
    elif detector == 'ACIS-I':
        aim_chipx += -obs['target_offset_z'] / scale + 20.5 / 0.492 * (-233.59 - evt['sim_z'])
        aim_chipy += +obs['target_offset_y'] / scale

    chipx, chipy, chip_id = dmcoords_chipx_chipy(evt_test)
    print('{} {:6s} aimpoint:{:6.1f},{:6.1f} test:{:6.1f},{:6.1f} '
          'flight:{:6.1f},{:6.1f} delta: {:.1f} arcsec'
         .format(obsid, detector, aim_chipx, aim_chipy, chipx, chipy, f_chipx, f_chipy,
                np.hypot(aim_chipx - chipx, aim_chipy - chipy) * scale))

In [20]:
for obs in dat:
    check_predicted_chipxy(obs)


18168 ACIS-S aimpoint: 200.7, 513.5 test: 174.1, 499.7 flight: 167.4, 498.2 delta: 14.7 arcsec
18091 ACIS-S aimpoint: 200.7, 513.5 test: 179.3, 499.4 flight: 170.7, 497.0 delta: 12.6 arcsec
18157 ACIS-S aimpoint: 200.7, 513.5 test: 185.4, 500.8 flight: 174.4, 497.2 delta: 9.8 arcsec
18725 ACIS-S aimpoint: 210.0, 556.6 test: 199.9, 548.9 flight: 177.8, 501.3 delta: 6.2 arcsec
18276 ACIS-I aimpoint: 966.7, 973.0 test: 964.6, 977.6 flight: 963.3, 979.9 delta: 2.5 arcsec
18201 ACIS-S aimpoint: 200.7, 513.6 test: 196.9, 512.8 flight: 198.3, 515.3 delta: 1.9 arcsec
18803 ACIS-S aimpoint: 200.7, 513.6 test: 197.8, 513.1 flight: 199.7, 515.8 delta: 1.5 arcsec
17720 ACIS-S aimpoint: 200.7, 513.5 test: 197.5, 511.4 flight: 203.2, 515.9 delta: 1.9 arcsec
18304 HRC-I  aimpoint:7591.0,7936.1 test:7566.1,7956.3 flight:7648.5,7848.9 delta: 4.2 arcsec
18212 ACIS-S aimpoint: 200.7, 513.5 test: 195.9, 511.4 flight: 198.5, 514.4 delta: 2.6 arcsec
18272 ACIS-I aimpoint: 966.7, 973.0 test: 965.3, 977.5 flight: 966.9, 973.7 delta: 2.3 arcsec
18790 ACIS-S aimpoint: 210.0, 556.7 test: 205.2, 555.9 flight: 197.0, 515.1 delta: 2.4 arcsec
18791 ACIS-I aimpoint: 966.7, 973.0 test: 965.2, 977.4 flight: 964.6, 978.3 delta: 2.3 arcsec
18800 ACIS-S aimpoint: 210.0, 556.7 test: 206.5, 556.5 flight: 194.1, 513.6 delta: 1.7 arcsec
18792 ACIS-I aimpoint: 966.7, 973.0 test: 965.6, 976.4 flight: 963.3, 980.7 delta: 1.7 arcsec
18469 ACIS-I aimpoint: 845.2, 887.6 test: 843.9, 887.4 flight: 845.7, 883.4 delta: 0.6 arcsec
18194 ACIS-S aimpoint: 200.7, 513.6 test: 198.4, 513.7 flight: 201.1, 516.8 delta: 1.1 arcsec
18801 ACIS-S aimpoint: 205.0, 516.7 test: 202.7, 515.3 flight: 198.8, 514.1 delta: 1.3 arcsec
18793 ACIS-I aimpoint: 966.7, 973.0 test: 967.4, 972.2 flight: 964.5, 977.6 delta: 0.5 arcsec
17213 ACIS-I aimpoint: 968.5, 972.4 test: 967.7, 966.9 flight: 964.6, 969.7 delta: 2.8 arcsec
18417 ACIS-I aimpoint: 869.1, 936.4 test: 866.9, 931.6 flight: 865.0, 935.0 delta: 2.6 arcsec
17721 ACIS-S aimpoint: 200.7, 513.5 test: 211.3, 519.4 flight: 208.4, 519.7 delta: 6.0 arcsec
16747 ACIS-S aimpoint: 205.0, 516.6 test: 213.5, 519.8 flight: 205.1, 516.4 delta: 4.5 arcsec
Obsid=18628 detector=ACIS-I: fail Wrong number of files []
18761 ACIS-I aimpoint: 966.7, 973.0 test: 972.1, 963.2 flight: 967.2, 972.5 delta: 5.5 arcsec
17323 HRC-S  aimpoint:2050.0,9023.0 test:2052.8,9054.2 flight:2059.7,8984.0 delta: 4.1 arcsec
18420 ACIS-S aimpoint: 653.1, 915.3 test: 660.1, 914.3 flight: 655.3, 913.8 delta: 3.5 arcsec
18794 ACIS-I aimpoint: 966.7, 973.0 test: 967.0, 971.8 flight: 964.7, 975.9 delta: 0.6 arcsec
18418 ACIS-S aimpoint: 103.1, 476.9 test: 100.0, 472.4 flight: 102.5, 475.3 delta: 2.7 arcsec
17722 ACIS-S aimpoint: 200.7, 513.5 test: 198.7, 508.4 flight: 199.7, 510.6 delta: 2.7 arcsec
18275 ACIS-I aimpoint: 966.7, 973.0 test: 966.0, 976.1 flight: 965.7, 976.3 delta: 1.6 arcsec
18619 ACIS-S aimpoint: 200.7, 257.2 test: 198.8, 253.6 flight: 197.5, 254.7 delta: 2.0 arcsec
18795 ACIS-I aimpoint: 966.7, 973.0 test: 966.4, 974.1 flight: 964.3, 977.8 delta: 0.5 arcsec
18144 ACIS-S aimpoint: 200.7, 476.9 test: 201.4, 475.3 flight: 201.7, 477.2 delta: 0.9 arcsec
18796 ACIS-I aimpoint: 966.7, 973.0 test: 969.5, 967.3 flight: 965.2, 975.6 delta: 3.1 arcsec
18145 ACIS-S aimpoint: 200.7, 477.0 test: 208.3, 479.5 flight: 200.0, 477.1 delta: 3.9 arcsec
18797 ACIS-I aimpoint: 966.7, 973.0 test: 969.4, 967.8 flight: 964.9, 976.6 delta: 2.9 arcsec
18798 ACIS-I aimpoint: 966.7, 973.0 test: 964.8, 976.4 flight: 964.1, 977.4 delta: 1.9 arcsec
18799 ACIS-S aimpoint: 200.7, 477.0 test: 199.8, 475.0 flight: 197.0, 475.4 delta: 1.1 arcsec

For pre-NOV1615 products with an empty zero-offsets aimpoint table

Check that TEST (JUL0415M) and FLIGHT attitudes from maneuver summary match to within 0.1 arcsec.

JUL0415M was constructed with an OR-list zero-offset aimpoint table which exists but has no row entries. This has the effect of telling SAUSAGE to run through the attitude replacement machinery but use 0.0 for the ACA offset y/z values. This should output attitudes that are precisely the same as the FLIGHT attitudes.

Results: pass


In [21]:
PRODUCTS = 'JUL0415M'
FLIGHT_DIR = '/data/mpcrit1/mplogs/2015'  # Must match PRODUCTS

In [22]:
# Get FLIGHT maneuver summary
filename = glob.glob(os.path.join(FLIGHT_DIR, PRODUCTS[:-1], 'ofls', 'mps', 'mm*.sum'))[0]
print('Reading', filename)
mmf = parse_cm.maneuver.read_maneuver_summary(filename, structured=True)
mmf = {m['final']['id']: m for m in mmf}  # Turn into a dict


Reading /data/mpcrit1/mplogs/2015/JUL0415/ofls/mps/mm185_1000.sum

In [23]:
# Get TEST maneuver summary
filename = glob.glob(os.path.join(TEST_DIR, PRODUCTS, 'ofls', 'mps', 'mm*.sum'))[0]
print('Reading', filename)
mmt = parse_cm.maneuver.read_maneuver_summary(filename, structured=True)
mmt = {m['final']['id']: m for m in mmt}  # Turn into a dict


Reading /proj/sot/ska/ops/SFE/JUL0415M/ofls/mps/mm185_1000.sum

In [24]:
# Make sure set of obsids are the same
set(mmf) == set(mmt)


Out[24]:
True

In [25]:
# Now do the actual attitude comparison
for trace_id, mf in mmf.items():
    mt = mmt[trace_id]['final']
    mf = mf['final']
    qt = Quat([mt['q1'], mt['q2'], mt['q3'], mt['q4']])
    qf = Quat([mf['q1'], mf['q2'], mf['q3'], mf['q4']])
    print(trace_id, ' ', end='')
    print_dq(qt, qf)


P970300  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P990400  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1706500  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P990500  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
RDE9701  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1730700  droll= -0.00, dpitch= -0.00, dyaw= -0.00 arcsec
1766800  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
P970100  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1665300  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
ECT9900  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P970200  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
ECT9800  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
I766800  droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1730800  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P980600  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1732900  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
1724200  droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P980500  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1769400  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P000100  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
I769500  droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1732800  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
1730500  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P990100  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1769500  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
I730700  droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
I769200  droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
I730900  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1769300  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
P970500  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P990200  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1712100  droll= -0.00, dpitch=  0.00, dyaw= -0.00 arcsec
I744000  droll= -0.00, dpitch= -0.00, dyaw=  0.00 arcsec
1732700  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
1730400  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1730900  droll= -0.00, dpitch= -0.00, dyaw= -0.00 arcsec
1673700  droll= -0.00, dpitch=  0.00, dyaw= -0.00 arcsec
RDE0001  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P980200  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
RDE9901  droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
I769300  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
1744000  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P980100  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
P970400  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1769200  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
1730600  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
1732600  droll=  0.00, dpitch= -0.00, dyaw=  0.00 arcsec
1732500  droll= -0.00, dpitch=  0.00, dyaw= -0.00 arcsec
1676700  droll= -0.00, dpitch=  0.00, dyaw=  0.00 arcsec
RDE9801  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec
P980300  droll=  0.00, dpitch=  0.00, dyaw=  0.00 arcsec

In [ ]: