Tutorial

For the impatient, see the Quickstart section below. To take full advantage of all the capabilities of annie, continue on through the rest of the tutorial as well.

Quickstart

The very simplest way to run an annie simulation is to take some or all of the simulation parameters from an existing or planned observation via the starcheck catalog. This is done with the run_from_starcheck() function:

>>> from annie import run_from_starcheck

>>> obsid = 20201
>>> stop = 200  # seconds.  Can also do `stop = 200 * u.s` with astropy units.

>>> sc = run_from_starcheck(obsid, verbose=True, stop=stop)
Running annie with:
{'att_cmd': [193.228633, -63.884565, 39.69144],
 'ccd_bgd_data': '2018:051:02:57:08.203',
 'dither': {'pitch_ampl': 8.0,
            'pitch_period': 707.1,
            'pitch_phase': 0.0,
            'yaw_ampl': 8.0,
            'yaw_period': 1000.0,
            'yaw_phase': 0.0},
 'starcat': <Table length=12>
sc_id obsid  idx   slot     id     idnote ...  yang  zang  dim   res  halfw pass notes
int64 int64 int64 int64   int64    object ... int64 int64 int64 int64 int64 str2  str2
----- ----- ----- ----- ---------- ------ ... ----- ----- ----- ----- ----- ---- -----
 2116 20201     1     0          1   None ...   919  -844     1     1    25   --    --
 2116 20201     2     1          5   None ... -1828  1053     1     1    25   --    --
 2116 20201     3     2          6   None ...   385  1697     1     1    25   --    --
 2116 20201     4     3 1178736784   None ...  2004  -622    28     1   160   a2    --
 2116 20201     5     4 1178737152   None ...   775  1438    32     1   180   --    --
 2116 20201     6     5 1178737496   None ...  1219   123    32     1   180   --    --
 2116 20201     7     6 1179259616   None ... -2112 -1949    28     1   160   a2    --
 2116 20201     8     7 1178736896   None ...  2420  -905     1     1    25   --    --
 2116 20201     9     7 1179257400   None ...    33   544    32     1   180   --    --
 2116 20201    10     0 1179257016   None ...    -8 -2234    29     1   165   --    --
 2116 20201    11     1 1179257288   None ...   942  -257    28     1   160   a2    --
 2116 20201    12     2 1179127856   None ... -1782   -21    28     1   160   a2    --,
 'stop': 200,
 't_ccd': -11.4}

 >>>

The run_from_starcheck() function can also take as input the starcheck dict returned from get_starcheck_catalog(), or the text copied from the starcheck report. In the latter case one could easily update catalog parameters to evaluate a potential catalog update.

In addition one can override any of the track_stars_setup() parameters for the observation, for instance simulate this observation at a higher CCD temperature:

>>> sc = run_from_starcheck(obsid, t_ccd=-5.0, stop=stop)

Now to look at the results, see the section on Exploring annie’s telemetry.

Creating spacecraft subsystems

All spacecraft subsystems required to run a simulation are created using the Spacecraft master controller:

>>> from annie.annie import Spacecraft
>>> sc = Spacecraft()

The main s/c subsystems include the Clock, Sky, ACA, CCD, PCAD and Telem objects:

>>> sc.__dict__
{'aca': <annie.aca.ACA at 0x7f50a85ca630>,
 'ccd': <annie.aca.CCD at 0x7f50a85c4198>,
 'clock': <Clock secs=0.000 ticks=0>,
 'logger': <Logger root (CRITICAL)>,
 'loglevel': 50,
 'pcad': <annie.pcad.PCAD at 0x7f50a85ca7f0>,
 'sky': <annie.sky.Sky at 0x7f50a85ca390>,
 'start': 0,
 'stop': 100,
 'telem': <annie.telem.Telem at 0x7f50a85ca438>}

Internal clock

The annie clock units are as follows:

Name

Attribute

Ticks

Seconds

Clock tick

tick

1

0.016015625

Minor cycle

mnc

1

0.016015625

Minor frame

mnf

16

0.25625

Frame

frame

64

1.025

Major frame

mjf

2048

32.8

