Design overview

Motivation

The annie package is focused on modeling the ACA behavior while guiding on stars. The key feature of the design is that it is modular and extensible, allowing flexibility in supporting variations of simulation parameters or algorithms such as:

  • Algorithm to simulate star image data:
    • Gaussian PSF

    • ACA PSF image data

  • Dark CCD background
    • Background from flight ACA dark current calibration

    • Time-history of arbitrarily defined background

    • Constant background

  • Background subtraction
    • Flight algorithm based on the eight corner pixels

    • Dynamic background using median-filterd edge pixels

    • Warm pixel detection within each image

  • Simulation strategy
    • Pure simulation

    • Plug in flight data for select components such as ACA images, background, or attitude

  • Centroiding algorithms

Code overview

Spacecraft

At the top level of the simulator there is a single Spacecraft class which serves as master controller for an annie simulation. It performs the following key functions:

  • Serves as a container for each of the Subsystems classes

  • Provides the track_stars_setup() convenience method to set up most of the supported configuration options for typical simulations of tracking guide stars.

  • Provides the run() method to actually run a simulation once everything is set up.

Running the simulation consists of a loop in which the spacecraft clock is incremented by one “tick”, and then the main process() method of each subsystem is called. One tick is a minor cycle (1.025 sec / 64 = 16.015625 msec). The subsystem is responsible for deciding if any action is needed on that tick. The order of subsystem processing does matter and thus the details of subsystems are somewhat coupled. For example it is assumed that PCAD runs first in order that the attitude is correct for other subsystems.

Subsystems

The core of annie is the concept of a subsystem. This is the object which does the actual work of simulating a subsytem such as the ACA or PCAD in a modular fashion. A subsystem can handle the scheduling and processing of regularly recurring tasks, external commands, and internally generated events. For details see the Subsystem details section.

Subsystems contain attributes which may be constants required for runtime processing or dynamically updated values.

All subsystems are based on the SubSystem: base class. One important feature of this class is that it provides access to all of the other subsystems (and corresponding attributes) via a dynamically generated attribute with the name of the other subsystem. For instance within the ACA subsystem class one could access the current true yaw using self.pcad.att_record.yaw_true. This cross-communication facilitates writing simulation code while still maintaining modularity. Note that there is a “trust” model in place and it is assumed each subsystem treats other subsystem attributes as read-only.

The implemented subsystems are:

  • Clock : on-board clock subsystem

  • PCAD : PCAD subsystem

  • ACA : ACA subsystem (i.e. PEA)

  • CCD : CCD subsystem (i.e AC electronics + CCD)

  • Sky : sky subsystem to provide stars in ACA field of view

  • Telem : telemetry collection subsystem

Data storage

In order to analyze simulation data, the data need to be collected and made available for use post-simulation. This is done via the Telem subsystem, which puts data into lists of Python records (class objects). This is not a direct simulation of actual Chandra telemetry, but instead “pseudo-telemetry” that is convenient for analysis.

  • StarDataRecord : ACA telemetry for each slot, collected each frame

  • SlotRecord : PCAD telemetry for each slot, collected each frame

  • AttitudeRecord : attitude telemetry, collected each minor frame

Subsystem details

The SubSystem base class is used by each of the subsystem classes and provides methods that are responsible for scheduling and processing the tasks, commands and events that are executed by each subsystem. The key method of this class that deals with generic processing is called process() and it is executed at each clock tick for each subsystem. This is done via the following code in the run() method:

while self.clock.tick():
    self.pcad.process()
    self.sky.process()
    self.aca.process()
    self.ccd.process()
    self.telem.process()

Tasks

Actions that are executed regularly, usually at the begining of each frame, or each minor frame. They are defined in the subsystem’s task attribute which is a list of tuples containing a method, time unit, and number of clock ticks within this unit at which the task is to be executed. For example, there are two tasks defined for the telemetry subsystem: main_processing() called at the begining of each frame with the goal of collecting the ACA star data record telemetry, and attitude_processing() called at the begining of each minor frame with the goal of updating the spacecraft attitude Telemetry:

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

