From 36549bb3dc3e50954d74f1d30b220880032ac4c3 Mon Sep 17 00:00:00 2001 From: "edward.safford" Date: Mon, 16 Sep 2024 21:27:20 +0000 Subject: [PATCH] Ref #45 Save work in progress. --- parm/templates/oznHoriz.yaml | 15 +- scripts/exobsmon_plot.sh | 16 +- ush/plotObsMon.py | 26 ++- ush/setUpData.py.mk1 | 186 -------------------- ush/setupdata.py | 332 ++++++++++++++++++++++------------- 5 files changed, 251 insertions(+), 324 deletions(-) delete mode 100644 ush/setUpData.py.mk1 diff --git a/parm/templates/oznHoriz.yaml b/parm/templates/oznHoriz.yaml index 366f8d0..b2c5618 100644 --- a/parm/templates/oznHoriz.yaml +++ b/parm/templates/oznHoriz.yaml @@ -7,12 +7,17 @@ datasets: type: MonDataSpace control_file: - - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.ctl + - {{DATA}}/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.ctl +# - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.ctl filenames: - - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATE | to_YMDH }}.ieee_d - - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATEm6 | to_YMDH }}.ieee_d - - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATEm12 | to_YMDH }}.ieee_d - - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATEm18 | to_YMDH }}.ieee_d + - {{DATA}}/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATE | to_YMDH }}.ieee_d + - {{DATA}}/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATEm6 | to_YMDH }}.ieee_d + - {{DATA}}/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATEm12 | to_YMDH }}.ieee_d + - {{DATA}}/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATEm18 | to_YMDH }}.ieee_d +# - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATE | to_YMDH }}.ieee_d +# - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATEm6 | to_YMDH }}.ieee_d +# - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATEm12 | to_YMDH }}.ieee_d +# - {{DATA}}/ozn_data/horiz/{{SENSOR}}_{{SAT}}.{{COMPONENT}}.{{ PDATEm18 | to_YMDH }}.ieee_d levels: {{LEVELS}} groups: - name: GsiIeee diff --git a/scripts/exobsmon_plot.sh b/scripts/exobsmon_plot.sh index c6ec0ef..9259c0b 100755 --- a/scripts/exobsmon_plot.sh +++ b/scripts/exobsmon_plot.sh @@ -19,6 +19,12 @@ if [[ ! -e ${chan_yaml} ]]; then exit 2 fi +#----------------------------------------------------- +# get data location and setup input data as necessary +#----------------------------------------------------- +#echo PDATE: $PDATE +#${APRUN_PY} ${USHobsmon}/setUpData.py -i ${yaml_file} -p ${PDATE} + #--------------------------------------------------------------- # split $yaml_file into sat/instr[/plot], minimization, and obs # in order to reduce the plot jobs to a more managable size @@ -59,13 +65,14 @@ if compgen -G "${DATA}/OM_PLOT*.yaml" > /dev/null; then case ${MACHINE_ID} in hera|orion|hercules) # submit plot job - plotjob_id=$(${SUB} --account ${ACCOUNT} -n ${ctr} -o ${logfile} -D . -J ${jobname} --time=1:00:00 \ +# plotjob_id=$(${SUB} --account ${ACCOUNT} -n ${ctr} -o ${logfile} -D . -J ${jobname} --time=1:00:00 \ + plotjob_id=$(${SUB} --account ${ACCOUNT} -n ${ctr} -o ${logfile} -D . -J ${jobname} --time=0:05:00 \ --mem=80000M --wrap "srun -l --multi-prog ${cmdfile}") # submit cleanup job to run after plot job - plotjob_id=`echo ${plotjob_id} | gawk '{ print $4 }'` - ${SUB} --account ${ACCOUNT} -n 1 -o ${logfile_clnup} -D . -J "OM_cleanup" --time=0:10:00 \ - -p ${SERVICE_PARTITION} --dependency=afterok:${plotjob_id} ${USHobsmon}/om_cleanup.sh +# plotjob_id=`echo ${plotjob_id} | gawk '{ print $4 }'` +# ${SUB} --account ${ACCOUNT} -n 1 -o ${logfile_clnup} -D . -J "OM_cleanup" --time=0:10:00 \ +# -p ${SERVICE_PARTITION} --dependency=afterok:${plotjob_id} ${USHobsmon}/om_cleanup.sh ;; @@ -88,3 +95,4 @@ if compgen -G "${DATA}/OM_PLOT*.yaml" > /dev/null; then esac fi fi + diff --git a/ush/plotObsMon.py b/ush/plotObsMon.py index 57141c6..6161a81 100755 --- a/ush/plotObsMon.py +++ b/ush/plotObsMon.py @@ -8,7 +8,8 @@ import os from re import sub import yaml -from setupdata import setupdata +from setupdata import OM_data + from wxflow import parse_j2yaml, save_as_yaml from wxflow import add_to_datetime, to_timedelta, to_datetime from eva.eva_driver import eva @@ -139,8 +140,8 @@ def loadConfig(satname, instrument, obstype, plot, cycle_tm, cycle_interval, args = parser.parse_args() cycle_tm = to_datetime(args.pdate) - data = os.environ.get('DATA', '.') - os.chdir(data) + workdir = os.environ.get('DATA', '.') + os.chdir(workdir) try: mon_sources = args.input @@ -188,11 +189,23 @@ def loadConfig(satname, instrument, obstype, plot, cycle_tm, cycle_interval, parm_location = os.path.join(parm, 'templates') plot_template = os.path.join(parm_location, plot_template) + # move to unique directory based on plot_yaml file + plot_dir = os.path.join(workdir, plot_yaml.split('.')[0]) + os.makedirs(plot_dir) + os.chdir(plot_dir) + + config['DATA'] = plot_dir +# logger.info(f'plot_dir: {plot_dir}') + genYaml(plot_template, plot_yaml, config) -# logger.info(f'config: {config}') - setupdata(config, logger) + + # need to send data_location into setupdata maybe instead of config? files we need should be specified in plot_yaml file + plotData = OM_data(data_location, config, plot_yaml, logger) + +# setupdata(data_location, config, plot_yaml, logger) + eva(plot_yaml) - os.remove(plot_yaml) +# os.remove(plot_yaml) if 'minimization' in mon_dict.keys(): satname = None @@ -214,6 +227,7 @@ def loadConfig(satname, instrument, obstype, plot, cycle_tm, cycle_interval, plot_template = os.path.join(parm_location, plot_template) genYaml(plot_template, plot_yaml, config) + setupdata(config, plot_yaml, logger) eva(plot_yaml) os.remove(plot_yaml) diff --git a/ush/setUpData.py.mk1 b/ush/setUpData.py.mk1 deleted file mode 100644 index 5886923..0000000 --- a/ush/setUpData.py.mk1 +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python3 -# setUpData.py -# - -import argparse -import yaml -import os -from re import sub -from datetime import datetime -from eva.utilities.logger import Logger -from wxflow import add_to_datetime, to_timedelta, to_datetime - -# -------------------------------------------------------------------------------------------- -def removeKey(d, keys): - """ - Remove keys from a dictionary - - Parameters: - d (dict): input dictionary - keys (list): keys to remove from dictionary - - Return: - modified dictionary if key(s) are found or input dictionary if no keys - are in input dictionary - """ - - r = dict(d) - for key in keys: - if key in d.keys(): - del r[key] - return r - -# -------------------------------------------------------------------------------------------- -def camelCase(s): - """ - Convert string with spaces, dashes, or underscores to cammel case. - - Parameters: - s (str): input string - Return: - string contents in camelCase - """ - - s = sub(r"(_|-)+", " ", s).title().replace(" ", "") - return ''.join([s[0].lower(), s[1:]]) - -# -------------------------------------------------------------------------------------------- -def get_cycles(pdate, logger, cycle_interval=6, max_cycles=121): - """ - Create a list of processing dates starting from input pdate to max_cycles * cycle_interval - - Parameters: - pdate (string): first processing date in YYYYMMDDHH format - logger: - - Return: - list of cycle times - """ - - cycle_tm = to_datetime(pdate) - cycles = [pdate] - for x in range(1, max_cycles): - new_date = add_to_datetime(cycle_tm, to_timedelta(f"-{cycle_interval*x}H")) - cycles.append(datetime.strftime(new_date, "%Y%m%d%H")) - - return cycles - -# -------------------------------------------------------------------------------------------- - - -if __name__ == "__main__": - """ - setUpData - - Read and parse input plot yaml files. Locate the requested data and load/link it - into local directories in the work space defined as $DATA. - - Example calling sequence: >python setUpData.py -i ../parm/gfs/gfs_plots.yaml -p [yyyymmddhh] - """ - - logger = Logger('setUpData') - parser = argparse.ArgumentParser() - - parser.add_argument('-i', '--input', type=str, help='Input YAML plot file', required=True) - parser.add_argument('-p', '--pdate', type=str, help='plot date (last cycle)', required=True) - args = parser.parse_args() - - try: - mon_sources = args.input - with open(mon_sources, 'r') as mon_sources_opened: - mon_dict = yaml.safe_load(mon_sources_opened) - except Exception as e: - logger.abort('setUpData is expecting a valid model plot yaml file, but encountered ' + - f'errors when attempting to load: {mon_sources}, error: {e}') - - try: - cycles = get_cycles(args.pdate, logger) - logger.info(f'cycles: {cycles}') - - except Exception as e: - logger.abort('setUpData is expecting a valid pdate but encountered ' + - f'errors when attempting to load: {mon_sources}, error: {e}') - - model = mon_dict.get('model') - data = mon_dict.get('data') - logger.info(f'model: {model}') - logger.info(f'data: {data}') - - # Create data directories in workspace, using env var $DATA - workspace = os.environ.get('DATA', '.') - os.chdir(workspace) - dir_list = ['con_data', 'mon_data', 'ozn_data/horz', 'ozn_data/time', 'rad_data'] - - for dir in dir_list: - path = os.path.join(workspace, dir) - os.makedirs(path, exist_ok=True) - - if 'satellites' in mon_dict.keys(): - for sat in mon_dict.get('satellites'): - satname = sat.get('name') - - for inst in sat.get('instruments'): - iname = inst.get('name') - logger.info(f'satname: {satname}, instrument: {iname}') - - ozn_instruments = ['gome', 'omi', 'ompslp', 'ompsnp', 'ompstc8'] - - if iname in ozn_instruments: - logger.info(f'{iname} is OZN') - plist = inst.get('plot_list') - logger.info(f'plist: {plist}') - for p in range (len(plist)): - logger.info(f'plist[p]: {plist[p]}') -# plt=plist[p].get('plot') - plt=camelCase(plist[p].get('plot')) - logger.info(f'plt: {plt}') - - else: - logger.info(f'{iname} is RAD') - - - - # -------------------------------------------------------------------- - # For instruments with a large number of channels split the plot_list - # -# channels = chan_dict.get(iname) -# nchans = 0 -# if channels is not None: -# nchans = len(channels.split(",")) - -# if nchans > 100: -# ctr = 0 -# for pl in inst.get('plot_list'): -# pd = sd -# pd['satellites'] = [{'name': satname, -# 'instruments': [{'name': iname, -# 'plot_list': [pl]}]}] -# fname = f'OM_PLOT_sat_{satname}_{iname}_{ctr}.yaml' -# file = open(fname, "w") -# yaml.dump(pd, file) -# file.close() -# ctr += 1 - -# else: -# pd = sd -# pd['satellites'] = [{'name': satname, -# 'instruments': [{'name': iname, -# 'plot_list': plist}]}] -# fname = f'OM_PLOT_sat_{satname}_{iname}.yaml' -# file = open(fname, "w") -# yaml.dump(pd, file) -# file.close() - -# if 'minimization' in mon_dict.keys(): -# md = removeKey(mon_dict, ['satellites', 'observations']) -# fname = f'OM_PLOT_minimization.yaml' -# file = open(fname, "w") -# yaml.dump(md, file) -# file.close() -# -# if 'observations' in mon_dict.keys(): -# od = removeKey(mon_dict, ['satellites', 'minimization']) -# fname = f'OM_PLOT_observations.yaml' -# file = open(fname, "w") -# yaml.dump(od, file) -# file.close() diff --git a/ush/setupdata.py b/ush/setupdata.py index 6ebbbe1..caa995e 100644 --- a/ush/setupdata.py +++ b/ush/setupdata.py @@ -7,82 +7,252 @@ from eva.utilities.logger import Logger from wxflow import add_to_datetime, to_timedelta, to_datetime +data_dir_struct = ["obs-mon", "glb-wkflw", "gfs-ops", "leg-mon"] +ozn_instruments = ['gome', 'omi', 'ompslp', 'ompsnp', 'ompstc8'] + + +class OM_data: + + def __init__(self, data_src, config, plot_yaml, logger): + + self.pdate = datetime.strftime(config.get('PDATE'), '%Y%m%d%H') + self.data_src = data_src +# self.config = config +# self.data_dest = data_dest + self.logger = logger + self.data_dir_type = "" + self.plot_dict = {} + self.ctl_file = None + self.data_files = [] + self.dir_struct= "" + + self.read_yaml(plot_yaml) + self.load_data_files() + self.set_data_type(config.get('SAT'), config.get('SENSOR')) + self.set_dir_struct() + + self.dump() + # -------------------------------------------------------------------------------------------- -def removeKey(d, keys): + def dump(self): + self.logger.info(f'dump OM_data:') + self.logger.info(f'==== =======') + self.logger.info(f' data_type: {self.data_type}') + self.logger.info(f' pdate: {self.pdate}') + self.logger.info(f' data_src: {self.data_src}') +# self.logger.info(f' config: {self.config}') +# self.logger.info(f' plot_dict: {self.plot_dict}') + self.logger.info(f' ctl_file: {self.ctl_file}') + self.logger.info(f' data_files: {self.data_files}') + +# self.logger(f'data_dest: {self.data_dest}') +# self.logger(f'data_dir_type: {self.data_dir_type}') +# for df in self.data_files: +# self.logger.info(f' {df}') + +# -------------------------------------------------------------------------------------------- + def set_data_type(self, sat, sensor): + + if sensor in ozn_instruments: + type = 'ozn' + elif sat is not None: + type = 'rad' + else: + type = None + self.data_type = type + +# -------------------------------------------------------------------------------------------- + def read_yaml(self, yaml_file): + """ + Read yaml file into plot_dict + + Parameters: + yaml_file (file): yaml file with dict info + logger (Logger): Logger object for logging messages. + + Return: + dictionary from yaml_file + """ + + pd = None + try: + with open(yaml_file, 'r') as yaml_file_opened: + pd = yaml.safe_load(yaml_file_opened) + + except Exception as e: + self.logger.info('Warning: unable to load yaml file into rtn_dict ' + + f'errors when attempting to load: {yaml_file}, error: {e}') + self.plot_dict = pd + +# -------------------------------------------------------------------------------------------- + def load_data_files(self): + + """ + + Parameters: + Return: + """ + self.logger.info(f'--> load_data_files') + + # Note it's possible to have more than 1 dataset so this will need to iterate + ds = self.plot_dict.get('datasets') + ds = ds[0] + + self.ctl_file = os.path.basename(ds['control_file'][0]) + self.data_files = [os.path.basename(f) for f in ds['filenames']] + self.logger.info(f'<-- load_data_files') + +# -------------------------------------------------------------------------------------------- + def set_dir_struct(self): + ### + ### + + self.logger.info(f'--> set_dir_struct') + self.dir_struct = 'obs-mon' + + if os.path.exists(os.path.join(self.data_src, self.data_type + '_data')): + self.logger.info(f'JACKPOT!') + +#(os.path.join(os.getcwd(), 'new_folder', 'file.txt')): + + self.logger.info(f'<-- set_dir_struct') + + +def locate_data(sat, sensor, data_path, cycle_times, logger): """ - Remove keys from a dictionary + Locate required data files, testing for each of the 4 possible + data storage schemes. Parameters: - d (dict): input dictionary - keys (list): keys to remove from dictionary + sat (string): satellite name + sensor (string): sensor (instrument) name + data_path (str): input data path from config file + cycle_times (list): list of required cycle times + logger (Logger): Logger object for logging messages. Return: - modified dictionary if key(s) are found or input dictionary if no keys - are in input dictionary + list of required data files (full path) """ - r = dict(d) - for key in keys: - if key in d.keys(): - del r[key] - return r + logger.info(f'<-- locate_data') + df = [] + + for cyc in cycle_times: + file = get_data_file(sat, sensor, data_path, cyc, logger, model='gfs', component='ges') + logger.info(f'file: {file}') + + logger.info(f'<-- locate_data') + return df # -------------------------------------------------------------------------------------------- -def camelCase(s): +def get_dir_type(data_location, data_type, pdate, ctl_file, logger): """ - Convert string with spaces, dashes, or underscores to cammel case. + Determine the type of data file structure pointed to by data_location. Parameters: - s (str): input string + data_location (str): path to data files. + data_type (str): type of data [con|min|ozn|rad] + pdate (datetime): plot date. + ctl_file (str): control file name + logger (Logger): Logger object for logging messages. + Return: - string contents in camelCase + dir type as enumerated in data_dir_struct list or None """ - - s = sub(r"(_|-)+", " ", s).title().replace(" ", "") - return ''.join([s[0].lower(), s[1:]]) + + logger.info(f'--> get_dir_type') + logger.info(f'data_location: {data_location}') + +# data_dir_struct = ["obs-mon", "glb-wkflw", "gfs-ops", "leg-mon"] + + for dir in data_dir_struct: + logger.info(f'testing dir: {dir}') + + logger.info(f'<-- get_dir_type') + return None # -------------------------------------------------------------------------------------------- -def get_cycles(pdate, logger, cycle_interval=6, max_cycles=121): +def load_data(data_location, config, plot_yaml, logger): """ - Create a list of processing dates starting from input pdate to max_cycles * cycle_interval + Determine necessary data and control files for plotting and untar/copy/link into + local work space directory. Parameters: - pdate (string): first processing date in YYYYMMDDHH format - logger: + data_location (str): path to data files + config + plot_yaml (file): yaml file with plot information + logger (Logger): Logger object for logging messages. Return: - list of cycle times + None """ - cycle_tm = to_datetime(pdate) - cycles = [pdate] - for x in range(1, max_cycles): - new_date = add_to_datetime(cycle_tm, to_timedelta(f"-{cycle_interval*x}H")) - cycles.append(datetime.strftime(new_date, "%Y%m%d%H")) - - return cycles + logger.info(f'--> load_data') + + sat = config.get('SAT') + sensor = config.get('SENSOR') + pdate = datetime.strftime(config.get('PDATE'), '%Y%m%d%H') + pdy = pdate[:-2] + hh = pdate[-2:] + logger.info(f' pdate, pdy, hh: {pdate} {pdy} {hh}') + + # for the moment assume only OZN data + data_type = 'ozn' + + # load plot_yaml and return the control and list of data files + ctl_file, data_files = getfiles(data_location, plot_yaml, pdate, logger) + + # determine type of data directory structure + dir_type = get_dir_type(data_location, data_type, pdate, ctl_file, logger) + + # iterate over data_files and copy/link to cwd + + + # this should tell us what kind of data we're after +# plot_template = config.get('PLOT_TEMPLATE') +# logger.info(f'plot_template: {plot_template}') + +# if sat is not None: +# logger.info(f'SAT IS NOT NONE') + +# if is_ozn_data(sensor): +# logger.info(f'OZN DATA') +# else: +# logger.info(f"NOPE ISN'T OZN DATA") + +# filtered_values = [value for key, value in config.items() if key.startswith('PDATEm')] +# cycle_times = [datetime.strftime(value, '%Y%m%d%H') for value in filtered_values] +# logger.info(f'cycle_times: {cycle_times}') + +# data_files = locate_data(sat, sensor, cycle_times, data_path, logger) + + logger.info(f'<-- load_data') # -------------------------------------------------------------------------------------------- -def setupdata(config, logger): +def setupdata(data_location, config, plot_yaml, logger): """ - Read in config and set up required data files locall. + Read in config and plot_yaml file and set up required data files locally. Parameters: - config (dict): dictionary of plot information + data_location (str): path to data files + plot_yaml (file): yaml file with plot information + logger (Logger): Logger object for logging messages. + Return: None """ - logger.info(f'--> setUpData') + logger.info(f'--> setupdata') + logger.info(f' data_location: {data_location}') + logger.info(f' plot_yaml: {plot_yaml}') logger.info(f' config: {config}') - workspace = os.environ.get('DATA', '.') - path = os.path.join(workspace, "ozn_data") - os.mkdir(path, exist_ok=True) - - logger.info(f'<-- setUpData') + # Load data into local directory + load_data(data_location, config, plot_yaml, logger) + + logger.info(f'<-- setupdata') # try: # mon_sources = args.input @@ -99,87 +269,3 @@ def setupdata(config, logger): # except Exception as e: # logger.abort('setUpData is expecting a valid pdate but encountered ' + # f'errors when attempting to load: {mon_sources}, error: {e}') - -# model = mon_dict.get('model') -# data = mon_dict.get('data') -# logger.info(f'model: {model}') -# logger.info(f'data: {data}') - -# # Create data directories in workspace, using env var $DATA -# workspace = os.environ.get('DATA', '.') -# os.chdir(workspace) -# dir_list = ['con_data', 'mon_data', 'ozn_data/horz', 'ozn_data/time', 'rad_data'] -# -# for dir in dir_list: -# path = os.path.join(workspace, dir) -# os.makedirs(path, exist_ok=True) -# -# if 'satellites' in mon_dict.keys(): -# for sat in mon_dict.get('satellites'): -# satname = sat.get('name') - -# for inst in sat.get('instruments'): -# iname = inst.get('name') -# logger.info(f'satname: {satname}, instrument: {iname}') -# -# ozn_instruments = ['gome', 'omi', 'ompslp', 'ompsnp', 'ompstc8'] - -# if iname in ozn_instruments: -# logger.info(f'{iname} is OZN') -# plist = inst.get('plot_list') -# logger.info(f'plist: {plist}') -# for p in range (len(plist)): -# logger.info(f'plist[p]: {plist[p]}') -# plt=plist[p].get('plot') -# plt=camelCase(plist[p].get('plot')) -# logger.info(f'plt: {plt}') - -# else: -# logger.info(f'{iname} is RAD') - - - - # -------------------------------------------------------------------- - # For instruments with a large number of channels split the plot_list - # -# channels = chan_dict.get(iname) -# nchans = 0 -# if channels is not None: -# nchans = len(channels.split(",")) - -# if nchans > 100: -# ctr = 0 -# for pl in inst.get('plot_list'): -# pd = sd -# pd['satellites'] = [{'name': satname, -# 'instruments': [{'name': iname, -# 'plot_list': [pl]}]}] -# fname = f'OM_PLOT_sat_{satname}_{iname}_{ctr}.yaml' -# file = open(fname, "w") -# yaml.dump(pd, file) -# file.close() -# ctr += 1 - -# else: -# pd = sd -# pd['satellites'] = [{'name': satname, -# 'instruments': [{'name': iname, -# 'plot_list': plist}]}] -# fname = f'OM_PLOT_sat_{satname}_{iname}.yaml' -# file = open(fname, "w") -# yaml.dump(pd, file) -# file.close() - -# if 'minimization' in mon_dict.keys(): -# md = removeKey(mon_dict, ['satellites', 'observations']) -# fname = f'OM_PLOT_minimization.yaml' -# file = open(fname, "w") -# yaml.dump(md, file) -# file.close() -# -# if 'observations' in mon_dict.keys(): -# od = removeKey(mon_dict, ['satellites', 'minimization']) -# fname = f'OM_PLOT_observations.yaml' -# file = open(fname, "w") -# yaml.dump(od, file) -# file.close()