One frame lasts 1.025 seconds and there are 64 clock ticks per frame:

>>> from annie.clock import TICKS_PER_FRAME
>>> print(TICKS_PER_FRAME)
64

The relative durations of these clock units is available in the TICKS_PER module dictionary:

>>> from annie.clock import TICKS_PER
>>> print(TICKS_PER.keys())
dict_keys(['mnc', 'mnf', 'mjf', 'frame', 'sec'])

>>> print(TICKS_PER['mnf'])
16

>>> print(TICKS_PER['sec'])
62.43902439024391

The clock module contains two classes, ClockTime and Clock.

ClockTime

The ClockTime class provides functionality to convert Date or DateTime objects into the internal annie clock time:

>>> from annie.clock import ClockTime
>>> import astropy.units as u

>>> ct = ClockTime(15)  # Initialize in ticks
>>> ct
<ClockTime secs=0.240, ticks=15>

>>> ClockTime(32.8 * u.s)  # Initialize in seconds
<ClockTime secs=32.800, ticks=2048>

>>> ClockTime('2017:001')  # Absolute date
<ClockTime secs=599616069.191 ticks=37439442369>

It also provides functionality to manipulate the internal time, e.g. derive the time at the next unit:

>>> ct.at_next('frame')
<ClockTime secs=1.025, ticks=64>

or derive the number of ticks that have passed since the start of a given unit:

>>> ct = ClockTime(41 * u.s)
>>> ct
<ClockTime secs=41, ticks=2560>

>>> ct.ticks_mod('mjf')
512  # 2560 - 2048

or perform arithmetic operations on the ClockTime objects:

>>> ct_delta = ClockTime(2)
>>> ct + ct_delta
<ClockTime secs=41.032, ticks=2562>

Clock

The Clock class controls the internal clock. It inherits from the ClockTime class. It also inherits from the SubSystem class, and thus annie’s clock is one of the spacecraft subsystems created within the Spacecraft master controller for the simulation:

>>> from annie.clock import Clock
>>> from annie.annie import Spacecraft
>>> import astropy.units as u

>>> sc = Spacecraft()

>>> c = sc.clock
>>> c.start = 0         # ticks

>>> c                   # Clock object
<Clock secs=0.000, ticks=0>

>>> c.start             # ClockTime object
<ClockTime secs=0.00, ticks=0>

The end of the simulation is specified by setting the stop attribute:

>>> c.stop = 100 * u.s  # Ticks or delta time via astropy units

The simulation progresses via successive calls to the tick() method until the stop time is reached, at which point the method returns False.

>>> # The first tick() method call initializes/starts the clock
>>> c.tick()
True
>>> c
<Clock secs=0.000, ticks=0>
>>>
>>> # The next and following tick() calls move the clock by 1 tick
>>> c.tick()
True
>>> c
<Clock secs=0.016, ticks=1>

Scheduling actions

Tasks

In each subsystem there are certain tasks that are executed regularly by the generic subsystem processing called every tick (annie.subsystem.SubSystem.process(), see annie.annie.Spacecraft.run() and Running an annie simulation). Information about these tasks can be accessed through the tasks attribute, which is a list of tuples containing method, time unit, and tick offset within this time unit.

For example, the PCAD subsystem performs attitude updates at the start of every minor frame and main processing at the start of every frame, while the ACA subsystem performs main processing one tick (one minor cycle) after the start of each frame:

>>> sc.pcad.tasks
[('update_att', 'mnf', 0), ('main_processing', 'frame', 0)]

>>> sc.aca.tasks
[('main_processing', 'frame', 1)]

Commands

Commands executed by the subsystems are scheduled using the subsystem’s command() method which adds the new commands to the subsystem’s commands dictionary:

>>> sc.pcad.commands
defaultdict(list, {})

>>> sc.pcad.pending_commands
[]

>>> sc.pcad.command('set_att_cmd', 0 * u.s, att=[0., 0., 0.])
>>> sc.pcad.command('set_att_cmd', 100 * u.s, att=[10., 20., 0.])

>>> sc.pcad.commands
defaultdict(list,
            {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd',
              {'att': [0.0, 0.0, 0.0]})],
             <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd',
              {'att': [10.0, 20.0, 0.0]})]})