[('main_processing', 'frame', 0), ('attitude_processing', 'mnf', 0)]

Execution of tasks happens at their scheduled time as part of generic subsystem processing that is called every clock tick (process()). The workflow of regularly executed tasks is illustrated below: PCAD, Sky, ACA, CCD, Telemetry.

Commands

Commands are actions that are scheduled to be executed one time as part of the generic subsystem processing. Commands are scheduled using the subsystem’s command() method. The parameters of the command() method include method to be called, time, and method’s **kwargs. The time can be given in units that are absolute (parameter absolute = True) or relative (time unit within the current frame; parameter absolute=False, default).

When the internal clock reaches the specified time, the subsystem’s processing transfers the command from the commands queue to the pending_commands list, where it awaits its execution.

Execution of the pending commands happens as part of the subsystem’s main processing, which is usually called at the beginning of each frame. For a simple example see the PCAD annie.pcad.PCAD.main_processing() which first calls annie.subsystem.SubSystem.execute_pending_commands() and then does ACA processing. A more complicated example is annie.aca.ACA.main_processing(), which sets up the full CCD processing cycle along with executing pending commands.

Because commands are queued in the pending_commands buffer, the time specified when using the command() method does not have to be fine tuned and equal to the time at which the command needs to be executed. The command will be executed at the first appropriate opportunity (as determined by the subsystem) after the specified command time. For example:

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

>>> # Define the Spacecraft and set the clock at tick 61
>>> # (near the end if the 1st frame).  Set the simulation
>>> # to end at 1.05 seconds (just after 1st frame).
>>> sc = Spacecraft()
>>> c = sc.clock
>>> c.start = 61  # ticks
>>> c.stop = 1.05 * u.s

>>> # Command setting the commanded attitude at absolute clock tick = 62
>>> sc.pcad.command('set_att_cmd', 62, absolute=True, att=[0., 0., 0.])
>>> print(sc.pcad.commands)
defaultdict(list,
         {<ClockTime secs=0.993 ticks=62>: [('set_att_cmd',
            {'att': [0.0, 0.0, 0.0]})]})

>>> while c.tick():
>>>     sc.pcad.process()
>>>     print('Clock ticks: ', c.ticks)
>>>     print('Pending commands: ', sc.pcad.pending_commands)
>>>     print('Cmd attitude: ', sc.pcad.att_record.att_cmd)
>>>     print()

Clock ticks:  61
Pending commands:  []
Cmd attitude:  None

Clock ticks:  62
Pending commands:  [('set_att_cmd', {'att': [0.0, 0.0, 0.0]})]
Cmd attitude:  None

Clock ticks:  63
Pending commands:  [('set_att_cmd', {'att': [0.0, 0.0, 0.0]})]
Cmd attitude:  None

Clock ticks:  64
Pending commands:  []
Cmd attitude:  <Quat q1=0.00000000 q2=-0.00000000 q3=0.00000000 q4=1.00000000>

Clock ticks:  65
Pending commands:  []
Cmd attitude:  <Quat q1=0.00000000 q2=-0.00000000 q3=0.00000000 q4=1.00000000>

In the current implementation commanding is used to set up the commanded, estimated and true attitudes at the start of a simulation, and command an ACA search() from within the PCAD class either at the start of the simulation or when a star is lost in the course of a simulation.

Events

Events are actions that are scheduled to be executed one time, at a strictly specified time. They are added to the subsystem’s events queue (events) using the add_event() method with parameters: method to be executed, time of execution (absolute ClockTime), method’s **kwargs.

Events in the events queue are executed as part of generic subsystem processing called every clock tick (process()) as soon as the clock reaches the specified time. For example:

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

>>> # Define the Spacecraft and set the clock at tick 61
>>> # (near the end if the 1st frame)
>>> sc = Spacecraft()
>>> c = sc.clock
>>> c.start = 61
>>> c.stop = 1.05 * u.s

