-
Notifications
You must be signed in to change notification settings - Fork 217
Create JEDI class #2805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
WalterKolczynski-NOAA
merged 54 commits into
NOAA-EMC:develop
from
DavidNew-NOAA:feature/jedi_class
Sep 7, 2024
Merged
Create JEDI class #2805
Changes from 15 commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
72cfc5b
Initial commit
DavidNew-NOAA dddaec2
Merge branch 'develop' into feature/jedi_class
DavidNew-NOAA 2a313b2
Merge branch 'develop' into feature/jedi_class
DavidNew-NOAA b2536c5
Move YAML save out of get_jedi_config method
DavidNew-NOAA a6bc7cc
pynorms
DavidNew-NOAA f5f4d94
Update exglobal_atmens_analysis_letkf.py
DavidNew-NOAA e5b7409
Addressing Rahul's comments
DavidNew-NOAA 0ff0521
Merge branch 'feature/jedi_class' of https://github.com/DavidNew-NOAA…
DavidNew-NOAA e0bd76c
Move jedi.py to its own directory and move logging from jedi.py funct…
DavidNew-NOAA 6617f8b
Merge branch 'develop' into feature/jedi_class
DavidNew-NOAA e85e993
Finished debugging
DavidNew-NOAA 43a6338
pynorms
DavidNew-NOAA ef68fad
Update some comments and logging
DavidNew-NOAA 0d01ad0
pynorms
DavidNew-NOAA d496307
pynorms
DavidNew-NOAA 6b95386
Merge branch 'develop' into feature/jedi_class
DavidNew-NOAA 056d07c
Merge latest develop
DavidNew-NOAA 44f172c
Refactor LETKF observer to work with JEDI class
DavidNew-NOAA 55efedf
Merge branch 'develop' into feature/jedi_class
DavidNew-NOAA e06f736
Fix small bug
DavidNew-NOAA 4da3d34
Merge branch 'develop' into feature/jedi_class
DavidNew-NOAA 3c6d5b5
Redo some things w/ JEDI class to address YAML archiving error
DavidNew-NOAA 79aca48
Merge branch 'develop' into feature/jedi_class
DavidNew-NOAA e0e3f40
pynorms
DavidNew-NOAA a6f4b81
Proposed change to enkf.yaml.j2
DavidNew-NOAA ed2613b
Revert "Proposed change to enkf.yaml.j2"
DavidNew-NOAA 86b604a
Fix error from my misunderstanding of how obs/sol jobs work for LETKF
DavidNew-NOAA 9b70195
Set JCB algorithm depending on whether running in observer-solver mod…
DavidNew-NOAA c279e45
Fix error in JCB prototype file name
DavidNew-NOAA 1e034f7
Address some comments
DavidNew-NOAA 526b961
The perils of copypasta
DavidNew-NOAA d3ecab3
Address comments and update GDAS hash
DavidNew-NOAA 02b3734
Mistakenly had class method marked as static method
DavidNew-NOAA 3d3ccf0
Update
DavidNew-NOAA d5e2fe0
Update jedi.py
DavidNew-NOAA ea97ddb
Merge branch 'develop' into feature/jedi_class
DavidNew-NOAA 16868a8
Update jedi.py
DavidNew-NOAA a423c6f
Update ush/python/pygfs/jedi/jedi.py
DavidNew-NOAA 35c3f49
Update ush/python/pygfs/jedi/jedi.py
DavidNew-NOAA 9a24b48
Update ush/python/pygfs/jedi/jedi.py
DavidNew-NOAA f1cb728
Update ush/python/pygfs/jedi/jedi.py
DavidNew-NOAA 3bb5336
Update ush/python/pygfs/task/atm_analysis.py
DavidNew-NOAA ba19cdf
Update ush/python/pygfs/task/atmens_analysis.py
DavidNew-NOAA 287a949
Update ush/python/pygfs/task/atm_analysis.py
DavidNew-NOAA 934d036
Update ush/python/pygfs/task/atm_analysis.py
DavidNew-NOAA 806010f
Update ush/python/pygfs/task/atm_analysis.py
DavidNew-NOAA 79c4d60
Address final comments
DavidNew-NOAA 50db563
Merge branch 'feature/jedi_class' of https://github.com/DavidNew-NOAA…
DavidNew-NOAA 00874e9
Forgot some comments
DavidNew-NOAA c039881
Final comment block
DavidNew-NOAA fc437a8
pynorms
DavidNew-NOAA 95974c3
Small bug
DavidNew-NOAA bc52fcb
Update ush/python/pygfs/task/atmens_analysis.py
aerorahul f445e28
Update ush/python/pygfs/task/atm_analysis.py
aerorahul File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,4 +21,4 @@ | |
|
|
||
| # Instantiate the atm analysis task | ||
| AtmAnl = AtmAnalysis(config) | ||
| AtmAnl.initialize() | ||
| AtmAnl.initialize_var() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,271 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| import os | ||
| from logging import getLogger | ||
| from typing import List, Dict, Any, Optional | ||
| from jcb import render | ||
| from wxflow import (AttrDict, | ||
| chdir, rm_p, | ||
| parse_j2yaml, | ||
| logit, | ||
| Task, | ||
| Executable, | ||
| WorkflowException) | ||
|
|
||
| logger = getLogger(__name__.split('.')[-1]) | ||
|
|
||
|
|
||
| class JEDI: | ||
|
|
||
| def __init__(self, task_config: AttrDict[str, Any]) -> None: | ||
|
|
||
| # For provenance, save incoming task_config as a private attribute of JEDI object | ||
| self._task_config = task_config | ||
|
|
||
| _exe_name = os.path.basename(task_config.JEDIEXE) | ||
|
|
||
| self.exe = os.path.join(task_config.DATA, _exe_name) | ||
| self.yaml = os.path.join(task_config.DATA, os.path.splitext(_exe_name)[0] + '.yaml') | ||
| self.config = AttrDict() | ||
| self.j2tmpl_dir = os.path.join(task_config.PARMgfs, 'gdas') | ||
|
|
||
| @logit(logger) | ||
| def set_config(self, task_config: AttrDict[str, Any], algorithm: Optional[str] = None) -> AttrDict: | ||
| """Compile a JEDI configuration dictionary from a template file and save to a YAML file | ||
|
|
||
| Parameters | ||
| ---------- | ||
| task_config : AttrDict | ||
| Dictionary of all configuration variables associated with a GDAS task. | ||
| algorithm (optional) : str | ||
| Name of the algorithm used to generate the JEDI configuration dictionary. | ||
| It will override the algorithm set in the task_config.JCB_<>_YAML file. | ||
|
|
||
| Returns | ||
| ---------- | ||
| None | ||
| """ | ||
|
|
||
| if 'JCB_BASE_YAML' in task_config.keys(): | ||
| # Step 1: Fill templates of the JCB base YAML file | ||
| jcb_config = parse_j2yaml(task_config.JCB_BASE_YAML, task_config) | ||
|
|
||
| # Step 2: If algorithm is present then override the algorithm in the JEDI | ||
| # config. Otherwise, if the algorithm J2-YAML is present, fill | ||
| # its templates and merge. | ||
| if algorithm: | ||
| jcb_config['algorithm'] = algorithm | ||
| elif 'JCB_ALGO' in task_config.keys(): | ||
| jcb_config['algorithm'] = task_config.JCB_ALGO | ||
| elif 'JCB_ALGO_YAML' in task_config.keys(): | ||
| jcb_algo_config = parse_j2yaml(task_config.JCB_ALGO_YAML, task_config) | ||
| jcb_config = {**jcb_config, **jcb_algo_config} | ||
|
DavidNew-NOAA marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Step 3: Generate the JEDI YAML using JCB | ||
| self.config = render(jcb_config) | ||
| elif 'JEDIYAML' in task_config.keys(): | ||
| # Generate JEDI YAML without using JCB | ||
| self.config = parse_j2yaml(task_config.JEDIYAML, task_config, | ||
|
WalterKolczynski-NOAA marked this conversation as resolved.
|
||
| searchpath=self.j2tmpl_dir) | ||
| else: | ||
| raise KeyError(f"Task config must contain JCB_BASE_YAML or JEDIYAML") | ||
|
DavidNew-NOAA marked this conversation as resolved.
Outdated
|
||
|
|
||
| @logit(logger) | ||
| def execute(self, task_config: AttrDict[str, Any], aprun_cmd: str, jedi_args: Optional[List] = None) -> None: | ||
| """Execute JEDI application | ||
|
|
||
| Parameters | ||
| ---------- | ||
| task_config: AttrDict | ||
| Attribute-dictionary of all configuration variables associated with a GDAS task. | ||
| aprun_cmd: str | ||
| String comprising the run command for the JEDI executable. | ||
| jedi_args (optional): List | ||
| List of strings comprising optional input arguments for the JEDI executable. | ||
|
|
||
| Returns | ||
| ---------- | ||
| jedi_config: AttrDict | ||
| Attribute-dictionary of JEDI configuration rendered from a template. | ||
| """ | ||
|
|
||
| chdir(task_config.DATA) | ||
|
|
||
| exec_cmd = Executable(aprun_cmd) | ||
| exec_cmd.add_default_arg(self.exe) | ||
| if jedi_args: | ||
| for arg in jedi_args: | ||
| exec_cmd.add_default_arg(arg) | ||
| exec_cmd.add_default_arg(self.yaml) | ||
|
|
||
| try: | ||
| exec_cmd() | ||
| except OSError: | ||
| raise OSError(f"Failed to execute {exec_cmd}") | ||
| except Exception: | ||
| raise WorkflowException(f"An error occured during execution of {exec_cmd}") | ||
|
DavidNew-NOAA marked this conversation as resolved.
Outdated
|
||
|
|
||
| @logit(logger) | ||
| def link_exe(self, task_config: AttrDict[str, Any]) -> None: | ||
| """Link JEDI executable to run directory | ||
|
|
||
| Parameters | ||
| ---------- | ||
| None | ||
|
DavidNew-NOAA marked this conversation as resolved.
Outdated
|
||
|
|
||
| Returns | ||
| ---------- | ||
| None | ||
| """ | ||
|
|
||
| # TODO: linking is not permitted per EE2. Needs work in JEDI to be able to copy the exec. | ||
|
DavidNew-NOAA marked this conversation as resolved.
Outdated
|
||
| logger.warn("Linking is not permitted per EE2.") | ||
| exe_dest = os.path.join(task_config.DATA, os.path.basename(task_config.JEDIEXE)) | ||
| if os.path.exists(exe_dest): | ||
| rm_p(exe_dest) | ||
| os.symlink(task_config.JEDIEXE, exe_dest) | ||
|
DavidNew-NOAA marked this conversation as resolved.
|
||
|
|
||
| @logit(logger) | ||
| def get_obs_dict(self, task_config: AttrDict[str, Any]) -> Dict[str, Any]: | ||
|
DavidNew-NOAA marked this conversation as resolved.
Outdated
|
||
| """Compile a dictionary of observation files to copy | ||
|
|
||
| This method extracts 'observers' from the JEDI yaml and from that list, extracts a list of | ||
| observation files that are to be copied to the run directory | ||
| from the observation input directory | ||
|
|
||
| Parameters | ||
| ---------- | ||
| task_config: AttrDict | ||
| Attribute-dictionary of all configuration variables associated with a GDAS task. | ||
|
|
||
| Returns | ||
| ---------- | ||
| obs_dict: Dict | ||
| a dictionary containing the list of observation files to copy for FileHandler | ||
| """ | ||
|
|
||
| observations = find_value_in_nested_dict(self.config, 'observations') | ||
|
|
||
| copylist = [] | ||
| for ob in observations['observers']: | ||
| obfile = ob['obs space']['obsdatain']['engine']['obsfile'] | ||
| basename = os.path.basename(obfile) | ||
| copylist.append([os.path.join(task_config.COM_OBS, basename), obfile]) | ||
| obs_dict = { | ||
| 'mkdir': [os.path.join(task_config.DATA, 'obs')], | ||
| 'copy': copylist | ||
| } | ||
| return obs_dict | ||
|
|
||
| @logit(logger) | ||
| @staticmethod | ||
| def get_bias_dict(self, task_config: Dict[str, Any]) -> Dict[str, Any]: | ||
| """Compile a dictionary of observation files to copy | ||
|
|
||
| This method extracts 'observers' from the JEDI yaml and from that list, extracts a list of | ||
| observation bias correction files that are to be copied to the run directory | ||
| from the component directory. | ||
| TODO: COM_ATMOS_ANALYSIS_PREV is hardwired here and this method is not appropriate in | ||
| `analysis.py` and should be implemented in the component where this is applicable. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| task_config: AttrDict | ||
| Attribute-dictionary of all configuration variables associated with a GDAS task. | ||
|
|
||
| Returns | ||
| ---------- | ||
| bias_dict: Dict | ||
| a dictionary containing the list of observation bias files to copy for FileHandler | ||
| """ | ||
|
|
||
| observations = find_value_in_nested_dict(self.config, 'observations') | ||
|
|
||
| copylist = [] | ||
| for ob in observations['observers']: | ||
| if 'obs bias' in ob.keys(): | ||
| obfile = ob['obs bias']['input file'] | ||
| obdir = os.path.dirname(obfile) | ||
| basename = os.path.basename(obfile) | ||
| prefix = '.'.join(basename.split('.')[:-2]) | ||
| for file in ['satbias.nc', 'satbias_cov.nc', 'tlapse.txt']: | ||
| bfile = f"{prefix}.{file}" | ||
| copylist.append([os.path.join(task_config.COM_ATMOS_ANALYSIS_PREV, bfile), os.path.join(obdir, bfile)]) | ||
|
DavidNew-NOAA marked this conversation as resolved.
|
||
| # TODO: Why is this specific to ATMOS? | ||
|
|
||
| bias_dict = { | ||
| 'mkdir': [os.path.join(task_config.DATA, 'bc')], | ||
| 'copy': copylist | ||
| } | ||
| return bias_dict | ||
|
|
||
|
|
||
| @logit(logger) | ||
| def find_value_in_nested_dict(nested_dict: Dict, target_key: str) -> Any: | ||
|
DavidNew-NOAA marked this conversation as resolved.
|
||
| """ | ||
| Recursively search through a nested dictionary and return the value for the target key. | ||
| This returns the first target key it finds. So if a key exists in a subsequent | ||
| nested dictionary, it will not be found. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| nested_dict : Dict | ||
| Dictionary to search | ||
| target_key : str | ||
| Key to search for | ||
|
|
||
| Returns | ||
| ------- | ||
| Any | ||
| Value of the target key | ||
|
|
||
| Raises | ||
| ------ | ||
| KeyError | ||
| If key is not found in dictionary | ||
|
|
||
| TODO: if this gives issues due to landing on an incorrect key in the nested | ||
| dictionary, we will have to implement a more concrete method to search for a key | ||
| given a more complete address. See resolved conversations in PR 2387 | ||
|
|
||
| # Example usage: | ||
| nested_dict = { | ||
| 'a': { | ||
| 'b': { | ||
| 'c': 1, | ||
| 'd': { | ||
| 'e': 2, | ||
| 'f': 3 | ||
| } | ||
| }, | ||
| 'g': 4 | ||
| }, | ||
| 'h': { | ||
| 'i': 5 | ||
| }, | ||
| 'j': { | ||
| 'k': 6 | ||
| } | ||
| } | ||
|
|
||
| user_key = input("Enter the key to search for: ") | ||
| result = find_value_in_nested_dict(nested_dict, user_key) | ||
| """ | ||
|
|
||
| if not isinstance(nested_dict, dict): | ||
| raise TypeError(f"Input is not of type(dict)") | ||
|
|
||
| result = nested_dict.get(target_key) | ||
| if result is not None: | ||
| return result | ||
|
|
||
| for value in nested_dict.values(): | ||
| if isinstance(value, dict): | ||
| try: | ||
| result = find_value_in_nested_dict(value, target_key) | ||
| if result is not None: | ||
| return result | ||
| except KeyError: | ||
| pass | ||
|
|
||
| raise KeyError(f"Key '{target_key}' not found in the nested dictionary") | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.