>>> sc.pcad.pending_commands
[]

>>> print(sc.pcad.att_record.att_cmd)
None

The commands to be executed at this time are then added to the subsystem’s pending_commands list by the generic subsystem processing called every tick (process(), see Running an annie simulation):

>>> sc.clock
<Clock secs=0.016, ticks=1>

>>> sc.pcad.process()

>>> sc.pcad.commands
defaultdict(list,
            {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd',
              {'att': [0.0, 0.0, 0.0]})],
             <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd',
              {'att': [10.0, 20.0, 0.0]})]})

>>> sc.pcad.pending_commands
[('set_att_cmd', {'att': [0.0, 0.0, 0.0]})]

>>> print(sc.pcad.att_record.att_cmd)
None

Finally, the commands are executed at their scheduled time as part of the subsystem’s regularly scheduled tasks (e.g. annie.pcad.PCAD.main_processing() which calls subsystem’s execute_pending_commands() method). This removes the command from the pending commands list. However, the command is kept in the commands list which acts as a log of commands scheduled during the simulation:

>>> sc.pcad.main_processing()

>>> sc.pcad.commands
defaultdict(list,
            {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd',
              {'att': [0.0, 0.0, 0.0]})],
             <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd',
              {'att': [10.0, 20.0, 0.0]})]})

>>> sc.pcad.pending_commands
[]

>>> print(sc.pcad.att_record.att_cmd)
<Quat q1=0.00000000 q2=-0.00000000 q3=0.00000000 q4=1.00000000>

The next scheduled command gets processed and executed when the clock reaches its scheduled time:

>>> sc.clock.ticks = 6245

>>> sc.pcad.process()

>>> sc.pcad.commands
defaultdict(list,
            {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd',
              {'att': [0.0, 0.0, 0.0]})],
             <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd',
              {'att': [10.0, 20.0, 0.0]})]})

>>> sc.pcad.pending_commands
[('set_att_cmd', {'att': [10.0, 20.0, 0.0]})]

>>> sc.pcad.main_processing()

>>> sc.pcad.commands
defaultdict(list,
            {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd',
              {'att': [0.0, 0.0, 0.0]})],
             <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd',
              {'att': [10.0, 20.0, 0.0]})]})

>>> sc.pcad.pending_commands
[]

>>> print(sc.pcad.att_record.att_cmd)
<Quat q1=0.01513444 q2=-0.17298739 q3=0.08583165 q4=0.98106026>

Events

Events to be executed at a given time are added to the subsystem’s events queue (annie.subsystem.SubSystem.events) using the subsystem’s annie.subsystem.SubSystem.add_event() method:

>>> # Schedule a `flush` event (change the ccd status to `flush`)
>>> sc.clock.ticks = 0  # reset the clock for the purpose of this example

>>> sc.ccd.events
defaultdict(list, {})

>>> sc.ccd.add_event('flush',  sc.clock + 1 * u.mnc)

>>> sc.ccd.events
defaultdict(list, {<ClockTime secs=0.016 ticks=1>: [('flush', {})]})

The events in the events queue are executed at their scheduled time by the generic subsystem processing called every tick (annie.subsystem.SubSystem.process(), see Running an annie simulation). The execution of the event does not remove the event from the events list which acts as a log of events scheduled for a given subsystem in the course of the simulation:

>>> sc.ccd.status
`idle`

>>> sc.clock.tick()
>>> sc.clock
<Clock secs=0.016 ticks=1>

>>> sc.ccd.process()
>>> sc.ccd.status
`flush`

>>> sc.ccd.events
defaultdict(list, {<ClockTime secs=0.016 ticks=1>: [('flush', {})]})

Setting up an annie simulation

Setting up an annie simulation requires providing a number of subsystem parameters and issuing relevant commands. This is most conveniently done using the annie.annie.Spacecraft.track_stars_setup() method. By convention, simulations normally start at time=0, and the end of the simulation is defined with a stop parameter (which then also corresponds to the duration). Once the parameters are set, the track_stars_setup() method calls PCAD’s annie.pcad.PCAD.track_guide_stars() method which commands ACA to search for the guide stars and start guiding.