>>> # Add flush event to be executed in 2 minor cycles (= 2 ticks)
>>> t_flush = c + 2 * u.mnc
>>> sc.ccd.add_event('flush', t_flush)
>>> print(sc.ccd.events)
defaultdict(<class 'list'>, {<ClockTime secs=1.009 ticks=63>: [('flush', {})]})

>>> while c.tick():
>>>     sc.ccd.process()
>>>     print('Clock ticks: ', c.ticks)
>>>     print('CCD status: ', sc.ccd.status)
>>>     print()

Clock ticks:  61
CCD status:  idle

Clock ticks:  62
CCD status:  idle

Clock ticks:  63
CCD status:  flush

Clock ticks:  64
CCD status:  flush

Clock ticks:  65
CCD status:  flush

In the current implementation, events functionality is used within the ACA class to schedule ACA and CCD events related to image processing that are to be executed in the next two frames providing that the CCD status is idle (see ACA).

Summary

Characteristic

Tasks

Commands

Events

Executed regularly

Yes

No

No

Get buffered and await execution

No

Yes

No

Requested time matches the execution time

Yes

No

Yes

Workflow of regularly executed tasks

PCAD

pcad

The workflow of tasks executed regularly by the PCAD subsystem is as follows: at the start of each minor frame the PCAD subsystem performs a task to update the attitude; see update_att(). Annie uses a simple Subsystem details. The estimated and true attitudes are updated by integrating the estimated and true rates. However, for the minor frames whose start coincides with the start of a frame with CCD status equal 'idle' (meaning that new star data have just been read; see read() and ACA subsystem workflow), the estimated attitude is updated using the star data and fast (linear approximation) attitude solution.

In addition, the PCAD system performs main processing at the start of every frame. This includes execution of pending commands (e.g. setting the initial commanded attitude or issuing the ACA search command for lost stars), and ACA processing.

There are two actions performed as part of ACA processing. The first one is to keep track of the GS_loss_count counters for each slot. A relevant counter gets incremented if the image in the corresponding slot is found to be in reacquisition (function equals ‘RACQ’) or a bad flag is set for this slot. The counter is reset to zero if the image function is ‘TRAK’ and no bad flags are set. If a GS_loss_count counter exceeds GS_LOSS_COUNT_THRESH (set to 100, i.e. 100 consequtive frames in 'RACQ' and/or with bad flags), the PCAD subsystem commands the ACA subsystem to search for a star in this slot, the ACA function gets set to 'SRCH', and the counter is reset to zero.

The second action performed by the PCAD subsystem as part of the regular ACA processing is to identify the guide stars, i.e. to assign a value (either 'STAR' or 'NULL_IMAGE') to the F_image attributes (one per slot). This is done for all slots by checking if the y and z angle residuals of an image tracked in a given slot are below the values defined with the GUIDE_DELTA_Y_THRESH and GUIDE_DELTA_Z_THRESH PCAD module constants (each set to 20 arcsec), and if the image magnitude does not exceed the bright star limit (PCAD’s bright_threshold attribute).

Sky

sky

The workflow of tasks executed regularly by the Sky subsystem is as follows: at the start of each frame the Sky subsystem checks if the current S/C attitude (att_true) is within 0.1 deg (in each axis) of the current sky attitude (att_sky). The sky attitude corresponds to the center of the circular star field fetched from the AGASC, and is typically set to the commanded attitude. If not (typically after a new commanded attitude has been commanded, e.g. while simulating a set of loads, not a single pointing) then the Sky attitude is updated and a new star field is fetched either from the AGASC or from a user provided astropy Table.

ACA

aca

The workflow of tasks executed regularly by the ACA subsystem is as follows: every frame the ACA subsystem waits 1 minor cycle (1 clock tick) and then starts its main_processing(). This adds the star_data_record_process_per_frame() method to the ACA events queue to be executed at the next minor cycle. This method is designed to perform processing and setup related to star data records. Currently, it increments the repeat_count counter for each slot. This counter can be equal 0 or 1, where 0 indicates a frame with new star data.

