From b617bae8ef8598113d1ae6461c77ab5c7fc96dac Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 30 Nov 2021 14:20:21 -0500 Subject: [PATCH 01/21] Fix unrelated comments --- acis_thermal_check/acis_obs.py | 2 +- acis_thermal_check/state_builder.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acis_thermal_check/acis_obs.py b/acis_thermal_check/acis_obs.py index 41441253..c6513428 100755 --- a/acis_thermal_check/acis_obs.py +++ b/acis_thermal_check/acis_obs.py @@ -273,7 +273,7 @@ def acis_filter(obsid_interval_list): """ This method will filter between the different types of ACIS observations: ACIS-I, ACIS-S, "hot" ACIS-S, and - cold science-orbit ECS. + cold science-orbit ECS. """ acis_hot = [] acis_s = [] diff --git a/acis_thermal_check/state_builder.py b/acis_thermal_check/state_builder.py index 76bf7dfc..26c248a9 100644 --- a/acis_thermal_check/state_builder.py +++ b/acis_thermal_check/state_builder.py @@ -7,8 +7,8 @@ import logging from Ska.File import get_globfiles -# Define state keys for states, corresponding to the legacy states in -# Chandra.cmd_states. + +# Define state keys for states STATE_KEYS = ['ccd_count', 'clocking', 'dec', 'dither', 'eclipse', 'fep_count', 'hetg', 'letg', 'obsid', 'pcad_mode', 'pitch', 'power_cmd', 'q1', 'q2', 'q3', 'q4', 'ra', 'roll', 'si_mode', From 9b830b8d48dcf6477d63cb85f86b58eb477babd5 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 30 Nov 2021 14:33:44 -0500 Subject: [PATCH 02/21] Implement obtaining limits from JSON files --- acis_thermal_check/apps/acisfp_check.py | 9 +++++- acis_thermal_check/apps/dpa_check.py | 5 +++- acis_thermal_check/main.py | 40 +++++++++++++++++-------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/acis_thermal_check/apps/acisfp_check.py b/acis_thermal_check/apps/acisfp_check.py index 711318f9..5d16d319 100755 --- a/acis_thermal_check/apps/acisfp_check.py +++ b/acis_thermal_check/apps/acisfp_check.py @@ -42,11 +42,18 @@ def __init__(self): 'TSCPOS': [(1, 2.5), (99, 2.5)] } hist_limit = [(-120.0, -100.0)] + limits_map = { + "planning.data_quality.high.acisi": "acis_i", + "planning.data_quality.high.aciss": "acis_s", + "planning.data_quality.high.aciss_hot": "acis_hot", + "planning.data_quality.high.cold_ecs": "cold_ecs" + } super(ACISFPCheck, self).__init__("fptemp", "acisfp", valid_limits, hist_limit, other_telem=['1dahtbon'], other_map={'1dahtbon': 'dh_heater', - "fptemp_11": "fptemp"}) + "fptemp_11": "fptemp"}, + limits_map=limits_map) # Create an empty observation list which will hold the results. This # list contains all ACIS and all ECS observations. self.acis_and_ecs_obs = [] diff --git a/acis_thermal_check/apps/dpa_check.py b/acis_thermal_check/apps/dpa_check.py index 283b4757..06529e92 100755 --- a/acis_thermal_check/apps/dpa_check.py +++ b/acis_thermal_check/apps/dpa_check.py @@ -29,8 +29,11 @@ def __init__(self): 'TSCPOS': [(1, 2.5), (99, 2.5)] } hist_limit = [20.0] + limits_map = { + "planning.caution.low": "zero_feps" + } super(DPACheck, self).__init__("1dpamzt", "dpa", valid_limits, - hist_limit) + hist_limit, limits_map=limits_map) def custom_prediction_viols(self, times, temp, viols, load_start): """ diff --git a/acis_thermal_check/main.py b/acis_thermal_check/main.py index da6cf529..dec8bbd1 100644 --- a/acis_thermal_check/main.py +++ b/acis_thermal_check/main.py @@ -78,6 +78,13 @@ class ACISThermalCheck: A dictionary which maps names to MSIDs, e.g.: {'sim_z': 'tscpos', 'dp_pitch': 'pitch'}. Used to map names understood by Xija to MSIDs. + limits_map : dictionary, optional + A dictionary mapping of limit names in the model specification + file to more user-friendly ones for use in acis_thermal_check. + The standard planning/yellow limits are already implemented + internally, so this dict is for "extra" ones such as the 0-FEPs + +12 C limit for 1DPAMZT. Default: None, meaning no extra + limits are specified. flag_cold_viols : boolean, optional If set, violations for the lower planning limit will be checked for and flagged, and @@ -92,15 +99,24 @@ class ACISThermalCheck: in *hist_limit*. """ def __init__(self, msid, name, validation_limits, hist_limit, - other_telem=None, other_map=None, + other_telem=None, other_map=None, limits_map=None, flag_cold_viols=False, hist_ops=None): self.msid = msid self.name = name - self._handle_limits() self.validation_limits = validation_limits self.hist_limit = hist_limit self.other_telem = other_telem self.other_map = other_map + self.limits_map = { + "odb.caution.high": "yellow_hi", + "odb.caution.low": "yellow_lo", + "safety.caution.high": "yellow_hi", + "safety.caution.low": "yellow_lo", + "planning.warning.high": "plan_hi", + "planning.warning.low": "plan_lo" + } + if limits_map is not None: + self.limits_map.update(limits_map) # Initially, the state_builder is set to None, as it will get # set up later self.state_builder = None @@ -111,13 +127,12 @@ def __init__(self, msid, name, validation_limits, hist_limit, self.perigee_passages = [] self.write_pickle = False - def _handle_limits(self): - from yaml import load, Loader - limits_file = TASK_DATA / 'acis_thermal_check/data/limits.yml' - with open(limits_file, "r") as f: - limits = load(f, Loader=Loader)[self.msid] + def _handle_limits(self, model_spec): + limits = model_spec["limits"][self.msid] for k, v in limits.items(): - setattr(self, f"{k}_limit", v) + if k == "unit": + continue + setattr(self, f"{self.limits_map[k]}_limit", v) def run(self, args, override_limits=None): """ @@ -156,9 +171,10 @@ def run(self, args, override_limits=None): # data to a pickle later self.write_pickle = args.run_start is not None - # This allows one to override the planning and yellow limits - # for a particular model run. THIS SHOULD ONLY BE USED FOR - # TESTING PURPOSES. + self._handle_limits(model_spec) + + # This allows one to override the limits for a particular model + # run. THIS SHOULD ONLY BE USED FOR TESTING PURPOSES. if override_limits is not None: for k, v in override_limits.items(): if hasattr(self, k): @@ -491,7 +507,7 @@ def _make_prediction_viols(self, times, temp, load_start, limit, lim_name, def make_prediction_viols(self, temps, load_start): """ Find limit violations where predicted temperature is above the - yellow limit minus margin. + specified limits. Parameters ---------- From b422f3296e63ff4f75e733d20ea73a7ccd255355 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 30 Nov 2021 20:57:15 -0500 Subject: [PATCH 03/21] Give the test JSON files the same limit dicts --- .../tests/acisfp/acisfp_test_spec.json | 11 +++++++++++ .../tests/bep_pcb/bep_pcb_test_spec.json | 10 +++++++++- acis_thermal_check/tests/dea/dea_test_spec.json | 8 ++++++++ acis_thermal_check/tests/dpa/dpa_test_spec.json | 14 ++++++++++---- acis_thermal_check/tests/psmc/psmc_test_spec.json | 8 ++++++++ 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/acis_thermal_check/tests/acisfp/acisfp_test_spec.json b/acis_thermal_check/tests/acisfp/acisfp_test_spec.json index 005dd7f3..3e891cae 100644 --- a/acis_thermal_check/tests/acisfp/acisfp_test_spec.json +++ b/acis_thermal_check/tests/acisfp/acisfp_test_spec.json @@ -441,6 +441,17 @@ 794 ] }, + "limits": { + "fptemp": { + "planning.data_quality.high.acisi": -112.0, + "planning.data_quality.high.aciss": -111.0, + "planning.data_quality.high.aciss_hot": -109.0, + "planning.data_quality.high.cold_ecs": -118.2, + "planning.warning.high": -84.0, + "safety.caution.high": -80.0, + "unit": "degC" + } + }, "mval_names": [], "name": "acisfp", "pars": [ diff --git a/acis_thermal_check/tests/bep_pcb/bep_pcb_test_spec.json b/acis_thermal_check/tests/bep_pcb/bep_pcb_test_spec.json index cc7108d1..370529e0 100644 --- a/acis_thermal_check/tests/bep_pcb/bep_pcb_test_spec.json +++ b/acis_thermal_check/tests/bep_pcb/bep_pcb_test_spec.json @@ -250,7 +250,15 @@ 801 ] }, - "limits": {}, + "limits": { + "tmp_bep_pcb": { + "planning.warning.high": 42.0, + "safety.caution.high": 44.0, + "planning.warning.low": 8.5, + "safety.caution.low": 6.5, + "unit": "degC" + } + }, "mval_names": [], "name": "bep_pcb", "pars": [ diff --git a/acis_thermal_check/tests/dea/dea_test_spec.json b/acis_thermal_check/tests/dea/dea_test_spec.json index 36206691..2bafec68 100644 --- a/acis_thermal_check/tests/dea/dea_test_spec.json +++ b/acis_thermal_check/tests/dea/dea_test_spec.json @@ -251,6 +251,14 @@ 1217 ] }, + "limits": { + "1deamzt": { + "odb.caution.high": 39.5, + "odb.warning.high": 42.5, + "planning.warning.high": 37.5, + "unit": "degC" + } + }, "mval_names": [], "name": "1deamzt", "pars": [ diff --git a/acis_thermal_check/tests/dpa/dpa_test_spec.json b/acis_thermal_check/tests/dpa/dpa_test_spec.json index 57a6f8b7..eca5fee5 100644 --- a/acis_thermal_check/tests/dpa/dpa_test_spec.json +++ b/acis_thermal_check/tests/dpa/dpa_test_spec.json @@ -241,10 +241,7 @@ "dt": 328.0, "gui_config": { "filename": "/home/gregg/THERMAL-MODELS/DPA-FITS/FALL_2019_RECAL/NOV_5/DEC_9_STEP_1.json", - "limits": { - "caution_hi": 39.5, - "planning_hi": 37.5 - }, + "msid": "1dpamzt", "plot_names": [ "1dpamzt data__time", @@ -258,6 +255,15 @@ 1028 ] }, + "limits": { + "1dpamzt": { + "odb.caution.high": 39.5, + "odb.warning.high": 41.5, + "planning.warning.high": 37.5, + "planning.caution.low": 12.0, + "unit": "degC" + } + }, "mval_names": [], "name": "dpa_state", "pars": [ diff --git a/acis_thermal_check/tests/psmc/psmc_test_spec.json b/acis_thermal_check/tests/psmc/psmc_test_spec.json index 4982042c..d23eebfd 100644 --- a/acis_thermal_check/tests/psmc/psmc_test_spec.json +++ b/acis_thermal_check/tests/psmc/psmc_test_spec.json @@ -297,6 +297,14 @@ 1246 ] }, + "limits": { + "1pdeaat": { + "odb.caution.high": 57, + "odb.warning.high": 62, + "planning.warning.high": 52.5, + "unit": "degC" + } + }, "mval_names": [], "name": "psmc", "pars": [ From 030695005cf0b1d9a918f8cd3b21ded9dd944c33 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 30 Nov 2021 21:09:09 -0500 Subject: [PATCH 04/21] Final touches to make this work --- acis_thermal_check/main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/acis_thermal_check/main.py b/acis_thermal_check/main.py index dec8bbd1..153c1fb0 100644 --- a/acis_thermal_check/main.py +++ b/acis_thermal_check/main.py @@ -29,6 +29,7 @@ from kadi import events from astropy.table import Table from pathlib import Path, PurePath +import json op_map = {"greater": ">", @@ -130,7 +131,7 @@ def __init__(self, msid, name, validation_limits, hist_limit, def _handle_limits(self, model_spec): limits = model_spec["limits"][self.msid] for k, v in limits.items(): - if k == "unit": + if k == "unit" or k not in self.limits_map: continue setattr(self, f"{self.limits_map[k]}_limit", v) @@ -162,6 +163,9 @@ def run(self, args, override_limits=None): else: model_spec = args.model_spec + if not isinstance(model_spec, dict): + model_spec = json.load(open(model_spec, 'r')) + proc = self._setup_proc_and_logger(args, model_spec) # Record the selected state builder in the class attributes @@ -1207,7 +1211,6 @@ def _setup_proc_and_logger(self, args, model_spec): attached to it as attributes """ import hashlib - import json if not args.outdir.exists(): args.outdir.mkdir(parents=True) From f508906a4e6679586bf80523c3216dc8b55a90aa Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 30 Nov 2021 22:43:46 -0500 Subject: [PATCH 05/21] Forgot the limits here --- .../tests/fep1_actel/fep1_actel_test_spec.json | 10 +++++++++- .../tests/fep1_mong/fep1_mong_test_spec.json | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/acis_thermal_check/tests/fep1_actel/fep1_actel_test_spec.json b/acis_thermal_check/tests/fep1_actel/fep1_actel_test_spec.json index 9d6e5fdc..9c0da2e6 100644 --- a/acis_thermal_check/tests/fep1_actel/fep1_actel_test_spec.json +++ b/acis_thermal_check/tests/fep1_actel/fep1_actel_test_spec.json @@ -251,7 +251,15 @@ 802 ] }, - "limits": {}, + "limits": { + "tmp_fep1_actel": { + "planning.warning.high": 46.0, + "safety.caution.high": 48.0, + "planning.warning.low": 2.0, + "safety.caution.low": 0.0, + "unit": "degC" + } + }, "mval_names": [], "name": "fep1_actel", "pars": [ diff --git a/acis_thermal_check/tests/fep1_mong/fep1_mong_test_spec.json b/acis_thermal_check/tests/fep1_mong/fep1_mong_test_spec.json index fce9bd3d..701542aa 100644 --- a/acis_thermal_check/tests/fep1_mong/fep1_mong_test_spec.json +++ b/acis_thermal_check/tests/fep1_mong/fep1_mong_test_spec.json @@ -250,7 +250,15 @@ 799 ] }, - "limits": {}, + "limits": { + "tmp_fep1_mong": { + "planning.warning.high": 47.0, + "safety.caution.high": 49.0, + "planning.warning.low": 2.0, + "safety.caution.low": 0.0, + "unit": "degC" + } + }, "mval_names": [], "name": "tmp_fep1_mong", "pars": [ From 8467bee5b4e4bf386986d8ae00ac4eaf1f6815f9 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 7 Dec 2021 07:43:32 -0500 Subject: [PATCH 06/21] Refactor the design here --- acis_thermal_check/__init__.py | 2 +- acis_thermal_check/apps/acisfp_check.py | 59 +++++----- acis_thermal_check/apps/dpa_check.py | 26 ++--- acis_thermal_check/main.py | 137 ++++++++++++++---------- acis_thermal_check/utils.py | 23 +++- 5 files changed, 149 insertions(+), 98 deletions(-) diff --git a/acis_thermal_check/__init__.py b/acis_thermal_check/__init__.py index a6c6d1db..45da8699 100644 --- a/acis_thermal_check/__init__.py +++ b/acis_thermal_check/__init__.py @@ -6,7 +6,7 @@ ACISThermalCheck, \ DPABoardTempCheck from acis_thermal_check.utils import \ - get_options, mylog + get_options, mylog, get_acis_limits from acis_thermal_check.acis_obs import \ fetch_ocat_data, acis_filter diff --git a/acis_thermal_check/apps/acisfp_check.py b/acis_thermal_check/apps/acisfp_check.py index 5d16d319..43eecf6f 100755 --- a/acis_thermal_check/apps/acisfp_check.py +++ b/acis_thermal_check/apps/acisfp_check.py @@ -172,21 +172,25 @@ def make_prediction_plots(self, outdir, states, temps, load_start): figsize=(12, 7.142857142857142), width=w1, load_start=load_start) plots[name]['ax'].set_title(self.msid.upper(), loc='left', pad=10) - # Draw a horizontal line indicating the FP Sensitive Observation Cut off - plots[name]['ax'].axhline(self.cold_ecs_limit, linestyle='--', - color='dodgerblue', linewidth=2.0, + # Draw a horizontal line indicating cold ECS cutoff + plots[name]['ax'].axhline(self.limits["cold_ecs"]["value"], + linestyle='--', linewidth=2.0, + color=self.limits["cold_ecs"]["color"], label='Cold ECS') - # Draw a horizontal line showing the ACIS-I -114 deg. C cutoff - plots[name]['ax'].axhline(self.acis_i_limit, linestyle='--', - color='purple', linewidth=2.0, + # Draw a horizontal line showing the ACIS-I cutoff + plots[name]['ax'].axhline(self.limits["acis_i"]["value"], + linestyle='--', linewidth=2.0, + color=self.limits["acis_i"]["color"], label="ACIS-I") - # Draw a horizontal line showing the ACIS-S -112 deg. C cutoff - plots[name]['ax'].axhline(self.acis_s_limit, linestyle='--', - color='blue', linewidth=2.0, - label='ACIS-S') - # Draw a horizontal line showing the ACIS-S -109 deg. C cutoff - plots[name]['ax'].axhline(self.acis_hot_limit, linestyle='--', - color='red', linewidth=2.0, + # Draw a horizontal line showing the ACIS-S cutoff + plots[name]['ax'].axhline(self.limits["acis_s"]["value"], + linestyle='--', linewidth=2.0, + color=self.limits["acis_s"]["color"], + label="ACIS-S") + # Draw a horizontal line showing the hot ACIS-S cutoff + plots[name]['ax'].axhline(self.limits["acis_hot"]["value"], + linestyle='--', linewidth=2.0, + color=self.limits["acis_hot"]["color"], label="Hot ACIS-S") # Get the width of this plot to make the widths of all the # prediction plots the same @@ -282,13 +286,18 @@ def make_prediction_viols(self, temps, load_start): # science run. These are load killers # ------------------------------------------------------------ # - mylog.info(f'\n\nACIS-I Science ({self.acis_i_limit} C) violations') + acis_i_limit = self.limits["acis_i"]["value"] + acis_s_limit = self.limits["acis_s"]["value"] + acis_hot_limit = self.limits["acis_hot"]["value"] + cold_ecs_limit = self.limits["cold_ecs"]["value"] + + mylog.info(f'\n\nACIS-I Science ({acis_i_limit} C) violations') # Create the violation data structure. acis_i_viols = self.search_obsids_for_viols("ACIS-I", - self.acis_i_limit, ACIS_I_obs, temp, times, load_start) + acis_i_limit, ACIS_I_obs, temp, times, load_start) - viols["ACIS_I"] = {"name": f"ACIS-I ({self.acis_i_limit} C)", + viols["ACIS_I"] = {"name": f"ACIS-I ({acis_i_limit} C)", "type": "Max", "values": acis_i_viols} @@ -297,11 +306,11 @@ def make_prediction_viols(self, temps, load_start): # science run. These are load killers # ------------------------------------------------------------ # - mylog.info(f'\n\nACIS-S Science ({self.acis_s_limit} C) violations') + mylog.info(f'\n\nACIS-S Science ({acis_s_limit} C) violations') acis_s_viols = self.search_obsids_for_viols("ACIS-S", - self.acis_s_limit, ACIS_S_obs, temp, times, load_start) - viols["ACIS_S"] = {"name": f"ACIS-S ({self.acis_s_limit} C)", + acis_s_limit, ACIS_S_obs, temp, times, load_start) + viols["ACIS_S"] = {"name": f"ACIS-S ({acis_s_limit} C)", "type": "Max", "values": acis_s_viols} @@ -310,23 +319,23 @@ def make_prediction_viols(self, temps, load_start): # science run which can run hot. These are load killers # ------------------------------------------------------------ # - mylog.info(f'\n\nACIS-S Science ({self.acis_hot_limit} C) violations') + mylog.info(f'\n\nACIS-S Science ({acis_hot_limit} C) violations') acis_hot_viols = self.search_obsids_for_viols("Hot ACIS-S", - self.acis_hot_limit, ACIS_hot_obs, temp, times, load_start) - viols["ACIS_S_hot"] = {"name": f"ACIS-S Hot ({self.acis_hot_limit} C)", + acis_hot_limit, ACIS_hot_obs, temp, times, load_start) + viols["ACIS_S_hot"] = {"name": f"ACIS-S Hot ({acis_hot_limit} C)", "type": "Max", "values": acis_hot_viols} # ------------------------------------------------------------ # Science Orbit ECS -119.5 violations; -119.5 violation check # ------------------------------------------------------------ - mylog.info(f'\n\nScience Orbit ECS ({self.cold_ecs_limit} C) violations') + mylog.info(f'\n\nScience Orbit ECS ({cold_ecs_limit} C) violations') ecs_viols = self.search_obsids_for_viols("Science Orbit ECS", - self.cold_ecs_limit, sci_ecs_obs, temp, times, load_start) + cold_ecs_limit, sci_ecs_obs, temp, times, load_start) - viols["ecs"] = {"name": f"Science Orbit ECS ({self.cold_ecs_limit} C)", + viols["ecs"] = {"name": f"Science Orbit ECS ({cold_ecs_limit} C)", "type": "Min", "values": ecs_viols} diff --git a/acis_thermal_check/apps/dpa_check.py b/acis_thermal_check/apps/dpa_check.py index 06529e92..71715345 100755 --- a/acis_thermal_check/apps/dpa_check.py +++ b/acis_thermal_check/apps/dpa_check.py @@ -55,13 +55,14 @@ def custom_prediction_viols(self, times, temp, viols, load_start): """ # Only check this violation when all FEPs are off mask = self.predict_model.comp['fep_count'].dvals == 0 - zf_viols = self._make_prediction_viols(times, temp, load_start, - self.zero_feps_limit, - "zero-feps", "min", - mask=mask) - viols["zero_feps"] = {"name": f"Zero FEPs ({self.zero_feps_limit} C)", - "type": "Min", - "values": zf_viols} + zf_viols = self._make_prediction_viols( + times, temp, load_start, self.limits["zero_feps"]["value"], + "zero-feps", "min", mask=mask) + viols["zero_feps"] = { + "name": f"Zero FEPs ({self.limits['zero_feps']['value']} C)", + "type": "Min", + "values": zf_viols + } def custom_prediction_plots(self, plots): """ @@ -74,9 +75,9 @@ def custom_prediction_plots(self, plots): and can be used to customize plots before they are written, e.g. add limit lines, etc. """ - plots[self.name]['ax'].axhline(self.zero_feps_limit, linestyle='--', - color='dodgerblue', label="Zero FEPs", - linewidth=2.0) + plots[self.name]['ax'].axhline(self.limits["zero_feps"]["value"], + linestyle='--', label="Zero FEPs", linewidth=2.0, + color=self.limits["zero_feps"]["color"], zorder=-8) def custom_validation_plots(self, plots): """ @@ -90,8 +91,9 @@ def custom_validation_plots(self, plots): e.g. add limit lines, etc. """ plots["1dpamzt"]['lines']['ax'].axhline( - self.zero_feps_limit, linestyle='--', color='dodgerblue', zorder=-8, - linewidth=2, label="Zero FEPs") + self.limits["zero_feps"]["value"], linestyle='--', zorder=-8, + color=self.limits["zero_feps"]["color"], linewidth=2, + label="Zero FEPs") def _calc_model_supp(self, model, state_times, states, ephem, state0): """ diff --git a/acis_thermal_check/main.py b/acis_thermal_check/main.py index 153c1fb0..503db2a9 100644 --- a/acis_thermal_check/main.py +++ b/acis_thermal_check/main.py @@ -25,7 +25,7 @@ config_logging, TASK_DATA, plot_two, \ mylog, plot_one, make_state_builder, \ calc_pitch_roll, thermal_blue, thermal_red, \ - paint_perigee + paint_perigee, get_acis_limits from kadi import events from astropy.table import Table from pathlib import Path, PurePath @@ -113,8 +113,8 @@ def __init__(self, msid, name, validation_limits, hist_limit, "odb.caution.low": "yellow_lo", "safety.caution.high": "yellow_hi", "safety.caution.low": "yellow_lo", - "planning.warning.high": "plan_hi", - "planning.warning.low": "plan_lo" + "planning.warning.high": "planning_hi", + "planning.warning.low": "planning_lo" } if limits_map is not None: self.limits_map.update(limits_map) @@ -127,13 +127,7 @@ def __init__(self, msid, name, validation_limits, hist_limit, self.hist_ops = hist_ops self.perigee_passages = [] self.write_pickle = False - - def _handle_limits(self, model_spec): - limits = model_spec["limits"][self.msid] - for k, v in limits.items(): - if k == "unit" or k not in self.limits_map: - continue - setattr(self, f"{self.limits_map[k]}_limit", v) + self.limits = {} def run(self, args, override_limits=None): """ @@ -175,9 +169,10 @@ def run(self, args, override_limits=None): # data to a pickle later self.write_pickle = args.run_start is not None - self._handle_limits(model_spec) + self.limits = get_acis_limits(self.msid, model_spec, + limits_map=self.limits_map) - # This allows one to override the limits for a particular model + # This allows one to override the limits for a particular model # run. THIS SHOULD ONLY BE USED FOR TESTING PURPOSES. if override_limits is not None: for k, v in override_limits.items(): @@ -527,22 +522,24 @@ def make_prediction_viols(self, temps, load_start): temp = temps[self.name] times = self.predict_model.times - hi_viols = self._make_prediction_viols(times, temp, load_start, - self.plan_hi_limit, - "planning", "max") + hi_viols = self._make_prediction_viols( + times, temp, load_start, self.limits["planning_hi"]["value"], + "planning", "max") viols = {"hi": - {"name": f"Hot ({self.plan_hi_limit} C)", + {"name": f"Hot ({self.limits['planning_hi']['value']} C)", "type": "Max", "values": hi_viols} } if self.flag_cold_viols: - lo_viols = self._make_prediction_viols(times, temp, load_start, - self.plan_lo_limit, - "planning", "min") - viols["lo"] = {"name": f"Cold ({self.plan_lo_limit} C)", - "type": "Min", - "values": lo_viols} + lo_viols = self._make_prediction_viols( + times, temp, load_start, self.limits["planning_lo"]["value"], + "planning", "min") + viols["lo"] = { + "name": f"Cold ({self.limits['planning_hi']['value']} C)", + "type": "Min", + "values": lo_viols + } # Handle any additional violations one wants to check, # can be overridden by a subclass @@ -758,24 +755,32 @@ def make_prediction_plots(self, outdir, states, temps, load_start): plots[self.name] = plot_two(fig_id=1, x=times, y=temps[self.name], x2=times, y2=self.predict_model.comp["pitch"].mvals, - xmin=plot_start, xlabel='Date', + xmin=plot_start, xlabel='Date', ylabel='Temperature ($^\circ$C)', ylabel2='Pitch (deg)', ylim2=(40, 180), width=w1, load_start=load_start) # Add horizontal lines for the planning and caution limits ymin, ymax = plots[self.name]['ax'].get_ylim() - ymax = max(self.yellow_hi_limit+1, ymax) + ymax = max(self.limits["yellow_hi"]["value"]+1, ymax) plots[self.name]['ax'].set_title(self.msid.upper(), loc='left', pad=10) - plots[self.name]['ax'].axhline(self.yellow_hi_limit, linestyle='-', - color='gold', linewidth=2.0, label='Yellow') - plots[self.name]['ax'].axhline(self.plan_hi_limit, linestyle='-', - color='C2', linewidth=2.0, label='Planning') + plots[self.name]['ax'].axhline(self.limits["yellow_hi"]["value"], + linestyle='-', linewidth=2.0, + color=self.limits["yellow_hi"]["color"], + label='Yellow') + plots[self.name]['ax'].axhline(self.limits["planning_hi"]["value"], + linestyle='-', linewidth=2.0, + color=self.limits["planning_hi"]["color"], + label='Planning') if self.flag_cold_viols: - ymin = min(self.yellow_lo_limit-1, ymin) - plots[self.name]['ax'].axhline(self.yellow_lo_limit, linestyle='-', - color='gold', linewidth=2.0, zorder=-8) - plots[self.name]['ax'].axhline(self.plan_lo_limit, linestyle='-', - color='C2', linewidth=2.0, zorder=-8) + ymin = min(self.limits["yellow_lo"]["value"]-1, ymin) + plots[self.name]['ax'].axhline(self.limits["yellow_hi"]["value"], + linestyle='-', linewidth=2.0, + color=self.limits["yellow_hi"]["color"], + label='Yellow', zorder=-8) + plots[self.name]['ax'].axhline(self.limits["planning_hi"]["value"], + linestyle='-', linewidth=2.0, + color=self.limits["planning_hi"]["color"], + label='Planning', zorder=-8) plots[self.name]['ax'].set_ylim(ymin, ymax) plots[self.name]['filename'] = self.msid.lower()+'.png' @@ -874,7 +879,8 @@ def make_validation_plots(self, tlm, model_spec, outdir): stop = tlm['date'][-1] states = self.state_builder.get_validation_states(start, stop) - mylog.info('Calculating %s thermal model for validation' % self.name.upper()) + mylog.info('Calculating %s thermal model for validation', + self.name.upper()) # Run the thermal model from the beginning of obtained telemetry # to the end, so we can compare its outputs to the real values @@ -923,7 +929,8 @@ def make_validation_plots(self, tlm, model_spec, outdir): rzs = events.rad_zones.filter(start, stop) plots = {} - mylog.info('Making %s model validation plots and quantile table' % self.name.upper()) + mylog.info('Making %s model validation plots and quantile table', + self.name.upper()) quantiles = (1, 5, 16, 50, 84, 95, 99) # store lines of quantile table in a string and write out later quant_table = '' @@ -936,10 +943,12 @@ def make_validation_plots(self, tlm, model_spec, outdir): fig = plt.figure(10 + fig_id, figsize=(12, 6)) fig.clf() scale = scales.get(msid, 1.0) - ticklocs, fig, ax = plot_cxctime(model.times, pred[msid] / scale, label='Model', - fig=fig, ls='-', lw=4, color=thermal_red) - ticklocs, fig, ax = plot_cxctime(model.times, tlm[msid] / scale, label='Data', - fig=fig, ls='-', lw=2, color=thermal_blue) + ticklocs, fig, ax = plot_cxctime( + model.times, pred[msid] / scale, label='Model', + fig=fig, ls='-', lw=4, color=thermal_red) + ticklocs, fig, ax = plot_cxctime( + model.times, tlm[msid] / scale, label='Data', + fig=fig, ls='-', lw=2, color=thermal_blue) if np.any(~good_mask): ticklocs, fig, ax = plot_cxctime(model.times[~good_mask], tlm[msid][~good_mask] / scale, @@ -960,29 +969,39 @@ def make_validation_plots(self, tlm, model_spec, outdir): if self.msid == msid: ymin, ymax = ax.get_ylim() if msid == "fptemp": - ax.axhline(self.cold_ecs_limit, linestyle='--', - color='dodgerblue', label='Cold ECS', + ax.axhline(self.limits["cold_ecs"]["value"], + linestyle='--', label='Cold ECS', + color=self.limits["cold_ecs"]["color"], + zorder=-8, linewidth=2) + ax.axhline(self.limits["acis_i"]["value"], + linestyle='--', label='ACIS-I', + color=self.limits["acis_i"]["color"], + zorder=-8, linewidth=2) + ax.axhline(self.limits["acis_s"]["value"], + linestyle='--', label='ACIS-S', + color=self.limits["acis_s"]["color"], + zorder=-8, linewidth=2) + ax.axhline(self.limits["acis_hot"]["value"], + linestyle='--', label='Hot ACIS', + color=self.limits["acis_hot"]["color"], zorder=-8, linewidth=2) - ax.axhline(self.acis_i_limit, linestyle='--', - color='purple', zorder=-8, label='ACIS-I', - linewidth=2) - ax.axhline(self.acis_s_limit, linestyle='--', - color='blue', zorder=-8, label='ACIS-S', - linewidth=2) - ax.axhline(self.acis_hot_limit, linestyle='--', - color='red', zorder=-8, label='Hot ACIS', - linewidth=2) - ymax = max(self.acis_hot_limit+1, ymax) + ymax = max(self.limits["acis_hot"]["value"]+1, ymax) else: - ax.axhline(self.yellow_hi_limit, linestyle='-', color='gold', - zorder=-8, linewidth=2, label='Yellow') - ax.axhline(self.plan_hi_limit, linestyle='-', color='C2', - zorder=-8, linewidth=2, label='Planning') - ymax = max(self.yellow_hi_limit+1, ymax) + ax.axhline(self.limits["yellow_hi"]["value"], + linestyle='-', linewidth=2, zorder=-8, + color=self.limits["yellow_hi"]["color"]) + ax.axhline(self.limits["planning_hi"]["value"], + linestyle='-', linewidth=2, zorder=-8, + color=self.limits["planning_hi"]["color"]) + ymax = max(self.limits["yellow_hi"]["value"]+1, ymax) if self.flag_cold_viols: - ax.axhline(self.yellow_lo_limit, linestyle='-', color='gold', linewidth=2) - ax.axhline(self.plan_lo_limit, linestyle='-', color='C2', linewidth=2) - ymin = min(self.yellow_lo_limit-1, ymin) + ax.axhline(self.limits["yellow_lo"]["value"], + linestyle='-', linewidth=2, zorder=-8, + color=self.limits["yellow_lo"]["color"]) + ax.axhline(self.limits["planning_lo"]["value"], + linestyle='-', linewidth=2, zorder=-8, + color=self.limits["planning_lo"]["color"]) + ymin = min(self.limits["yellow_lo"]["value"]-1, ymin) ax.set_ylim(ymin, ymax) ax.set_xlim(xmin, xmax) diff --git a/acis_thermal_check/utils.py b/acis_thermal_check/utils.py index 936461ff..86462840 100644 --- a/acis_thermal_check/utils.py +++ b/acis_thermal_check/utils.py @@ -5,7 +5,7 @@ from Ska.Matplotlib import cxctime2plotdate import Ska.Numpy from pathlib import Path, PurePath - +from xija.limits import get_limit_color TASK_DATA = Path(PurePath(__file__).parent / '..').resolve() @@ -475,3 +475,24 @@ def paint_perigee(perigee_passages, states, plots): perigee_time = cxctime2plotdate([CxoTime(eachpassage[1]).secs]) plot['ax'].vlines(perigee_time, ymin, ymax, linestyle=':', color='black', linewidth=2.0) + + +def get_acis_limits(msid, model_spec, limits_map=None): + import json + if msid == "fptemp_11": + msid = "fptemp" + if limits_map is None: + limits_map = {} + if not isinstance(model_spec, dict): + model_spec = json.load(open(model_spec, 'r')) + json_limits = model_spec["limits"][msid] + limits = {} + for k, v in json_limits.items(): + if k == "unit": + continue + key = limits_map.get(k, k) + limits[key] = { + "value": v, + "color": get_limit_color(k) + } + return limits \ No newline at end of file From aee7c8078ab21f0c77213f383875d4a644bf5da5 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 7 Dec 2021 09:02:47 -0500 Subject: [PATCH 07/21] docstring here --- acis_thermal_check/utils.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/acis_thermal_check/utils.py b/acis_thermal_check/utils.py index 86462840..57ed9d2b 100644 --- a/acis_thermal_check/utils.py +++ b/acis_thermal_check/utils.py @@ -478,6 +478,29 @@ def paint_perigee(perigee_passages, states, plots): def get_acis_limits(msid, model_spec, limits_map=None): + """ + Given a MSID and a model specification (JSON or dict), + return the values and line colors of the limits specified + in the file. + + Parameters + ---------- + msid : string + The MSID to get the limits for. + model_spec : string or dict + The xija model specification. If a string, it is + assumed to be a JSON file to be read in + limits_map : dict, optional + If supplied, this will change the keys of the output + dict, which are normally the limit names in the model + specification, with other names, e.g. replaces + "odb.caution.high" with "yellow_hi". Default: None + + Returns + ------- + A dict of dicts, with each dict corresponding to the value of the + limit and the color of the line on plots. + """ import json if msid == "fptemp_11": msid = "fptemp" From 3ca27c4473f86df298fec4dfac6acbf0d5421881 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Wed, 8 Dec 2021 12:16:36 -0500 Subject: [PATCH 08/21] Whitespace --- acis_thermal_check/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acis_thermal_check/utils.py b/acis_thermal_check/utils.py index 57ed9d2b..9cc9e496 100644 --- a/acis_thermal_check/utils.py +++ b/acis_thermal_check/utils.py @@ -485,7 +485,7 @@ def get_acis_limits(msid, model_spec, limits_map=None): Parameters ---------- - msid : string + msid : string The MSID to get the limits for. model_spec : string or dict The xija model specification. If a string, it is From 1b895cbf26d4cc12ae804e294b2b3d404bc88c0a Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 28 Dec 2021 15:56:38 -0500 Subject: [PATCH 09/21] Add newline --- acis_thermal_check/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acis_thermal_check/utils.py b/acis_thermal_check/utils.py index 9cc9e496..f30a9f92 100644 --- a/acis_thermal_check/utils.py +++ b/acis_thermal_check/utils.py @@ -518,4 +518,4 @@ def get_acis_limits(msid, model_spec, limits_map=None): "value": v, "color": get_limit_color(k) } - return limits \ No newline at end of file + return limits From 7fd7a734136373c20c8c63637f446278ea4ecbcd Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 28 Dec 2021 16:03:19 -0500 Subject: [PATCH 10/21] This file is no longer needed --- acis_thermal_check/data/limits.yml | 36 ------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 acis_thermal_check/data/limits.yml diff --git a/acis_thermal_check/data/limits.yml b/acis_thermal_check/data/limits.yml deleted file mode 100644 index 2775b9ac..00000000 --- a/acis_thermal_check/data/limits.yml +++ /dev/null @@ -1,36 +0,0 @@ -1dpamzt: - plan_hi: 37.5 - yellow_hi: 39.5 - zero_feps: 12.0 - -1deamzt: - plan_hi: 37.5 - yellow_hi: 39.5 - -1pdeaat: - plan_hi: 52.5 - yellow_hi: 57.0 - -fptemp: - cold_ecs: -118.2 - acis_s: -111.0 - acis_i: -112.0 - acis_hot: -109.0 - -tmp_fep1_actel: - plan_hi: 46.0 - yellow_hi: 48.0 - plan_lo: 2.0 - yellow_lo: 0.0 - -tmp_fep1_mong: - plan_hi: 47.0 - yellow_hi: 49.0 - plan_lo: 2.0 - yellow_lo: 0.0 - -tmp_bep_pcb: - plan_hi: 42.0 - yellow_hi: 44.0 - plan_lo: 8.5 - yellow_lo: 6.5 From f0b38ab5df61a2b1d085307ba76b637d2ba4d020 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Tue, 28 Dec 2021 17:23:56 -0500 Subject: [PATCH 11/21] Doc updates --- doc/source/developing_models.rst | 58 ++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/doc/source/developing_models.rst b/doc/source/developing_models.rst index edf9958e..bf8d8263 100644 --- a/doc/source/developing_models.rst +++ b/doc/source/developing_models.rst @@ -86,6 +86,30 @@ display the histogram for all temperatures, but only for those temperatures greater than a lower limit, which is contained in the ``hist_limit`` list. This should also be defined in ``__init__``. +If your model has special limits in the JSON model specification file which are +not included in this default set: + +.. code-block:: python + + { + "odb.caution.high", + "odb.caution.low", + "safety.caution.high", + "safety.caution.low", + "planning.warning.high", + "planning.warning.low" + } + +You must include them in a special dictionary ``limits_map`` which will be passed +to the ``ACISThermalCheck`` subclass. This dictionary maps the name of the limit +in the JSON file to something shorter (and perhaps more descriptive). All limits +can then be accessed using the ``self.limits`` dictionary, which for each element +has a dictionary which specifies the numerical ``"value"`` of the limit and the +``"color"`` which should be used on plots. Examples of how this is used are shown +below. In this case, the 1DPAMZT model has a limit at +12 :math:$^\circ$C which +is only applied when 0 FEPs are on. This is the ``"planning.caution.low"`` limit, +which is renamed to ``"zero_feps"`` in this case. + The example of this class definition for the 1DPAMZT model is shown here. Both limit objects that were created are passed to the ``__init__`` of the superclass. @@ -100,9 +124,13 @@ limit objects that were created are passed to the ``__init__`` of the superclass } # Specify the validation histogram limits hist_limit = [20.0] + # Add the "zero_feps" limit + limits_map = { + "planning.caution.low": "zero_feps" + } # Call the superclass' __init__ with the arguments super(DPACheck, self).__init__("1dpamzt", "dpa", valid_limits, - hist_limit) + hist_limit, limits_map=limits_map) Custom Violations Checking and Plotting +++++++++++++++++++++++++++++++++++++++ @@ -120,7 +148,7 @@ where we reference the example below for adding the "zero FEPs" limit to the 1DPAMZT model: * The limit value itself, in this case +12 :math:$^\circ$C, stored - in ``self.zero_feps_limit`` as shown below. + in ``self.limits["zero_feps"]["value"]`` as shown below. * The name of the limit, which in this case is ``"zero-feps"``. * Which type of temperature limit this is, (in this case) ``"min"`` or ``"max"``. @@ -154,13 +182,14 @@ we are done. """ # Only check this violation when all FEPs are off mask = self.predict_model.comp['fep_count'].dvals == 0 - zf_viols = self._make_prediction_viols(times, temp, load_start, - self.zero_feps_limit, - "zero-feps", "min", - mask=mask) - viols["zero_feps"] = {"name": f"Zero FEPs ({self.zero_feps_limit} C)", - "type": "Min", - "values": zf_viols} + zf_viols = self._make_prediction_viols( + times, temp, load_start, self.limits["zero_feps"]["value"], + "zero-feps", "min", mask=mask) + viols["zero_feps"] = { + "name": f"Zero FEPs ({self.limits['zero_feps']['value']} C)", + "type": "Min", + "values": zf_viols + } We also want to show this limit on the plot for the 1DPAMZT model. For this, we use the ``custom_prediction_plots`` method of the ``ACISThermalCheck`` @@ -205,9 +234,9 @@ shown here: and can be used to customize plots before they are written, e.g. add limit lines, etc. """ - plots[self.name]['ax'].axhline(self.zero_feps_limit, linestyle='--', - color='dodgerblue', label="Zero FEPs", - linewidth=2.0) + plots[self.name]['ax'].axhline(self.limits["zero_feps"]["value"], + linestyle='--', label="Zero FEPs", linewidth=2.0, + color=self.limits["zero_feps"]["color"], zorder=-8) Something similar can be done for the validation plots in ``custom_validation_plots``, except here the input ``plots`` structure is @@ -230,8 +259,9 @@ only need to worry about the first, as shown below. e.g. add limit lines, etc. """ plots["1dpamzt"]['lines']['ax'].axhline( - self.zero_feps_limit, linestyle='--', color='dodgerblue', zorder=-8, - linewidth=2, label="Zero FEPs") + self.limits["zero_feps"]["value"], linestyle='--', zorder=-8, + color=self.limits["zero_feps"]["color"], linewidth=2, + label="Zero FEPs") The ``_calc_model_supp`` Method +++++++++++++++++++++++++++++++ From 3e2ff583e062562f10bc1e771f8c5a8f1afc06a8 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Wed, 29 Dec 2021 23:24:41 -0600 Subject: [PATCH 12/21] This should have gotten changed --- acis_thermal_check/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acis_thermal_check/main.py b/acis_thermal_check/main.py index 503db2a9..e866b6d7 100644 --- a/acis_thermal_check/main.py +++ b/acis_thermal_check/main.py @@ -176,10 +176,10 @@ def run(self, args, override_limits=None): # run. THIS SHOULD ONLY BE USED FOR TESTING PURPOSES. if override_limits is not None: for k, v in override_limits.items(): - if hasattr(self, k): - limit = getattr(self, k) + if k in self.limits: + limit = self.limits[k]["value"] mylog.warning("Replacing %s %.2f with %.2f" % (k, limit, v)) - setattr(self, k, v) + self.limits[k]["value"] = v # Determine the start and stop times either from whatever was # stored in state_builder or punt by using NOW and None for From a6eb353bb83e1b00dd326641f8b47750d43fcb6f Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Wed, 29 Dec 2021 23:24:58 -0600 Subject: [PATCH 13/21] Fix names --- .../tests/acisfp/answers/DEC0919A_viol.json | 4 ++-- .../tests/acisfp/answers/JUN1421A_viol.json | 2 +- .../tests/acisfp/answers/SEP1321A_viol.json | 2 +- .../tests/bep_pcb/answers/JUL2919A_viol.json | 8 ++++---- acis_thermal_check/tests/dea/answers/DEC0919A_viol.json | 4 ++-- acis_thermal_check/tests/dpa/answers/JUL3018A_viol.json | 4 ++-- .../tests/fep1_actel/answers/JUL2919A_viol.json | 8 ++++---- .../tests/fep1_mong/answers/JUL2919A_viol.json | 8 ++++---- acis_thermal_check/tests/psmc/answers/FEB1020A_viol.json | 4 ++-- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/acis_thermal_check/tests/acisfp/answers/DEC0919A_viol.json b/acis_thermal_check/tests/acisfp/answers/DEC0919A_viol.json index 463f7015..76e7c812 100644 --- a/acis_thermal_check/tests/acisfp/answers/DEC0919A_viol.json +++ b/acis_thermal_check/tests/acisfp/answers/DEC0919A_viol.json @@ -1,8 +1,8 @@ { "run_start": "2019:338:14:25:26.816", "limits": { - "acis_i_limit": -115.0, - "acis_s_limit": -113.0 + "acis_i": -115.0, + "acis_s": -113.0 }, "datestarts": [ "2019:345:04:41:02.816", diff --git a/acis_thermal_check/tests/acisfp/answers/JUN1421A_viol.json b/acis_thermal_check/tests/acisfp/answers/JUN1421A_viol.json index cb7a682d..9bc0e9b2 100644 --- a/acis_thermal_check/tests/acisfp/answers/JUN1421A_viol.json +++ b/acis_thermal_check/tests/acisfp/answers/JUN1421A_viol.json @@ -1,7 +1,7 @@ { "run_start": "2021:162:23:36:54.816", "limits": { - "cold_ecs_limit": -121.0 + "cold_ecs": -121.0 }, "datestarts": [ "2021:166:08:20:22.816" diff --git a/acis_thermal_check/tests/acisfp/answers/SEP1321A_viol.json b/acis_thermal_check/tests/acisfp/answers/SEP1321A_viol.json index 5062cda8..ae4a61a8 100644 --- a/acis_thermal_check/tests/acisfp/answers/SEP1321A_viol.json +++ b/acis_thermal_check/tests/acisfp/answers/SEP1321A_viol.json @@ -1,7 +1,7 @@ { "run_start": "2021:251:23:10:06.816", "limits": { - "acis_hot_limit": -110.0 + "acis_hot": -110.0 }, "datestarts": [ "2021:256:16:41:34.816", diff --git a/acis_thermal_check/tests/bep_pcb/answers/JUL2919A_viol.json b/acis_thermal_check/tests/bep_pcb/answers/JUL2919A_viol.json index 42c4adf5..c314dc99 100644 --- a/acis_thermal_check/tests/bep_pcb/answers/JUL2919A_viol.json +++ b/acis_thermal_check/tests/bep_pcb/answers/JUL2919A_viol.json @@ -1,10 +1,10 @@ { "run_start": "2019:203:10:57:34.816", "limits": { - "yellow_hi_limit": 33.0, - "plan_hi_limit": 31.0, - "yellow_lo_limit": 18.0, - "plan_lo_limit": 16.0 + "yellow_hi": 33.0, + "planning_hi": 31.0, + "yellow_lo": 18.0, + "planning_lo": 16.0 }, "datestarts": [ "2019:210:01:16:43.016", diff --git a/acis_thermal_check/tests/dea/answers/DEC0919A_viol.json b/acis_thermal_check/tests/dea/answers/DEC0919A_viol.json index d6f16ac2..46c7e6c8 100644 --- a/acis_thermal_check/tests/dea/answers/DEC0919A_viol.json +++ b/acis_thermal_check/tests/dea/answers/DEC0919A_viol.json @@ -16,8 +16,8 @@ ], "run_start": "2019:338:14:25:26.816", "limits": { - "yellow_hi_limit": 37.2, - "plan_hi_limit": 35.2 + "yellow_hi": 37.2, + "planning_hi": 35.2 }, "duration": [ "0.46", diff --git a/acis_thermal_check/tests/dpa/answers/JUL3018A_viol.json b/acis_thermal_check/tests/dpa/answers/JUL3018A_viol.json index 8a3fe556..6c66021c 100644 --- a/acis_thermal_check/tests/dpa/answers/JUL3018A_viol.json +++ b/acis_thermal_check/tests/dpa/answers/JUL3018A_viol.json @@ -16,8 +16,8 @@ ], "run_start": "2018:205:00:42:38.816", "limits": { - "yellow_hi_limit": 37.2, - "plan_hi_limit": 35.2 + "yellow_hi": 37.2, + "planning_hi": 35.2 }, "duration": [ "3.94", diff --git a/acis_thermal_check/tests/fep1_actel/answers/JUL2919A_viol.json b/acis_thermal_check/tests/fep1_actel/answers/JUL2919A_viol.json index a9cda885..91330c4c 100644 --- a/acis_thermal_check/tests/fep1_actel/answers/JUL2919A_viol.json +++ b/acis_thermal_check/tests/fep1_actel/answers/JUL2919A_viol.json @@ -1,10 +1,10 @@ { "run_start": "2019:203:10:57:34.816", "limits": { - "yellow_hi_limit": 41.0, - "plan_hi_limit": 39.0, - "yellow_lo_limit": 18.0, - "plan_lo_limit": 16.0 + "yellow_hi": 41.0, + "planning_hi": 39.0, + "yellow_lo": 18.0, + "planning_lo": 16.0 }, "datestarts": [ "2019:211:07:39:02.816", diff --git a/acis_thermal_check/tests/fep1_mong/answers/JUL2919A_viol.json b/acis_thermal_check/tests/fep1_mong/answers/JUL2919A_viol.json index e4098aca..b0a03e95 100644 --- a/acis_thermal_check/tests/fep1_mong/answers/JUL2919A_viol.json +++ b/acis_thermal_check/tests/fep1_mong/answers/JUL2919A_viol.json @@ -1,10 +1,10 @@ { "run_start": "2019:203:10:57:34.816", "limits": { - "yellow_hi_limit": 41.0, - "plan_hi_limit": 39.0, - "yellow_lo_limit": 18.0, - "plan_lo_limit": 16.0 + "yellow_hi": 41.0, + "planning_hi": 39.0, + "yellow_lo": 18.0, + "planning_lo": 16.0 }, "datestarts": [ "2019:210:08:13:01.216", diff --git a/acis_thermal_check/tests/psmc/answers/FEB1020A_viol.json b/acis_thermal_check/tests/psmc/answers/FEB1020A_viol.json index 644f2a7a..4540dad1 100644 --- a/acis_thermal_check/tests/psmc/answers/FEB1020A_viol.json +++ b/acis_thermal_check/tests/psmc/answers/FEB1020A_viol.json @@ -13,8 +13,8 @@ ], "run_start": "2020:041:00:20:02.553", "limits": { - "yellow_hi_limit": 51.5, - "plan_hi_limit": 47.0 + "yellow_hi": 51.5, + "planning_hi": 47.0 }, "duration": [ "10.17", From 4111e994e91afc42dc37f964905830deae813509 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Wed, 29 Dec 2021 23:27:39 -0600 Subject: [PATCH 14/21] Doc fix --- doc/source/developing_models.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/developing_models.rst b/doc/source/developing_models.rst index bf8d8263..54a2904d 100644 --- a/doc/source/developing_models.rst +++ b/doc/source/developing_models.rst @@ -680,8 +680,8 @@ After the test is run with the ``--answer_store`` flag set ], "run_start": "2018:205:00:42:38.816", "limits": { - "yellow_hi_limit": 37.2, - "plan_hi_limit": 35.2 + "yellow_hi": 37.2, + "planning_hi": 35.2 }, "duration": [ "3.94", From 6c8ca1496741bf894adfe6d615d8621bc3bf8924 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Thu, 30 Dec 2021 14:55:20 -0600 Subject: [PATCH 15/21] Turn this into a class to make it simpler --- acis_thermal_check/apps/acisfp_check.py | 24 ++++----- acis_thermal_check/apps/dpa_check.py | 12 ++--- acis_thermal_check/main.py | 70 ++++++++++++------------- acis_thermal_check/utils.py | 11 ++-- doc/source/developing_models.rst | 58 ++++++++++---------- 5 files changed, 91 insertions(+), 84 deletions(-) diff --git a/acis_thermal_check/apps/acisfp_check.py b/acis_thermal_check/apps/acisfp_check.py index 43eecf6f..094d69c0 100755 --- a/acis_thermal_check/apps/acisfp_check.py +++ b/acis_thermal_check/apps/acisfp_check.py @@ -173,24 +173,24 @@ def make_prediction_plots(self, outdir, states, temps, load_start): width=w1, load_start=load_start) plots[name]['ax'].set_title(self.msid.upper(), loc='left', pad=10) # Draw a horizontal line indicating cold ECS cutoff - plots[name]['ax'].axhline(self.limits["cold_ecs"]["value"], + plots[name]['ax'].axhline(self.limits["cold_ecs"].value, linestyle='--', linewidth=2.0, - color=self.limits["cold_ecs"]["color"], + color=self.limits["cold_ecs"].color, label='Cold ECS') # Draw a horizontal line showing the ACIS-I cutoff - plots[name]['ax'].axhline(self.limits["acis_i"]["value"], + plots[name]['ax'].axhline(self.limits["acis_i"].value, linestyle='--', linewidth=2.0, - color=self.limits["acis_i"]["color"], + color=self.limits["acis_i"].color, label="ACIS-I") # Draw a horizontal line showing the ACIS-S cutoff - plots[name]['ax'].axhline(self.limits["acis_s"]["value"], + plots[name]['ax'].axhline(self.limits["acis_s"].value, linestyle='--', linewidth=2.0, - color=self.limits["acis_s"]["color"], + color=self.limits["acis_s"].color, label="ACIS-S") # Draw a horizontal line showing the hot ACIS-S cutoff - plots[name]['ax'].axhline(self.limits["acis_hot"]["value"], + plots[name]['ax'].axhline(self.limits["acis_hot"].value, linestyle='--', linewidth=2.0, - color=self.limits["acis_hot"]["color"], + color=self.limits["acis_hot"].color, label="Hot ACIS-S") # Get the width of this plot to make the widths of all the # prediction plots the same @@ -286,10 +286,10 @@ def make_prediction_viols(self, temps, load_start): # science run. These are load killers # ------------------------------------------------------------ # - acis_i_limit = self.limits["acis_i"]["value"] - acis_s_limit = self.limits["acis_s"]["value"] - acis_hot_limit = self.limits["acis_hot"]["value"] - cold_ecs_limit = self.limits["cold_ecs"]["value"] + acis_i_limit = self.limits["acis_i"].value + acis_s_limit = self.limits["acis_s"].value + acis_hot_limit = self.limits["acis_hot"].value + cold_ecs_limit = self.limits["cold_ecs"].value mylog.info(f'\n\nACIS-I Science ({acis_i_limit} C) violations') diff --git a/acis_thermal_check/apps/dpa_check.py b/acis_thermal_check/apps/dpa_check.py index 71715345..db7b4dc9 100755 --- a/acis_thermal_check/apps/dpa_check.py +++ b/acis_thermal_check/apps/dpa_check.py @@ -56,10 +56,10 @@ def custom_prediction_viols(self, times, temp, viols, load_start): # Only check this violation when all FEPs are off mask = self.predict_model.comp['fep_count'].dvals == 0 zf_viols = self._make_prediction_viols( - times, temp, load_start, self.limits["zero_feps"]["value"], + times, temp, load_start, self.limits["zero_feps"].value, "zero-feps", "min", mask=mask) viols["zero_feps"] = { - "name": f"Zero FEPs ({self.limits['zero_feps']['value']} C)", + "name": f"Zero FEPs ({self.limits['zero_feps'].value} C)", "type": "Min", "values": zf_viols } @@ -75,9 +75,9 @@ def custom_prediction_plots(self, plots): and can be used to customize plots before they are written, e.g. add limit lines, etc. """ - plots[self.name]['ax'].axhline(self.limits["zero_feps"]["value"], + plots[self.name]['ax'].axhline(self.limits["zero_feps"].value, linestyle='--', label="Zero FEPs", linewidth=2.0, - color=self.limits["zero_feps"]["color"], zorder=-8) + color=self.limits["zero_feps"].color, zorder=-8) def custom_validation_plots(self, plots): """ @@ -91,8 +91,8 @@ def custom_validation_plots(self, plots): e.g. add limit lines, etc. """ plots["1dpamzt"]['lines']['ax'].axhline( - self.limits["zero_feps"]["value"], linestyle='--', zorder=-8, - color=self.limits["zero_feps"]["color"], linewidth=2, + self.limits["zero_feps"].value, linestyle='--', zorder=-8, + color=self.limits["zero_feps"].color, linewidth=2, label="Zero FEPs") def _calc_model_supp(self, model, state_times, states, ephem, state0): diff --git a/acis_thermal_check/main.py b/acis_thermal_check/main.py index e866b6d7..3c9333cb 100644 --- a/acis_thermal_check/main.py +++ b/acis_thermal_check/main.py @@ -177,9 +177,9 @@ def run(self, args, override_limits=None): if override_limits is not None: for k, v in override_limits.items(): if k in self.limits: - limit = self.limits[k]["value"] + limit = self.limits[k].value mylog.warning("Replacing %s %.2f with %.2f" % (k, limit, v)) - self.limits[k]["value"] = v + self.limits[k].value = v # Determine the start and stop times either from whatever was # stored in state_builder or punt by using NOW and None for @@ -523,20 +523,20 @@ def make_prediction_viols(self, temps, load_start): times = self.predict_model.times hi_viols = self._make_prediction_viols( - times, temp, load_start, self.limits["planning_hi"]["value"], + times, temp, load_start, self.limits["planning_hi"].value, "planning", "max") viols = {"hi": - {"name": f"Hot ({self.limits['planning_hi']['value']} C)", + {"name": f"Hot ({self.limits['planning_hi'].value} C)", "type": "Max", "values": hi_viols} } if self.flag_cold_viols: lo_viols = self._make_prediction_viols( - times, temp, load_start, self.limits["planning_lo"]["value"], + times, temp, load_start, self.limits["planning_lo"].value, "planning", "min") viols["lo"] = { - "name": f"Cold ({self.limits['planning_hi']['value']} C)", + "name": f"Cold ({self.limits['planning_hi'].value} C)", "type": "Min", "values": lo_viols } @@ -761,25 +761,25 @@ def make_prediction_plots(self, outdir, states, temps, load_start): width=w1, load_start=load_start) # Add horizontal lines for the planning and caution limits ymin, ymax = plots[self.name]['ax'].get_ylim() - ymax = max(self.limits["yellow_hi"]["value"]+1, ymax) + ymax = max(self.limits["yellow_hi"].value+1, ymax) plots[self.name]['ax'].set_title(self.msid.upper(), loc='left', pad=10) - plots[self.name]['ax'].axhline(self.limits["yellow_hi"]["value"], + plots[self.name]['ax'].axhline(self.limits["yellow_hi"].value, linestyle='-', linewidth=2.0, - color=self.limits["yellow_hi"]["color"], + color=self.limits["yellow_hi"].color, label='Yellow') - plots[self.name]['ax'].axhline(self.limits["planning_hi"]["value"], + plots[self.name]['ax'].axhline(self.limits["planning_hi"].value, linestyle='-', linewidth=2.0, - color=self.limits["planning_hi"]["color"], + color=self.limits["planning_hi"].color, label='Planning') if self.flag_cold_viols: - ymin = min(self.limits["yellow_lo"]["value"]-1, ymin) - plots[self.name]['ax'].axhline(self.limits["yellow_hi"]["value"], + ymin = min(self.limits["yellow_lo"].value-1, ymin) + plots[self.name]['ax'].axhline(self.limits["yellow_hi"].value, linestyle='-', linewidth=2.0, - color=self.limits["yellow_hi"]["color"], + color=self.limits["yellow_hi"].color, label='Yellow', zorder=-8) - plots[self.name]['ax'].axhline(self.limits["planning_hi"]["value"], + plots[self.name]['ax'].axhline(self.limits["planning_hi"].value, linestyle='-', linewidth=2.0, - color=self.limits["planning_hi"]["color"], + color=self.limits["planning_hi"].color, label='Planning', zorder=-8) plots[self.name]['ax'].set_ylim(ymin, ymax) plots[self.name]['filename'] = self.msid.lower()+'.png' @@ -969,39 +969,39 @@ def make_validation_plots(self, tlm, model_spec, outdir): if self.msid == msid: ymin, ymax = ax.get_ylim() if msid == "fptemp": - ax.axhline(self.limits["cold_ecs"]["value"], + ax.axhline(self.limits["cold_ecs"].value, linestyle='--', label='Cold ECS', - color=self.limits["cold_ecs"]["color"], + color=self.limits["cold_ecs"].color, zorder=-8, linewidth=2) - ax.axhline(self.limits["acis_i"]["value"], + ax.axhline(self.limits["acis_i"].value, linestyle='--', label='ACIS-I', - color=self.limits["acis_i"]["color"], + color=self.limits["acis_i"].color, zorder=-8, linewidth=2) - ax.axhline(self.limits["acis_s"]["value"], + ax.axhline(self.limits["acis_s"].value, linestyle='--', label='ACIS-S', - color=self.limits["acis_s"]["color"], + color=self.limits["acis_s"].color, zorder=-8, linewidth=2) - ax.axhline(self.limits["acis_hot"]["value"], + ax.axhline(self.limits["acis_hot"].value, linestyle='--', label='Hot ACIS', - color=self.limits["acis_hot"]["color"], + color=self.limits["acis_hot"].color, zorder=-8, linewidth=2) - ymax = max(self.limits["acis_hot"]["value"]+1, ymax) + ymax = max(self.limits["acis_hot"].value+1, ymax) else: - ax.axhline(self.limits["yellow_hi"]["value"], + ax.axhline(self.limits["yellow_hi"].value, linestyle='-', linewidth=2, zorder=-8, - color=self.limits["yellow_hi"]["color"]) - ax.axhline(self.limits["planning_hi"]["value"], + color=self.limits["yellow_hi"].color) + ax.axhline(self.limits["planning_hi"].value, linestyle='-', linewidth=2, zorder=-8, - color=self.limits["planning_hi"]["color"]) - ymax = max(self.limits["yellow_hi"]["value"]+1, ymax) + color=self.limits["planning_hi"].color) + ymax = max(self.limits["yellow_hi"].value+1, ymax) if self.flag_cold_viols: - ax.axhline(self.limits["yellow_lo"]["value"], + ax.axhline(self.limits["yellow_lo"].value, linestyle='-', linewidth=2, zorder=-8, - color=self.limits["yellow_lo"]["color"]) - ax.axhline(self.limits["planning_lo"]["value"], + color=self.limits["yellow_lo"].color) + ax.axhline(self.limits["planning_lo"].value, linestyle='-', linewidth=2, zorder=-8, - color=self.limits["planning_lo"]["color"]) - ymin = min(self.limits["yellow_lo"]["value"]-1, ymin) + color=self.limits["planning_lo"].color) + ymin = min(self.limits["yellow_lo"].value-1, ymin) ax.set_ylim(ymin, ymax) ax.set_xlim(xmin, xmax) diff --git a/acis_thermal_check/utils.py b/acis_thermal_check/utils.py index f30a9f92..e6a41b6a 100644 --- a/acis_thermal_check/utils.py +++ b/acis_thermal_check/utils.py @@ -477,6 +477,12 @@ def paint_perigee(perigee_passages, states, plots): color='black', linewidth=2.0) +class ACISLimit: + def __init__(self, value, color): + self.value = value + self.color = color + + def get_acis_limits(msid, model_spec, limits_map=None): """ Given a MSID and a model specification (JSON or dict), @@ -514,8 +520,5 @@ def get_acis_limits(msid, model_spec, limits_map=None): if k == "unit": continue key = limits_map.get(k, k) - limits[key] = { - "value": v, - "color": get_limit_color(k) - } + limits[key] = ACISLimit(v, get_limit_color(k)) return limits diff --git a/doc/source/developing_models.rst b/doc/source/developing_models.rst index 54a2904d..3924ee21 100644 --- a/doc/source/developing_models.rst +++ b/doc/source/developing_models.rst @@ -104,8 +104,8 @@ You must include them in a special dictionary ``limits_map`` which will be passe to the ``ACISThermalCheck`` subclass. This dictionary maps the name of the limit in the JSON file to something shorter (and perhaps more descriptive). All limits can then be accessed using the ``self.limits`` dictionary, which for each element -has a dictionary which specifies the numerical ``"value"`` of the limit and the -``"color"`` which should be used on plots. Examples of how this is used are shown +has a dictionary which specifies the numerical ``value`` of the limit and the +``color`` which should be used on plots. Examples of how this is used are shown below. In this case, the 1DPAMZT model has a limit at +12 :math:$^\circ$C which is only applied when 0 FEPs are on. This is the ``"planning.caution.low"`` limit, which is renamed to ``"zero_feps"`` in this case. @@ -148,7 +148,7 @@ where we reference the example below for adding the "zero FEPs" limit to the 1DPAMZT model: * The limit value itself, in this case +12 :math:$^\circ$C, stored - in ``self.limits["zero_feps"]["value"]`` as shown below. + in ``self.limits["zero_feps"].value`` as shown below. * The name of the limit, which in this case is ``"zero-feps"``. * Which type of temperature limit this is, (in this case) ``"min"`` or ``"max"``. @@ -183,10 +183,10 @@ we are done. # Only check this violation when all FEPs are off mask = self.predict_model.comp['fep_count'].dvals == 0 zf_viols = self._make_prediction_viols( - times, temp, load_start, self.limits["zero_feps"]["value"], + times, temp, load_start, self.limits["zero_feps"].value, "zero-feps", "min", mask=mask) viols["zero_feps"] = { - "name": f"Zero FEPs ({self.limits['zero_feps']['value']} C)", + "name": f"Zero FEPs ({self.limits['zero_feps'].value} C)", "type": "Min", "values": zf_viols } @@ -234,9 +234,9 @@ shown here: and can be used to customize plots before they are written, e.g. add limit lines, etc. """ - plots[self.name]['ax'].axhline(self.limits["zero_feps"]["value"], + plots[self.name]['ax'].axhline(self.limits["zero_feps"].value, linestyle='--', label="Zero FEPs", linewidth=2.0, - color=self.limits["zero_feps"]["color"], zorder=-8) + color=self.limits["zero_feps"].color, zorder=-8) Something similar can be done for the validation plots in ``custom_validation_plots``, except here the input ``plots`` structure is @@ -259,8 +259,8 @@ only need to worry about the first, as shown below. e.g. add limit lines, etc. """ plots["1dpamzt"]['lines']['ax'].axhline( - self.limits["zero_feps"]["value"], linestyle='--', zorder=-8, - color=self.limits["zero_feps"]["color"], linewidth=2, + self.limits["zero_feps"].value, linestyle='--', zorder=-8, + color=self.limits["zero_feps"].color, linewidth=2, label="Zero FEPs") The ``_calc_model_supp`` Method @@ -361,7 +361,7 @@ of the 1DPAMZT model is shown below: from acis_thermal_check import \ ACISThermalCheck, \ get_options - + class DPACheck(ACISThermalCheck): def __init__(self): @@ -370,9 +370,12 @@ of the 1DPAMZT model is shown below: 'TSCPOS': [(1, 2.5), (99, 2.5)] } hist_limit = [20.0] + limits_map = { + "planning.caution.low": "zero_feps" + } super(DPACheck, self).__init__("1dpamzt", "dpa", valid_limits, - hist_limit) - + hist_limit, limits_map=limits_map) + def custom_prediction_viols(self, times, temp, viols, load_start): """ Custom handling of limit violations. This is for checking the @@ -393,13 +396,14 @@ of the 1DPAMZT model is shown below: """ # Only check this violation when all FEPs are off mask = self.predict_model.comp['fep_count'].dvals == 0 - zf_viols = self._make_prediction_viols(times, temp, load_start, - self.zero_feps_limit, - "zero-feps", "min", - mask=mask) - viols["zero_feps"] = {"name": f"Zero FEPs ({self.zero_feps_limit} C)", - "type": "Min", - "values": zf_viols} + zf_viols = self._make_prediction_viols( + times, temp, load_start, self.limits["zero_feps"].value, + "zero-feps", "min", mask=mask) + viols["zero_feps"] = { + "name": f"Zero FEPs ({self.limits['zero_feps'].value} C)", + "type": "Min", + "values": zf_viols + } def custom_prediction_plots(self, plots): """ @@ -412,10 +416,10 @@ of the 1DPAMZT model is shown below: and can be used to customize plots before they are written, e.g. add limit lines, etc. """ - plots[self.name]['ax'].axhline(self.zero_feps_limit, linestyle='--', - color='dodgerblue', label="Zero FEPs", - linewidth=2.0) - + plots[self.name]['ax'].axhline(self.limits["zero_feps"].value, + linestyle='--', label="Zero FEPs", linewidth=2.0, + color=self.limits["zero_feps"].color, zorder=-8) + def custom_validation_plots(self, plots): """ Customization of validation plots. @@ -428,8 +432,9 @@ of the 1DPAMZT model is shown below: e.g. add limit lines, etc. """ plots["1dpamzt"]['lines']['ax'].axhline( - self.zero_feps_limit, linestyle='--', color='dodgerblue', zorder=-8, - linewidth=2, label="Zero FEPs") + self.limits["zero_feps"].value, linestyle='--', zorder=-8, + color=self.limits["zero_feps"].color, linewidth=2, + label="Zero FEPs") def _calc_model_supp(self, model, state_times, states, ephem, state0): """ @@ -452,7 +457,7 @@ of the 1DPAMZT model is shown below: def main(): - args = get_options("dpa") + args = get_options() dpa_check = DPACheck() try: dpa_check.run(args) @@ -467,7 +472,6 @@ of the 1DPAMZT model is shown below: if __name__ == '__main__': main() - Setting Up An Entry Point ========================= From 1000bc5d377b383de5322eccbb55297a5aa2027b Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Fri, 31 Dec 2021 15:29:17 -0600 Subject: [PATCH 16/21] Refactor of plotting interface for prediction plots to reduce clutter --- acis_thermal_check/apps/acisfp_check.py | 137 ++++++------ acis_thermal_check/apps/dpa_check.py | 2 +- acis_thermal_check/main.py | 91 ++++---- acis_thermal_check/utils.py | 263 ++++++++---------------- 4 files changed, 200 insertions(+), 293 deletions(-) diff --git a/acis_thermal_check/apps/acisfp_check.py b/acis_thermal_check/apps/acisfp_check.py index 094d69c0..f647319c 100755 --- a/acis_thermal_check/apps/acisfp_check.py +++ b/acis_thermal_check/apps/acisfp_check.py @@ -23,7 +23,7 @@ get_options, \ mylog from acis_thermal_check.utils import \ - plot_two, paint_perigee + paint_perigee, PredictPlot import os import sys from astropy.table import Table @@ -145,7 +145,7 @@ def make_prediction_plots(self, outdir, states, temps, load_start): self.acis_and_ecs_obs = hrc_science_obs_filter(observation_intervals) # create an empty dictionary called plots to contain the returned - # figures, axes 1 and axes 2 of the plot_two call + # figures, axes 1 and axes 2 of the PredictPlot class plots = {} # Start time of loads being reviewed expressed in units for plotdate() @@ -163,39 +163,39 @@ def make_prediction_plots(self, outdir, states, temps, load_start): fontsize = [12, 9, 9] for i in range(3): name = f"{self.name}_{i+1}" - plots[name] = plot_two(fig_id=i+1, x=times, y=temps[self.name], - x2=self.predict_model.times, - y2=self.predict_model.comp["pitch"].mvals, - xlabel='Date', ylabel='Temperature ($^\circ$C)', - ylabel2='Pitch (deg)', xmin=plot_start, - ylim=ylim[i], ylim2=(40, 180), - figsize=(12, 7.142857142857142), - width=w1, load_start=load_start) - plots[name]['ax'].set_title(self.msid.upper(), loc='left', pad=10) + plots[name] = PredictPlot(fig_id=i+1, x=times, y=temps[self.name], + x2=self.predict_model.times, + y2=self.predict_model.comp["pitch"].mvals, + xlabel='Date', ylabel='Temperature ($^\circ$C)', + ylabel2='Pitch (deg)', xmin=plot_start, + ylim=ylim[i], ylim2=(40, 180), + figsize=(12, 7.142857142857142), + width=w1, load_start=load_start) + plots[name].ax.set_title(self.msid.upper(), loc='left', pad=10) # Draw a horizontal line indicating cold ECS cutoff - plots[name]['ax'].axhline(self.limits["cold_ecs"].value, - linestyle='--', linewidth=2.0, - color=self.limits["cold_ecs"].color, - label='Cold ECS') + plots[name].ax.axhline(self.limits["cold_ecs"].value, + linestyle='--', linewidth=2.0, + color=self.limits["cold_ecs"].color, + label='Cold ECS') # Draw a horizontal line showing the ACIS-I cutoff - plots[name]['ax'].axhline(self.limits["acis_i"].value, - linestyle='--', linewidth=2.0, - color=self.limits["acis_i"].color, - label="ACIS-I") + plots[name].ax.axhline(self.limits["acis_i"].value, + linestyle='--', linewidth=2.0, + color=self.limits["acis_i"].color, + label="ACIS-I") # Draw a horizontal line showing the ACIS-S cutoff - plots[name]['ax'].axhline(self.limits["acis_s"].value, - linestyle='--', linewidth=2.0, - color=self.limits["acis_s"].color, - label="ACIS-S") + plots[name].ax.axhline(self.limits["acis_s"].value, + linestyle='--', linewidth=2.0, + color=self.limits["acis_s"].color, + label="ACIS-S") # Draw a horizontal line showing the hot ACIS-S cutoff - plots[name]['ax'].axhline(self.limits["acis_hot"].value, - linestyle='--', linewidth=2.0, - color=self.limits["acis_hot"].color, - label="Hot ACIS-S") + plots[name].ax.axhline(self.limits["acis_hot"].value, + linestyle='--', linewidth=2.0, + color=self.limits["acis_hot"].color, + label="Hot ACIS-S") # Get the width of this plot to make the widths of all the # prediction plots the same if i == 0: - w1, _ = plots[name]['fig'].get_size_inches() + w1, _ = plots[name].fig.get_size_inches() # Now draw horizontal lines on the plot running from start to stop # and label them with the Obsid @@ -204,28 +204,27 @@ def make_prediction_plots(self, outdir, states, temps, load_start): textypos[i], fontsize[i], plot_start) # These next lines are dummies so we can get the obsids in the legend - plots[name]['ax'].errorbar([0.0, 0.0], [1.0, 1.0], xerr=1.0, - lw=2, xlolims=True, color='red', - capsize=4, capthick=2, label='ACIS-I') - plots[name]['ax'].errorbar([0.0, 0.0], [1.0, 1.0], xerr=1.0, - lw=2, xlolims=True, color='green', - capsize=4, capthick=2, label='ACIS-S') - plots[name]['ax'].errorbar([0.0, 0.0], [1.0, 1.0], xerr=1.0, - lw=2, xlolims=True, color='blue', - capsize=4, capthick=2, label='ECS') + plots[name].ax.errorbar([0.0, 0.0], [1.0, 1.0], xerr=1.0, + lw=2, xlolims=True, color='red', + capsize=4, capthick=2, label='ACIS-I') + plots[name].ax.errorbar([0.0, 0.0], [1.0, 1.0], xerr=1.0, + lw=2, xlolims=True, color='green', + capsize=4, capthick=2, label='ACIS-S') + plots[name].ax.errorbar([0.0, 0.0], [1.0, 1.0], xerr=1.0, + lw=2, xlolims=True, color='blue', + capsize=4, capthick=2, label='ECS') # Make the legend on the temperature plot - plots[name]['ax'].legend(bbox_to_anchor=(0.15, 0.99), - loc='lower left', - ncol=4, fontsize=14) + plots[name].ax.legend(bbox_to_anchor=(0.15, 0.99), + loc='lower left', ncol=4, fontsize=14) + # Build the file name filename = f'{self.msid.lower()}' \ f'M{-int(ylim[i][0])}toM{-int(ylim[i][1])}.png' - plots[name]['filename'] = filename + plots[name].filename = filename self._make_state_plots(plots, 3, w1, plot_start, - states, load_start, - figsize=(12, 6)) + states, load_start) # Now plot any perigee passages that occur between xmin and xmax # for eachpassage in perigee_passages: @@ -237,9 +236,9 @@ def make_prediction_plots(self, outdir, states, temps, load_start): # customizations have been made for key in plots: if key != self.msid: - outfile = os.path.join(outdir, plots[key]['filename']) + outfile = os.path.join(outdir, plots[key].filename) mylog.info('Writing plot file %s' % outfile) - plots[key]['fig'].savefig(outfile) + plots[key].fig.savefig(outfile) return plots @@ -454,24 +453,24 @@ def draw_obsids(obs_list, plots, msid, ypos, endcapstart, endcapstop, if in_fp.startswith("ACIS-") or obsid > 60000: # For each ACIS Obsid, draw a horizontal line to show # its start and stop - plots[msid]['ax'].hlines(ypos, - obs_start, - obs_stop, - linestyle='-', - color=color, - linewidth=2.0) + plots[msid].ax.hlines(ypos, + obs_start, + obs_stop, + linestyle='-', + color=color, + linewidth=2.0) # Plot vertical end caps for each obsid to visually show start/stop - plots[msid]['ax'].vlines(obs_start, - endcapstart, - endcapstop, - color=color, - linewidth=2.0) - plots[msid]['ax'].vlines(obs_stop, - endcapstart, - endcapstop, - color=color, - linewidth=2.0) + plots[msid].ax.vlines(obs_start, + endcapstart, + endcapstop, + color=color, + linewidth=2.0) + plots[msid].ax.vlines(obs_stop, + endcapstart, + endcapstop, + color=color, + linewidth=2.0) # Now print the obsid in the middle of the time span, # above the line, and rotate 90 degrees. @@ -479,14 +478,14 @@ def draw_obsids(obs_list, plots, msid, ypos, endcapstart, endcapstop, obs_time = obs_start + (obs_stop - obs_start)/2 if obs_time > plot_start: # Now plot the obsid. - plots[msid]['ax'].text(obs_time, - textypos, - obsid_txt, - color=color, - va='bottom', - ma='left', - rotation=90, - fontsize=fontsize) + plots[msid].ax.text(obs_time, + textypos, + obsid_txt, + color=color, + va='bottom', + ma='left', + rotation=90, + fontsize=fontsize) def main(): diff --git a/acis_thermal_check/apps/dpa_check.py b/acis_thermal_check/apps/dpa_check.py index db7b4dc9..eb10f74b 100755 --- a/acis_thermal_check/apps/dpa_check.py +++ b/acis_thermal_check/apps/dpa_check.py @@ -75,7 +75,7 @@ def custom_prediction_plots(self, plots): and can be used to customize plots before they are written, e.g. add limit lines, etc. """ - plots[self.name]['ax'].axhline(self.limits["zero_feps"].value, + plots[self.name].ax.axhline(self.limits["zero_feps"].value, linestyle='--', label="Zero FEPs", linewidth=2.0, color=self.limits["zero_feps"].color, zorder=-8) diff --git a/acis_thermal_check/main.py b/acis_thermal_check/main.py index 3c9333cb..36bafd74 100644 --- a/acis_thermal_check/main.py +++ b/acis_thermal_check/main.py @@ -22,8 +22,8 @@ from astropy.io import ascii version = acis_thermal_check.__version__ from acis_thermal_check.utils import \ - config_logging, TASK_DATA, plot_two, \ - mylog, plot_one, make_state_builder, \ + config_logging, TASK_DATA, PredictPlot, \ + mylog, make_state_builder, \ calc_pitch_roll, thermal_blue, thermal_red, \ paint_perigee, get_acis_limits from kadi import events @@ -339,7 +339,8 @@ def make_week_predict(self, tstart, tstop, tlm, T_init, model_spec, plt.rc("grid", linewidth=1.5) temps = {self.name: model.comp[self.msid].mvals} - # make_prediction_plots runs the validation of the model against previous telemetry + # make_prediction_plots runs the validation of the model + # against previous telemetry plots = self.make_prediction_plots(outdir, states, temps, tstart) # make_prediction_viols determines the violations and prints them out @@ -584,8 +585,8 @@ def write_states(self, outdir, states): states_table['pitch'].format = '%.2f' states_table['tstart'].format = '%.2f' states_table['tstop'].format = '%.2f' - states_table.write(outfile, format='ascii', delimiter='\t', overwrite=True, - fast_writer=False) + states_table.write(outfile, format='ascii', delimiter='\t', + overwrite=True, fast_writer=False) def write_temps(self, outdir, times, temps): """ @@ -666,9 +667,9 @@ def _gather_perigee(self, plot_start, load_start): crm_file.close() def _make_state_plots(self, plots, num_figs, w1, plot_start, - states, load_start, figsize=(12, 6)): + states, load_start): # Make a plot of ACIS CCDs and SIM-Z position - plots['pow_sim'] = plot_two( + plots['pow_sim'] = PredictPlot( fig_id=num_figs+1, title='ACIS CCDs/FEPs and SIM-Z position', xlabel='Date', @@ -682,16 +683,16 @@ def _make_state_plots(self, plots, num_figs, w1, plot_start, y2=pointpair(states['simpos']), ylabel2='SIM-Z (steps)', ylim2=(-105000, 105000), - figsize=figsize, width=w1, load_start=load_start) - plots['pow_sim']['ax'].lines[0].set_label('CCDs') - plots['pow_sim']['ax'].lines[1].set_label('FEPs') - plots['pow_sim']['ax'].legend(fancybox=True, framealpha=0.5, loc=2) - plots['pow_sim']['filename'] = 'pow_sim.png' + width=w1, load_start=load_start) + plots['pow_sim'].ax.lines[0].set_label('CCDs') + plots['pow_sim'].ax.lines[1].set_label('FEPs') + plots['pow_sim'].ax.legend(fancybox=True, framealpha=0.5, loc=2) + plots['pow_sim'].filename = 'pow_sim.png' if self.msid == "fptemp": plt_name = "roll_taco" # Make a plot of off-nominal roll and earth solid angle - plots['roll_taco'] = plot_two( + plots['roll_taco'] = PredictPlot( fig_id=num_figs + 2, title='Off-Nominal Roll and Earth Solid Angle in Rad FOV', xlabel='Date', @@ -704,12 +705,12 @@ def _make_state_plots(self, plots, num_figs, w1, plot_start, y2=self.predict_model.comp['earthheat__fptemp'].dvals, ylabel2='Earth Solid Angle (sr)', ylim2=(1.0e-3, 1.0), - figsize=figsize, width=w1, load_start=load_start) - plots['roll_taco']['ax2'].set_yscale("log") + width=w1, load_start=load_start) + plots['roll_taco'].ax2.set_yscale("log") else: plt_name = "roll" # Make a plot of off-nominal roll - plots['roll'] = plot_one( + plots['roll'] = PredictPlot( fig_id=num_figs+2, title='Off-Nominal Roll', xlabel='Date', @@ -718,8 +719,8 @@ def _make_state_plots(self, plots, num_figs, w1, plot_start, xmin=plot_start, ylabel='Roll Angle (deg)', ylim=(-20.0, 20.0), - figsize=figsize, width=w1, load_start=load_start) - plots[plt_name]['filename'] = f'{plt_name}.png' + width=w1, load_start=load_start) + plots[plt_name].filename = f'{plt_name}.png' def make_prediction_plots(self, outdir, states, temps, load_start): """ @@ -752,41 +753,39 @@ def make_prediction_plots(self, outdir, states, temps, load_start): w1 = None mylog.info('Making temperature prediction plots') - plots[self.name] = plot_two(fig_id=1, x=times, y=temps[self.name], - x2=times, - y2=self.predict_model.comp["pitch"].mvals, - xmin=plot_start, xlabel='Date', - ylabel='Temperature ($^\circ$C)', - ylabel2='Pitch (deg)', ylim2=(40, 180), - width=w1, load_start=load_start) + plots[self.name] = PredictPlot(fig_id=1, x=times, y=temps[self.name], + x2=times, y2=self.predict_model.comp["pitch"].mvals, + xmin=plot_start, xlabel='Date', ylabel='Temperature ($^\circ$C)', + ylabel2='Pitch (deg)', ylim2=(40, 180), width=w1, + load_start=load_start) # Add horizontal lines for the planning and caution limits - ymin, ymax = plots[self.name]['ax'].get_ylim() + ymin, ymax = plots[self.name].ax.get_ylim() ymax = max(self.limits["yellow_hi"].value+1, ymax) - plots[self.name]['ax'].set_title(self.msid.upper(), loc='left', pad=10) - plots[self.name]['ax'].axhline(self.limits["yellow_hi"].value, - linestyle='-', linewidth=2.0, - color=self.limits["yellow_hi"].color, - label='Yellow') - plots[self.name]['ax'].axhline(self.limits["planning_hi"].value, - linestyle='-', linewidth=2.0, - color=self.limits["planning_hi"].color, - label='Planning') + plots[self.name].ax.set_title(self.msid.upper(), loc='left', pad=10) + plots[self.name].ax.axhline(self.limits["yellow_hi"].value, + linestyle='-', linewidth=2.0, + color=self.limits["yellow_hi"].color, + label='Yellow') + plots[self.name].ax.axhline(self.limits["planning_hi"].value, + linestyle='-', linewidth=2.0, + color=self.limits["planning_hi"].color, + label='Planning') if self.flag_cold_viols: ymin = min(self.limits["yellow_lo"].value-1, ymin) - plots[self.name]['ax'].axhline(self.limits["yellow_hi"].value, + plots[self.name].ax.axhline(self.limits["yellow_hi"].value, linestyle='-', linewidth=2.0, color=self.limits["yellow_hi"].color, label='Yellow', zorder=-8) - plots[self.name]['ax'].axhline(self.limits["planning_hi"].value, + plots[self.name].ax.axhline(self.limits["planning_hi"].value, linestyle='-', linewidth=2.0, color=self.limits["planning_hi"].color, label='Planning', zorder=-8) - plots[self.name]['ax'].set_ylim(ymin, ymax) - plots[self.name]['filename'] = self.msid.lower()+'.png' + plots[self.name].ax.set_ylim(ymin, ymax) + plots[self.name].filename = self.msid.lower()+'.png' # The next line is to ensure that the width of the axes # of all the weekly prediction plots are the same. - w1, _ = plots[self.name]['fig'].get_size_inches() + w1, _ = plots[self.name].fig.get_size_inches() self._make_state_plots(plots, 1, w1, plot_start, states, load_start) @@ -800,9 +799,9 @@ def make_prediction_plots(self, outdir, states, temps, load_start): # Make the legend on the temperature plot # only now after we've allowed for # customizations - plots['default']['ax'].legend(bbox_to_anchor=(0.25, 0.99), - loc='lower left', - ncol=4, fontsize=14) + plots['default'].ax.legend(bbox_to_anchor=(0.25, 0.99), + loc='lower left', ncol=4, + fontsize=14) # Now plot any perigee passages that occur between xmin and xmax # for eachpassage in perigee_passages: @@ -812,9 +811,9 @@ def make_prediction_plots(self, outdir, states, temps, load_start): # customizations have been made for key in plots: if key != self.msid: - outfile = outdir / plots[key]['filename'] + outfile = outdir / plots[key].filename mylog.debug('Writing plot file %s' % outfile) - plots[key]['fig'].savefig(outfile) + plots[key].fig.savefig(outfile) return plots @@ -1123,7 +1122,7 @@ def make_validation_plots(self, tlm, model_spec, outdir): for plot in plots.values(): for key in plot: if key in ['lines', 'hist']: - outfile = outdir / plot[key]['filename'] + outfile = outdir / plot[key]["filename"] mylog.debug('Writing plot file %s' % outfile) plot[key]['fig'].savefig(outfile) diff --git a/acis_thermal_check/utils.py b/acis_thermal_check/utils.py index e6a41b6a..1801ccb4 100644 --- a/acis_thermal_check/utils.py +++ b/acis_thermal_check/utils.py @@ -110,107 +110,12 @@ def emit(self, record): logger.addHandler(filehandler) -def plot_one(fig_id, x, y, yy=None, linestyle='-', - ll='--', color=thermal_blue, - linewidth=2, xmin=None, xmax=None, - ylim=None, xlabel='', ylabel='', title='', - figsize=(12, 6), load_start=None, - width=None): +class PlotDate: + _color = None + _color2 = None """ - Plot one quantities with a date x-axis and a left - y-axis. - - Parameters - ---------- - fig_id : integer - The ID for this particular figure. - x : NumPy array - Times in seconds since the beginning of the mission for - the left y-axis quantity. - y : NumPy array - Quantity to plot against the times on the left x-axis. - yy : NumPy array, optional - A second quantity to plot against the times on the - left x-axis. Default: None - linestyle : string, optional - The style of the line for the left y-axis. - ll : string, optional - The style of the second line for the left y-axis. - color : string, optional - The color of the line for the left y-axis. - linewidth : string, optional - The width of the lines. Default: 2 - xmin : float, optional - The left-most value of the x-axis. - xmax : float, optional - The right-most value of the x-axis. - ylim : 2-tuple, optional - The limits for the left y-axis. - xlabel : string, optional - The label of the x-axis. - ylabel : string, optional - The label for the left y-axis. - title : string, optional - The title for the plot. - figsize : 2-tuple of floats - Size of plot in width and height in inches. - """ - # Convert times to dates - xt = cxctime2plotdate(x) - fig = plt.figure(fig_id, figsize=figsize) - fig.clf() - ax = fig.add_subplot(1, 1, 1) - # Plot left y-axis - ax.plot_date(xt, y, fmt='-', linestyle=linestyle, linewidth=linewidth, - color=color) - if yy is not None: - ax.plot_date(xt, yy, fmt='-', linestyle=ll, linewidth=linewidth, - color=color) - if xmin is None: - xmin = min(xt) - if xmax is None: - xmax = max(xt) - ax.set_xlim(xmin, xmax) - if ylim: - ax.set_ylim(*ylim) - ax.set_xlabel(xlabel) - ax.set_ylabel(ylabel) - ax.set_title(title) - ax.grid() - - if load_start is not None: - # Add a vertical line to mark the start time of the load - ax.axvline(load_start, linestyle='-', color='g', linewidth=2.0) - - Ska.Matplotlib.set_time_ticks(ax) - for label in ax.xaxis.get_ticklabels(): - label.set_rotation_mode("anchor") - label.set_rotation(30) - label.set_horizontalalignment('right') - ax.tick_params(which='major', axis='x', length=6) - ax.tick_params(which='minor', axis='x', length=3) - - fig.subplots_adjust(bottom=0.22, right=0.87) - # The next several lines ensure that the width of the axes - # of all the weekly prediction plots are the same - if width is not None: - w2, _ = fig.get_size_inches() - lm = fig.subplotpars.left * width / w2 - rm = fig.subplotpars.right * width / w2 - fig.subplots_adjust(left=lm, right=rm) - - return {'fig': fig, 'ax': ax} - - -def plot_two(fig_id, x, y, x2, y2, yy=None, linewidth=2, - linestyle='-', linestyle2='-', ll='--', - color=thermal_blue, color2='magenta', - xmin=None, xmax=None, ylim=None, ylim2=None, - xlabel='', ylabel='', ylabel2='', title='', - figsize=(12, 6), load_start=None, width=None): - """ - Plot two quantities with a date x-axis, one on the left - y-axis and the other on the right y-axis. + Plot quantities with a date x-axis, on the left + y-axis and optionally another on the right y-axis. Parameters ---------- @@ -229,18 +134,6 @@ def plot_two(fig_id, x, y, x2, y2, yy=None, linewidth=2, yy : NumPy array, optional A second quantity to plot against the times on the left x-axis. Default: None - linewidth : string, optional - The width of the lines. Default: 2 - linestyle : string, optional - The style of the line for the left y-axis. - linestyle2 : string, optional - The style of the line for the right y-axis. - ll : string, optional - The style of the second line for the left y-axis. - color : string, optional - The color of the line for the left y-axis. - color2 : string, optional - The color of the line for the right y-axis. xmin : float, optional The left-most value of the x-axis. xmax : float, optional @@ -260,66 +153,82 @@ def plot_two(fig_id, x, y, x2, y2, yy=None, linewidth=2, figsize : 2-tuple of floats Size of plot in width and height in inches. """ - # Convert times to dates - xt = cxctime2plotdate(x) - fig = plt.figure(fig_id, figsize=figsize) - fig.clf() - ax = fig.add_subplot(1, 1, 1) - # Plot left y-axis - ax.plot_date(xt, y, fmt='-', linestyle=linestyle, linewidth=linewidth, - color=color) - if yy is not None: - ax.plot_date(xt, yy, fmt='-', linestyle=ll, linewidth=linewidth, - color=color) - if xmin is None: - xmin = min(xt) - if xmax is None: - xmax = max(xt) - ax.set_xlim(xmin, xmax) - if ylim: - ax.set_ylim(*ylim) - ax.set_xlabel(xlabel) - ax.set_ylabel(ylabel) - ax.set_title(title) - ax.grid() - - # Plot right y-axis - - ax2 = ax.twinx() - xt2 = cxctime2plotdate(x2) - ax2.plot_date(xt2, y2, fmt='-', linestyle=linestyle2, linewidth=linewidth, - color=color2) - ax2.set_xlim(xmin, xmax) - if ylim2: - ax2.set_ylim(*ylim2) - ax2.set_ylabel(ylabel2, color=color2) - ax2.xaxis.set_visible(False) - - if load_start is not None: - # Add a vertical line to mark the start time of the load - ax.axvline(load_start, linestyle='-', color='g', linewidth=2.0) - - Ska.Matplotlib.set_time_ticks(ax) - for label in ax.xaxis.get_ticklabels(): - label.set_rotation_mode("anchor") - label.set_rotation(30) - label.set_horizontalalignment('right') - [label.set_color(color2) for label in ax2.yaxis.get_ticklabels()] - ax.tick_params(which='major', axis='x', length=6) - ax.tick_params(which='minor', axis='x', length=3) - fig.subplots_adjust(bottom=0.22, right=0.87) - # The next several lines ensure that the width of the axes - # of all the weekly prediction plots are the same - if width is not None: - w2, _ = fig.get_size_inches() - lm = fig.subplotpars.left * width / w2 - rm = fig.subplotpars.right * width / w2 - fig.subplots_adjust(left=lm, right=rm) - - ax.set_zorder(10) - ax.patch.set_visible(False) - - return {'fig': fig, 'ax': ax, 'ax2': ax2} + def __init__(self, fig_id, x, y, x2=None, y2=None, yy=None, + xmin=None, xmax=None, ylim=None, ylim2=None, + xlabel='', ylabel='', ylabel2='', title='', + figsize=(12, 6), load_start=None, width=None): + # Convert times to dates + xt = cxctime2plotdate(x) + fig = plt.figure(fig_id, figsize=figsize) + fig.clf() + ax = fig.add_subplot(1, 1, 1) + # Plot left y-axis + ax.plot_date(xt, y, fmt='-', linestyle='-', linewidth=2, + color=self._color) + if yy is not None: + ax.plot_date(xt, yy, fmt='-', linestyle='--', linewidth=2, + color=self._color2) + if xmin is None: + xmin = min(xt) + if xmax is None: + xmax = max(xt) + ax.set_xlim(xmin, xmax) + if ylim: + ax.set_ylim(*ylim) + ax.set_xlabel(xlabel) + ax.set_ylabel(ylabel) + ax.set_title(title) + ax.grid() + + # Plot right y-axis + + if x2 is not None and y2 is not None: + ax2 = ax.twinx() + xt2 = cxctime2plotdate(x2) + ax2.plot_date(xt2, y2, fmt='-', linestyle='-', + linewidth=2, color="magenta") + ax2.set_xlim(xmin, xmax) + if ylim2: + ax2.set_ylim(*ylim2) + ax2.set_ylabel(ylabel2, color="magenta") + ax2.xaxis.set_visible(False) + else: + ax2 = None + + if load_start is not None: + # Add a vertical line to mark the start time of the load + ax.axvline(load_start, linestyle='-', color='g', linewidth=2.0) + + Ska.Matplotlib.set_time_ticks(ax) + for label in ax.xaxis.get_ticklabels(): + label.set_rotation_mode("anchor") + label.set_rotation(30) + label.set_horizontalalignment('right') + if ax2 is not None: + [label.set_color("magenta") for label in ax2.yaxis.get_ticklabels()] + ax.tick_params(which='major', axis='x', length=6) + ax.tick_params(which='minor', axis='x', length=3) + fig.subplots_adjust(bottom=0.22, right=0.87) + # The next several lines ensure that the width of the axes + # of all the weekly prediction plots are the same + if width is not None: + w2, _ = fig.get_size_inches() + lm = fig.subplotpars.left * width / w2 + rm = fig.subplotpars.right * width / w2 + fig.subplots_adjust(left=lm, right=rm) + + ax.set_zorder(10) + ax.patch.set_visible(False) + + self.fig = fig + self.ax = ax + self.ax2 = ax2 + self.filename = None + + +class PredictPlot(PlotDate): + _color = thermal_blue + _color2 = thermal_blue def get_options(opts=None): @@ -463,18 +372,18 @@ def paint_perigee(perigee_passages, states, plots): # necessitated by SKA xpos = cxctime2plotdate([CxoTime(eachpassage[0]).secs]) - ymin, ymax = plot['ax'].get_ylim() + ymin, ymax = plot.ax.get_ylim() # now plot the line. - plot['ax'].vlines(xpos, ymin, ymax, linestyle=':', color='red', - linewidth=2.0) + plot.ax.vlines(xpos, ymin, ymax, linestyle=':', color='red', + linewidth=2.0) # Plot the perigee passage time so long as it was specified in # the CTI_report file if eachpassage[1] != "Not-within-load": perigee_time = cxctime2plotdate([CxoTime(eachpassage[1]).secs]) - plot['ax'].vlines(perigee_time, ymin, ymax, linestyle=':', - color='black', linewidth=2.0) + plot.ax.vlines(perigee_time, ymin, ymax, linestyle=':', + color='black', linewidth=2.0) class ACISLimit: From 7fa629a160c294abd269c1112a9f47845895a8f8 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Sat, 1 Jan 2022 12:51:42 -0600 Subject: [PATCH 17/21] add_limit_line method --- acis_thermal_check/apps/acisfp_check.py | 20 +++-------- acis_thermal_check/apps/dpa_check.py | 5 ++- acis_thermal_check/main.py | 20 +++-------- acis_thermal_check/utils.py | 17 +++++++++ doc/source/developing_models.rst | 47 ++++++++----------------- 5 files changed, 41 insertions(+), 68 deletions(-) diff --git a/acis_thermal_check/apps/acisfp_check.py b/acis_thermal_check/apps/acisfp_check.py index f647319c..10aedd4b 100755 --- a/acis_thermal_check/apps/acisfp_check.py +++ b/acis_thermal_check/apps/acisfp_check.py @@ -173,25 +173,13 @@ def make_prediction_plots(self, outdir, states, temps, load_start): width=w1, load_start=load_start) plots[name].ax.set_title(self.msid.upper(), loc='left', pad=10) # Draw a horizontal line indicating cold ECS cutoff - plots[name].ax.axhline(self.limits["cold_ecs"].value, - linestyle='--', linewidth=2.0, - color=self.limits["cold_ecs"].color, - label='Cold ECS') + plots[self.name].add_limit_line(self.limits["cold_ecs"], "Cold ECS", ls='--') # Draw a horizontal line showing the ACIS-I cutoff - plots[name].ax.axhline(self.limits["acis_i"].value, - linestyle='--', linewidth=2.0, - color=self.limits["acis_i"].color, - label="ACIS-I") + plots[self.name].add_limit_line(self.limits["acis_i"], "ACIS-I", ls='--') # Draw a horizontal line showing the ACIS-S cutoff - plots[name].ax.axhline(self.limits["acis_s"].value, - linestyle='--', linewidth=2.0, - color=self.limits["acis_s"].color, - label="ACIS-S") + plots[self.name].add_limit_line(self.limits["acis_s"], "ACIS-S", ls='--') # Draw a horizontal line showing the hot ACIS-S cutoff - plots[name].ax.axhline(self.limits["acis_hot"].value, - linestyle='--', linewidth=2.0, - color=self.limits["acis_hot"].color, - label="Hot ACIS-S") + plots[self.name].add_limit_line(self.limits["acis_hot"], "Hot ACIS-S", ls='--') # Get the width of this plot to make the widths of all the # prediction plots the same if i == 0: diff --git a/acis_thermal_check/apps/dpa_check.py b/acis_thermal_check/apps/dpa_check.py index eb10f74b..e8749b23 100755 --- a/acis_thermal_check/apps/dpa_check.py +++ b/acis_thermal_check/apps/dpa_check.py @@ -75,9 +75,8 @@ def custom_prediction_plots(self, plots): and can be used to customize plots before they are written, e.g. add limit lines, etc. """ - plots[self.name].ax.axhline(self.limits["zero_feps"].value, - linestyle='--', label="Zero FEPs", linewidth=2.0, - color=self.limits["zero_feps"].color, zorder=-8) + plots[self.name].add_limit_line(self.limits["zero_feps"], + "Zero FEPs", ls='--') def custom_validation_plots(self, plots): """ diff --git a/acis_thermal_check/main.py b/acis_thermal_check/main.py index 36bafd74..e10f87ab 100644 --- a/acis_thermal_check/main.py +++ b/acis_thermal_check/main.py @@ -762,24 +762,12 @@ def make_prediction_plots(self, outdir, states, temps, load_start): ymin, ymax = plots[self.name].ax.get_ylim() ymax = max(self.limits["yellow_hi"].value+1, ymax) plots[self.name].ax.set_title(self.msid.upper(), loc='left', pad=10) - plots[self.name].ax.axhline(self.limits["yellow_hi"].value, - linestyle='-', linewidth=2.0, - color=self.limits["yellow_hi"].color, - label='Yellow') - plots[self.name].ax.axhline(self.limits["planning_hi"].value, - linestyle='-', linewidth=2.0, - color=self.limits["planning_hi"].color, - label='Planning') + plots[self.name].add_limit_line(self.limits["yellow_hi"], "Yellow") + plots[self.name].add_limit_line(self.limits["planning_hi"], "Planning") if self.flag_cold_viols: ymin = min(self.limits["yellow_lo"].value-1, ymin) - plots[self.name].ax.axhline(self.limits["yellow_hi"].value, - linestyle='-', linewidth=2.0, - color=self.limits["yellow_hi"].color, - label='Yellow', zorder=-8) - plots[self.name].ax.axhline(self.limits["planning_hi"].value, - linestyle='-', linewidth=2.0, - color=self.limits["planning_hi"].color, - label='Planning', zorder=-8) + plots[self.name].add_limit_line(self.limits["yellow_lo"], "Yellow") + plots[self.name].add_limit_line(self.limits["planning_lo"], "Planning") plots[self.name].ax.set_ylim(ymin, ymax) plots[self.name].filename = self.msid.lower()+'.png' diff --git a/acis_thermal_check/utils.py b/acis_thermal_check/utils.py index 1801ccb4..ce141fff 100644 --- a/acis_thermal_check/utils.py +++ b/acis_thermal_check/utils.py @@ -225,6 +225,23 @@ def __init__(self, fig_id, x, y, x2=None, y2=None, yy=None, self.ax2 = ax2 self.filename = None + def add_limit_line(self, limit, label, ls='-'): + """ + Add a horizontal line for a given limit to the plot. + + Parameters + ---------- + limit : ACISLimit object + Contains information about the value of the limit + and the color it should be plotted with. + label : string + The label to give the line. + ls : string, optional + The line style for the limit line. Default: "-" + """ + self.ax.axhline(limit.value, linestyle=ls, linewidth=2.0, + color=limit.color, label=label, zorder=-8) + class PredictPlot(PlotDate): _color = thermal_blue diff --git a/doc/source/developing_models.rst b/doc/source/developing_models.rst index 3924ee21..8981716d 100644 --- a/doc/source/developing_models.rst +++ b/doc/source/developing_models.rst @@ -197,29 +197,12 @@ class. This gives us access to all of the prediction plots which will appear on the thermal model webpage. The ``plots`` dict that is the sole argument to ``custom_prediction_plots`` -contains the plots for the temperature being modeled, the has a structure -like this: - -.. code-block:: python - - { - "dpa": { - "ax": - "fig":
- "filename": "1dpamzt.png" - }, - "pow_sim": { - "ax": , - "fig":
, - "filename": "pow_sim.png" - }, - ... - } - -showing that each sub-dict contains the Matplotlib ``Figure`` and ``AxesSubplot`` -instances that you can access to add lines, etc., as well as the plot filename. -An example for this is done to add the zero-FEPs line for the 1DPAMZT model is -shown here: +contains ``PredictPlot`` objects for the temperature being modeled as well +as other quantities. Each of these ``PredictPlot`` objects has Matplotlib +``Figure`` and ``AxesSubplot`` instances attached for further annotating or +adjusting plots, as well as the plot filename. To add a limit line, call the +``add_limit_line`` method of the ``PredictPlot`` object. An example for this +is done to add the zero-FEPs line for the 1DPAMZT model is shown here: .. code-block:: python @@ -234,16 +217,15 @@ shown here: and can be used to customize plots before they are written, e.g. add limit lines, etc. """ - plots[self.name]['ax'].axhline(self.limits["zero_feps"].value, - linestyle='--', label="Zero FEPs", linewidth=2.0, - color=self.limits["zero_feps"].color, zorder=-8) + plots[self.name].add_limit_line(self.limits["zero_feps"], + "Zero FEPs", ls='--') Something similar can be done for the validation plots in ``custom_validation_plots``, except here the input ``plots`` structure is -a bit different. The dict for some plots has two sub-dicts, ``"lines"`` -and ``"hist"``, the former for the actual model vs. data comparison and -the latter for the histogram of model-data error. In practice, you will -only need to worry about the first, as shown below. +a bit different. Each item of ``plots`` is a dict has two sub-dicts, +``"lines"`` and ``"hist"``, the former for the actual model vs. data +comparison and the latter for the histogram of model-data error. In practice, +you will only need to worry about the first, as shown below. .. code-block:: python @@ -416,9 +398,8 @@ of the 1DPAMZT model is shown below: and can be used to customize plots before they are written, e.g. add limit lines, etc. """ - plots[self.name]['ax'].axhline(self.limits["zero_feps"].value, - linestyle='--', label="Zero FEPs", linewidth=2.0, - color=self.limits["zero_feps"].color, zorder=-8) + plots[self.name].add_limit_line(self.limits["zero_feps"], + "Zero FEPs", ls='--') def custom_validation_plots(self, plots): """ From fa356246af2f45754691e2231ec48a9849555b8d Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Wed, 5 Jan 2022 11:45:08 -0500 Subject: [PATCH 18/21] Bugfix --- acis_thermal_check/apps/acisfp_check.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acis_thermal_check/apps/acisfp_check.py b/acis_thermal_check/apps/acisfp_check.py index 10aedd4b..28e2af53 100755 --- a/acis_thermal_check/apps/acisfp_check.py +++ b/acis_thermal_check/apps/acisfp_check.py @@ -173,13 +173,13 @@ def make_prediction_plots(self, outdir, states, temps, load_start): width=w1, load_start=load_start) plots[name].ax.set_title(self.msid.upper(), loc='left', pad=10) # Draw a horizontal line indicating cold ECS cutoff - plots[self.name].add_limit_line(self.limits["cold_ecs"], "Cold ECS", ls='--') + plots[name].add_limit_line(self.limits["cold_ecs"], "Cold ECS", ls='--') # Draw a horizontal line showing the ACIS-I cutoff - plots[self.name].add_limit_line(self.limits["acis_i"], "ACIS-I", ls='--') + plots[name].add_limit_line(self.limits["acis_i"], "ACIS-I", ls='--') # Draw a horizontal line showing the ACIS-S cutoff - plots[self.name].add_limit_line(self.limits["acis_s"], "ACIS-S", ls='--') + plots[name].add_limit_line(self.limits["acis_s"], "ACIS-S", ls='--') # Draw a horizontal line showing the hot ACIS-S cutoff - plots[self.name].add_limit_line(self.limits["acis_hot"], "Hot ACIS-S", ls='--') + plots[name].add_limit_line(self.limits["acis_hot"], "Hot ACIS-S", ls='--') # Get the width of this plot to make the widths of all the # prediction plots the same if i == 0: From c273ad9b8cb3459fe6eabf4b8420ee8f696804d5 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Wed, 5 Jan 2022 12:30:50 -0500 Subject: [PATCH 19/21] Minor bugfixes --- acis_thermal_check/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acis_thermal_check/main.py b/acis_thermal_check/main.py index e10f87ab..2be64a94 100644 --- a/acis_thermal_check/main.py +++ b/acis_thermal_check/main.py @@ -537,7 +537,7 @@ def make_prediction_viols(self, temps, load_start): times, temp, load_start, self.limits["planning_lo"].value, "planning", "min") viols["lo"] = { - "name": f"Cold ({self.limits['planning_hi'].value} C)", + "name": f"Cold ({self.limits['planning_lo'].value} C)", "type": "Min", "values": lo_viols } @@ -766,8 +766,8 @@ def make_prediction_plots(self, outdir, states, temps, load_start): plots[self.name].add_limit_line(self.limits["planning_hi"], "Planning") if self.flag_cold_viols: ymin = min(self.limits["yellow_lo"].value-1, ymin) - plots[self.name].add_limit_line(self.limits["yellow_lo"], "Yellow") - plots[self.name].add_limit_line(self.limits["planning_lo"], "Planning") + plots[self.name].add_limit_line(self.limits["yellow_lo"], None) + plots[self.name].add_limit_line(self.limits["planning_lo"], None) plots[self.name].ax.set_ylim(ymin, ymax) plots[self.name].filename = self.msid.lower()+'.png' From 2cf59eeab8264684c3674dbfc95ded12569c6b12 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Wed, 5 Jan 2022 12:57:05 -0500 Subject: [PATCH 20/21] Should only be checking obsids within the load --- acis_thermal_check/acis_obs.py | 7 ++++++- acis_thermal_check/apps/acisfp_check.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/acis_thermal_check/acis_obs.py b/acis_thermal_check/acis_obs.py index c6513428..9a421353 100755 --- a/acis_thermal_check/acis_obs.py +++ b/acis_thermal_check/acis_obs.py @@ -133,7 +133,7 @@ def fetch_ocat_data(obsid_list): return table_dict -def find_obsid_intervals(cmd_states): +def find_obsid_intervals(cmd_states, load_start): """ User reads the SKA commanded states archive, via a call to the SKA kadi.commands.states.get_states, @@ -191,6 +191,11 @@ def find_obsid_intervals(cmd_states): if 60000 > eachstate['obsid'] >= 38001: continue + # Only check states which are at least partially + # within the load being reviewed + if eachstate["tstop"] < load_start: + continue + pow_cmd = eachstate['power_cmd'] # is this the first WSPOW of the interval? diff --git a/acis_thermal_check/apps/acisfp_check.py b/acis_thermal_check/apps/acisfp_check.py index 28e2af53..425670b1 100755 --- a/acis_thermal_check/apps/acisfp_check.py +++ b/acis_thermal_check/apps/acisfp_check.py @@ -139,7 +139,7 @@ def make_prediction_plots(self, outdir, states, temps, load_start): # extract the OBSID's from the commanded states. NOTE: this contains all # observations including ECS runs and HRC observations - observation_intervals = find_obsid_intervals(states) + observation_intervals = find_obsid_intervals(states, load_start) # Filter out any HRC science observations BUT keep ACIS ECS observations self.acis_and_ecs_obs = hrc_science_obs_filter(observation_intervals) From b8e0793430f94a57986cf499d88442272688c7a1 Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Mon, 24 Jan 2022 21:35:06 -0500 Subject: [PATCH 21/21] Fix sentence --- acis_thermal_check/acis_obs.py | 2 +- doc/source/developing_models.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acis_thermal_check/acis_obs.py b/acis_thermal_check/acis_obs.py index 9a421353..37e3cae4 100755 --- a/acis_thermal_check/acis_obs.py +++ b/acis_thermal_check/acis_obs.py @@ -191,7 +191,7 @@ def find_obsid_intervals(cmd_states, load_start): if 60000 > eachstate['obsid'] >= 38001: continue - # Only check states which are at least partially + # Only check states which are at least partially # within the load being reviewed if eachstate["tstop"] < load_start: continue diff --git a/doc/source/developing_models.rst b/doc/source/developing_models.rst index 8981716d..2cf61480 100644 --- a/doc/source/developing_models.rst +++ b/doc/source/developing_models.rst @@ -222,7 +222,7 @@ is done to add the zero-FEPs line for the 1DPAMZT model is shown here: Something similar can be done for the validation plots in ``custom_validation_plots``, except here the input ``plots`` structure is -a bit different. Each item of ``plots`` is a dict has two sub-dicts, +a bit different. Each item of ``plots`` is a dict with two sub-dicts, ``"lines"`` and ``"hist"``, the former for the actual model vs. data comparison and the latter for the histogram of model-data error. In practice, you will only need to worry about the first, as shown below.