The simulation parameter fall into four categories:
  • Stars (sky and catalog),

  • Attitude,

  • Conditions,

  • Image and image processing.


sky

  • stars_source, stars_data - the stars available in the ACA field of view are fetched from the AGASC if stars_source='agasc' and stars_data=None. Alternatively, the user may build an arbitrary synthetic sky star field, e.g. to test a scenario with a spoiler star nearby a guide star. In this case the setup should read stars_source='table' and stars_data should be an astropy Table with columns: id, yag, zag, mag.

  • starcat parameter defines the catalog with the guide stars to be tracked. Should be supplied as an astropy Table. See also Running an annie simulation.

attitude

  • att_source, att_data - by default the attitudes for an annie run are simulated (att_source='simulation', att_data=None) based on the initial (commanded) attitude, dither pattern and attitude update control law. However, for the sake of flight data validation, it is possible to use a list of on-board or ground quaternions (att_source=('telemetry'|'ground'), att_data=<list of quaternions>)

  • att_cmd - The commanded attitude must be provided as att_cmd=[ra, dec, roll]

  • att_est - estimated attitude

  • att_true - true attitude

  • dither - The user may provide the dither pattern as a dictionary, or use the default (standard ACIS 8 arcsec dither).

conditions

  • t_ccd_ref, t_ccd - parameter t_ccd_ref is used to set the reference temperature corresponding to the conditions at the time of an ACA DCC ccd_bgd_source='DCC', or the temperature corresponding to a synthetic dark background (ccd_bgd_source='Custom_1024x1024'). The user may trigger temperature scaling of the background data by defining the CCD temeprature t_ccd

  • sp_flags, ms_flags, dp_flags, ir_flags - the user may provide various flags (SP - saturated pixel, MS - multiple stars, DP - defective pixel, IR - ionizing radiation) and test their impact on the ACA tracking the stars. The flags are provided as dicts of lists ordered by the slot number.

  • TODO: proper testing, simulating the flags.

image

  • img_source, img_data - by default the star images are simulated (img_source='simulation' and img_data=None). However, the user may choose to ‘shine’ the flight data for the purpose of comparing the simulation and the telemetry. In this case the input should read img_source='telemetry' and img_data=<list of flight images>

  • ccd_bgd_source, ccd_bgd_data - by default the simulator uses dark background derived from the ACA DCC map taken at the date nearest to the date of the simulation. However, the user may choose from the number of custom options to replace the default background with an arbitrary background (see track_stars_setup() and set_ccd_bgd_data() for details)

  • bgd_algorithm - this parameter sets the background subtraction algorithm

Background setup examples:

>>> # No background, will use ccd_bgd_data = 0.
>>> bgd_algorithm = 'Constant'
>>> ccd_bgd_source = None

>>> # Constant background
>>> bgd_algorithm = 'Constant'
>>> ccd_bgd_source = 'Constant'
>>> ccd_bgd_data = 20.

>>> # Flight algorithm and bgd data
>>> bgd_algorithm = 'FlightBgd'
>>> ccd_bgd_source = 'telemetry'
>>> ccd_bgd_data = <list of telemetered BGDAVG values>

>>> # Flight algorithm and ACA DCC bgd data
>>> bgd_algorithm = 'FlightBgd'
>>> ccd_bgd_source = 'DCC'
>>> ccd_bgd_data = '2017:100'    # fetch the ACA DCC nearest to this date

>>> # Dynamic bgd algorithm and ACA DCC bgd data
>>> bgd_algorithm = 'DynamBgd'
>>> % ccd_bgd_source = 'DCC'       # use the default setting for ccd_bgd_data = the date of the simulation

>>> # Dynamic bgd algorithm and custom 1024x1024 DCC map scaled with temperature
>>> bgd_algorithm = 'DynamBgd'
>>> ccd_bgd_source = 'Custom_1024x1024'
>>> ccd_bgd_data = <custom 1024x1024 ndarray or ACAImage>
>>> t_ccd_ref = -14              # indicate that the custom bgd map was computed at -14C
>>> t_ccd = -5                   # scale it with temperature to -5C

