From a0b335cdbbe92c645bfecb7b4650542d9cf03e36 Mon Sep 17 00:00:00 2001 From: Jean Connelly Date: Tue, 14 Apr 2020 18:42:11 -0400 Subject: [PATCH 1/7] Use RLTT and SCHEDULE_STOP_TIME in thermal calc --- starcheck/calc_ccd_temps.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/starcheck/calc_ccd_temps.py b/starcheck/calc_ccd_temps.py index 2fbd1628..e16bb4bc 100755 --- a/starcheck/calc_ccd_temps.py +++ b/starcheck/calc_ccd_temps.py @@ -152,8 +152,19 @@ def get_ccd_temps(oflsdir, outdir='out', # Get tstart, tstop, commands from backstop file in opt.oflsdir bs_cmds = get_bs_cmds(oflsdir) - tstart = DateTime(bs_cmds[0]['date']).secs - tstop = DateTime(bs_cmds[-1]['date']).secs + + # Use RLTT and SCHEDULED_STOP_TIME if available + rltt = bs_cmds['event_type'] == 'RUNNING_LOAD_TERMINATION_TIME' + if np.any(rltt): + tstart = DateTime(bs_cmds['date'][rltt][0]).secs + else: + tstart = DateTime(bs_cmds[0]['date']).secs + sched_stop = bs_cmds['event_type'] == 'SCHEDULED_STOP_TIME' + if np.any(sched_stop): + tstop = DateTime(bs_cmds['date'][sched_stop][0]).secs + else: + tstop = DateTime(bs_cmds[-1]['date']).secs + proc['datestart'] = DateTime(tstart).date proc['datestop'] = DateTime(tstop).date @@ -294,8 +305,8 @@ def get_week_states(tstart, tstop, bs_cmds, tlm): """ Make states from last available telemetry through the end of the backstop commands - :param tstart: start time from first backstop command - :param tstop: stop time from last backstop command + :param tstart: start time from RLTT if available else first backstop command + :param tstop: stop time from SCHEDULED_STOP_TIME if available else last backstop command :param bs_cmds: backstop commands for products under review :param tlm: available pitch and aacccdpt telemetry recarray from fetch :returns: numpy recarray of states @@ -306,7 +317,7 @@ def get_week_states(tstart, tstop, bs_cmds, tlm): init_tlm_time = np.mean(tlm['time'][ok]) # Get commands from last telemetry up to (but not including) - # first backstop command. + # commands at tstart (RLTT if present else first backstop cmd) cmds = kadi.commands.get_cmds(init_tlm_time, tstart) # Add in the backstop commands From d76c4476bc7c94d7e62db5f715e691f683ed309f Mon Sep 17 00:00:00 2001 From: Jean Connelly Date: Tue, 21 Apr 2020 10:49:26 -0400 Subject: [PATCH 2/7] Misc cleanup of the RLTT section --- starcheck/calc_ccd_temps.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/starcheck/calc_ccd_temps.py b/starcheck/calc_ccd_temps.py index e16bb4bc..599b19e4 100755 --- a/starcheck/calc_ccd_temps.py +++ b/starcheck/calc_ccd_temps.py @@ -154,16 +154,20 @@ def get_ccd_temps(oflsdir, outdir='out', bs_cmds = get_bs_cmds(oflsdir) # Use RLTT and SCHEDULED_STOP_TIME if available - rltt = bs_cmds['event_type'] == 'RUNNING_LOAD_TERMINATION_TIME' - if np.any(rltt): - tstart = DateTime(bs_cmds['date'][rltt][0]).secs + ok = bs_cmds['event_type'] == 'RUNNING_LOAD_TERMINATION_TIME' + if np.any(ok): + + # The RLTT is defined so that running commands at exactly the RLTT are + # included in propagation. However get_cmds() uses the convention of + # getting commands date_start <= cmd_date < date_stop, so add 0.001 to RLTT. + tstart = DateTime(bs_cmds['date'][ok][0]).secs + 0.001 else: - tstart = DateTime(bs_cmds[0]['date']).secs - sched_stop = bs_cmds['event_type'] == 'SCHEDULED_STOP_TIME' - if np.any(sched_stop): - tstop = DateTime(bs_cmds['date'][sched_stop][0]).secs + tstart = DateTime(bs_cmds['date'][0]).secs + ok = bs_cmds['event_type'] == 'SCHEDULED_STOP_TIME' + if np.any(ok): + tstop = DateTime(bs_cmds['date'][ok][0]).secs else: - tstop = DateTime(bs_cmds[-1]['date']).secs + tstop = DateTime(bs_cmds['date'][0]).secs proc['datestart'] = DateTime(tstart).date proc['datestop'] = DateTime(tstop).date From 383bd248d8ae6af55d477df5b22b617dd5a30314 Mon Sep 17 00:00:00 2001 From: Jean Connelly Date: Tue, 21 Apr 2020 11:50:23 -0400 Subject: [PATCH 3/7] Add comment about dither state and RLTT --- starcheck/src/starcheck.pl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/starcheck/src/starcheck.pl b/starcheck/src/starcheck.pl index d96323c8..010b09eb 100755 --- a/starcheck/src/starcheck.pl +++ b/starcheck/src/starcheck.pl @@ -403,7 +403,13 @@ } else { warning("Could not find Maneuver Error file in output/ directory\n") }; +# Get an initial dither state from kadi. Dither states are then built from backstop commands +# after this time. If the running loads will be terminated in advance of new commands in the loads +# in review, and the RUNNING_LOAD_TERMINATION_TIME backstop "pseudo" command is available, that +# command will be the first command ($bs[0]) and the kadi dither state will be fetched at that time. +# This is expected and appropriate. my $kadi_dither = get_dither_kadi_state($bs[0]->{date}); + # Read DITHER history file and backstop to determine expected dither state my ($dither_error, $dither) = Ska::Parse_CM_File::dither($dither_file, \@bs, $kadi_dither); From 4ef8d51066c7b538eb1c587319397d7c85afeaa8 Mon Sep 17 00:00:00 2001 From: Tom Aldcroft Date: Thu, 30 Apr 2020 09:34:25 -0400 Subject: [PATCH 4/7] Use RLTT and scheduled_stop explicitly --- starcheck/calc_ccd_temps.py | 113 +++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/starcheck/calc_ccd_temps.py b/starcheck/calc_ccd_temps.py index 599b19e4..c0c53608 100755 --- a/starcheck/calc_ccd_temps.py +++ b/starcheck/calc_ccd_temps.py @@ -110,11 +110,9 @@ def get_ccd_temps(oflsdir, outdir='out', :param outdir: output directory for plots :param json_obsids: file-like object or string containing JSON of starcheck Obsid objects - :param model_spec: xija ACA model specification - :param run_start_time: Chandra.Time date used as a reference time to determine initial - seed state with temperature telemetry. The initial seed state will - be at the end of available telemetry that is also before run_start_time - and before the beginning of backstop cmds. + :param model_spec: xija ACA model specification file name + :param run_start_time: Chandra.Time date, clock time when starcheck was run, + or a user-provided value (usually for regression testing). :param verbose: Verbosity (0=quiet, 1=normal, 2=debug) :returns: JSON dictionary of labeled dwell intervals with max temperatures """ @@ -147,59 +145,68 @@ def get_ccd_temps(oflsdir, outdir='out', # json_obsids can be either a string or a file-like object. Try those options in order. try: sc_obsids = json.loads(json_obsids) + with open('test_obsids.json', 'w') as fh: + fh.write(json_obsids) except TypeError: sc_obsids = json.load(json_obsids) - # Get tstart, tstop, commands from backstop file in opt.oflsdir + # Get commands from backstop file in oflsdir bs_cmds = get_bs_cmds(oflsdir) + bs_dates = bs_cmds['date'] - # Use RLTT and SCHEDULED_STOP_TIME if available + # Running loads termination time is the last time of "current running loads" + # (or in the case of a safing action, "current approved load commands" in + # kadi commands) which should be included in propagation. Starting from + # around 2020-April this is included as a commmand in the loads, while prior + # to that we just use the first command in the backstop loads. ok = bs_cmds['event_type'] == 'RUNNING_LOAD_TERMINATION_TIME' - if np.any(ok): + rltt = DateTime(bs_dates[ok][0] if np.any(ok) else bs_dates[0]) - # The RLTT is defined so that running commands at exactly the RLTT are - # included in propagation. However get_cmds() uses the convention of - # getting commands date_start <= cmd_date < date_stop, so add 0.001 to RLTT. - tstart = DateTime(bs_cmds['date'][ok][0]).secs + 0.001 - else: - tstart = DateTime(bs_cmds['date'][0]).secs + # First actual command in backstop loads (all the NOT-RLTT commands) + bs_start = DateTime(bs_dates[~ok][0]) + + # Scheduled stop time is the end of propagation, either the explicit + # time as a pseudo-command in the loads or the last backstop command time. ok = bs_cmds['event_type'] == 'SCHEDULED_STOP_TIME' - if np.any(ok): - tstop = DateTime(bs_cmds['date'][ok][0]).secs - else: - tstop = DateTime(bs_cmds['date'][0]).secs + sched_stop = DateTime(bs_dates[ok][0] if np.any(ok) else bs_dates[-1]) - proc['datestart'] = DateTime(tstart).date - proc['datestop'] = DateTime(tstop).date + proc['datestart'] = bs_start.date + proc['datestop'] = sched_stop.date - # Get temperature telemetry for 1 days prior to - # min(last available telem, backstop tstart, run_start_time) - # where run_start_time is for regression testing. - end_time = fetch.get_time_range('aacccdpt', format='secs')[1] - tlm = get_telem_values(min(end_time, tstart, run_start_time.secs), - ['aacccdpt'], - days=1) + # Get temperature telemetry for 1 day prior to min(last available telem, + # backstop start, run_start_time) where run_start_time is for regression + # testing. + tlm_end_time = min(fetch.get_time_range('aacccdpt', format='secs')[1], + bs_start.secs, run_start_time.secs) + tlm = get_telem_values(tlm_end_time, ['aacccdpt'], days=1) - states = get_week_states(tstart, tstop, bs_cmds, tlm) + states = get_week_states(rltt, sched_stop, bs_cmds, tlm) - # If the last obsid interval extends over the end of states + # If the last obsid interval extends over the end of states then # extend the state / predictions - if ((states[-1]['obsid'] == sc_obsids[-1]['obsid']) & - (sc_obsids[-1]['obs_tstop'] > states[-1]['tstop'])): - tstop = sc_obsids[-1]['obs_tstop'] - states[-1]['tstop'] = sc_obsids[-1]['obs_tstop'] - states[-1]['datestop'] = DateTime(sc_obsids[-1]['obs_tstop']).date - - if tstart > DateTime(MODEL_VALID_FROM).secs: - times, ccd_temp = make_week_predict(model_spec, states, tstop) + last_state = states[-1] + last_sc_obsid = sc_obsids[-1] + if ((last_state['obsid'] == last_sc_obsid['obsid']) & + (last_sc_obsid['obs_tstop'] > last_state['tstop'])): + obs_tstop = last_sc_obsid['obs_tstop'] + last_state['tstop'] = obs_tstop + last_state['datestop'] = DateTime(obs_tstop).date + + # Extend last state to reflect scheduled stop time + if last_state['datestop'] < sched_stop.date: + last_state['tstop'] = sched_stop.secs + last_state['datestop'] = sched_stop.date + + if rltt.date > DateTime(MODEL_VALID_FROM).date: + ccd_times, ccd_temps = make_week_predict(model_spec, states, sched_stop) else: - times, ccd_temp = mock_telem_predict(states) + ccd_times, ccd_temps = mock_telem_predict(states) - make_check_plots(outdir, states, times, - ccd_temp, tstart, tstop, char=char) + make_check_plots(outdir, states, ccd_times, ccd_temps, + tstart=bs_start.secs, tstop=sched_stop.secs, char=char) intervals = get_obs_intervals(sc_obsids) obsreqs = None if orlist is None else {obs['obsid']: obs for obs in read_or_list(orlist)} - obstemps = get_interval_data(intervals, times, ccd_temp, obsreqs) + obstemps = get_interval_data(intervals, ccd_times, ccd_temps, obsreqs) return json.dumps(obstemps, sort_keys=True, indent=4, cls=NumpyAwareJSONEncoder) @@ -305,24 +312,25 @@ def calc_model(model_spec, states, start, stop, aacccdpt=None, aacccdpt_times=No return model -def get_week_states(tstart, tstop, bs_cmds, tlm): +def get_week_states(rltt, sched_stop, bs_cmds, tlm): """ - Make states from last available telemetry through the end of the backstop commands + Make states from last available telemetry through the end of the schedule - :param tstart: start time from RLTT if available else first backstop command - :param tstop: stop time from SCHEDULED_STOP_TIME if available else last backstop command + :param rltt: running load termination time (discard running load commands after rltt) + :param sched_stop: create states out through scheduled stop time :param bs_cmds: backstop commands for products under review :param tlm: available pitch and aacccdpt telemetry recarray from fetch :returns: numpy recarray of states """ # Get temperature data at the end of available telemetry - ok = tlm['time'] > tlm['time'][-1] - 1400 - init_aacccdpt = np.mean(tlm['aacccdpt'][ok]) - init_tlm_time = np.mean(tlm['time'][ok]) + times = tlm['time'] + i0 = np.searchsorted(times, times[-1] - 1400) + init_aacccdpt = np.mean(tlm['aacccdpt'][i0:]) + init_tlm_time = np.mean(tlm['time'][i0:]) - # Get commands from last telemetry up to (but not including) - # commands at tstart (RLTT if present else first backstop cmd) - cmds = kadi.commands.get_cmds(init_tlm_time, tstart) + # Get currently running (or approved) commands from last telemetry up to + # and including commands at RLTT + cmds = kadi.commands.get_cmds(init_tlm_time, rltt, inclusive_stop=True) # Add in the backstop commands cmds = cmds.add_cmds(bs_cmds) @@ -522,7 +530,8 @@ def make_check_plots(outdir, states, times, temps, tstart, tstop, char): :param states: commanded states :param times: time stamps (sec) for temperature arrays :param temps: dict of temperatures - :param tstart: load start time + :param tstart: load start time (secs) + :param tstop: schedule stop time (secs) :rtype: dict of review information including plot file names """ plots = {} From e94bb8c789f4a9a3717ebed904b7901a6cc3adba Mon Sep 17 00:00:00 2001 From: Tom Aldcroft Date: Thu, 30 Apr 2020 13:52:25 -0400 Subject: [PATCH 5/7] Allow for running on platform with no USER env var --- starcheck/calc_ccd_temps.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/starcheck/calc_ccd_temps.py b/starcheck/calc_ccd_temps.py index c0c53608..53caa778 100755 --- a/starcheck/calc_ccd_temps.py +++ b/starcheck/calc_ccd_temps.py @@ -77,8 +77,10 @@ def get_options(): default=sys.stdout, help="output destination for temperature JSON, file or stdout") parser.add_argument("--model-spec", + default="starcheck/data/aca_spec.json", help="xija ACA model specification file") parser.add_argument("--char-file", + default="starcheck/data/characteristics.yaml", help="starcheck characteristics file") parser.add_argument("--orlist", help="OR list") @@ -123,7 +125,7 @@ def get_ccd_temps(oflsdir, outdir='out', config_logging(outdir, verbose) # Store info relevant to processing for use in outputs - proc = {'run_user': os.environ['USER'], + proc = {'run_user': os.environ.get('USER'), 'execution_time': time.ctime(), 'run_start_time': run_start_time, 'errors': []} @@ -179,7 +181,7 @@ def get_ccd_temps(oflsdir, outdir='out', tlm_end_time = min(fetch.get_time_range('aacccdpt', format='secs')[1], bs_start.secs, run_start_time.secs) tlm = get_telem_values(tlm_end_time, ['aacccdpt'], days=1) - + print(DateTime(tlm_end_time).date) states = get_week_states(rltt, sched_stop, bs_cmds, tlm) # If the last obsid interval extends over the end of states then From 2b0948f059e7c8482bd0a577ff8eb09b2bfcb589 Mon Sep 17 00:00:00 2001 From: Tom Aldcroft Date: Thu, 30 Apr 2020 19:05:16 -0400 Subject: [PATCH 6/7] Make the RLTT handling work correctly --- starcheck/calc_ccd_temps.py | 50 +++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/starcheck/calc_ccd_temps.py b/starcheck/calc_ccd_temps.py index 53caa778..3efcfac3 100755 --- a/starcheck/calc_ccd_temps.py +++ b/starcheck/calc_ccd_temps.py @@ -18,6 +18,7 @@ import numpy as np import json import yaml +from pathlib import Path # Matplotlib setup # Use Agg backend for command-line (non-interactive) operation @@ -33,6 +34,7 @@ import Ska.DBI import Ska.engarchive.fetch_sci as fetch from Chandra.Time import DateTime +import kadi import kadi.commands import kadi.commands.states as kadi_states import xija @@ -77,10 +79,8 @@ def get_options(): default=sys.stdout, help="output destination for temperature JSON, file or stdout") parser.add_argument("--model-spec", - default="starcheck/data/aca_spec.json", help="xija ACA model specification file") parser.add_argument("--char-file", - default="starcheck/data/characteristics.yaml", help="starcheck characteristics file") parser.add_argument("--orlist", help="OR list") @@ -99,7 +99,7 @@ def get_options(): def get_ccd_temps(oflsdir, outdir='out', - json_obsids=sys.stdin, + json_obsids=None, model_spec=None, char_file=None, orlist=None, run_start_time=None, verbose=1, **kwargs): @@ -111,16 +111,29 @@ def get_ccd_temps(oflsdir, outdir='out', :param oflsdir: products directory :param outdir: output directory for plots :param json_obsids: file-like object or string containing JSON of - starcheck Obsid objects - :param model_spec: xija ACA model specification file name + starcheck Obsid objects (default='/starcheck/obsids.json') + :param model_spec: xija ACA model spec file (default=package aca_spec.json) + :param char_file: starcheck characteristics file (default=package characteristics.yaml) :param run_start_time: Chandra.Time date, clock time when starcheck was run, or a user-provided value (usually for regression testing). :param verbose: Verbosity (0=quiet, 1=normal, 2=debug) + :param kwargs: extra args, including test_rltt and test_sched_stop for testing + :returns: JSON dictionary of labeled dwell intervals with max temperatures """ if not os.path.exists(outdir): os.mkdir(outdir) + module_dir = Path(__file__).parent + if model_spec is None: + model_spec = str(module_dir / 'data' / 'aca_spec.json') + if char_file is None: + char_file = str(module_dir / 'data' / 'characteristics.yaml') + + if json_obsids is None: + # Only happens in testing, so use existing obsids file in OFLS dir + json_obsids = Path(oflsdir, 'starcheck', 'obsids.json').read_text() + run_start_time = DateTime(run_start_time) config_logging(outdir, verbose) @@ -135,6 +148,7 @@ def get_ccd_temps(oflsdir, outdir='out', % (TASK_NAME, proc['execution_time'], proc['run_user'])) logger.info("# Continuity run_start_time = {}".format(run_start_time.date)) logger.info('# {} version = {}'.format(TASK_NAME, VERSION)) + logger.info(f'# kadi version = {kadi.__version__}') logger.info('###############################' '######################################\n') @@ -147,8 +161,6 @@ def get_ccd_temps(oflsdir, outdir='out', # json_obsids can be either a string or a file-like object. Try those options in order. try: sc_obsids = json.loads(json_obsids) - with open('test_obsids.json', 'w') as fh: - fh.write(json_obsids) except TypeError: sc_obsids = json.load(json_obsids) @@ -172,6 +184,14 @@ def get_ccd_temps(oflsdir, outdir='out', ok = bs_cmds['event_type'] == 'SCHEDULED_STOP_TIME' sched_stop = DateTime(bs_dates[ok][0] if np.any(ok) else bs_dates[-1]) + if 'test_rltt' in kwargs: + rltt = DateTime(kwargs['test_rltt']) + if 'test_sched_stop' in kwargs: + sched_stop = DateTime(kwargs['test_sched_stop']) + + logger.info(f'RLTT = {rltt.date}') + logger.info(f'sched_stop = {sched_stop.date}') + proc['datestart'] = bs_start.date proc['datestop'] = sched_stop.date @@ -181,11 +201,10 @@ def get_ccd_temps(oflsdir, outdir='out', tlm_end_time = min(fetch.get_time_range('aacccdpt', format='secs')[1], bs_start.secs, run_start_time.secs) tlm = get_telem_values(tlm_end_time, ['aacccdpt'], days=1) - print(DateTime(tlm_end_time).date) states = get_week_states(rltt, sched_stop, bs_cmds, tlm) # If the last obsid interval extends over the end of states then - # extend the state / predictions + # extend the state / predictions. last_state = states[-1] last_sc_obsid = sc_obsids[-1] if ((last_state['obsid'] == last_sc_obsid['obsid']) & @@ -194,11 +213,6 @@ def get_ccd_temps(oflsdir, outdir='out', last_state['tstop'] = obs_tstop last_state['datestop'] = DateTime(obs_tstop).date - # Extend last state to reflect scheduled stop time - if last_state['datestop'] < sched_stop.date: - last_state['tstop'] = sched_stop.secs - last_state['datestop'] = sched_stop.date - if rltt.date > DateTime(MODEL_VALID_FROM).date: ccd_times, ccd_temps = make_week_predict(model_spec, states, sched_stop) else: @@ -339,8 +353,8 @@ def get_week_states(rltt, sched_stop, bs_cmds, tlm): # Get the states for available commands. This automatically gets continuity. state_keys = ['obsid', 'pitch', 'q1', 'q2', 'q3', 'q4', 'eclipse'] - states = kadi_states.get_states(cmds=cmds, state_keys=state_keys, - merge_identical=True) + states = kadi_states.get_states(cmds=cmds, start=init_tlm_time, stop=sched_stop, + state_keys=state_keys, merge_identical=True) states['tstart'] = DateTime(states['datestart']).secs states['tstop'] = DateTime(states['datestop']).secs @@ -460,6 +474,10 @@ def emit(self, record): logger = logging.getLogger(TASK_NAME) logger.setLevel(loglevel) + # Remove existing handlers if calc_ccd_temps is called multiple times + for handler in list(logger.handlers): + logger.removeHandler(handler) + formatter = logging.Formatter('%(message)s') console = logging.StreamHandler() From 340a67295b6d653988c823c003cec6ea4c54feb1 Mon Sep 17 00:00:00 2001 From: Tom Aldcroft Date: Tue, 12 May 2020 14:19:36 -0400 Subject: [PATCH 7/7] Add a clarifying comment about end-schedule handliing --- starcheck/calc_ccd_temps.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/starcheck/calc_ccd_temps.py b/starcheck/calc_ccd_temps.py index 3efcfac3..3e70478d 100755 --- a/starcheck/calc_ccd_temps.py +++ b/starcheck/calc_ccd_temps.py @@ -203,8 +203,13 @@ def get_ccd_temps(oflsdir, outdir='out', tlm = get_telem_values(tlm_end_time, ['aacccdpt'], days=1) states = get_week_states(rltt, sched_stop, bs_cmds, tlm) - # If the last obsid interval extends over the end of states then - # extend the state / predictions. + # If the last obsid interval extends over the end of states then extend the + # state / predictions. In the absence of something useful like + # SCHEDULED_STOP, if the schedule ends in NPNT (and has no maneuver in + # backstop to define end time), the obsid stop time for the last observation + # in the schedule might be set from the stop time listed in the processing + # summary. Going forward from backstop 6.9 this clause is likely not being + # run. last_state = states[-1] last_sc_obsid = sc_obsids[-1] if ((last_state['obsid'] == last_sc_obsid['obsid']) &