The subsequent workflow depends on the CCD status. If CCD status is ‘idle’ then the main_processing() continues: pending commands get executed at the current minor cycle (or 1st clock tick), that is before the star_data_record_process_per_frame() scheduled at tick number 2, and a number of events are added to the ACA and CCD events queues, as illustrated in the diagram. These events include star_data_record_prepare_read() which predicts and sets the row0, col0 for the next readout and resets the repeat_count counter to zero, and flush() which alters the CCD status from ‘idle’ to ‘flush’. Both are scheduled at tick number 2. This is followed by the integrate() event which changes the CCD status to ‘integrate’ at tick number 3. Then, approximately half way through the last minor frame of the current frame, integ_time / 2 seconds after integration, stars and background are scheduled to be shone on the CCD using an appropriate shine() method. Finally, the read() method is scheduled integ_time after the integration (that is towards the end of the third minor frame of the next frame). The read() method sets the CCD status back to ‘idle’ and the main processing ACA cycle repeats.

Telemetry

telem

The workflow of tasks executed regularly by the telemetry subsystem is as follows: at the start of each minor frame, following the attitude updates performed by the PCAD subsystem (see the PCAD workflow) the telemetry subsystem executes attitude_processing(). This makes a deep copy of the current attitude data stored in the att_record, adds a time stamp, and appends these data to the annie.telem.Telem.att_records record.

In addition, the telemetry subsystem executes main_processing() at the start of each frame. This makes a deep copy of the current star data stored in the ACA’s star_data_records, adds a time stamp, and appends these data to the annie.telem.Telem.star_data_records record. Similarly, a deep copy of the slot record data stored in PCAD’s annie.pcad.PCAD.slot_records is copied, time stamped and appended to the annie.telem.Telem.slot_records record.

Attitude control law

The subsections below summarize the concepts of the commanded, true, sky and estimated attitudes, as well as the attitude control law adopted by the simulator. Attitude control law is a pdf document that presents these concepts in more detail. An illustration of the adopted control law can be found in this Jupyter notebook.

Commanded attitude

The commanded attitude is either fetched from the archive if the user provides ‘obsid’ while setting up an annie simulation, or it is passed directly by the user during the setup together with an astropy Table containing a custom star catalog (see Setting up an annie simulation). It is representative of the center of the dither pattern. It is stored in att_record as att_cmd.

True attitude

The true attitude is the actual simulated spacecraft attitude and is used (for example) to compute star positions in the ACA field of view and for post-facto analysis of attitude errors. The true attitude is found by integrating the true rates. The true rates are computed as a sum of the commanded dither rates and control rates that are proportional to the attitude errors, R_control = att_error * RATE_SCALE. The adopted control law assumes that a 2 arcsec error results in a 0.1 arcsec/sec adjustment to the rate. The control rate is allowed to take values in the range -RATE_MAX < R_control < RATE_MAX. The true attitude is stored in att_record as att_true.

Sky attitude

The sky attitude is set at the start of a simulation to match the current commanded attitude (see update_stars()). It is stored as att_sky. The sky attitude is used to get stars available in ACA field of view (get_stars(), stars) if stars_source equals 'agasc' (Setting up an annie simulation). If the true attitude is found to differ from the commanded attitude by more than 0.1 deg in any of the yaw, pitch, roll axes (typically after a new commanded attitude has been provided, e.g. in the case of simulating a set of observations), the sky attitude is overwritten with the commanded attitude and the ACA FOV stars are fetched again.

Estimated attitude

In the absence of new star data, the estimated attitude is computed in the same way as the true attitute, by integrating the estimated rates assumed to be equal to the true rates. However, every frame with new star data (i.e. frames that start with annie.aca.CCD.status equal ‘idle’ and have annie.aca.ACA.repeat_count equal 0), the estimated attitude is found from the guide stars using the fast attitude solution algorithm. The estimated attitude is stored in att_record as att_est. It is used to derive attitude errors as the deviations between the estimated and commanded attitudes, given the dither pattern.