Running an annie simulation

Once all parameters are set and the stars are found and identified, the simulation is ready to be performed. This is done using the annie.annie.Spacecraft.run() method, which initializes the annie clock and processes all the s/c subsystems at each clock tick, until the clock reaches the stop time specified by the user.

The minimal setup requires the following settings:
  • the stop time of the simulation (stop parameter), and

  • a star catalog (an astropy Table, starcat parameter)

  • a commanded attitude.

With this minimum setup, the system will perform a full simulation (image and attitude data will be simulated). Image processing will be performed assuming the ACA DCC background taken at the date closest to the date of the simulation, and the flight algorithm will be used to subtract the background (average bgd value derived from the eight corner pixels):

>>> # Example 1 (This uses the get_att, get_starcat, and get_dither helper methods from mica.starcheck)

>>> from annie.annie import Spacecraft
>>> import astropy.units as u
>>> from mica.starcheck import get_att, get_starcheck, get_dither

>>> sc = Spacecraft()

>>> stop = 20 * u.s
>>> obsid = 8008

>>> sc.track_stars_setup(stop=stop,
                         att_cmd=get_att(obsid),
                         starcat=get_starcat(obsid),
                         dither=get_dither(obsid))
>>> sc.run()

or:

>>> # Example 2

>>> from annie.annie import Spacecraft
>>> import astropy.units as u
>>> from astropy.table import Table

>>> sc = Spacecraft()

>>> stop = 20 * u.s
>>> starcat = Table.read("""
  slot type sz mag maxmag yang zang halfw
  0 BOT 8x8 10.1 11.5 0.0 0.0 120
  1 BOT 8x8 10.1 11.5 1000.0 0.0 120
  2 BOT 8x8 10.1 11.5 0.0 1000.0 120
  3 BOT 8x8 10.1 11.5 -1000.0 0.0 120
  """, format='ascii', guess=False)

>>> sc.track_stars_setup(stop=stop,
                         starcat=starcat,
                         stars_source='table',
                         stars_data=starcat,
                         att_cmd=[0., 0., 0.])
>>> sc.run()

Exploring annie’s telemetry

Telemetry is collected by the telem module by the means of regularly executed tasks defined for the Telem class. These tasks include (see Telemetry):

Telemetry tables

The spacecraft telem object has three sub-attributes that contain most of the useful output from an annie simulation. These are mostly just tabularized versions the full telemetry described above.

  • sc.telem.aca_slots: dict of ACA StarDataRecord tables keyed by slot

  • sc.telem.pcad_slots: dict of PCAD star SlotRecord info tables () keyed by slot

  • sc.telem.pcad_att: PCAD AttitudeRecord info table

These attributes are the recommended way to access the annie simulation results.

The example below shows how to access these attributes to plot the y-angle centroids. In most cases the telemetry from the first 8.2 seconds of simulation are not interesting (where acquisition occurs), so we can just clip that using the clip() method:

>>> from annie import run_from_starcheck
>>> sc = run_from_starcheck(20201, stop=700)
>>> sc.telem.clip()

Now let’s plot the telemetry:

>>> import matplotlib.pyplot as plt
>>> %matplotlib
>>> aca_slot = sc.telem.aca_slots[3]
>>> plt.plot(aca_slot['time'], aca_slot['zag'])
>>> plt.xlabel('Time (sec)')
>>> plt.grid()
>>> plt.title('Z-angle (arcsec)')
_images/yag_example.png

The next example shows how to compute the offsets between the true and estimated attitudes:

>>> times = sc.telem.pcad_att['time']
>>> atts_true = sc.telem.pcad_att['att_true']
>>> atts_est = sc.telem.pcad_att['att_est']
>>> dr = []
>>> dp = []
>>> dy = []
>>> for att_true, att_est in zip(atts_true, atts_est):
>>>     dq = att_true.dq(att_est)
>>>     dr.append(dq.roll0 * 3600)
>>>     dy.append(dq.yaw * 3600)
>>>     dp.append(dq.pitch * 3600)
>>>
>>> plt.plot(times, dr)
>>> plt.ylabel('offset (arcsec)')
>>> plt.xlabel('Time (s)')
>>> plt.grid()
>>> plt.title('Delta roll')
_images/droll_example.png

