-
Notifications
You must be signed in to change notification settings - Fork 21
Add climo wrapper #539
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
Add climo wrapper #539
Changes from 25 commits
dd48ab1
9c2e202
f74645d
c7c7f1d
8886a26
dfa40e5
dd06814
c4825a1
5467c55
3b29798
e7f4367
542a0f3
e0a230d
6a081be
fc5ce14
fa2d0df
691418f
7e3172a
a5a9afc
b34b538
4ee12af
7773a83
7e3bca6
5318365
6fce01e
868f02a
7126721
892cd67
22d193e
dab3775
2f00fff
e0c9dd5
3fd53f5
c592969
1c60dbd
d785566
3bc0e87
7f629bf
8bbd1e8
9508f4b
a310e5b
f79da97
a188316
1154893
d4ffcd3
0b2f833
93937b2
1359293
49fa0a6
dd10e4b
76cbf57
632a372
b0fc58d
04f0b0d
6ce3dfe
709deb6
c504126
c54cc7f
fd7f999
142513d
129d2dc
7b0bb2b
8a9dbb0
4cf6da6
423699d
c9eae1d
bc52ad8
3a183fc
d00329b
6cf201b
b5c9dcf
a63a058
58d08f9
ff659f7
926ae4c
7f5fe52
4006b4e
65c2905
b3db6c4
20c3c67
9b1cf2e
b347735
2ae2dee
39b521c
edb5f17
137f2b1
fc88604
bb4c298
3934af0
e722c91
1d93dd5
29ffd69
013088b
95bae4f
f6b221c
65d4a62
8886291
4b405e4
be974fc
9d58b14
86e5a63
d153832
9d65240
e0fe628
e3d61c2
b76136a
beab0a8
04adfbe
1fd9686
5ff7586
fa1070f
2d2840f
e4b8066
dc16413
9a04a13
e0f754e
b2f6ac0
2472d3d
16ff1f1
cc18f08
9e509f8
87dee76
a751f29
c779ae6
2ea1a62
30a024d
8280676
2d97382
a752650
2440bcc
fb1ddd6
958cf45
008cf24
cc42140
2a962f6
0c07ebc
1211192
7f650b6
4010ce3
ed18d34
0e0b752
7dd313c
4bdcff9
6117d24
dbef853
5dabfd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,9 @@ | |
|
|
||
| from .mask_atmos_plevel import mask_atmos_plevel_subtool | ||
| from .generate_time_averages.generate_time_averages import generate | ||
| from .generate_time_averages.wrapper import generate_wrapper | ||
| from .regrid_xy.regrid_xy import regrid_xy | ||
| from .generate_time_averages.combine import combine | ||
|
|
||
| @click.group(help=click.style(" - app subcommands", fg=(250,154,90))) | ||
| def app_cli(): | ||
|
|
@@ -111,5 +113,77 @@ def gen_time_averages(inf, outf, pkg, var, unwgt, avg_type): | |
| generate(inf, outf, pkg, var, unwgt, avg_type) | ||
| click.echo(f'Finished in total time {round(time.perf_counter() - start_time , 2)} second(s)') | ||
|
|
||
| @app_cli.command() | ||
| @click.option("--cycle-point", | ||
| type = str, | ||
| required = True, | ||
| help = "Beginning cycle-point in ISO8601") | ||
| @click.option("--dir", | ||
| type = str, | ||
| required = True, | ||
| help = "Root directory containing the shards") | ||
| @click.option("--sources", | ||
| type = str, | ||
| required = True, | ||
| help = "Sources (history file) input file, comma-separated") | ||
| @click.option("--output-interval", | ||
| type = str, | ||
| required = True, | ||
| help = "ISO interval of the desired climatology") | ||
| @click.option("--input-interval", | ||
| type = str, | ||
| required = True, | ||
| help = "ISO interval of the input timeseries") | ||
| @click.option("--grid", | ||
| type = str, | ||
| required = True, | ||
| help = "Grid label corresponding to the shards directory (e.g. 'native' and 'regrid-xy/180_288.conserve_order2'") | ||
| @click.option("--frequency", | ||
| type = str, | ||
| required = True, | ||
| help = "Frequency of desired climatology: 'mon' or 'yr'") | ||
| def gen_time_averages_wrapper(cycle_point, dir, sources, output_interval, input_interval, grid, frequency): | ||
| """ | ||
| Wrapper for climatology tool. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better description needed |
||
| Timeaverages all variables for a desired cycle point, source, and grid. | ||
| """ | ||
| sources_list = sources.split(',') | ||
ceblanton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| generate_wrapper(cycle_point, dir, sources_list, output_interval, input_interval, grid, frequency) | ||
|
|
||
| @app_cli.command() | ||
| @click.option("--in-dir", | ||
| type = str, | ||
| required = True, | ||
| help = "Input directory") | ||
| @click.option("--out-dir", | ||
| type = str, | ||
| required = True, | ||
| help = "Output directory") | ||
| @click.option("--component", | ||
| type = str, | ||
| required = True, | ||
| help = "Component name to combine") | ||
| @click.option("--begin", | ||
| type = str, | ||
| required = True, | ||
| help = "Beginning year") | ||
| @click.option("--end", | ||
| type = str, | ||
| required = True, | ||
| help = "Ending year") | ||
| @click.option("--frequency", | ||
| type = str, | ||
| required = True, | ||
| help = "Climatology frequency; 'mon' or 'yr'") | ||
| @click.option("--interval", | ||
| type = str, | ||
| required = True, | ||
| help = "Climatology interval in ISO8601") | ||
| def combine_time_averages(in_dir, out_dir, component, begin, end, frequency, interval): | ||
| """ | ||
| Combine per-variable climatologies into one file | ||
| """ | ||
| combine(in_dir, out_dir, component, begin, end, frequency, interval) | ||
|
|
||
| if __name__ == "__main__": | ||
ceblanton marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| app_cli() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| '''required for generate_time_averages module import functionality''' | ||
| __all__ = ['generate_time_averages', 'timeAverager', | ||
| __all__ = ['generate_time_averages', 'timeAverager', 'wrapper', | ||
| 'frenctoolsTimeAverager', 'cdoTimeAverager', 'frepytoolsTimeAverager'] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| import logging | ||
| from pathlib import Path | ||
| import glob | ||
| import os | ||
| import subprocess | ||
| import metomi.isodatetime.parsers | ||
|
|
||
| fre_logger = logging.getLogger(__name__) | ||
| duration_parser = metomi.isodatetime.parsers.DurationParser() | ||
|
|
||
| def form_bronx_directory_name(frequency: str, interval: str) -> str: | ||
| """ | ||
| Form the legacy Bronx timeaverage directory name | ||
| given a frequency and interval. | ||
| :param frequency: Frequency of the climatology | ||
| :type frequency: 'mon' or 'yr' | ||
| :param interval: Interval of the climatology | ||
| :type interval: ISO8601 duration | ||
| :return: Corresponding Bronx directory name | ||
| :rtype: str | ||
| """ | ||
|
|
||
| if frequency == "mon": | ||
| frequency_label = "monthly" | ||
| elif frequency == "yr": | ||
| frequency_label = "annual" | ||
| else: | ||
| raise ValueError(f"Frequency '{frequency}' not recognized") | ||
| interval_object = duration_parser.parse(interval) | ||
| return frequency_label + '_' + str(interval_object.years) + 'yr' | ||
|
|
||
|
|
||
| def check_glob(target: str) -> None: | ||
| """ | ||
| Verify that at least one file is resolved by the glob. | ||
| Raises FileNotFoundError if no files are found. | ||
| :param target: Glob target to resolve | ||
| :type target: str | ||
| :rtype: None | ||
| """ | ||
| files = glob.glob(target) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic can be de-nested |
||
| if len(files) >= 1: | ||
| fre_logger.debug(f"{target} has {len(files)} files") | ||
| else: | ||
| raise FileNotFoundError(f"{target} resolves to no files") | ||
|
|
||
|
|
||
| def combine(root_in_dir: str, root_out_dir: str, component: str, begin: str, end: str, frequency: str, interval: str) -> None: | ||
| """ | ||
| Combine per-variable climatologies into one file. | ||
| :param root_in_dir: Root timeaverage shards directory, up to the "av" | ||
| :param type: str | ||
| :param root_out_dir: Root output postprocess directory, up to the "pp" | ||
| :param type: str | ||
| :param component: Component to process | ||
| :param type: str | ||
| :param begin: Beginning of the climatology | ||
ilaflott marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| :param type: YYYY | ||
| :param end: Ending of the climatology | ||
ilaflott marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| :param type: YYYY | ||
| :param frequency: Sampling type of the climatology | ||
| :param type: 'mon' or 'yr' | ||
| :param interval: Length of the climatology | ||
| :param type: ISO8601 duration | ||
ceblanton marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| :rtype: None | ||
ceblanton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """ | ||
| if frequency == "yr": | ||
| frequency_iso = "P1Y" | ||
| elif frequency == "mon": | ||
| frequency_iso = "P1M" | ||
| else: | ||
| raise ValueError(f"Frequency '{frequency}' not known") | ||
| outdir = Path(root_out_dir) / component / "av" / form_bronx_directory_name(frequency, interval) | ||
| fre_logger.debug(f"Output dir = '{outdir}'") | ||
| outdir.mkdir(exist_ok=True, parents=True) | ||
|
|
||
| if begin == end: | ||
| date_string = f"{begin:04d}" | ||
| else: | ||
| date_string = f"{begin:04d}-{end:04d}" | ||
|
|
||
| indir = Path(root_in_dir) / frequency_iso / interval | ||
| fre_logger.debug(f"Input dir = '{indir}'") | ||
| gotta_go_back_here = os.getcwd() | ||
| os.chdir(indir) | ||
|
|
||
| if frequency == 'yr': | ||
| source = component + '.' + date_string + '.*.nc' | ||
| target = component + '.' + date_string + '.nc' | ||
| check_glob(source) | ||
| subprocess.run(['cdo', '-O', 'merge', source, target], check=True) | ||
| fre_logger.debug(f"Output file created: {target}") | ||
| fre_logger.debug(f"Copying to {outdir}") | ||
| subprocess.run(['cp', '-v', target, outdir], check=True) | ||
| elif frequency == 'mon': | ||
| for MM in range(1,13): | ||
| source = f"{component}.{date_string}.*.{MM:02d}.nc" | ||
| target = f"{component}.{date_string}.{MM:02d}.nc" | ||
| check_glob(source) | ||
| subprocess.run(['cdo', '-O', 'merge', source, target], check=True) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should use the |
||
| fre_logger.debug(f"Output file created: {target}") | ||
| fre_logger.debug(f"Copying to {outdir}") | ||
| subprocess.run(['cp', '-v', target, outdir], check=True) | ||
| else: | ||
| raise ValueError(f"Frequency '{frequency}' not known") | ||
|
|
||
| os.chdir(gotta_go_back_here) | ||
ilaflott marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
ilaflott marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| import pytest | ||
| import subprocess | ||
| from pathlib import Path | ||
| import xarray as xr | ||
| #import metomi.isodatetimeparsers.parsers | ||
|
|
||
| from .. import combine | ||
ceblanton marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| @pytest.fixture() | ||
| def create_annual_per_variable_climatologies(tmp_path): | ||
| """ | ||
| Create per-variable climatologies. | ||
| in/atmos/P1Y/P2Y/alb_sfc.nc | ||
| in/atmos/P1Y/P2Y/aliq.nc | ||
ilaflott marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| """ | ||
| # path to input files | ||
| input_file_dir = Path('fre/tests/test_files/climatology/outputs/annual') | ||
| input_files = [ | ||
| input_file_dir / 'atmos.1980-1981.alb_sfc.cdl', | ||
| input_file_dir / 'atmos.1980-1981.aliq.cdl' | ||
| ] | ||
| for file_ in input_files: | ||
| assert file_.exists() | ||
|
|
||
| # write netcdf files | ||
| output_dir = Path(tmp_path, 'in', 'atmos', 'P1Y', 'P2Y') | ||
| output_dir.mkdir(parents=True) | ||
|
|
||
| # write netcdfs from the cdfs | ||
| for file_ in input_files: | ||
| output_file = output_dir / file_.stem | ||
| output_file = Path(str(output_file) + '.nc') | ||
ilaflott marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| command = ['ncgen', '-o', output_file, file_] | ||
| sp = subprocess.run(command, check=True) | ||
| assert sp.returncode == 0 | ||
| assert output_file.exists() | ||
|
|
||
| yield tmp_path | ||
|
|
||
| @pytest.fixture() | ||
| def create_monthly_per_variable_climatologies(tmp_path): | ||
| """ | ||
| Create per-variable climatologies. | ||
| in/atmos/P1M/P2Y/alb_sfc.[01-12].nc | ||
ilaflott marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| in/atmos/P1M/P2Y/aliq.[01-12].nc | ||
| """ | ||
| # path to input CDL files | ||
| input_dir = Path('fre/tests/test_files/climatology/outputs/monthly') | ||
|
|
||
| # two variables | ||
| input_basenames = [ | ||
| 'atmos.1980-1981.alb_sfc', | ||
| 'atmos.1980-1981.aliq' | ||
| ] | ||
|
|
||
| # path to test output NC files | ||
| output_dir = Path(tmp_path, 'in', 'atmos', 'P1M', 'P2Y') | ||
| output_dir.mkdir(parents=True) | ||
|
|
||
| # write netcdf files | ||
| for i in range(1,13): | ||
| for name in input_basenames: | ||
| input_file = input_dir / f"{name}.{i:02d}.cdl" | ||
| assert input_file.exists() | ||
|
|
||
| output_file = output_dir / f"{name}.{i:02d}.nc" | ||
| command = ['ncgen', '-o', output_file, input_file] | ||
| sp = subprocess.run(command, check=True) | ||
| assert sp.returncode == 0 | ||
| assert output_file.exists() | ||
|
|
||
| yield tmp_path | ||
|
|
||
| def test_combine_annual_av(create_annual_per_variable_climatologies): | ||
ilaflott marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """ | ||
| Combine per-variable annual climatologies into combined annual climatology file | ||
| """ | ||
|
|
||
| combine.combine(create_annual_per_variable_climatologies / 'in' / 'atmos', create_annual_per_variable_climatologies / 'out', 'atmos', 1980, 1981, 'yr', 'P2Y') | ||
|
|
||
| output_dir = Path(create_annual_per_variable_climatologies, 'out', 'atmos', 'av', 'annual_2yr') | ||
| output_file = output_dir / 'atmos.1980-1981.nc' | ||
|
|
||
| assert output_file.exists() | ||
|
|
||
| def test_combine_monthly_av(create_monthly_per_variable_climatologies): | ||
| """ | ||
| Combine per-variable monthly climatologies into combined monthly climatology file | ||
| """ | ||
|
|
||
| combine.combine(create_monthly_per_variable_climatologies / 'in' / 'atmos', create_monthly_per_variable_climatologies / 'out', 'atmos', 1980, 1981, 'mon', 'P2Y') | ||
|
|
||
| output_dir = Path(create_monthly_per_variable_climatologies, 'out', 'atmos', 'av', 'monthly_2yr') | ||
| for i in range(1,13): | ||
| output_file = output_dir / f'atmos.1980-1981.{i:02d}.nc' | ||
| assert output_file.exists() | ||
Uh oh!
There was an error while loading. Please reload this page.