Slot records

The telemetry records are dictionaries keyed by slot number, with values that are lists containing the relevant records ordered by time. The example below shows how to access the slot record telemetry that stores the GS_loss_count counter Kalman status, star residuals, star magnitude, slot number, ACA function and a time stamp for each frame:

>>> sc.telem.slot_records.keys()
dict_keys([3, 4, 5, 6, 7])

>>> # The first two slot records for slot number 3
>>> sc.telem.slot_records[3][:2]
[<SlotRecord: slot=3 time=0.0 F_image=NULL_IMAGE>,
 <SlotRecord: slot=3 time=1.025 F_image=NULL_IMAGE>]

>>> # Explore the content of an individual slot record
>>> sr = sc.telem.slot_records[3][10] # 10th record for slot number 3
>>> print(sr)
{'F_image': 'STAR',
 'GS_loss_count': 0,
 'Kalman_ok': True,
 'clock': <ClockTime secs=10.250 ticks=640>,
 'delta_y': 0.040003284291393326,
 'delta_z': 0.076399486542315209,
 'function': 'TRAK',
 'mag': 9.2567258517843527,
 'slot': 3,
 'time': 10.249999999999998}

Plot the image function for slot number 7:

>>> import numpy as np
>>> F_images = np.array([sr.F_image for sr in sc.telem.slot_records[7]])
>>> times = np.array([sr.time for sr in sc.telem.slot_records[7]])
>>> state_codes = [(0, 'NULL_IMAGE'), (1, 'STAR')]
>>> vals = np.zeros_like(F_images)
>>> for state_code in state_codes:
...     ok = F_images == state_code[1]
...     vals[ok] = state_code[0]

>>> from Ska.Matplotlib import plot_cxctime
>>> plot_cxctime(times, vals, 'o--', state_codes=state_codes)
_images/F_image_example.png

Star data records

To explore the star data use star_data_records. Note that the star data record telemetry starts at t = 1.025 sec, as opposed to the slot record telemetry that starts at t = 0 sec:

>>> # Explore star data telemetry resulting from Example 1 above
>>> sc.telem.star_data_records.keys()
dict_keys([3, 4, 5, 6, 7])

>>> # The first two star data records for slot number 3
>>>sc.telem.star_data_records[3][:2]
[<StarDataRecord: slot=3 time=1.025 row0=46 col0=222>,
 <StarDataRecord: slot=3 time=2.05 row0=511 col0=511>]

>>> # Explore the content of an individual slot record
>>> sdr = sc.telem.star_data_records[3][10] # 10th record for slot number 3
>>> print(sdr)
{'bgd': 17.0,
'bgd_avg': 17.0,
'bgd_rms': 13.714108945658715,
'bgd_status': array([ True, False,  True,  True,  True,  True,  True, False], dtype=bool),
'brightest': True,
'clock': <ClockTime secs=11.275 ticks=704>,
'col': 245.49913455685868,
'col1': 250,
'fid': False,
'function': 'TRAK',
'halfwidth': 120,
'image': <ACAImage row0=66 col0=242
array([[  8,  45,   5,  35, 119,   7,   5,   5],
      [ 27,  89,   7,   5,  13,   5,  30,  35],
      [ 41,   6,  23,  56, 121,  39,  17,   3],
      [ 95,  12, 161, 479, 461, 198,  14,  11],
      [  6,  17, 188, 833, 863, 220,  44,  22],
      [ 51,  12, 107, 397, 412, 108,  70, 165],
      [ 28,  23,  43, 161,  69,  56,  40,  69],
      [ 44,  35,  25,   9,  12,  24,  18,  37]])>,
'img_sum': 4753.3399205036621,
'last_sdr': <weakref at 0x7fa1325a6db8; to 'StarDataRecord' at 0x7fa1325b37b8>,
'mag': 9.2567258524020062,
'maxmag': 10.859,
'min_img_sum': 543.32441108392015,
'rate_c': 0.027156531569687559,
'rate_r': -0.023609485876363578,
'repeat_count': 0,
'row': 69.972698185568873,
'row1': 74,
'search_count': 4,
'size': 6,
'slot': 3,
'threshold': 10.859,
'time': 11.274999999999999,
'yag': -318.80588100432664,
'yag_cat': -318.30117934550685,
'zag': 1202.8794098096994,
'zag_cat': 1202.290747547197}

Plot the ACA function for slot number 3:

>>> import numpy as np
>>> funcs = np.array([sdr.function for sdr in sc.telem.star_data_records[3]])
>>> times = np.array([sdr.time for sdr in sc.telem.star_data_records[3]])
>>> state_codes = [(0, 'NONE'), (1, 'TRAK'), (2, 'SRCH'), (3, 'RACQ')]
>>> vals = np.zeros_like(funcs)
>>> for state_code in state_codes:
        ok = funcs == state_code[1]
        vals[ok] = state_code[0]
>>> from Ska.Matplotlib import plot_cxctime
>>> plot_cxctime(times, vals, 'o--', state_codes=state_codes)
_images/function_example.png

Attitude records

The att_records attribute is a list containing an AttitudeRecord for each minor frame. It allows for exploration of the spacecraft attitude during the simulation. It contains the time history of the commanded, true and estimated attitudes, their yaw, pitch and roll components, and dither, true and estimated rates:

>>> # Access the 10th attitude record
>>> ar = sc.telem.attitude_records[10]
>>> print(ar)
{'att_cmd': <Quat q1=0.14961427 q2=0.49089671 q3=0.83147065 q4=0.21282047>,
 'att_est': <Quat q1=0.14961408 q2=0.49089675 q3=0.83147077 q4=0.21282004>,
 'att_true': <Quat q1=0.14961406 q2=0.49089675 q3=0.83147078 q4=0.21281999>,
 'clock': <ClockTime secs=2.562 ticks=160>,
 'last_att_record': <weakref at 0x7fa1325800e8; to 'AttitudeRecord' at 0x7fa1325e3ac8>,
 'pitch_cmd': 0.0,
 'pitch_dither': 0.18214420697121447,
 'pitch_dither_rate': 0.071068381251204654,
 'pitch_err': -0.018200254323126025,
 'pitch_est': 0.16394395264808845,
 'pitch_est_rate': 0.07108680873629852,
 'pitch_true': 0.18401325204517213,
 'pitch_true_rate': 0.071978393967360957,
 'roll_cmd': 0.0,
 'roll_dither': 0.0,
 'roll_dither_rate': 0.0,
 'roll_err': 0.0,
 'roll_est': 0.0,
 'roll_est_rate': 0.0,
 'roll_true': 0.0,
 'roll_true_rate': 0.0,
 'time': 2.5624999999999996,
 'yaw_cmd': 0.0,
 'yaw_dither': 0.12879973380786744,
 'yaw_dither_rate': 0.050258967404293226,
 'yaw_err': -0.0128749648904041,
 'yaw_est': 0.11592476891746334,
 'yaw_est_rate': 0.05026548245743669,
 'yaw_true': 0.13012066185289703,
 'yaw_true_rate': 0.050902715648813428}

Kalman tracking

The Kalman status and the number of Kalman stars are collected for each frame and stored in annie.telem.Telem.Kalman_status and annie.telem.Telem.n_Kalman_stars. The example below illustrates that there were enough Kalman stars in each slot and for each frame once the ACA function changed from ‘SRCH’ to ‘TRAK’ (Kalman_status = True; see Star data records to learn how to access the ACA function data), and that the actual number of Kalman stars for each fo these frames was n_Kalman_stars = 5:

>>> # Explore telemetry resulting from Example 1 above

>>> # Frame no. 7 is the last frame with ACA function equal 'SRCH'
>>> # while frame no. 8 is the first frame with ACA function equal 'TRAK'
>>> sc.telem.Kalman_status[7:9]
>>> [[False, False, False, False, False], [True, True, True, True, True]]

>>> # The number of Kalman stars was 5 in all frames
>>> # with ACA function equal 'TRAK'
>>> sc.telem.n_Kalman_stars
>>> [0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]