diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..eccebad8 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 9b3fba783744d64d10cfea86ed98a866 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/badges.doctree b/.doctrees/badges.doctree new file mode 100644 index 00000000..d4054b38 Binary files /dev/null and b/.doctrees/badges.doctree differ diff --git a/.doctrees/contributing_to_doc.doctree b/.doctrees/contributing_to_doc.doctree new file mode 100644 index 00000000..94819a6c Binary files /dev/null and b/.doctrees/contributing_to_doc.doctree differ diff --git a/.doctrees/developer_usage.doctree b/.doctrees/developer_usage.doctree new file mode 100644 index 00000000..7a3ee757 Binary files /dev/null and b/.doctrees/developer_usage.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 00000000..a2366bf0 Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/fre.app.doctree b/.doctrees/fre.app.doctree new file mode 100644 index 00000000..21aa70e4 Binary files /dev/null and b/.doctrees/fre.app.doctree differ diff --git a/.doctrees/fre.app.freapp.doctree b/.doctrees/fre.app.freapp.doctree new file mode 100644 index 00000000..2b598806 Binary files /dev/null and b/.doctrees/fre.app.freapp.doctree differ diff --git a/.doctrees/fre.app.generate_time_averages.cdoTimeAverager.doctree b/.doctrees/fre.app.generate_time_averages.cdoTimeAverager.doctree new file mode 100644 index 00000000..b8bf933d Binary files /dev/null and b/.doctrees/fre.app.generate_time_averages.cdoTimeAverager.doctree differ diff --git a/.doctrees/fre.app.generate_time_averages.doctree b/.doctrees/fre.app.generate_time_averages.doctree new file mode 100644 index 00000000..ef898a2f Binary files /dev/null and b/.doctrees/fre.app.generate_time_averages.doctree differ diff --git a/.doctrees/fre.app.generate_time_averages.frenctoolsTimeAverager.doctree b/.doctrees/fre.app.generate_time_averages.frenctoolsTimeAverager.doctree new file mode 100644 index 00000000..2601b1d2 Binary files /dev/null and b/.doctrees/fre.app.generate_time_averages.frenctoolsTimeAverager.doctree differ diff --git a/.doctrees/fre.app.generate_time_averages.frepytoolsTimeAverager.doctree b/.doctrees/fre.app.generate_time_averages.frepytoolsTimeAverager.doctree new file mode 100644 index 00000000..41403a74 Binary files /dev/null and b/.doctrees/fre.app.generate_time_averages.frepytoolsTimeAverager.doctree differ diff --git a/.doctrees/fre.app.generate_time_averages.generate_time_averages.doctree b/.doctrees/fre.app.generate_time_averages.generate_time_averages.doctree new file mode 100644 index 00000000..fd138d7c Binary files /dev/null and b/.doctrees/fre.app.generate_time_averages.generate_time_averages.doctree differ diff --git a/.doctrees/fre.app.generate_time_averages.tests.doctree b/.doctrees/fre.app.generate_time_averages.tests.doctree new file mode 100644 index 00000000..09d780e9 Binary files /dev/null and b/.doctrees/fre.app.generate_time_averages.tests.doctree differ diff --git a/.doctrees/fre.app.generate_time_averages.tests.test_generate_time_averages.doctree b/.doctrees/fre.app.generate_time_averages.tests.test_generate_time_averages.doctree new file mode 100644 index 00000000..431d93ef Binary files /dev/null and b/.doctrees/fre.app.generate_time_averages.tests.test_generate_time_averages.doctree differ diff --git a/.doctrees/fre.app.generate_time_averages.timeAverager.doctree b/.doctrees/fre.app.generate_time_averages.timeAverager.doctree new file mode 100644 index 00000000..a9f58dbf Binary files /dev/null and b/.doctrees/fre.app.generate_time_averages.timeAverager.doctree differ diff --git a/.doctrees/fre.app.mask_atmos_plevel.doctree b/.doctrees/fre.app.mask_atmos_plevel.doctree new file mode 100644 index 00000000..1abaf802 Binary files /dev/null and b/.doctrees/fre.app.mask_atmos_plevel.doctree differ diff --git a/.doctrees/fre.app.regrid_xy.doctree b/.doctrees/fre.app.regrid_xy.doctree new file mode 100644 index 00000000..7280a324 Binary files /dev/null and b/.doctrees/fre.app.regrid_xy.doctree differ diff --git a/.doctrees/fre.app.regrid_xy.regrid_xy.doctree b/.doctrees/fre.app.regrid_xy.regrid_xy.doctree new file mode 100644 index 00000000..cd089731 Binary files /dev/null and b/.doctrees/fre.app.regrid_xy.regrid_xy.doctree differ diff --git a/.doctrees/fre.catalog.doctree b/.doctrees/fre.catalog.doctree new file mode 100644 index 00000000..95457f3b Binary files /dev/null and b/.doctrees/fre.catalog.doctree differ diff --git a/.doctrees/fre.catalog.frecatalog.doctree b/.doctrees/fre.catalog.frecatalog.doctree new file mode 100644 index 00000000..cbe12d89 Binary files /dev/null and b/.doctrees/fre.catalog.frecatalog.doctree differ diff --git a/.doctrees/fre.catalog.tests.doctree b/.doctrees/fre.catalog.tests.doctree new file mode 100644 index 00000000..1b976f93 Binary files /dev/null and b/.doctrees/fre.catalog.tests.doctree differ diff --git a/.doctrees/fre.catalog.tests.test_fre_catalog.doctree b/.doctrees/fre.catalog.tests.test_fre_catalog.doctree new file mode 100644 index 00000000..37530c94 Binary files /dev/null and b/.doctrees/fre.catalog.tests.test_fre_catalog.doctree differ diff --git a/.doctrees/fre.check.doctree b/.doctrees/fre.check.doctree new file mode 100644 index 00000000..3b46dae0 Binary files /dev/null and b/.doctrees/fre.check.doctree differ diff --git a/.doctrees/fre.check.frecheck.doctree b/.doctrees/fre.check.frecheck.doctree new file mode 100644 index 00000000..615cfb1d Binary files /dev/null and b/.doctrees/fre.check.frecheck.doctree differ diff --git a/.doctrees/fre.check.frecheckexample.doctree b/.doctrees/fre.check.frecheckexample.doctree new file mode 100644 index 00000000..bc300b6d Binary files /dev/null and b/.doctrees/fre.check.frecheckexample.doctree differ diff --git a/.doctrees/fre.cmor.cmor_mixer.doctree b/.doctrees/fre.cmor.cmor_mixer.doctree new file mode 100644 index 00000000..bc1a7c79 Binary files /dev/null and b/.doctrees/fre.cmor.cmor_mixer.doctree differ diff --git a/.doctrees/fre.cmor.doctree b/.doctrees/fre.cmor.doctree new file mode 100644 index 00000000..d17b0990 Binary files /dev/null and b/.doctrees/fre.cmor.doctree differ diff --git a/.doctrees/fre.cmor.frecmor.doctree b/.doctrees/fre.cmor.frecmor.doctree new file mode 100644 index 00000000..b0caa220 Binary files /dev/null and b/.doctrees/fre.cmor.frecmor.doctree differ diff --git a/.doctrees/fre.cmor.tests.doctree b/.doctrees/fre.cmor.tests.doctree new file mode 100644 index 00000000..67f21d3a Binary files /dev/null and b/.doctrees/fre.cmor.tests.doctree differ diff --git a/.doctrees/fre.cmor.tests.test_cmor_run_subtool.doctree b/.doctrees/fre.cmor.tests.test_cmor_run_subtool.doctree new file mode 100644 index 00000000..f54b4456 Binary files /dev/null and b/.doctrees/fre.cmor.tests.test_cmor_run_subtool.doctree differ diff --git a/.doctrees/fre.doctree b/.doctrees/fre.doctree new file mode 100644 index 00000000..98f51c19 Binary files /dev/null and b/.doctrees/fre.doctree differ diff --git a/.doctrees/fre.fre.doctree b/.doctrees/fre.fre.doctree new file mode 100644 index 00000000..09457009 Binary files /dev/null and b/.doctrees/fre.fre.doctree differ diff --git a/.doctrees/fre.lazy_group.doctree b/.doctrees/fre.lazy_group.doctree new file mode 100644 index 00000000..cbb0e0c5 Binary files /dev/null and b/.doctrees/fre.lazy_group.doctree differ diff --git a/.doctrees/fre.list.doctree b/.doctrees/fre.list.doctree new file mode 100644 index 00000000..15d5a4b4 Binary files /dev/null and b/.doctrees/fre.list.doctree differ diff --git a/.doctrees/fre.list.frelist.doctree b/.doctrees/fre.list.frelist.doctree new file mode 100644 index 00000000..64d0d374 Binary files /dev/null and b/.doctrees/fre.list.frelist.doctree differ diff --git a/.doctrees/fre.list.frelistexample.doctree b/.doctrees/fre.list.frelistexample.doctree new file mode 100644 index 00000000..93f20692 Binary files /dev/null and b/.doctrees/fre.list.frelistexample.doctree differ diff --git a/.doctrees/fre.make.createCheckout.doctree b/.doctrees/fre.make.createCheckout.doctree new file mode 100644 index 00000000..5f003a2d Binary files /dev/null and b/.doctrees/fre.make.createCheckout.doctree differ diff --git a/.doctrees/fre.make.createCompile.doctree b/.doctrees/fre.make.createCompile.doctree new file mode 100644 index 00000000..ee8e69b7 Binary files /dev/null and b/.doctrees/fre.make.createCompile.doctree differ diff --git a/.doctrees/fre.make.createDocker.doctree b/.doctrees/fre.make.createDocker.doctree new file mode 100644 index 00000000..c0f1d221 Binary files /dev/null and b/.doctrees/fre.make.createDocker.doctree differ diff --git a/.doctrees/fre.make.createMakefile.doctree b/.doctrees/fre.make.createMakefile.doctree new file mode 100644 index 00000000..e80b9cb8 Binary files /dev/null and b/.doctrees/fre.make.createMakefile.doctree differ diff --git a/.doctrees/fre.make.doctree b/.doctrees/fre.make.doctree new file mode 100644 index 00000000..47f4806f Binary files /dev/null and b/.doctrees/fre.make.doctree differ diff --git a/.doctrees/fre.make.fremake.doctree b/.doctrees/fre.make.fremake.doctree new file mode 100644 index 00000000..fd4b5b3e Binary files /dev/null and b/.doctrees/fre.make.fremake.doctree differ diff --git a/.doctrees/fre.make.runFremake.doctree b/.doctrees/fre.make.runFremake.doctree new file mode 100644 index 00000000..8290fb11 Binary files /dev/null and b/.doctrees/fre.make.runFremake.doctree differ diff --git a/.doctrees/fre.make.tests.doctree b/.doctrees/fre.make.tests.doctree new file mode 100644 index 00000000..36154b3a Binary files /dev/null and b/.doctrees/fre.make.tests.doctree differ diff --git a/.doctrees/fre.make.tests.test_create_makefile.doctree b/.doctrees/fre.make.tests.test_create_makefile.doctree new file mode 100644 index 00000000..60a21626 Binary files /dev/null and b/.doctrees/fre.make.tests.test_create_makefile.doctree differ diff --git a/.doctrees/fre.pp.checkoutScript.doctree b/.doctrees/fre.pp.checkoutScript.doctree new file mode 100644 index 00000000..cb187b0f Binary files /dev/null and b/.doctrees/fre.pp.checkoutScript.doctree differ diff --git a/.doctrees/fre.pp.configure_script_xml.doctree b/.doctrees/fre.pp.configure_script_xml.doctree new file mode 100644 index 00000000..0166c6c6 Binary files /dev/null and b/.doctrees/fre.pp.configure_script_xml.doctree differ diff --git a/.doctrees/fre.pp.configure_script_yaml.doctree b/.doctrees/fre.pp.configure_script_yaml.doctree new file mode 100644 index 00000000..e552403e Binary files /dev/null and b/.doctrees/fre.pp.configure_script_yaml.doctree differ diff --git a/.doctrees/fre.pp.doctree b/.doctrees/fre.pp.doctree new file mode 100644 index 00000000..4c246c3c Binary files /dev/null and b/.doctrees/fre.pp.doctree differ diff --git a/.doctrees/fre.pp.frepp.doctree b/.doctrees/fre.pp.frepp.doctree new file mode 100644 index 00000000..9662390a Binary files /dev/null and b/.doctrees/fre.pp.frepp.doctree differ diff --git a/.doctrees/fre.pp.install.doctree b/.doctrees/fre.pp.install.doctree new file mode 100644 index 00000000..4c9d7bda Binary files /dev/null and b/.doctrees/fre.pp.install.doctree differ diff --git a/.doctrees/fre.pp.run.doctree b/.doctrees/fre.pp.run.doctree new file mode 100644 index 00000000..bb7208ac Binary files /dev/null and b/.doctrees/fre.pp.run.doctree differ diff --git a/.doctrees/fre.pp.status.doctree b/.doctrees/fre.pp.status.doctree new file mode 100644 index 00000000..f8821075 Binary files /dev/null and b/.doctrees/fre.pp.status.doctree differ diff --git a/.doctrees/fre.pp.tests.doctree b/.doctrees/fre.pp.tests.doctree new file mode 100644 index 00000000..e4afad9b Binary files /dev/null and b/.doctrees/fre.pp.tests.doctree differ diff --git a/.doctrees/fre.pp.tests.test_configure_script_yaml.doctree b/.doctrees/fre.pp.tests.test_configure_script_yaml.doctree new file mode 100644 index 00000000..69544775 Binary files /dev/null and b/.doctrees/fre.pp.tests.test_configure_script_yaml.doctree differ diff --git a/.doctrees/fre.pp.tests.test_rose_quoting.doctree b/.doctrees/fre.pp.tests.test_rose_quoting.doctree new file mode 100644 index 00000000..c22dd33d Binary files /dev/null and b/.doctrees/fre.pp.tests.test_rose_quoting.doctree differ diff --git a/.doctrees/fre.pp.validate.doctree b/.doctrees/fre.pp.validate.doctree new file mode 100644 index 00000000..dd45f653 Binary files /dev/null and b/.doctrees/fre.pp.validate.doctree differ diff --git a/.doctrees/fre.pp.wrapper.doctree b/.doctrees/fre.pp.wrapper.doctree new file mode 100644 index 00000000..b3cbb17f Binary files /dev/null and b/.doctrees/fre.pp.wrapper.doctree differ diff --git a/.doctrees/fre.run.doctree b/.doctrees/fre.run.doctree new file mode 100644 index 00000000..31206658 Binary files /dev/null and b/.doctrees/fre.run.doctree differ diff --git a/.doctrees/fre.run.frerun.doctree b/.doctrees/fre.run.frerun.doctree new file mode 100644 index 00000000..fe0cb785 Binary files /dev/null and b/.doctrees/fre.run.frerun.doctree differ diff --git a/.doctrees/fre.run.frerunexample.doctree b/.doctrees/fre.run.frerunexample.doctree new file mode 100644 index 00000000..5a21b7a3 Binary files /dev/null and b/.doctrees/fre.run.frerunexample.doctree differ diff --git a/.doctrees/fre.test.doctree b/.doctrees/fre.test.doctree new file mode 100644 index 00000000..0ab9c073 Binary files /dev/null and b/.doctrees/fre.test.doctree differ diff --git a/.doctrees/fre.test.fretest.doctree b/.doctrees/fre.test.fretest.doctree new file mode 100644 index 00000000..7c6e1085 Binary files /dev/null and b/.doctrees/fre.test.fretest.doctree differ diff --git a/.doctrees/fre.test.fretestexample.doctree b/.doctrees/fre.test.fretestexample.doctree new file mode 100644 index 00000000..9c971dfe Binary files /dev/null and b/.doctrees/fre.test.fretestexample.doctree differ diff --git a/.doctrees/fre.tests.doctree b/.doctrees/fre.tests.doctree new file mode 100644 index 00000000..efcb08ea Binary files /dev/null and b/.doctrees/fre.tests.doctree differ diff --git a/.doctrees/fre.tests.test_fre_app_cli.doctree b/.doctrees/fre.tests.test_fre_app_cli.doctree new file mode 100644 index 00000000..9f1ccc05 Binary files /dev/null and b/.doctrees/fre.tests.test_fre_app_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_catalog_cli.doctree b/.doctrees/fre.tests.test_fre_catalog_cli.doctree new file mode 100644 index 00000000..6a919d8b Binary files /dev/null and b/.doctrees/fre.tests.test_fre_catalog_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_check_cli.doctree b/.doctrees/fre.tests.test_fre_check_cli.doctree new file mode 100644 index 00000000..4999be3d Binary files /dev/null and b/.doctrees/fre.tests.test_fre_check_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_cli.doctree b/.doctrees/fre.tests.test_fre_cli.doctree new file mode 100644 index 00000000..0da4a685 Binary files /dev/null and b/.doctrees/fre.tests.test_fre_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_cmor_cli.doctree b/.doctrees/fre.tests.test_fre_cmor_cli.doctree new file mode 100644 index 00000000..e123ad0e Binary files /dev/null and b/.doctrees/fre.tests.test_fre_cmor_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_list_cli.doctree b/.doctrees/fre.tests.test_fre_list_cli.doctree new file mode 100644 index 00000000..558288a4 Binary files /dev/null and b/.doctrees/fre.tests.test_fre_list_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_make_cli.doctree b/.doctrees/fre.tests.test_fre_make_cli.doctree new file mode 100644 index 00000000..12fbe45e Binary files /dev/null and b/.doctrees/fre.tests.test_fre_make_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_pp_cli.doctree b/.doctrees/fre.tests.test_fre_pp_cli.doctree new file mode 100644 index 00000000..f1ca42c9 Binary files /dev/null and b/.doctrees/fre.tests.test_fre_pp_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_run_cli.doctree b/.doctrees/fre.tests.test_fre_run_cli.doctree new file mode 100644 index 00000000..90056855 Binary files /dev/null and b/.doctrees/fre.tests.test_fre_run_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_test_cli.doctree b/.doctrees/fre.tests.test_fre_test_cli.doctree new file mode 100644 index 00000000..95b6a904 Binary files /dev/null and b/.doctrees/fre.tests.test_fre_test_cli.doctree differ diff --git a/.doctrees/fre.tests.test_fre_yamltools_cli.doctree b/.doctrees/fre.tests.test_fre_yamltools_cli.doctree new file mode 100644 index 00000000..3f9ca539 Binary files /dev/null and b/.doctrees/fre.tests.test_fre_yamltools_cli.doctree differ diff --git a/.doctrees/fre.yamltools.combine_yamls.doctree b/.doctrees/fre.yamltools.combine_yamls.doctree new file mode 100644 index 00000000..51769bed Binary files /dev/null and b/.doctrees/fre.yamltools.combine_yamls.doctree differ diff --git a/.doctrees/fre.yamltools.doctree b/.doctrees/fre.yamltools.doctree new file mode 100644 index 00000000..0263e915 Binary files /dev/null and b/.doctrees/fre.yamltools.doctree differ diff --git a/.doctrees/fre.yamltools.freyamltools.doctree b/.doctrees/fre.yamltools.freyamltools.doctree new file mode 100644 index 00000000..08744558 Binary files /dev/null and b/.doctrees/fre.yamltools.freyamltools.doctree differ diff --git a/.doctrees/fre.yamltools.freyamltoolsexample.doctree b/.doctrees/fre.yamltools.freyamltoolsexample.doctree new file mode 100644 index 00000000..4dcd544d Binary files /dev/null and b/.doctrees/fre.yamltools.freyamltoolsexample.doctree differ diff --git a/.doctrees/fre.yamltools.tests.doctree b/.doctrees/fre.yamltools.tests.doctree new file mode 100644 index 00000000..2eb4cbe1 Binary files /dev/null and b/.doctrees/fre.yamltools.tests.doctree differ diff --git a/.doctrees/fre.yamltools.tests.test_combine_yamls.doctree b/.doctrees/fre.yamltools.tests.test_combine_yamls.doctree new file mode 100644 index 00000000..a3c5734e Binary files /dev/null and b/.doctrees/fre.yamltools.tests.test_combine_yamls.doctree differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 00000000..59837475 Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/modules.doctree b/.doctrees/modules.doctree new file mode 100644 index 00000000..13138b10 Binary files /dev/null and b/.doctrees/modules.doctree differ diff --git a/.doctrees/setup.doctree b/.doctrees/setup.doctree new file mode 100644 index 00000000..6e272bca Binary files /dev/null and b/.doctrees/setup.doctree differ diff --git a/.doctrees/usage.doctree b/.doctrees/usage.doctree new file mode 100644 index 00000000..2e7fc934 Binary files /dev/null and b/.doctrees/usage.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/_images/cov_badge.svg b/_images/cov_badge.svg new file mode 100644 index 00000000..4466895b --- /dev/null +++ b/_images/cov_badge.svg @@ -0,0 +1 @@ +coverage: 59.91%coverage59.91% \ No newline at end of file diff --git a/_images/pytest_badge.svg b/_images/pytest_badge.svg new file mode 100644 index 00000000..0c68c6ed --- /dev/null +++ b/_images/pytest_badge.svg @@ -0,0 +1 @@ +tests: 132tests132 \ No newline at end of file diff --git a/_sources/badges.rst.txt b/_sources/badges.rst.txt new file mode 100644 index 00000000..93fbb81b --- /dev/null +++ b/_sources/badges.rst.txt @@ -0,0 +1,6 @@ +====== +Badges +====== + +.. image:: cov_badge.svg +.. image:: pytest_badge.svg diff --git a/_sources/contributing_to_doc.rst.txt b/_sources/contributing_to_doc.rst.txt new file mode 100644 index 00000000..edaf8476 --- /dev/null +++ b/_sources/contributing_to_doc.rst.txt @@ -0,0 +1,44 @@ +=========================== +Documentation-Documentation +=========================== + +Welcome to ``fre-cli``'s Documentation-documentation- where we document how the documentation is +documented + +How to Contribute to ``fre-cli``'s documentation +================================================ + + + +1. fork and poke at the settings + + * Fork ``fre-cli`` on github + * On github, navigate to your ``fre-cli`` fork, and click “settings” + * In “settings”, click “pages” + * In “pages”, under “build and deployment”, make sure “source” is set to “Deploy from a branch” + * Under that, find “Branch”, make sure the branch selected is ``gh-pages``. + * The branch ``gh-pages`` is “automagic”- i.e. do not change anything about it nor create a new one, + nor interact with anything in that branch directly. + + +2. enable workflows for your fork + + * note: this step may depend on user-specific settings! + * Back on top where you found “settings”, find and click “actions” to the left + * Enable running the workflow actions assoc with the ``fre-cli`` repo under ``.github/workflows`` + + +3. run your forks first workflow + + * The documentation builds as the last steps to ``create_test_conda_env.yml`` when theres a push to ``main``. + To get your first workflow run on your fork, comment out the ``github.ref == ‘refs/heads/main’`` bit + so that it runs when you push to any branch, and make a small, trivial, commit somewhere to your + remote fork. + * You should be able to find the deployed webpage from a successful workflow at + https://your_username.github.io/fre-cli (if you did not change the fork’s name from ``fre-cli``, that is). + * If you’re only editing docs, you can make the turn-around time on your workflow ~3 min faster by + commenting-out the ``pylint`` and ``pytest`` steps in ``create_test_conda_env.yml``, and disable running the + ``build_conda.yml`` workflow + + + diff --git a/_sources/developer_usage.rst.txt b/_sources/developer_usage.rst.txt new file mode 100644 index 00000000..7ab3376f --- /dev/null +++ b/_sources/developer_usage.rst.txt @@ -0,0 +1,118 @@ +=============== +Developer Usage +=============== + +Developers are free to use the user guide above to familiarize with the CLI and save time from +having to install any dependencies, but development within a Conda environment is heavily +recommended regardless. + +Gain access to the repository with ``git clone --recursive git@github.com:NOAA-GFDL/fre-cli.git`` or your fork's +link (recommended) and an SSH RSA key. Once inside the repository, developers can test local changes +by running a ``pip install .`` inside of the root directory to install the ``fre-cli`` package locally +with the newest local changes. Test as a normal user would use the CLI. + + +Adding New Tools +================ + + +From Other Repositories +----------------------- + +Currently, the solution to this task is to approach it using Conda packages. The tool that is being +added must reside within a repository that contains a ``meta.yaml`` that includes Conda dependencies +like the one in this repository and ideally a ``setup.py`` (may be subject to change due to deprecation) +that may include any potentially needed pip dependencies + +* Once published as a Conda package, ideally on the `NOAA-GFDL conda channel `_, + an addition can be made to the ``run`` section under ``requirements`` in ``meta.yaml`` of the ``fre-cli`` + following the syntax ``channel::package`` + +* On pushes to the main branch, the package located at https://anaconda.org/NOAA-GFDL/fre-cli will automatically + be updated using by the workflow defined in ``.github/workflows/publish_conda.yml`` + + +Checklist +--------- + +For the new tool you are trying to develop, there are a few criteria to satisfy + +1. Create a subdirectory for the tool group inside the ``fre/`` directory; i.e. ``fre/`` + +2. Add an ``__init__.py`` inside of ``fre/`` + +* typically this file should be empty, but it depends on the ````'s needs +* even if empty, the file facillitates module importability and must be present + +3. Add a file named ``fre//fre.py``. This will serve as the main entry point for ``fre`` + into the ````'s functionality + +4. Add a ``click`` group named after ```` within ``fre//fre.py`` + +* This ``click`` group will contain all the functionality under the ```` + +5. Create separate files as needed for different commands; do not code out the full + implemetation of ```` inside of a ``click`` command within ``fre//fre.py``. + +* better yet, consider what structure your tool may need in the future for maintainability's sake +* if you need, specify a ```` like ``fre//``. ``fre/app`` currently has + this structure + +6. Be sure to import the contents of the needed subcommand scripts inside of ``fre.py`` + +* i.e. from ``fre..toolCommandScript import *`` + +7. At this point, you can copy and paste the parts of your main ``click`` command from its script + into ``fre.py`` when implementing the function reflective of the command function + +* Everything will remain the same; i.e. arguments, options, etc. + +* However, this new function within ``fre.py`` must a new line after the arguments, options, + and other command components; ``@click.pass_context`` + +* Along with this, a new argument ``context`` must now be added to the parameters of the command + (preferably at the beginning, but it won't break it if it's not) + +8. From here, all that needs to be added after defining the command with a name is + ``context.forward(mainFunctionOfToolCommand)``, and done! + +9. The last step is to replicate the command in the same way as done in ``fre.py`` + inside of ``fre.py``, but make sure to add ``from fre import `` and + ``from fre. import *`` + +Please refer to this issue when encountering naming issues: +`NOAA-GFDL#31 `_ + + +Example ``fre/`` Directory Structure +------------------------------------ + +``fre/`` +├── ``__init__.py`` +├── ``fre.py`` +├── ``fre`` +│ ├── ``__init__.py`` +│ ├── ``toolCommandScript.py`` +│ └── ``fre.py`` + + +``MANIFEST.in`` +--------------- + +In the case where non-python files like templates, examples, and outputs are to be included in the ``fre-cli`` package, +``MANIFEST.in`` can provide the solution. Ensure that the file exists within the correct folder, and add a line to the +``MANIFEST.in`` file saying something like ``include fre/fre/fileName.fileExtension`` + +* For more efficiency, if there are multiple files of the same type needed, the ``MANIFEST.in`` addition can be something + like ``recursive-include fre/fre *.fileExtension`` which would recursively include every file matching that + ``fileExtension`` within the specified directory and its respective subdirectories. + + +Adding Documentation +-------------------- + +see section "Documentation-Documentation" + + + + diff --git a/_sources/fre.app.freapp.rst.txt b/_sources/fre.app.freapp.rst.txt new file mode 100644 index 00000000..0f13758c --- /dev/null +++ b/_sources/fre.app.freapp.rst.txt @@ -0,0 +1,7 @@ +fre.app.freapp module +===================== + +.. automodule:: fre.app.freapp + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.generate_time_averages.cdoTimeAverager.rst.txt b/_sources/fre.app.generate_time_averages.cdoTimeAverager.rst.txt new file mode 100644 index 00000000..963996e6 --- /dev/null +++ b/_sources/fre.app.generate_time_averages.cdoTimeAverager.rst.txt @@ -0,0 +1,7 @@ +fre.app.generate\_time\_averages.cdoTimeAverager module +======================================================= + +.. automodule:: fre.app.generate_time_averages.cdoTimeAverager + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.generate_time_averages.frenctoolsTimeAverager.rst.txt b/_sources/fre.app.generate_time_averages.frenctoolsTimeAverager.rst.txt new file mode 100644 index 00000000..e6e28d69 --- /dev/null +++ b/_sources/fre.app.generate_time_averages.frenctoolsTimeAverager.rst.txt @@ -0,0 +1,7 @@ +fre.app.generate\_time\_averages.frenctoolsTimeAverager module +============================================================== + +.. automodule:: fre.app.generate_time_averages.frenctoolsTimeAverager + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.generate_time_averages.frepytoolsTimeAverager.rst.txt b/_sources/fre.app.generate_time_averages.frepytoolsTimeAverager.rst.txt new file mode 100644 index 00000000..1cc3cad5 --- /dev/null +++ b/_sources/fre.app.generate_time_averages.frepytoolsTimeAverager.rst.txt @@ -0,0 +1,7 @@ +fre.app.generate\_time\_averages.frepytoolsTimeAverager module +============================================================== + +.. automodule:: fre.app.generate_time_averages.frepytoolsTimeAverager + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.generate_time_averages.generate_time_averages.rst.txt b/_sources/fre.app.generate_time_averages.generate_time_averages.rst.txt new file mode 100644 index 00000000..cc549144 --- /dev/null +++ b/_sources/fre.app.generate_time_averages.generate_time_averages.rst.txt @@ -0,0 +1,7 @@ +fre.app.generate\_time\_averages.generate\_time\_averages module +================================================================ + +.. automodule:: fre.app.generate_time_averages.generate_time_averages + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.generate_time_averages.rst.txt b/_sources/fre.app.generate_time_averages.rst.txt new file mode 100644 index 00000000..5d0efae2 --- /dev/null +++ b/_sources/fre.app.generate_time_averages.rst.txt @@ -0,0 +1,30 @@ +fre.app.generate\_time\_averages package +======================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + fre.app.generate_time_averages.tests + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.app.generate_time_averages.cdoTimeAverager + fre.app.generate_time_averages.frenctoolsTimeAverager + fre.app.generate_time_averages.frepytoolsTimeAverager + fre.app.generate_time_averages.generate_time_averages + fre.app.generate_time_averages.timeAverager + +Module contents +--------------- + +.. automodule:: fre.app.generate_time_averages + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.generate_time_averages.tests.rst.txt b/_sources/fre.app.generate_time_averages.tests.rst.txt new file mode 100644 index 00000000..2f8dfe7f --- /dev/null +++ b/_sources/fre.app.generate_time_averages.tests.rst.txt @@ -0,0 +1,18 @@ +fre.app.generate\_time\_averages.tests package +============================================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.app.generate_time_averages.tests.test_generate_time_averages + +Module contents +--------------- + +.. automodule:: fre.app.generate_time_averages.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.generate_time_averages.tests.test_generate_time_averages.rst.txt b/_sources/fre.app.generate_time_averages.tests.test_generate_time_averages.rst.txt new file mode 100644 index 00000000..dcb1d107 --- /dev/null +++ b/_sources/fre.app.generate_time_averages.tests.test_generate_time_averages.rst.txt @@ -0,0 +1,7 @@ +fre.app.generate\_time\_averages.tests.test\_generate\_time\_averages module +============================================================================ + +.. automodule:: fre.app.generate_time_averages.tests.test_generate_time_averages + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.generate_time_averages.timeAverager.rst.txt b/_sources/fre.app.generate_time_averages.timeAverager.rst.txt new file mode 100644 index 00000000..e28e25a1 --- /dev/null +++ b/_sources/fre.app.generate_time_averages.timeAverager.rst.txt @@ -0,0 +1,7 @@ +fre.app.generate\_time\_averages.timeAverager module +==================================================== + +.. automodule:: fre.app.generate_time_averages.timeAverager + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.mask_atmos_plevel.rst.txt b/_sources/fre.app.mask_atmos_plevel.rst.txt new file mode 100644 index 00000000..8f8cd773 --- /dev/null +++ b/_sources/fre.app.mask_atmos_plevel.rst.txt @@ -0,0 +1,7 @@ +fre.app.mask\_atmos\_plevel module +================================== + +.. automodule:: fre.app.mask_atmos_plevel + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.regrid_xy.regrid_xy.rst.txt b/_sources/fre.app.regrid_xy.regrid_xy.rst.txt new file mode 100644 index 00000000..5e79b34b --- /dev/null +++ b/_sources/fre.app.regrid_xy.regrid_xy.rst.txt @@ -0,0 +1,7 @@ +fre.app.regrid\_xy.regrid\_xy module +==================================== + +.. automodule:: fre.app.regrid_xy.regrid_xy + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.regrid_xy.rst.txt b/_sources/fre.app.regrid_xy.rst.txt new file mode 100644 index 00000000..f25ad175 --- /dev/null +++ b/_sources/fre.app.regrid_xy.rst.txt @@ -0,0 +1,18 @@ +fre.app.regrid\_xy package +========================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.app.regrid_xy.regrid_xy + +Module contents +--------------- + +.. automodule:: fre.app.regrid_xy + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.app.rst.txt b/_sources/fre.app.rst.txt new file mode 100644 index 00000000..48d9d6db --- /dev/null +++ b/_sources/fre.app.rst.txt @@ -0,0 +1,28 @@ +fre.app package +=============== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + fre.app.generate_time_averages + fre.app.regrid_xy + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.app.freapp + fre.app.mask_atmos_plevel + +Module contents +--------------- + +.. automodule:: fre.app + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.catalog.frecatalog.rst.txt b/_sources/fre.catalog.frecatalog.rst.txt new file mode 100644 index 00000000..52951337 --- /dev/null +++ b/_sources/fre.catalog.frecatalog.rst.txt @@ -0,0 +1,7 @@ +fre.catalog.frecatalog module +============================= + +.. automodule:: fre.catalog.frecatalog + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.catalog.rst.txt b/_sources/fre.catalog.rst.txt new file mode 100644 index 00000000..3e67fbef --- /dev/null +++ b/_sources/fre.catalog.rst.txt @@ -0,0 +1,26 @@ +fre.catalog package +=================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + fre.catalog.tests + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.catalog.frecatalog + +Module contents +--------------- + +.. automodule:: fre.catalog + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.catalog.tests.rst.txt b/_sources/fre.catalog.tests.rst.txt new file mode 100644 index 00000000..9ade00a0 --- /dev/null +++ b/_sources/fre.catalog.tests.rst.txt @@ -0,0 +1,18 @@ +fre.catalog.tests package +========================= + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.catalog.tests.test_fre_catalog + +Module contents +--------------- + +.. automodule:: fre.catalog.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.catalog.tests.test_fre_catalog.rst.txt b/_sources/fre.catalog.tests.test_fre_catalog.rst.txt new file mode 100644 index 00000000..324db88f --- /dev/null +++ b/_sources/fre.catalog.tests.test_fre_catalog.rst.txt @@ -0,0 +1,7 @@ +fre.catalog.tests.test\_fre\_catalog module +=========================================== + +.. automodule:: fre.catalog.tests.test_fre_catalog + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.check.frecheck.rst.txt b/_sources/fre.check.frecheck.rst.txt new file mode 100644 index 00000000..a1d3ac2f --- /dev/null +++ b/_sources/fre.check.frecheck.rst.txt @@ -0,0 +1,7 @@ +fre.check.frecheck module +========================= + +.. automodule:: fre.check.frecheck + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.check.frecheckexample.rst.txt b/_sources/fre.check.frecheckexample.rst.txt new file mode 100644 index 00000000..a59c5ff0 --- /dev/null +++ b/_sources/fre.check.frecheckexample.rst.txt @@ -0,0 +1,7 @@ +fre.check.frecheckexample module +================================ + +.. automodule:: fre.check.frecheckexample + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.check.rst.txt b/_sources/fre.check.rst.txt new file mode 100644 index 00000000..d352ad67 --- /dev/null +++ b/_sources/fre.check.rst.txt @@ -0,0 +1,19 @@ +fre.check package +================= + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.check.frecheck + fre.check.frecheckexample + +Module contents +--------------- + +.. automodule:: fre.check + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.cmor.cmor_mixer.rst.txt b/_sources/fre.cmor.cmor_mixer.rst.txt new file mode 100644 index 00000000..7c483725 --- /dev/null +++ b/_sources/fre.cmor.cmor_mixer.rst.txt @@ -0,0 +1,7 @@ +fre.cmor.cmor\_mixer module +=========================== + +.. automodule:: fre.cmor.cmor_mixer + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.cmor.frecmor.rst.txt b/_sources/fre.cmor.frecmor.rst.txt new file mode 100644 index 00000000..edc4da23 --- /dev/null +++ b/_sources/fre.cmor.frecmor.rst.txt @@ -0,0 +1,7 @@ +fre.cmor.frecmor module +======================= + +.. automodule:: fre.cmor.frecmor + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.cmor.rst.txt b/_sources/fre.cmor.rst.txt new file mode 100644 index 00000000..794a3382 --- /dev/null +++ b/_sources/fre.cmor.rst.txt @@ -0,0 +1,27 @@ +fre.cmor package +================ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + fre.cmor.tests + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.cmor.cmor_mixer + fre.cmor.frecmor + +Module contents +--------------- + +.. automodule:: fre.cmor + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.cmor.tests.rst.txt b/_sources/fre.cmor.tests.rst.txt new file mode 100644 index 00000000..f0f56a4b --- /dev/null +++ b/_sources/fre.cmor.tests.rst.txt @@ -0,0 +1,18 @@ +fre.cmor.tests package +====================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.cmor.tests.test_cmor_run_subtool + +Module contents +--------------- + +.. automodule:: fre.cmor.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.cmor.tests.test_cmor_run_subtool.rst.txt b/_sources/fre.cmor.tests.test_cmor_run_subtool.rst.txt new file mode 100644 index 00000000..0deb02a1 --- /dev/null +++ b/_sources/fre.cmor.tests.test_cmor_run_subtool.rst.txt @@ -0,0 +1,7 @@ +fre.cmor.tests.test\_cmor\_run\_subtool module +============================================== + +.. automodule:: fre.cmor.tests.test_cmor_run_subtool + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.fre.rst.txt b/_sources/fre.fre.rst.txt new file mode 100644 index 00000000..7d308f0c --- /dev/null +++ b/_sources/fre.fre.rst.txt @@ -0,0 +1,7 @@ +fre.fre module +============== + +.. automodule:: fre.fre + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.lazy_group.rst.txt b/_sources/fre.lazy_group.rst.txt new file mode 100644 index 00000000..4a5ca466 --- /dev/null +++ b/_sources/fre.lazy_group.rst.txt @@ -0,0 +1,7 @@ +fre.lazy\_group module +====================== + +.. automodule:: fre.lazy_group + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.list.frelist.rst.txt b/_sources/fre.list.frelist.rst.txt new file mode 100644 index 00000000..88c20f31 --- /dev/null +++ b/_sources/fre.list.frelist.rst.txt @@ -0,0 +1,7 @@ +fre.list.frelist module +======================= + +.. automodule:: fre.list.frelist + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.list.frelistexample.rst.txt b/_sources/fre.list.frelistexample.rst.txt new file mode 100644 index 00000000..606858aa --- /dev/null +++ b/_sources/fre.list.frelistexample.rst.txt @@ -0,0 +1,7 @@ +fre.list.frelistexample module +============================== + +.. automodule:: fre.list.frelistexample + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.list.rst.txt b/_sources/fre.list.rst.txt new file mode 100644 index 00000000..13d48b97 --- /dev/null +++ b/_sources/fre.list.rst.txt @@ -0,0 +1,19 @@ +fre.list package +================ + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.list.frelist + fre.list.frelistexample + +Module contents +--------------- + +.. automodule:: fre.list + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.make.createCheckout.rst.txt b/_sources/fre.make.createCheckout.rst.txt new file mode 100644 index 00000000..f656965c --- /dev/null +++ b/_sources/fre.make.createCheckout.rst.txt @@ -0,0 +1,7 @@ +fre.make.createCheckout module +============================== + +.. automodule:: fre.make.createCheckout + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.make.createCompile.rst.txt b/_sources/fre.make.createCompile.rst.txt new file mode 100644 index 00000000..a1c4f29d --- /dev/null +++ b/_sources/fre.make.createCompile.rst.txt @@ -0,0 +1,7 @@ +fre.make.createCompile module +============================= + +.. automodule:: fre.make.createCompile + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.make.createDocker.rst.txt b/_sources/fre.make.createDocker.rst.txt new file mode 100644 index 00000000..8449f890 --- /dev/null +++ b/_sources/fre.make.createDocker.rst.txt @@ -0,0 +1,7 @@ +fre.make.createDocker module +============================ + +.. automodule:: fre.make.createDocker + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.make.createMakefile.rst.txt b/_sources/fre.make.createMakefile.rst.txt new file mode 100644 index 00000000..272cf69d --- /dev/null +++ b/_sources/fre.make.createMakefile.rst.txt @@ -0,0 +1,7 @@ +fre.make.createMakefile module +============================== + +.. automodule:: fre.make.createMakefile + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.make.fremake.rst.txt b/_sources/fre.make.fremake.rst.txt new file mode 100644 index 00000000..df3d5223 --- /dev/null +++ b/_sources/fre.make.fremake.rst.txt @@ -0,0 +1,7 @@ +fre.make.fremake module +======================= + +.. automodule:: fre.make.fremake + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.make.rst.txt b/_sources/fre.make.rst.txt new file mode 100644 index 00000000..830ec390 --- /dev/null +++ b/_sources/fre.make.rst.txt @@ -0,0 +1,31 @@ +fre.make package +================ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + fre.make.tests + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.make.createCheckout + fre.make.createCompile + fre.make.createDocker + fre.make.createMakefile + fre.make.fremake + fre.make.runFremake + +Module contents +--------------- + +.. automodule:: fre.make + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.make.runFremake.rst.txt b/_sources/fre.make.runFremake.rst.txt new file mode 100644 index 00000000..feeefce0 --- /dev/null +++ b/_sources/fre.make.runFremake.rst.txt @@ -0,0 +1,7 @@ +fre.make.runFremake module +========================== + +.. automodule:: fre.make.runFremake + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.make.tests.rst.txt b/_sources/fre.make.tests.rst.txt new file mode 100644 index 00000000..6ac68a21 --- /dev/null +++ b/_sources/fre.make.tests.rst.txt @@ -0,0 +1,18 @@ +fre.make.tests package +====================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.make.tests.test_create_makefile + +Module contents +--------------- + +.. automodule:: fre.make.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.make.tests.test_create_makefile.rst.txt b/_sources/fre.make.tests.test_create_makefile.rst.txt new file mode 100644 index 00000000..e8ee1c4e --- /dev/null +++ b/_sources/fre.make.tests.test_create_makefile.rst.txt @@ -0,0 +1,7 @@ +fre.make.tests.test\_create\_makefile module +============================================ + +.. automodule:: fre.make.tests.test_create_makefile + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.checkoutScript.rst.txt b/_sources/fre.pp.checkoutScript.rst.txt new file mode 100644 index 00000000..521591a4 --- /dev/null +++ b/_sources/fre.pp.checkoutScript.rst.txt @@ -0,0 +1,7 @@ +fre.pp.checkoutScript module +============================ + +.. automodule:: fre.pp.checkoutScript + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.configure_script_xml.rst.txt b/_sources/fre.pp.configure_script_xml.rst.txt new file mode 100644 index 00000000..b831c98d --- /dev/null +++ b/_sources/fre.pp.configure_script_xml.rst.txt @@ -0,0 +1,7 @@ +fre.pp.configure\_script\_xml module +==================================== + +.. automodule:: fre.pp.configure_script_xml + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.configure_script_yaml.rst.txt b/_sources/fre.pp.configure_script_yaml.rst.txt new file mode 100644 index 00000000..96f84f0d --- /dev/null +++ b/_sources/fre.pp.configure_script_yaml.rst.txt @@ -0,0 +1,7 @@ +fre.pp.configure\_script\_yaml module +===================================== + +.. automodule:: fre.pp.configure_script_yaml + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.frepp.rst.txt b/_sources/fre.pp.frepp.rst.txt new file mode 100644 index 00000000..6a9c1ce0 --- /dev/null +++ b/_sources/fre.pp.frepp.rst.txt @@ -0,0 +1,7 @@ +fre.pp.frepp module +=================== + +.. automodule:: fre.pp.frepp + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.install.rst.txt b/_sources/fre.pp.install.rst.txt new file mode 100644 index 00000000..594c8b32 --- /dev/null +++ b/_sources/fre.pp.install.rst.txt @@ -0,0 +1,7 @@ +fre.pp.install module +===================== + +.. automodule:: fre.pp.install + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.rst.txt b/_sources/fre.pp.rst.txt new file mode 100644 index 00000000..fc639acb --- /dev/null +++ b/_sources/fre.pp.rst.txt @@ -0,0 +1,34 @@ +fre.pp package +============== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + fre.pp.tests + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.pp.checkoutScript + fre.pp.configure_script_xml + fre.pp.configure_script_yaml + fre.pp.frepp + fre.pp.install + fre.pp.run + fre.pp.status + fre.pp.validate + fre.pp.wrapper + +Module contents +--------------- + +.. automodule:: fre.pp + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.run.rst.txt b/_sources/fre.pp.run.rst.txt new file mode 100644 index 00000000..6250a2f2 --- /dev/null +++ b/_sources/fre.pp.run.rst.txt @@ -0,0 +1,7 @@ +fre.pp.run module +================= + +.. automodule:: fre.pp.run + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.status.rst.txt b/_sources/fre.pp.status.rst.txt new file mode 100644 index 00000000..d8d49ec6 --- /dev/null +++ b/_sources/fre.pp.status.rst.txt @@ -0,0 +1,7 @@ +fre.pp.status module +==================== + +.. automodule:: fre.pp.status + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.tests.rst.txt b/_sources/fre.pp.tests.rst.txt new file mode 100644 index 00000000..acc1ffa8 --- /dev/null +++ b/_sources/fre.pp.tests.rst.txt @@ -0,0 +1,19 @@ +fre.pp.tests package +==================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.pp.tests.test_configure_script_yaml + fre.pp.tests.test_rose_quoting + +Module contents +--------------- + +.. automodule:: fre.pp.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.tests.test_configure_script_yaml.rst.txt b/_sources/fre.pp.tests.test_configure_script_yaml.rst.txt new file mode 100644 index 00000000..0b041582 --- /dev/null +++ b/_sources/fre.pp.tests.test_configure_script_yaml.rst.txt @@ -0,0 +1,7 @@ +fre.pp.tests.test\_configure\_script\_yaml module +================================================= + +.. automodule:: fre.pp.tests.test_configure_script_yaml + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.tests.test_rose_quoting.rst.txt b/_sources/fre.pp.tests.test_rose_quoting.rst.txt new file mode 100644 index 00000000..2c8c40c9 --- /dev/null +++ b/_sources/fre.pp.tests.test_rose_quoting.rst.txt @@ -0,0 +1,7 @@ +fre.pp.tests.test\_rose\_quoting module +======================================= + +.. automodule:: fre.pp.tests.test_rose_quoting + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.validate.rst.txt b/_sources/fre.pp.validate.rst.txt new file mode 100644 index 00000000..ae646482 --- /dev/null +++ b/_sources/fre.pp.validate.rst.txt @@ -0,0 +1,7 @@ +fre.pp.validate module +====================== + +.. automodule:: fre.pp.validate + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.pp.wrapper.rst.txt b/_sources/fre.pp.wrapper.rst.txt new file mode 100644 index 00000000..f5939e6f --- /dev/null +++ b/_sources/fre.pp.wrapper.rst.txt @@ -0,0 +1,7 @@ +fre.pp.wrapper module +===================== + +.. automodule:: fre.pp.wrapper + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.rst.txt b/_sources/fre.rst.txt new file mode 100644 index 00000000..79412d83 --- /dev/null +++ b/_sources/fre.rst.txt @@ -0,0 +1,37 @@ +fre package +=========== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + fre.app + fre.catalog + fre.check + fre.cmor + fre.list + fre.make + fre.pp + fre.run + fre.test + fre.tests + fre.yamltools + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.fre + fre.lazy_group + +Module contents +--------------- + +.. automodule:: fre + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.run.frerun.rst.txt b/_sources/fre.run.frerun.rst.txt new file mode 100644 index 00000000..5680f712 --- /dev/null +++ b/_sources/fre.run.frerun.rst.txt @@ -0,0 +1,7 @@ +fre.run.frerun module +===================== + +.. automodule:: fre.run.frerun + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.run.frerunexample.rst.txt b/_sources/fre.run.frerunexample.rst.txt new file mode 100644 index 00000000..d9e596d2 --- /dev/null +++ b/_sources/fre.run.frerunexample.rst.txt @@ -0,0 +1,7 @@ +fre.run.frerunexample module +============================ + +.. automodule:: fre.run.frerunexample + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.run.rst.txt b/_sources/fre.run.rst.txt new file mode 100644 index 00000000..a72df62d --- /dev/null +++ b/_sources/fre.run.rst.txt @@ -0,0 +1,19 @@ +fre.run package +=============== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.run.frerun + fre.run.frerunexample + +Module contents +--------------- + +.. automodule:: fre.run + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.test.fretest.rst.txt b/_sources/fre.test.fretest.rst.txt new file mode 100644 index 00000000..60bf1707 --- /dev/null +++ b/_sources/fre.test.fretest.rst.txt @@ -0,0 +1,7 @@ +fre.test.fretest module +======================= + +.. automodule:: fre.test.fretest + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.test.fretestexample.rst.txt b/_sources/fre.test.fretestexample.rst.txt new file mode 100644 index 00000000..c2d08546 --- /dev/null +++ b/_sources/fre.test.fretestexample.rst.txt @@ -0,0 +1,7 @@ +fre.test.fretestexample module +============================== + +.. automodule:: fre.test.fretestexample + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.test.rst.txt b/_sources/fre.test.rst.txt new file mode 100644 index 00000000..bd42b910 --- /dev/null +++ b/_sources/fre.test.rst.txt @@ -0,0 +1,19 @@ +fre.test package +================ + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.test.fretest + fre.test.fretestexample + +Module contents +--------------- + +.. automodule:: fre.test + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.rst.txt b/_sources/fre.tests.rst.txt new file mode 100644 index 00000000..31faac1d --- /dev/null +++ b/_sources/fre.tests.rst.txt @@ -0,0 +1,28 @@ +fre.tests package +================= + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.tests.test_fre_app_cli + fre.tests.test_fre_catalog_cli + fre.tests.test_fre_check_cli + fre.tests.test_fre_cli + fre.tests.test_fre_cmor_cli + fre.tests.test_fre_list_cli + fre.tests.test_fre_make_cli + fre.tests.test_fre_pp_cli + fre.tests.test_fre_run_cli + fre.tests.test_fre_test_cli + fre.tests.test_fre_yamltools_cli + +Module contents +--------------- + +.. automodule:: fre.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_app_cli.rst.txt b/_sources/fre.tests.test_fre_app_cli.rst.txt new file mode 100644 index 00000000..a75903f7 --- /dev/null +++ b/_sources/fre.tests.test_fre_app_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_app\_cli module +==================================== + +.. automodule:: fre.tests.test_fre_app_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_catalog_cli.rst.txt b/_sources/fre.tests.test_fre_catalog_cli.rst.txt new file mode 100644 index 00000000..9f2dd71b --- /dev/null +++ b/_sources/fre.tests.test_fre_catalog_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_catalog\_cli module +======================================== + +.. automodule:: fre.tests.test_fre_catalog_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_check_cli.rst.txt b/_sources/fre.tests.test_fre_check_cli.rst.txt new file mode 100644 index 00000000..c4ca1c46 --- /dev/null +++ b/_sources/fre.tests.test_fre_check_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_check\_cli module +====================================== + +.. automodule:: fre.tests.test_fre_check_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_cli.rst.txt b/_sources/fre.tests.test_fre_cli.rst.txt new file mode 100644 index 00000000..ce168b5d --- /dev/null +++ b/_sources/fre.tests.test_fre_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_cli module +=============================== + +.. automodule:: fre.tests.test_fre_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_cmor_cli.rst.txt b/_sources/fre.tests.test_fre_cmor_cli.rst.txt new file mode 100644 index 00000000..83026d82 --- /dev/null +++ b/_sources/fre.tests.test_fre_cmor_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_cmor\_cli module +===================================== + +.. automodule:: fre.tests.test_fre_cmor_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_list_cli.rst.txt b/_sources/fre.tests.test_fre_list_cli.rst.txt new file mode 100644 index 00000000..96b571d2 --- /dev/null +++ b/_sources/fre.tests.test_fre_list_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_list\_cli module +===================================== + +.. automodule:: fre.tests.test_fre_list_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_make_cli.rst.txt b/_sources/fre.tests.test_fre_make_cli.rst.txt new file mode 100644 index 00000000..af10cebc --- /dev/null +++ b/_sources/fre.tests.test_fre_make_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_make\_cli module +===================================== + +.. automodule:: fre.tests.test_fre_make_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_pp_cli.rst.txt b/_sources/fre.tests.test_fre_pp_cli.rst.txt new file mode 100644 index 00000000..2c0d5249 --- /dev/null +++ b/_sources/fre.tests.test_fre_pp_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_pp\_cli module +=================================== + +.. automodule:: fre.tests.test_fre_pp_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_run_cli.rst.txt b/_sources/fre.tests.test_fre_run_cli.rst.txt new file mode 100644 index 00000000..00dd76df --- /dev/null +++ b/_sources/fre.tests.test_fre_run_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_run\_cli module +==================================== + +.. automodule:: fre.tests.test_fre_run_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_test_cli.rst.txt b/_sources/fre.tests.test_fre_test_cli.rst.txt new file mode 100644 index 00000000..5998f908 --- /dev/null +++ b/_sources/fre.tests.test_fre_test_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_test\_cli module +===================================== + +.. automodule:: fre.tests.test_fre_test_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.tests.test_fre_yamltools_cli.rst.txt b/_sources/fre.tests.test_fre_yamltools_cli.rst.txt new file mode 100644 index 00000000..7ecd930b --- /dev/null +++ b/_sources/fre.tests.test_fre_yamltools_cli.rst.txt @@ -0,0 +1,7 @@ +fre.tests.test\_fre\_yamltools\_cli module +========================================== + +.. automodule:: fre.tests.test_fre_yamltools_cli + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.yamltools.combine_yamls.rst.txt b/_sources/fre.yamltools.combine_yamls.rst.txt new file mode 100644 index 00000000..6fffbda5 --- /dev/null +++ b/_sources/fre.yamltools.combine_yamls.rst.txt @@ -0,0 +1,7 @@ +fre.yamltools.combine\_yamls module +=================================== + +.. automodule:: fre.yamltools.combine_yamls + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.yamltools.freyamltools.rst.txt b/_sources/fre.yamltools.freyamltools.rst.txt new file mode 100644 index 00000000..e59f3d03 --- /dev/null +++ b/_sources/fre.yamltools.freyamltools.rst.txt @@ -0,0 +1,7 @@ +fre.yamltools.freyamltools module +================================= + +.. automodule:: fre.yamltools.freyamltools + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.yamltools.freyamltoolsexample.rst.txt b/_sources/fre.yamltools.freyamltoolsexample.rst.txt new file mode 100644 index 00000000..031fd514 --- /dev/null +++ b/_sources/fre.yamltools.freyamltoolsexample.rst.txt @@ -0,0 +1,7 @@ +fre.yamltools.freyamltoolsexample module +======================================== + +.. automodule:: fre.yamltools.freyamltoolsexample + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.yamltools.rst.txt b/_sources/fre.yamltools.rst.txt new file mode 100644 index 00000000..397c0d03 --- /dev/null +++ b/_sources/fre.yamltools.rst.txt @@ -0,0 +1,28 @@ +fre.yamltools package +===================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + fre.yamltools.tests + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.yamltools.combine_yamls + fre.yamltools.freyamltools + fre.yamltools.freyamltoolsexample + +Module contents +--------------- + +.. automodule:: fre.yamltools + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.yamltools.tests.rst.txt b/_sources/fre.yamltools.tests.rst.txt new file mode 100644 index 00000000..a378a141 --- /dev/null +++ b/_sources/fre.yamltools.tests.rst.txt @@ -0,0 +1,18 @@ +fre.yamltools.tests package +=========================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + fre.yamltools.tests.test_combine_yamls + +Module contents +--------------- + +.. automodule:: fre.yamltools.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/fre.yamltools.tests.test_combine_yamls.rst.txt b/_sources/fre.yamltools.tests.test_combine_yamls.rst.txt new file mode 100644 index 00000000..57bec304 --- /dev/null +++ b/_sources/fre.yamltools.tests.test_combine_yamls.rst.txt @@ -0,0 +1,7 @@ +fre.yamltools.tests.test\_combine\_yamls module +=============================================== + +.. automodule:: fre.yamltools.tests.test_combine_yamls + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..111c8302 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,25 @@ +.. Fre-Cli documentation master file, created by sphinx-quickstart on Wed Mar 6 22:28:21 2024. + You can adapt this file completely to your liking, but it should at least contain the root + \`toctree\` directive (no backslashes) + +=================================== +Welcome to Fre-Cli's documentation! +=================================== + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + setup + usage + developer_usage + contributing_to_doc + badges + + +Indices +======= + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/_sources/modules.rst.txt b/_sources/modules.rst.txt new file mode 100644 index 00000000..7736c81c --- /dev/null +++ b/_sources/modules.rst.txt @@ -0,0 +1,7 @@ +fre +=== + +.. toctree:: + :maxdepth: 4 + + fre diff --git a/_sources/setup.rst.txt b/_sources/setup.rst.txt new file mode 100644 index 00000000..7dfd1492 --- /dev/null +++ b/_sources/setup.rst.txt @@ -0,0 +1,34 @@ +===== +Setup +===== + +Set up Conda environment +======================== + +various options work quite well: + + +If on workstation +----------------- + +``module load fre/2024.01`` + + +Create New Conda environment +---------------------------- + +if you're at GFDL: +``module load miniforge`` + +create an empty environment to start +``conda create -n [environmentName]`` + +activate the empty environment: +``conda activate [environmentName]`` + +append necessary channels: +``conda config --append channels noaa-gfdl; conda config --append channels conda-forge;`` + +install ``fre-cli`` into the activated environment from `the GFDL conda channel https://anaconda.org/NOAA-GFDL/fre-cli`_ : +``conda install noaa-gfdl::fre-cli`` + diff --git a/_sources/usage.rst.txt b/_sources/usage.rst.txt new file mode 100644 index 00000000..0a7a0399 --- /dev/null +++ b/_sources/usage.rst.txt @@ -0,0 +1,102 @@ +===== +Usage +===== + +for setup, see the setup section. + + +Calling ``fre`` +=============== + +Brief rundown of commands also provided below: + +* Enter commands and follow ``--help`` messages for guidance +* If the user just runs ``fre``, it will list all the command groups following ``fre``, such as + ``run``, ``make``, ``pp``, etc. and once the user specifies a command group, the list of available + subcommands for that group will be shown +* Commands that require arguments to run will alert user about missing arguments, and will also list + the rest of the optional parameters if ``--help`` is executed +* Argument flags are not positional, can be specified in any order as long as they are specified +* Can run directly from any directory, no need to clone repository +* May need to deactivate environment and reactivate it in order for changes to apply +* ``fre/setup.py`` allows ``fre/fre.py`` to be ran as ``fre`` on the command line by defining it as an + *entry point*. Without it, the call would be instead, something like ``python fre/fre.py`` + + +Tools +===== + +A few subtools are currently in development: + + +fre app +------- + +1. ``generate-time-averages`` +2. ``regrid_xy`` + + +fre catalog +----------- + +1. ``builder`` Generate a catalog + +* Builds json and csv format catalogs from user input directory path +* Minimal Syntax: ``fre catalog builder -i [input path] -o [output path]`` +* Module(s) needed: n/a +* Example: ``fre catalog builder -i /archive/am5/am5/am5f3b1r0/c96L65_am5f3b1r0_pdclim1850F/gfdl.ncrc5-deploy-prod-openmp/pp -o ~/output --overwrite`` + +2. ``validate`` Validate the catalog + + +fre cmor +-------- + +1. ``run`` + +* placehold + + +fre make +-------- + +1. ``run-fremake`` + +* placehold + + +fre pp +------ + +1. ``configure`` + +* Postprocessing yaml configuration +* Minimal Syntax: ``fre pp configure -y [user-edit yaml file]`` +* Module(s) needed: n/a +* Example: ``fre pp configure -y /home/$user/pp/ue2/user-edits/edits.yaml`` + +2. ``checkout`` + +* Checkout template file and clone gitlab.gfdl.noaa.gov/fre2/workflows/postprocessing.git repository +* Minimal Syntax: ``fre pp checkout -e [experiment name] -p [platform name] -t [target name]`` +* Module(s) needed: n/a +* Example: ``fre pp checkout -e c96L65_am5f4b4r0_amip -p gfdl.ncrc5-deploy -t prod-openmp`` + + +fre yamltools +------------- + +1. ``combine-yamls`` + +* placehold + + +not-yet-implemented +------------------- + +#. ``fre check`` +#. ``fre list`` +#. ``fre run`` +#. ``fre test`` + + diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 00000000..81415803 --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 00000000..f316efcb --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 00000000..88ba55b9 --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 00000000..0f14f106 --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/custom.css b/_static/custom.css new file mode 100644 index 00000000..05a52e90 --- /dev/null +++ b/_static/custom.css @@ -0,0 +1,274 @@ +/** + + CHANGES FOR CUSTOM.CSS SHOULD BE MADE INSIDE CUSTOM.SCSS + + + + Requirements: + + - pip install compile-scss https://pypi.org/project/compile-scss/ + + + +To make changes to styling, first run the following in the root of the project: + + - pip install -e .[dev] + + + +Then you can edit and make changes to this file and see the results by doing the following: + + Steps: + + - Run: compile_scss --watch from inside /renku_sphinx_theme + + - That command will compile scss changes into css. + + - After doing changes commit scss and css files. + +*/ +.wy-nav-side { + background: #01192d; +} + +@media screen and (min-width: 1100px) { + .wy-nav-content-wrap { + background:#01192d; + } +} + +a { + color: #009568; +} + +a:hover { + color: #00bd84; +} + +a:visited { + color: #009568; +} + +.rst-content { + color: #3d3d3d; +} + +.rst-content code.literal { + color: #d26a98; +} + +.rst-content tt.literal { + color: #d26a98; +} + +.rst-content .note { + color: #003274; + background: #ccddf3; + padding: 1rem; + margin-bottom: 1rem; +} + +.rst-content .note .admonition-title { + display: none; +} + +.rst-content .warning { + color: #605000; + background: #fcf4cc; + padding: 1rem; + margin-bottom: 1rem; +} + +.rst-content .warning .admonition-title { + display: none; +} + +.rst-content .highlight { + background: #f5f5f5; +} + +.wy-side-scroll { + background-color: #01192d; +} + +.wy-side-nav-search { + background-color: #01192d; +} + +.wy-side-nav-search input[type="text"] { + width: 100%; + border-radius: 0px; + padding: 6px 12px; + border-color: #01192d; +} + +.wy-menu-vertical a { + font-size: 100%; + color: #d9d9d9; + padding-top: 0.6rem; + padding-bottom: 0.6rem; + background-color: inherit; +} + +.wy-menu-vertical a:hover { + background-color: unset; + opacity: 1; +} + +.wy-menu-vertical li.current > a { + background-color: #01192d; + color: white; +} + +.wy-menu-vertical li.current > a span.toctree-expand { + display: block; + font-size: inherit; + line-height: inherit; + color: inherit; +} + +.wy-menu-vertical li.current > a span.toctree-expand:before { + display: block; + font-size: inherit; + line-height: inherit; + color: inherit; +} + +.wy-menu-vertical li.current > a span.toctree-expand:hover { + color: white; +} + +.wy-menu-vertical li.current > a:hover { + background-color: #01192d; + color: white; +} + +.wy-menu-vertical li.current > a:hover span.toctree-expand { + color: white; +} + +.wy-menu-vertical .toctree-l1 { + opacity: 0.5; +} + +.wy-menu-vertical .toctree-l1:hover { + opacity: 1; + background-color: inherit; +} + +.wy-menu-vertical li.toctree-l1.current { + opacity: 1; + background-color: inherit; +} + +.wy-menu-vertical li.toctree-l1.current > a { + border: 0px; +} + +.wy-menu-vertical .toctree-l2:hover { + background-color: #566673; +} + +.wy-menu-vertical li.toctree-l2.current > a { + background-color: #566673; + color: white; +} + +.wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a { + background-color: #e4e7ea; + color: #838383; +} + +.wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a:hover { + color: #3d3d3d; +} + +.wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a:hover span.toctree-expand { + color: #3d3d3d; +} + +.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current > a { + color: #3d3d3d; +} + +.wy-menu-vertical li.toctree-l2 a { + border: 0px; + background-color: #566673; + color: #d9d9d9; +} + +.wy-menu-vertical li.toctree-l2 a span.toctree-expand { + display: block; + font-size: inherit; + line-height: inherit; + color: inherit; +} + +.wy-menu-vertical li.toctree-l2 a span.toctree-expand:before { + display: block; + font-size: inherit; + line-height: inherit; + color: inherit; +} + +.wy-menu-vertical li.toctree-l2 a span.toctree-expand:hover { + color: white; +} + +.wy-menu-vertical li.toctree-l2 a:hover { + color: white; + background-color: #566673; +} + +.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand { + color: white; +} + +.wy-menu-vertical li.toctree-l3.current > a { + background-color: #e4e7ea; + color: #838383; +} + +.wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a { + background-color: #e4e7ea; + color: #838383; +} + +.wy-menu-vertical li.toctree-l3.current li.toctree-l4.current > a { + color: #3d3d3d; +} + +.wy-nav-top { + background-color: #01192d; +} + +.btn { + display: inline-block; + font-weight: 400; + line-height: 1.5; + color: #3d3d3d; + text-align: center; + text-decoration: none; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + border-radius: 0; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + box-shadow: unset; +} + +.btn-neutral { + background: unset !important; + color: #838383 !important; +} + +.btn-neutral:active { + padding: 0.375rem 0.75rem; + box-shadow: unset; +} diff --git a/_static/custom.scss b/_static/custom.scss new file mode 100644 index 00000000..035b16a0 --- /dev/null +++ b/_static/custom.scss @@ -0,0 +1,292 @@ +/** + CHANGES FOR CUSTOM.CSS SHOULD BE MADE INSIDE CUSTOM.SCSS + + Requirements: + - pip install compile-scss https://pypi.org/project/compile-scss/ + +To make changes to styling, first run the following in the root of the project: + - pip install -e .[dev] + +Then you can edit and make changes to this file and see the results by doing the following: + Steps: + - Run: compile_scss --watch from inside /renku_sphinx_theme + - That command will compile scss changes into css. + - After doing changes commit scss and css files. +*/ + +$rk-blue: #01192d; +$rk-green: #009568; +$rk-green-lighter: hsl(162, 100%, 37%); +$rk-background: #f5f5f5; +$rk-white: #f5f5f5; +$rk-dark: #3d3d3d; +$rk-warning-background: #fcf4cc; +$rk-warning-font: #605000; +$rk-info-background: #ccddf3; +$rk-info-font: #003274; +$rk-light-blue: #566673; +$rk-gray: #838383; +$rk-light-gray: #e4e7ea; +$rk-pink: #d26a98; + +//side background +.wy-nav-side { + background: $rk-blue; +} + +@media screen and (min-width: 1100px) { + .wy-nav-content-wrap { + background:$rk-blue; + } +} + +//all links are green +a { + color: $rk-green; + &:hover { + color: $rk-green-lighter; + } + &:visited { + color: $rk-green; + } +} + +// right content -- docs +.rst-content { + color: $rk-dark; + + // code color + code.literal { + color: $rk-pink; + } + + tt.literal { + color: $rk-pink; + } + + // note style + .note { + .admonition-title { + display: none; + } + color: $rk-info-font; + background: $rk-info-background; + padding: 1rem; + margin-bottom: 1rem; + } + + // warning style + .warning { + .admonition-title { + display: none; + } + color: $rk-warning-font; + background: $rk-warning-background; + padding: 1rem; + margin-bottom: 1rem; + } + + // code blocks + .highlight { + background: $rk-background; + } +} + +// menu background +.wy-side-scroll { + background-color: $rk-blue; +} + +// side nav +.wy-side-nav-search { + background-color: $rk-blue; + input[type="text"] { + width: 100%; + border-radius: 0px; + padding: 6px 12px; + border-color: $rk-blue; + } +} + +// Vertical menu +.wy-menu-vertical { + a { + font-size: 100%; + color: #d9d9d9; + padding-top: 0.6rem; + padding-bottom: 0.6rem; + background-color: inherit; + &:hover { + background-color: unset; + opacity: 1; + } + } + + // all links inside the menu + li.current { + > a { + background-color: $rk-blue; + color: white; + span.toctree-expand { + display: block; + font-size: inherit; + line-height: inherit; + color: inherit; + &:before { + display: block; + font-size: inherit; + line-height: inherit; + color: inherit; + } + &:hover { + color: white; + } + } + &:hover { + background-color: $rk-blue; + color: white; + span.toctree-expand { + color: white; + } + } + } + } + + // level 1 link + .toctree-l1 { + opacity: 0.5; + &:hover { + opacity: 1; + background-color: inherit; + } + } + + // selected level 1 link + li.toctree-l1.current { + opacity: 1; + background-color: inherit; + > a { + border: 0px; + } + } + + .toctree-l2 { + &:hover { + background-color: #566673; + } + } + + li.toctree-l2.current { + > a { + background-color: $rk-light-blue; + color: white; + } + li.toctree-l3 { + > a { + background-color: $rk-light-gray; + color: $rk-gray; + &:hover { + color: $rk-dark; + span.toctree-expand { + color: $rk-dark; + } + } + } + } + li.toctree-l3.current { + > a { + color: $rk-dark; + } + } + } + + li.toctree-l2 { + a { + border: 0px; + background-color: $rk-light-blue; + color: #d9d9d9; + // + span.toctree-expand { + display: block; + font-size: inherit; + line-height: inherit; + color: inherit; + &:before { + display: block; + font-size: inherit; + line-height: inherit; + color: inherit; + } + &:hover { + color: white; + } + } + // + &:hover { + color: white; + background-color: $rk-light-blue; + /// + span.toctree-expand { + color: white; + } + /// + } + } + } + + li.toctree-l3.current { + > a { + background-color: $rk-light-gray; + color: $rk-gray; + } + li.toctree-l4 { + > a { + background-color: $rk-light-gray; + color: $rk-gray; + } + &.current { + > a { + color: $rk-dark; + } + } + } + } +} + +// Responsive small navigation +.wy-nav-top { + background-color: $rk-blue; +} + +// Button +.btn { + display: inline-block; + font-weight: 400; + line-height: 1.5; + color: $rk-dark; + text-align: center; + text-decoration: none; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + border-radius: 0; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + box-shadow: unset; +} + +// Neutral button - prev next +.btn-neutral { + background: unset !important; + color: $rk-gray !important; + &:active { + padding: 0.375rem 0.75rem; + box-shadow: unset; + } +} diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 00000000..4d67807d --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 00000000..529239f0 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '1.0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/favicon.png b/_static/favicon.png new file mode 100644 index 00000000..948a9b20 Binary files /dev/null and b/_static/favicon.png differ diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/fonts.css b/_static/fonts.css new file mode 100644 index 00000000..a7acf062 --- /dev/null +++ b/_static/fonts.css @@ -0,0 +1,41 @@ +/* Webfont: Calcutta-Bold */ +@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,500;1,400&display=swap"); +@font-face { + font-family: 'Calcutta'; + src: url("fonts/calcutta/Calcutta-Bold.eot"); + /* IE9 Compat Modes */ + src: url("fonts/calcutta/Calcutta-Bold.eot?#iefix") format("embedded-opentype"), url("fonts/calcutta/Calcutta-Bold.woff") format("woff"), url("fonts/calcutta/Calcutta-Bold.ttf") format("truetype"), url("fonts/calcutta/Calcutta-Bold.svg#Calcutta-Bold") format("svg"); + /* Legacy iOS */ + font-style: normal; + font-weight: 700; + text-rendering: optimizeLegibility !important; } + +/* Webfont: Calcutta-Medium */ +@font-face { + font-family: 'Calcutta'; + src: url("fonts/calcutta/Calcutta-Medium.eot"); + /* IE9 Compat Modes */ + src: url("fonts/calcutta/Calcutta-Medium.eot?#iefix") format("embedded-opentype"), url("fonts/calcutta/Calcutta-Medium.woff") format("woff"), url("fonts/calcutta/Calcutta-Medium.ttf") format("truetype"), url("fonts/calcutta/Calcutta-Medium.svg#Calcutta-Medium") format("svg"); + /* Legacy iOS */ + font-style: normal; + font-weight: 500; + text-rendering: optimizeLegibility !important; } + +/* Webfont: Calcutta-Regular */ +@font-face { + font-family: 'Calcutta'; + src: url("fonts/calcutta/Calcutta-Regular.eot"); + /* IE9 Compat Modes */ + src: url("fonts/calcutta/Calcutta-Regular.eot?#iefix") format("embedded-opentype"), url("fonts/calcutta/Calcutta-Regular.woff") format("woff"), url("fonts/calcutta/Calcutta-Regular.ttf") format("truetype"), url("fonts/calcutta/Calcutta-Regular.svg#Calcutta-Regular") format("svg"); + /* Legacy iOS */ + font-style: normal; + font-weight: 400; + text-rendering: optimizeLegibility; } + +body { + font-family: 'Calcutta', sans-serif; +} + +h1,h2,h3,h4,h5,h6{ + font-family: 'Calcutta', sans-serif; +} diff --git a/_static/fonts/Lato/lato-bold.eot b/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 00000000..3361183a Binary files /dev/null and b/_static/fonts/Lato/lato-bold.eot differ diff --git a/_static/fonts/Lato/lato-bold.ttf b/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 00000000..29f691d5 Binary files /dev/null and b/_static/fonts/Lato/lato-bold.ttf differ diff --git a/_static/fonts/Lato/lato-bold.woff b/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/_static/fonts/Lato/lato-bold.woff differ diff --git a/_static/fonts/Lato/lato-bold.woff2 b/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/_static/fonts/Lato/lato-bolditalic.eot b/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 00000000..3d415493 Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/_static/fonts/Lato/lato-bolditalic.ttf b/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 00000000..f402040b Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/_static/fonts/Lato/lato-bolditalic.woff b/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/_static/fonts/Lato/lato-bolditalic.woff2 b/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/_static/fonts/Lato/lato-italic.eot b/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 00000000..3f826421 Binary files /dev/null and b/_static/fonts/Lato/lato-italic.eot differ diff --git a/_static/fonts/Lato/lato-italic.ttf b/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 00000000..b4bfc9b2 Binary files /dev/null and b/_static/fonts/Lato/lato-italic.ttf differ diff --git a/_static/fonts/Lato/lato-italic.woff b/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/_static/fonts/Lato/lato-italic.woff differ diff --git a/_static/fonts/Lato/lato-italic.woff2 b/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/_static/fonts/Lato/lato-regular.eot b/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 00000000..11e3f2a5 Binary files /dev/null and b/_static/fonts/Lato/lato-regular.eot differ diff --git a/_static/fonts/Lato/lato-regular.ttf b/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 00000000..74decd9e Binary files /dev/null and b/_static/fonts/Lato/lato-regular.ttf differ diff --git a/_static/fonts/Lato/lato-regular.woff b/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/_static/fonts/Lato/lato-regular.woff differ diff --git a/_static/fonts/Lato/lato-regular.woff2 b/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 00000000..79dc8efe Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 00000000..df5d1df2 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 00000000..2f7ca78a Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 00000000..eb52a790 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/_static/fonts/calcutta/Calcutta-Bold.eot b/_static/fonts/calcutta/Calcutta-Bold.eot new file mode 100644 index 00000000..55192621 Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Bold.eot differ diff --git a/_static/fonts/calcutta/Calcutta-Bold.otf b/_static/fonts/calcutta/Calcutta-Bold.otf new file mode 100644 index 00000000..5a75fe6d Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Bold.otf differ diff --git a/_static/fonts/calcutta/Calcutta-Bold.svg b/_static/fonts/calcutta/Calcutta-Bold.svg new file mode 100644 index 00000000..15f25092 --- /dev/null +++ b/_static/fonts/calcutta/Calcutta-Bold.svg @@ -0,0 +1,1042 @@ + + + + +Created by FontForge 20190801 at Tue Mar 28 12:10:51 2017 + By Unknown +Copyright (c) 2014 Indian Type Foundry (info@indiantypefoundry.com); Copyright (c) 2017 Cristiano Sobral (cssobral2013@gmail.com) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/fonts/calcutta/Calcutta-Bold.ttf b/_static/fonts/calcutta/Calcutta-Bold.ttf new file mode 100644 index 00000000..b7e9582b Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Bold.ttf differ diff --git a/_static/fonts/calcutta/Calcutta-Bold.woff b/_static/fonts/calcutta/Calcutta-Bold.woff new file mode 100644 index 00000000..78974b49 Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Bold.woff differ diff --git a/_static/fonts/calcutta/Calcutta-Medium.eot b/_static/fonts/calcutta/Calcutta-Medium.eot new file mode 100644 index 00000000..090dbf96 Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Medium.eot differ diff --git a/_static/fonts/calcutta/Calcutta-Medium.otf b/_static/fonts/calcutta/Calcutta-Medium.otf new file mode 100644 index 00000000..48101a65 Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Medium.otf differ diff --git a/_static/fonts/calcutta/Calcutta-Medium.svg b/_static/fonts/calcutta/Calcutta-Medium.svg new file mode 100644 index 00000000..0ea6ee0f --- /dev/null +++ b/_static/fonts/calcutta/Calcutta-Medium.svg @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/fonts/calcutta/Calcutta-Medium.ttf b/_static/fonts/calcutta/Calcutta-Medium.ttf new file mode 100644 index 00000000..1382599c Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Medium.ttf differ diff --git a/_static/fonts/calcutta/Calcutta-Medium.woff b/_static/fonts/calcutta/Calcutta-Medium.woff new file mode 100644 index 00000000..bb7bd669 Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Medium.woff differ diff --git a/_static/fonts/calcutta/Calcutta-Regular.eot b/_static/fonts/calcutta/Calcutta-Regular.eot new file mode 100644 index 00000000..bdd39cfd Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Regular.eot differ diff --git a/_static/fonts/calcutta/Calcutta-Regular.otf b/_static/fonts/calcutta/Calcutta-Regular.otf new file mode 100644 index 00000000..ad69e4ae Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Regular.otf differ diff --git a/_static/fonts/calcutta/Calcutta-Regular.svg b/_static/fonts/calcutta/Calcutta-Regular.svg new file mode 100644 index 00000000..b08e6cbf --- /dev/null +++ b/_static/fonts/calcutta/Calcutta-Regular.svg @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/fonts/calcutta/Calcutta-Regular.ttf b/_static/fonts/calcutta/Calcutta-Regular.ttf new file mode 100644 index 00000000..e6874967 Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Regular.ttf differ diff --git a/_static/fonts/calcutta/Calcutta-Regular.woff b/_static/fonts/calcutta/Calcutta-Regular.woff new file mode 100644 index 00000000..4be98062 Binary files /dev/null and b/_static/fonts/calcutta/Calcutta-Regular.woff differ diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 00000000..c4c6022f --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t +
Languages
+ ${config.projects.translations + .map( + (translation) => ` +
+ ${translation.language.code} +
+ `, + ) + .join("\n")} + + `; + return languagesHTML; + } + + function renderVersions(config) { + if (!config.versions.active.length) { + return ""; + } + const versionsHTML = ` +
+
Versions
+ ${config.versions.active + .map( + (version) => ` +
+ ${version.slug} +
+ `, + ) + .join("\n")} +
+ `; + return versionsHTML; + } + + function renderDownloads(config) { + if (!Object.keys(config.versions.current.downloads).length) { + return ""; + } + const downloadsNameDisplay = { + pdf: "PDF", + epub: "Epub", + htmlzip: "HTML", + }; + + const downloadsHTML = ` +
+
Downloads
+ ${Object.entries(config.versions.current.downloads) + .map( + ([name, url]) => ` +
+ ${downloadsNameDisplay[name]} +
+ `, + ) + .join("\n")} +
+ `; + return downloadsHTML; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const flyout = ` +
+ + Read the Docs + v: ${config.versions.current.slug} + + +
+
+ ${renderLanguages(config)} + ${renderVersions(config)} + ${renderDownloads(config)} +
+
On Read the Docs
+
+ Project Home +
+
+ Builds +
+
+ Downloads +
+
+
+
Search
+
+
+ +
+
+
+
+ + Hosted by Read the Docs + +
+
+ `; + + // Inject the generated flyout into the body HTML element. + document.body.insertAdjacentHTML("beforeend", flyout); + + // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. + document + .querySelector("#flyout-search-form") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); + }) +} + +if (themeLanguageSelector || themeVersionSelector) { + function onSelectorSwitch(event) { + const option = event.target.selectedIndex; + const item = event.target.options[option]; + window.location.href = item.dataset.url; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const versionSwitch = document.querySelector( + "div.switch-menus > div.version-switch", + ); + if (themeVersionSelector) { + let versions = config.versions.active; + if (config.versions.current.hidden || config.versions.current.type === "external") { + versions.unshift(config.versions.current); + } + const versionSelect = ` + + `; + + versionSwitch.innerHTML = versionSelect; + versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + + const languageSwitch = document.querySelector( + "div.switch-menus > div.language-switch", + ); + + if (themeLanguageSelector) { + if (config.projects.translations.length) { + // Add the current language to the options on the selector + let languages = config.projects.translations.concat( + config.projects.current, + ); + languages = languages.sort((a, b) => + a.language.name.localeCompare(b.language.name), + ); + + const languageSelect = ` + + `; + + languageSwitch.innerHTML = languageSelect; + languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + else { + languageSwitch.remove(); + } + } + }); +} + +document.addEventListener("readthedocs-addons-data-ready", function (event) { + // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. + document + .querySelector("[role='search'] input") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); +}); \ No newline at end of file diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 00000000..367b8ed8 --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, if available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 00000000..d96755fd Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 00000000..7107cec9 Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 00000000..84ab3030 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 00000000..b08d58c9 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,620 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 00000000..8a96c69a --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/badges.html b/badges.html new file mode 100644 index 00000000..b2ce99f1 --- /dev/null +++ b/badges.html @@ -0,0 +1,115 @@ + + + + + + + + + Badges — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Badges

+_images/cov_badge.svg +_images/pytest_badge.svg +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/contributing_to_doc.html b/contributing_to_doc.html new file mode 100644 index 00000000..fdf47740 --- /dev/null +++ b/contributing_to_doc.html @@ -0,0 +1,165 @@ + + + + + + + + + Documentation-Documentation — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Documentation-Documentation

+

Welcome to fre-cli’s Documentation-documentation- where we document how the documentation is +documented

+
+

How to Contribute to fre-cli’s documentation

+
    +
  1. fork and poke at the settings

    +
      +
    • Fork fre-cli on github

    • +
    • On github, navigate to your fre-cli fork, and click “settings”

    • +
    • In “settings”, click “pages”

    • +
    • In “pages”, under “build and deployment”, make sure “source” is set to “Deploy from a branch”

    • +
    • Under that, find “Branch”, make sure the branch selected is gh-pages.

    • +
    • +
      The branch gh-pages is “automagic”- i.e. do not change anything about it nor create a new one,

      nor interact with anything in that branch directly.

      +
      +
      +
    • +
    +
  2. +
  3. enable workflows for your fork

    +
      +
    • note: this step may depend on user-specific settings!

    • +
    • Back on top where you found “settings”, find and click “actions” to the left

    • +
    • Enable running the workflow actions assoc with the fre-cli repo under .github/workflows

    • +
    +
  4. +
  5. run your forks first workflow

    +
      +
    • +
      The documentation builds as the last steps to create_test_conda_env.yml when theres a push to main.

      To get your first workflow run on your fork, comment out the github.ref == ‘refs/heads/main’ bit +so that it runs when you push to any branch, and make a small, trivial, commit somewhere to your +remote fork.

      +
      +
      +
    • +
    • +
      You should be able to find the deployed webpage from a successful workflow at

      https://your_username.github.io/fre-cli (if you did not change the fork’s name from fre-cli, that is).

      +
      +
      +
    • +
    • If you’re only editing docs, you can make the turn-around time on your workflow ~3 min faster by

    • +
    +

    commenting-out the pylint and pytest steps in create_test_conda_env.yml, and disable running the +build_conda.yml workflow

    +
  6. +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/developer_usage.html b/developer_usage.html new file mode 100644 index 00000000..bb0d6da8 --- /dev/null +++ b/developer_usage.html @@ -0,0 +1,232 @@ + + + + + + + + + Developer Usage — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Developer Usage

+

Developers are free to use the user guide above to familiarize with the CLI and save time from +having to install any dependencies, but development within a Conda environment is heavily +recommended regardless.

+

Gain access to the repository with git clone --recursive git@github.com:NOAA-GFDL/fre-cli.git or your fork’s +link (recommended) and an SSH RSA key. Once inside the repository, developers can test local changes +by running a pip install . inside of the root directory to install the fre-cli package locally +with the newest local changes. Test as a normal user would use the CLI.

+
+

Adding New Tools

+
+

From Other Repositories

+

Currently, the solution to this task is to approach it using Conda packages. The tool that is being +added must reside within a repository that contains a meta.yaml that includes Conda dependencies +like the one in this repository and ideally a setup.py (may be subject to change due to deprecation) +that may include any potentially needed pip dependencies

+
    +
  • Once published as a Conda package, ideally on the NOAA-GFDL conda channel, +an addition can be made to the run section under requirements in meta.yaml of the fre-cli +following the syntax channel::package

  • +
  • On pushes to the main branch, the package located at https://anaconda.org/NOAA-GFDL/fre-cli will automatically +be updated using by the workflow defined in .github/workflows/publish_conda.yml

  • +
+
+
+

Checklist

+

For the new tool you are trying to develop, there are a few criteria to satisfy

+
    +
  1. Create a subdirectory for the tool group inside the fre/ directory; i.e. fre/<tool>

  2. +
  3. Add an __init__.py inside of fre/<tool>

  4. +
+
    +
  • typically this file should be empty, but it depends on the <tool>’s needs

  • +
  • even if empty, the file facillitates module importability and must be present

  • +
+
    +
  1. Add a file named fre/<tool>/fre<tool>.py. This will serve as the main entry point for fre +into the <tool>’s functionality

  2. +
  3. Add a click group named after <tool> within fre/<tool>/fre<tool>.py

  4. +
+
    +
  • This click group will contain all the functionality under the <tool>

  • +
+
    +
  1. Create separate files as needed for different commands; do not code out the full +implemetation of <tool> inside of a click command within fre/<tool>/fre<tool>.py.

  2. +
+
    +
  • better yet, consider what structure your tool may need in the future for maintainability’s sake

  • +
  • if you need, specify a <subtool> like fre/<tool>/<subtool>. fre/app currently has +this structure

  • +
+
    +
  1. Be sure to import the contents of the needed subcommand scripts inside of fre<tool>.py

  2. +
+
    +
  • i.e. from fre.<tool>.toolCommandScript import *

  • +
+
    +
  1. At this point, you can copy and paste the parts of your main click command from its script +into fre<tool>.py when implementing the function reflective of the command function

  2. +
+
    +
  • Everything will remain the same; i.e. arguments, options, etc.

  • +
  • However, this new function within fre<tool>.py must a new line after the arguments, options, +and other command components; @click.pass_context

  • +
  • Along with this, a new argument context must now be added to the parameters of the command +(preferably at the beginning, but it won’t break it if it’s not)

  • +
+
    +
  1. From here, all that needs to be added after defining the command with a name is +context.forward(mainFunctionOfToolCommand), and done!

  2. +
  3. +
    The last step is to replicate the command in the same way as done in fre<tool>.py

    inside of fre.py, but make sure to add from fre import <tool> and +from fre.<tool> import *

    +
    +
    +
  4. +
+

Please refer to this issue when encountering naming issues: +NOAA-GFDL#31

+
+
+

Example fre/ Directory Structure

+

fre/ +├── __init__.py +├── fre.py +├── fre<tool> +│ ├── __init__.py +│ ├── toolCommandScript.py +│ └── fre<tool>.py

+
+
+

MANIFEST.in

+

In the case where non-python files like templates, examples, and outputs are to be included in the fre-cli package, +MANIFEST.in can provide the solution. Ensure that the file exists within the correct folder, and add a line to the +MANIFEST.in file saying something like include fre/fre<tool>/fileName.fileExtension

+
    +
  • For more efficiency, if there are multiple files of the same type needed, the MANIFEST.in addition can be something +like recursive-include fre/fre<tool> *.fileExtension which would recursively include every file matching that +fileExtension within the specified directory and its respective subdirectories.

  • +
+
+
+

Adding Documentation

+

see section “Documentation-Documentation”

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.freapp.html b/fre.app.freapp.html new file mode 100644 index 00000000..dbbabc3b --- /dev/null +++ b/fre.app.freapp.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.app.freapp module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.app.freapp module

+

fre app calls

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.generate_time_averages.cdoTimeAverager.html b/fre.app.generate_time_averages.cdoTimeAverager.html new file mode 100644 index 00000000..6b317378 --- /dev/null +++ b/fre.app.generate_time_averages.cdoTimeAverager.html @@ -0,0 +1,125 @@ + + + + + + + + + fre.app.generate_time_averages.cdoTimeAverager module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.app.generate_time_averages.cdoTimeAverager module

+

class using (mostly) cdo functions for time-averages

+
+
+class fre.app.generate_time_averages.cdoTimeAverager.cdoTimeAverager(pkg, var, unwgt, avg_type, stddev_type)
+

Bases: timeAverager

+

class inheriting from abstract base class timeAverager +generates time-averages using cdo (mostly, see weighted approach)

+
+
+generate_timavg(infile=None, outfile=None)
+

use cdo package routines via python bindings

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.generate_time_averages.frenctoolsTimeAverager.html b/fre.app.generate_time_averages.frenctoolsTimeAverager.html new file mode 100644 index 00000000..eab53648 --- /dev/null +++ b/fre.app.generate_time_averages.frenctoolsTimeAverager.html @@ -0,0 +1,125 @@ + + + + + + + + + fre.app.generate_time_averages.frenctoolsTimeAverager module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • + View page source +
  • +
+
+
+
+
+ +
+

fre.app.generate_time_averages.frenctoolsTimeAverager module

+

class for utilizing timavg.csh (aka script to TAVG fortran exe) in frenc-tools

+
+
+class fre.app.generate_time_averages.frenctoolsTimeAverager.frenctoolsTimeAverager(pkg, var, unwgt, avg_type, stddev_type)
+

Bases: timeAverager

+

class inheriting from abstract base class timeAverager +generates time-averages using fre-nctools

+
+
+generate_timavg(infile=None, outfile=None)
+

use fre-nctool’s CLI timavg.csh with subprocess call

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.generate_time_averages.frepytoolsTimeAverager.html b/fre.app.generate_time_averages.frepytoolsTimeAverager.html new file mode 100644 index 00000000..39d055f4 --- /dev/null +++ b/fre.app.generate_time_averages.frepytoolsTimeAverager.html @@ -0,0 +1,127 @@ + + + + + + + + + fre.app.generate_time_averages.frepytoolsTimeAverager module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • + View page source +
  • +
+
+
+
+
+ +
+

fre.app.generate_time_averages.frepytoolsTimeAverager module

+

class for python-native routine usuing netCDF4 and numpy to crunch time-averages

+
+
+class fre.app.generate_time_averages.frepytoolsTimeAverager.frepytoolsTimeAverager(pkg, var, unwgt, avg_type, stddev_type)
+

Bases: timeAverager

+

class inheriting from abstract base class timeAverager +generates time-averages using a python-native approach +avoids using other third party statistics functions by design.

+
+
+generate_timavg(infile=None, outfile=None)
+

frepytools approach in a python-native manner. +deliberately avoids pre-packaged routines.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.generate_time_averages.generate_time_averages.html b/fre.app.generate_time_averages.generate_time_averages.html new file mode 100644 index 00000000..567c0363 --- /dev/null +++ b/fre.app.generate_time_averages.generate_time_averages.html @@ -0,0 +1,117 @@ + + + + + + + + + fre.app.generate_time_averages.generate_time_averages module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • + View page source +
  • +
+
+
+
+
+ +
+

fre.app.generate_time_averages.generate_time_averages module

+

tools for generating time averages from various packages

+
+
+fre.app.generate_time_averages.generate_time_averages.generate_time_average(infile=None, outfile=None, pkg=None, var=None, unwgt=False, avg_type=None, stddev_type=None)
+

steering function to various averaging functions above

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.generate_time_averages.html b/fre.app.generate_time_averages.html new file mode 100644 index 00000000..4c2dc3a2 --- /dev/null +++ b/fre.app.generate_time_averages.html @@ -0,0 +1,194 @@ + + + + + + + + + fre.app.generate_time_averages package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.app.generate_time_averages package

+
+

Subpackages

+ +
+
+

Submodules

+ +
+
+

Module contents

+

required for generate_time_averages module import functionality

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.generate_time_averages.tests.html b/fre.app.generate_time_averages.tests.html new file mode 100644 index 00000000..5f07a024 --- /dev/null +++ b/fre.app.generate_time_averages.tests.html @@ -0,0 +1,141 @@ + + + + + + + + + fre.app.generate_time_averages.tests package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fre.app.generate_time_averages.tests.test_generate_time_averages.html b/fre.app.generate_time_averages.tests.test_generate_time_averages.html new file mode 100644 index 00000000..18d9b5c1 --- /dev/null +++ b/fre.app.generate_time_averages.tests.test_generate_time_averages.html @@ -0,0 +1,219 @@ + + + + + + + + + fre.app.generate_time_averages.tests.test_generate_time_averages module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • + View page source +
  • +
+
+
+
+
+ +
+

fre.app.generate_time_averages.tests.test_generate_time_averages module

+

for testing fre app generate-time-averages

+
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.run_avgtype_pkg_calculations(infile=None, outfile=None, pkg=None, avg_type=None, unwgt=None, stddev_type=None)
+

test-harness function, called by other test functions.

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_cdo_time_avgs()
+

generates a weighted time averaged file using cdo

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_cdo_time_unwgt_avgs()
+

generates an unweighted time averaged file using cdo

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_cdo_time_unwgt_stddevs()
+

generates a time averaged file using cdo

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_compare_cdo_to_fre_nctools()
+

compares cdo pkg answer to fre_nctools pkg answer

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_compare_fre_python_tools_to_cdo()
+

compares fre_python_tools pkg answer to cdo pkg answer

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_compare_fre_python_tools_to_fre_nctools()
+

compares fre_python_tools pkg answer to fre_nctools pkg answer

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_compare_unwgt_fre_python_tools_to_unwgt_cdo()
+

compares fre_python_tools pkg answer to cdo pkg answer

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_fre_python_tools_time_avgs()
+

generates a time averaged file using fre_python_tools’s version

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_fre_python_tools_time_avgs_stddevs()
+

generates a time averaged file using fre_python_tools’s version

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_fre_python_tools_time_unwgt_avgs()
+

generates a time averaged file using fre_python_tools’s version

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_fre_python_tools_time_unwgt_avgs_stddevs()
+

generates a time averaged file using fre_python_tools’s version

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_monthly_cdo_time_unwgt_avgs()
+

generates an unweighted monthly time averaged file using cdo

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_monthly_cdo_time_unwgt_stddevs()
+

generates a monthly time averaged file using cdo

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_seasonal_cdo_time_unwgt_avgs()
+

generates an unweighted seasonal time averaged file using cdo

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_seasonal_cdo_time_unwgt_stddevs()
+

generates a seasonal time averaged file using cdo

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_time_avg_file_dir_exists()
+

look for input test file directory

+
+ +
+
+fre.app.generate_time_averages.tests.test_generate_time_averages.test_time_avg_input_file_exists()
+

look for input test file

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.generate_time_averages.timeAverager.html b/fre.app.generate_time_averages.timeAverager.html new file mode 100644 index 00000000..b1eb2363 --- /dev/null +++ b/fre.app.generate_time_averages.timeAverager.html @@ -0,0 +1,157 @@ + + + + + + + + + fre.app.generate_time_averages.timeAverager module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.app.generate_time_averages.timeAverager module

+

core class structure for this module.

+
+
+class fre.app.generate_time_averages.timeAverager.timeAverager(pkg, var, unwgt, avg_type, stddev_type)
+

Bases: object

+

abstract base class for generating time averages + related statistical quantities +this class must be inherited by another for functionality.

+
+
+avg_type: str
+
+ +
+
+generate_timavg(infile=None, outfile=None)
+

# this is a hint: this is to be defined by classes inheriting from the abstract one +this function is never to be fully defined here by design.

+
+ +
+
+pkg: str
+
+ +
+
+stddev_type: str
+
+ +
+
+unwgt: bool
+
+ +
+
+var: str
+
+ +
+
+var_has_time_units(an_nc_var=None)
+

checks if variable’s units are of time

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.html b/fre.app.html new file mode 100644 index 00000000..20e7ff27 --- /dev/null +++ b/fre.app.html @@ -0,0 +1,194 @@ + + + + + + + + + fre.app package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/fre.app.mask_atmos_plevel.html b/fre.app.mask_atmos_plevel.html new file mode 100644 index 00000000..15b2197c --- /dev/null +++ b/fre.app.mask_atmos_plevel.html @@ -0,0 +1,153 @@ + + + + + + + + + fre.app.mask_atmos_plevel module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.app.mask_atmos_plevel module

+

This script contains the refineDiags that produce data at the same +frequency as the input data (no reduction) such as surface albedo, +masking fields,… +It can accept any file and will only compute refineDiags in fields +are present.

+
+
+fre.app.mask_atmos_plevel.mask_field_above_surface_pressure(ds, var, ds_ps)
+

mask data with pressure larger than surface pressure

+
+ +
+
+fre.app.mask_atmos_plevel.post_write(filename, var_with_bounds, bounds_variables)
+

fix a posteriori attributes that xarray.to_netcdf +did not do properly using low level netcdf lib

+
+ +
+
+fre.app.mask_atmos_plevel.preprocess(ds)
+

add needs_atmos_masking attribute if var ends with _unmsk

+
+ +
+
+fre.app.mask_atmos_plevel.pressure_coordinate(ds, varname)
+

check if dataArray has pressure coordinate fitting requirements +and return it

+
+ +
+
+fre.app.mask_atmos_plevel.set_netcdf_encoding(ds, pressure_vars)
+

set preferred options for netcdf encoding

+
+ +
+
+fre.app.mask_atmos_plevel.write_dataset(ds, template, outfile)
+

prepare the dataset and dump into netcdf file

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.regrid_xy.html b/fre.app.regrid_xy.html new file mode 100644 index 00000000..f09744aa --- /dev/null +++ b/fre.app.regrid_xy.html @@ -0,0 +1,135 @@ + + + + + + + + + fre.app.regrid_xy package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.app.regrid_xy.regrid_xy.html b/fre.app.regrid_xy.regrid_xy.html new file mode 100644 index 00000000..7515f9ab --- /dev/null +++ b/fre.app.regrid_xy.regrid_xy.html @@ -0,0 +1,188 @@ + + + + + + + + + fre.app.regrid_xy.regrid_xy module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.app.regrid_xy.regrid_xy module

+

remaps scalar and/or vector fields. It is capable of remapping +from a spherical grid onto a different one, e.g. spherical, +or tripolar. By default, it does so using a conservative scheme. +most valid input args to fregrid are valid in this script

+
+
+fre.app.regrid_xy.regrid_xy.check_interp_method(nc_variable, interp_method)
+

print warning if optional interp_method clashes with nc file attribute field, if present

+
+ +
+
+fre.app.regrid_xy.regrid_xy.check_per_component_settings(component_list, rose_app_cfg)
+

for a source file ref’d by multiple components check per-component +settings for uniqueness. output list of bools of same length to check +in componenet loop

+
+ +
+
+fre.app.regrid_xy.regrid_xy.freq_to_date_format(iso_freq)
+

Print legacy Bronx-like date template format given a frequency (ISO 8601 duration)

+
+ +
+
+fre.app.regrid_xy.regrid_xy.get_mosaic_file_name(grid_spec_file, mosaic_type)
+

read string from a numpy masked array WHY

+
+ +
+
+fre.app.regrid_xy.regrid_xy.get_mosaic_grid_file_name(input_mosaic)
+

get mosaic grid file name from NESTED numpy masked array WHY

+
+ +
+
+fre.app.regrid_xy.regrid_xy.main()
+

steering, local test/debug

+
+ +
+
+fre.app.regrid_xy.regrid_xy.make_component_list(config, source)
+

make list of relevant component names where source file appears in sources

+
+ +
+
+fre.app.regrid_xy.regrid_xy.make_regrid_var_list(target_file, interp_method=None)
+

create default list of variables to be regridded within target file.

+
+ +
+
+fre.app.regrid_xy.regrid_xy.regrid_xy(input_dir=None, output_dir=None, begin=None, tmp_dir=None, remap_dir=None, source=None, grid_spec=None, def_xy_interp=None)
+

calls fre-nctools’ fregrid to regrid net cdf files

+
+ +
+
+fre.app.regrid_xy.regrid_xy.safe_rose_config_get(config, section, field)
+

read optional variables from rose configuration, and don’t error on None value

+
+ +
+
+fre.app.regrid_xy.regrid_xy.test_import()
+

for quickly testing import within pytest

+
+ +
+
+fre.app.regrid_xy.regrid_xy.truncate_date(date, freq)
+

truncates iso freq to iso date time

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.catalog.frecatalog.html b/fre.catalog.frecatalog.html new file mode 100644 index 00000000..9d711a62 --- /dev/null +++ b/fre.catalog.frecatalog.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.catalog.frecatalog module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.catalog.frecatalog module

+

entry point for fre catalog subcommands

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.catalog.html b/fre.catalog.html new file mode 100644 index 00000000..49c1db7a --- /dev/null +++ b/fre.catalog.html @@ -0,0 +1,139 @@ + + + + + + + + + fre.catalog package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.catalog package

+
+

Subpackages

+ +
+
+

Submodules

+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.catalog.tests.html b/fre.catalog.tests.html new file mode 100644 index 00000000..2446eff6 --- /dev/null +++ b/fre.catalog.tests.html @@ -0,0 +1,124 @@ + + + + + + + + + fre.catalog.tests package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.catalog.tests package

+
+

Submodules

+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.catalog.tests.test_fre_catalog.html b/fre.catalog.tests.test_fre_catalog.html new file mode 100644 index 00000000..5e5bcd04 --- /dev/null +++ b/fre.catalog.tests.test_fre_catalog.html @@ -0,0 +1,115 @@ + + + + + + + + + fre.catalog.tests.test_fre_catalog module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.catalog.tests.test_fre_catalog module

+
+
+fre.catalog.tests.test_fre_catalog.test_fre_catalog_import()
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.check.frecheck.html b/fre.check.frecheck.html new file mode 100644 index 00000000..5ebc9507 --- /dev/null +++ b/fre.check.frecheck.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.check.frecheck module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.check.frecheck module

+

fre check

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.check.frecheckexample.html b/fre.check.frecheckexample.html new file mode 100644 index 00000000..d05d7010 --- /dev/null +++ b/fre.check.frecheckexample.html @@ -0,0 +1,113 @@ + + + + + + + + + fre.check.frecheckexample module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.check.frecheckexample module

+

experimentation file for integrating one file’s functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.check.html b/fre.check.html new file mode 100644 index 00000000..f3c9c7e9 --- /dev/null +++ b/fre.check.html @@ -0,0 +1,122 @@ + + + + + + + + + fre.check package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.check package

+
+

Submodules

+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.cmor.cmor_mixer.html b/fre.cmor.cmor_mixer.html new file mode 100644 index 00000000..4f82939e --- /dev/null +++ b/fre.cmor.cmor_mixer.html @@ -0,0 +1,269 @@ + + + + + + + + + fre.cmor.cmor_mixer module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.cmor.cmor_mixer module

+

python module housing the metadata processing routines utilizing the cmor module, in addition to +click API entry points +see README.md for additional information on fre cmor run (cmor_mixer.py) usage

+
+
+fre.cmor.cmor_mixer.check_dataset_for_ocean_grid(ds)
+

checks netCDF4.Dataset ds for ocean grid origin, and throws an error if it finds one. accepts +one argument. this function has no return.

+
+

ds: netCDF4.Dataset object containing variables with associated dimensional information.

+
+
+ +
+
+fre.cmor.cmor_mixer.cmor_run_subtool(indir=None, json_var_list=None, json_table_config=None, json_exp_config=None, outdir=None, opt_var_name=None)
+
+
primary steering function for the cmor_mixer tool, i.e essentially main. Accepts six args:
+
indir: string, directory containing netCDF files. keys specified in json_var_list are local

variable names used for targeting specific files

+
+
json_var_list: string, path pointing to a json file containing directory of key/value

pairs. the keys are the “local” names used in the filename, and the +values pointed to by those keys are strings representing the name of the +variable contained in targeted files. the key and value are often the same, +but it is not required.

+
+
json_table_config: json file containing CMIP-compliant per-variable/metadata for specific

MIP table. The MIP table can generally be identified by the specific +filename (e.g. “Omon”)

+
+
json_exp_config: json file containing metadata dictionary for CMORization. this metadata is effectively

appended to the final output file’s header

+
+
outdir: string, directory root that will contain the full output and output directory

structure generated by the cmor module upon request.

+
+
opt_var_name: string, optional, specify a variable name to specifically process only filenames matching

that variable name. I.e., this string help target local_vars, not target_vars.

+
+
+
+
+
+ +
+
+fre.cmor.cmor_mixer.cmorize_target_var_files(indir=None, target_var=None, local_var=None, iso_datetime_arr=None, name_of_set=None, json_exp_config=None, outdir=None, proj_table_vars=None, json_table_config=None)
+

processes a target directory/file +this routine is almost entirely exposed data movement before/after calling +rewrite_netcdf_file_var it is also the most hopelessly opaque routine in this entire dang macro. +this badboy right here accepts… lord help us… !!!NINE!!! arguments, NINE.

+
+

indir: string, path to target directories containing netcdf files to cmorize +target_var: string, name of variable inside the netcdf file to cmorize +local_var: string, value of the variable name in the filename, right before the .nc

+
+

extension. often identical to target_var but not always.

+
+
+
iso_datetime_arr: list of strings, each one a unique ISO datetime string found in targeted

netcdf filenames

+
+
name_of_set: string, representing the post-processing component (GFDL convention) of the

targeted files.

+
+
+

json_exp_config: see cmor_run_subtool arg desc +outdir: string, path to output directory root to move the cmor module output to, including

+
+

the whole directory structure

+
+

proj_table_vars: an opened json file object, read from json_table_config +json_table_config: see cmor_run_subtool arg desc

+
+
+ +
+
+fre.cmor.cmor_mixer.copy_nc(in_nc, out_nc)
+

copy target input netcdf file in_nc to target out_nc. I have to think this is not a trivial copy +operation, as if it were, using shutil’s copy would be sufficient. accepts two arguments

+
+

in_nc: string, path to an input netcdf file we wish to copy +out_nc: string, an output path to copy the targeted input netcdf file to

+
+
+ +
+
+fre.cmor.cmor_mixer.create_tmp_dir(outdir)
+

creates a tmp_dir based on targeted output directory root. returns the name of the tmp dir. +accepts one argument:

+
+
+
outdir: string, representing the final output directory root for the cmor modules netcdf

file output. tmp_dir will be slightly different depending on the output directory +targeted

+
+
+
+
+ +
+
+fre.cmor.cmor_mixer.get_iso_datetimes(var_filenames, iso_datetime_arr=None)
+
+
appends iso datetime strings found amongst filenames to iso_datetime_arr.
+
var_filenames: non-empty list of strings representing filenames. some of which presumably

contain datetime strings

+
+
iso_datetime_arr: list of strings, empty or non-empty, representing datetimes found in

var_filenames entries. the objet pointed to by the reference +iso_datetime_arr is manipulated, and so need-not be returned

+
+
+
+
+
+ +
+
+fre.cmor.cmor_mixer.get_var_filenames(indir, var_filenames=None, local_var=None)
+
+
appends files ending in .nc located within indir to list var_filenames accepts three arguments

indir: string, representing a path to a directory containing files ending in .nc extension +var_filenames: list of strings, empty or non-empty, to append discovered filenames to. the

+
+

object pointed to by the reference var_filenames is manipulated, and so need +not be returned.

+
+

local_var: string, optional, if not None, will be used for ruling out filename targets

+
+
+
+ +
+
+fre.cmor.cmor_mixer.get_vertical_dimension(ds, target_var)
+

determines the vertical dimensionality of target_var within netCDF4 Dataset ds. accepts two +arguments and returns an object represnting the vertical dimensions assoc with the target_var.

+
+

ds: netCDF4.Dataset object containing variables with associated dimensional information. +target_var: string, representating a variable contained within the netCDF4.Dataset ds

+
+
+ +
+
+fre.cmor.cmor_mixer.rewrite_netcdf_file_var(proj_table_vars=None, local_var=None, netcdf_file=None, target_var=None, json_exp_config=None, json_table_config=None)
+

rewrite the input netcdf file nc_fl containing target_var in a CMIP-compliant manner. +accepts six arguments, all required:

+
+

proj_table_vars: json dictionary object, variable table read from json_table_config. +local_var: string, variable name used for finding files locally containing target_var,

+
+

this argument is often equal to target_var.

+
+

netcdf_file: string, representing path to intput netcdf file. +target_var: string, representing the variable name attached to the data object in the netcdf file. +json_exp_config: string, representing path to json configuration file holding metadata for appending to output

+
+

this argument is most used for making sure the right grid label is getting attached to the right output

+
+
+
json_table_config: string, representing path to json configuration file holding variable names for a given table.

proj_table_vars is read from this file, but both are passed anyways.

+
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.cmor.frecmor.html b/fre.cmor.frecmor.html new file mode 100644 index 00000000..117261f7 --- /dev/null +++ b/fre.cmor.frecmor.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.cmor.frecmor module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.cmor.frecmor module

+

fre cmor

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.cmor.html b/fre.cmor.html new file mode 100644 index 00000000..957ad5f1 --- /dev/null +++ b/fre.cmor.html @@ -0,0 +1,160 @@ + + + + + + + + + fre.cmor package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fre.cmor.tests.html b/fre.cmor.tests.html new file mode 100644 index 00000000..fa71b390 --- /dev/null +++ b/fre.cmor.tests.html @@ -0,0 +1,132 @@ + + + + + + + + + fre.cmor.tests package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fre.cmor.tests.test_cmor_run_subtool.html b/fre.cmor.tests.test_cmor_run_subtool.html new file mode 100644 index 00000000..d6e1719c --- /dev/null +++ b/fre.cmor.tests.test_cmor_run_subtool.html @@ -0,0 +1,168 @@ + + + + + + + + + fre.cmor.tests.test_cmor_run_subtool module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.cmor.tests.test_cmor_run_subtool module

+

tests for fre.cmor.cmor_run_subtool

+
+
+fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case1(capfd)
+

fre cmor run, test-use case

+
+ +
+
+fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case1_output_compare_data(capfd)
+

I/O data-only comparison of test case1

+
+ +
+
+fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case1_output_compare_metadata(capfd)
+

I/O metadata-only comparison of test case1

+
+ +
+
+fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case2(capfd)
+

fre cmor run, test-use case2

+
+ +
+
+fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case2_output_compare_data(capfd)
+

I/O data-only comparison of test case2

+
+ +
+
+fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case2_output_compare_metadata(capfd)
+

I/O metadata-only comparison of test case2

+
+ +
+
+fre.cmor.tests.test_cmor_run_subtool.test_setup_cmor_cmip_table_repo()
+

setup routine, if it doesnt exist, clone the repo holding CMOR/CMIP6 tables

+
+ +
+
+fre.cmor.tests.test_cmor_run_subtool.test_setup_fre_cmor_run_subtool(capfd)
+

checks for outputfile from prev pytest runs, removes it if it’s present. +this routine also checks to make sure the desired input file is present

+
+ +
+
+fre.cmor.tests.test_cmor_run_subtool.test_setup_fre_cmor_run_subtool_case2(capfd)
+

make a copy of the input file to the slightly different name. +checks for outputfile from prev pytest runs, removes it if it’s present. +this routine also checks to make sure the desired input file is present

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.fre.html b/fre.fre.html new file mode 100644 index 00000000..29a8f630 --- /dev/null +++ b/fre.fre.html @@ -0,0 +1,116 @@ + + + + + + + + + fre.fre module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.fre module

+

Main host file for FRE-CLI program scripts +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL +2023-2024 +principal click group for main/fre allows for subgroup functions to +be called via this script. I.e. ‘fre’ is the entry point

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.html b/fre.html new file mode 100644 index 00000000..50e621f4 --- /dev/null +++ b/fre.html @@ -0,0 +1,468 @@ + + + + + + + + + fre package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre package

+
+

Subpackages

+
+ +
+
+
+

Submodules

+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.lazy_group.html b/fre.lazy_group.html new file mode 100644 index 00000000..f276b6de --- /dev/null +++ b/fre.lazy_group.html @@ -0,0 +1,133 @@ + + + + + + + + + fre.lazy_group module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.lazy_group module

+

for lazy-style loading of commands and subcommands while using click +see https://click.palletsprojects.com/en/8.1.x/complex/

+
+
+class fre.lazy_group.LazyGroup(*args, lazy_subcommands=None, **kwargs)
+

Bases: Group

+

class defining lazygroup command/subcommand loading

+
+
+get_command(ctx, cmd_name)
+

Given a context and a command name, this returns a +Command object if it exists or returns None.

+
+ +
+
+list_commands(ctx)
+

Returns a list of subcommand names in the order they should +appear.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.list.frelist.html b/fre.list.frelist.html new file mode 100644 index 00000000..f18ca3bb --- /dev/null +++ b/fre.list.frelist.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.list.frelist module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.list.frelist module

+

fre list

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.list.frelistexample.html b/fre.list.frelistexample.html new file mode 100644 index 00000000..c31f5faa --- /dev/null +++ b/fre.list.frelistexample.html @@ -0,0 +1,113 @@ + + + + + + + + + fre.list.frelistexample module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.list.frelistexample module

+

experimentation file for integrating one file’s functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.list.html b/fre.list.html new file mode 100644 index 00000000..73130f32 --- /dev/null +++ b/fre.list.html @@ -0,0 +1,122 @@ + + + + + + + + + fre.list package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.list package

+
+

Submodules

+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.make.createCheckout.html b/fre.make.createCheckout.html new file mode 100644 index 00000000..dc9496e5 --- /dev/null +++ b/fre.make.createCheckout.html @@ -0,0 +1,115 @@ + + + + + + + + + fre.make.createCheckout module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.make.createCheckout module

+
+
+fre.make.createCheckout.checkout_create(yamlfile, platform, target, no_parallel_checkout, jobs, execute, verbose)
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.make.createCompile.html b/fre.make.createCompile.html new file mode 100644 index 00000000..80cf35ac --- /dev/null +++ b/fre.make.createCompile.html @@ -0,0 +1,115 @@ + + + + + + + + + fre.make.createCompile module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.make.createCompile module

+
+
+fre.make.createCompile.compile_create(yamlfile, platform, target, jobs, parallel, execute, verbose)
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.make.createDocker.html b/fre.make.createDocker.html new file mode 100644 index 00000000..b74b1042 --- /dev/null +++ b/fre.make.createDocker.html @@ -0,0 +1,115 @@ + + + + + + + + + fre.make.createDocker module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.make.createDocker module

+
+
+fre.make.createDocker.dockerfile_create(yamlfile, platform, target, execute)
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.make.createMakefile.html b/fre.make.createMakefile.html new file mode 100644 index 00000000..264b39a1 --- /dev/null +++ b/fre.make.createMakefile.html @@ -0,0 +1,115 @@ + + + + + + + + + fre.make.createMakefile module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.make.createMakefile module

+
+
+fre.make.createMakefile.makefile_create(yamlfile, platform, target)
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.make.fremake.html b/fre.make.fremake.html new file mode 100644 index 00000000..0df622e8 --- /dev/null +++ b/fre.make.fremake.html @@ -0,0 +1,110 @@ + + + + + + + + + fre.make.fremake module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.make.fremake module

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.make.html b/fre.make.html new file mode 100644 index 00000000..237ac419 --- /dev/null +++ b/fre.make.html @@ -0,0 +1,163 @@ + + + + + + + + + fre.make package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fre.make.runFremake.html b/fre.make.runFremake.html new file mode 100644 index 00000000..38060ab8 --- /dev/null +++ b/fre.make.runFremake.html @@ -0,0 +1,119 @@ + + + + + + + + + fre.make.runFremake module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.make.runFremake module

+

date 2023 +author(s): Tom Robinson, Dana Singh, Bennett Chang +fre make is used to create, run and checkout code, and compile a model.

+
+
+fre.make.runFremake.fremake_run(yamlfile, platform, target, parallel, jobs, no_parallel_checkout, verbose)
+

run fremake via click

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.make.tests.html b/fre.make.tests.html new file mode 100644 index 00000000..fa3f19a3 --- /dev/null +++ b/fre.make.tests.html @@ -0,0 +1,128 @@ + + + + + + + + + fre.make.tests package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.make.tests.test_create_makefile.html b/fre.make.tests.test_create_makefile.html new file mode 100644 index 00000000..8c8f2752 --- /dev/null +++ b/fre.make.tests.test_create_makefile.html @@ -0,0 +1,141 @@ + + + + + + + + + fre.make.tests.test_create_makefile module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.make.tests.test_create_makefile module

+

Test fre make create-makefile

+
+
+fre.make.tests.test_create_makefile.test_bm_makefile_creation()
+

Check the makefile is created when a bare-metal platform is used

+
+ +
+
+fre.make.tests.test_create_makefile.test_compileyaml_exists()
+

Check the compile yaml exists

+
+ +
+
+fre.make.tests.test_create_makefile.test_container_makefile_creation()
+

Check the makefile is created when the container platform is used

+
+ +
+
+fre.make.tests.test_create_makefile.test_modelyaml_exists()
+

Check the model yaml exists

+
+ +
+
+fre.make.tests.test_create_makefile.test_platformyaml_exists()
+

Check the platform yaml exists

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.checkoutScript.html b/fre.pp.checkoutScript.html new file mode 100644 index 00000000..58c57e13 --- /dev/null +++ b/fre.pp.checkoutScript.html @@ -0,0 +1,110 @@ + + + + + + + + + fre.pp.checkoutScript module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.checkoutScript module

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.configure_script_xml.html b/fre.pp.configure_script_xml.html new file mode 100644 index 00000000..53103afb --- /dev/null +++ b/fre.pp.configure_script_xml.html @@ -0,0 +1,183 @@ + + + + + + + + + fre.pp.configure_script_xml module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.configure_script_xml module

+

Primary Usage: fre-bronx-to-canopy -x XML -e EXP -p PLATFORM -t TARGET

+

The Bronx-to-Canopy XML converter overwrites 3 files: +- rose-suite.conf +- app/remap-pp-components/rose-app.conf +- app/regrid-xy/rose-app.conf

+
+
+fre.pp.configure_script_xml.chunk_from_legacy(legacy_chunk)
+

Return ISO8601 duration given Bronx-style chunk

+
+
Arguments:
    +
  1. legacy_chunk[str]: The Bronx chunk

  2. +
+
+
+
+ +
+
+fre.pp.configure_script_xml.duration_to_seconds(duration)
+

Returns the conversion of a chunk duration to seconds

+
+
Arguments:
    +
  1. duration[str]: The original chunk duration

  2. +
+
+
+
+ +
+
+fre.pp.configure_script_xml.frelist_xpath(xml, platform, target, experiment, xpath)
+
+
Returns filepaths of FRE XML elements that use X-path notation

using Bronx’s ‘frelist’ command via subprocess module

+
+
Arguments:
    +
  1. args[str]: Argparse user-input arguments

  2. +
  3. xpath[str]: X-path (XML) notation required by ‘frelist’

  4. +
+
+
+
+ +
+
+fre.pp.configure_script_xml.freq_from_legacy(legacy_freq)
+

Return ISO8601 duration given Bronx-style frequencies

+
+
Arguments:
    +
  1. legacy_freq[str]: The Bronx frequency

  2. +
+
+
+
+ +
+
+fre.pp.configure_script_xml.main(xml, platform, target, experiment, do_analysis, historydir, refinedir, ppdir, do_refinediag, pp_start, pp_stop, validate, verbose, quiet, dual)
+

The meat of the converter

+
+
Arguments:
    +
  1. args[argparse Namespace]: Arguments given at the command line

  2. +
+
+
Tasks:
    +
  1. Generate key-value pairs for the rose-suite.conf.

  2. +
  3. Generate content for the regrid-xy app

  4. +
  5. Generate content for the remap-pp-components app

  6. +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.configure_script_yaml.html b/fre.pp.configure_script_yaml.html new file mode 100644 index 00000000..c649e3d0 --- /dev/null +++ b/fre.pp.configure_script_yaml.html @@ -0,0 +1,158 @@ + + + + + + + + + fre.pp.configure_script_yaml module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.configure_script_yaml module

+

Script creates rose-apps and rose-suite +files for the workflow from the pp yaml.

+
+
+fre.pp.configure_script_yaml.quote_rose_values(value)
+

rose-suite.conf template variables must be quoted unless they are +boolean, in which case do not quote them.

+
+ +
+
+fre.pp.configure_script_yaml.rose_init(experiment, platform, target)
+

Initialize the rose suite and app configurations.

+
+ +
+
+fre.pp.configure_script_yaml.set_rose_apps(yamlfile, rose_regrid, rose_remap)
+

Set items in the regrid and remap rose app configurations.

+
+ +
+
+fre.pp.configure_script_yaml.set_rose_suite(yamlfile, rose_suite)
+

Set items in the rose suite configuration.

+
+ +
+
+fre.pp.configure_script_yaml.validate_yaml(yamlfile)
+

Using the schema.json file, the yaml format is validated.

+
+ +
+
+fre.pp.configure_script_yaml.yamlInfo(yamlfile, experiment, platform, target)
+

Using a valid pp.yaml, the rose-app and rose-suite +configuration files are created in the cylc-src +directory. The pp.yaml is also copied to the +cylc-src directory.

+
+ +
+
+fre.pp.configure_script_yaml.yaml_load(yamlfile)
+

Load the given yaml and validate.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.frepp.html b/fre.pp.frepp.html new file mode 100644 index 00000000..5131ef8f --- /dev/null +++ b/fre.pp.frepp.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.pp.frepp module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.frepp module

+

fre pp

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.html b/fre.pp.html new file mode 100644 index 00000000..9d6684c0 --- /dev/null +++ b/fre.pp.html @@ -0,0 +1,169 @@ + + + + + + + + + fre.pp package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fre.pp.install.html b/fre.pp.install.html new file mode 100644 index 00000000..fe82319b --- /dev/null +++ b/fre.pp.install.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.pp.install module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.install module

+

fre pp install

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.run.html b/fre.pp.run.html new file mode 100644 index 00000000..018e10df --- /dev/null +++ b/fre.pp.run.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.pp.run module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.run module

+

fre pp run

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.status.html b/fre.pp.status.html new file mode 100644 index 00000000..cd65b433 --- /dev/null +++ b/fre.pp.status.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.pp.status module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.status module

+

fre pp status

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.tests.html b/fre.pp.tests.html new file mode 100644 index 00000000..f964a896 --- /dev/null +++ b/fre.pp.tests.html @@ -0,0 +1,130 @@ + + + + + + + + + fre.pp.tests package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.tests.test_configure_script_yaml.html b/fre.pp.tests.test_configure_script_yaml.html new file mode 100644 index 00000000..f87ac527 --- /dev/null +++ b/fre.pp.tests.test_configure_script_yaml.html @@ -0,0 +1,125 @@ + + + + + + + + + fre.pp.tests.test_configure_script_yaml module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.tests.test_configure_script_yaml module

+

Test configure_script_yaml

+
+
+fre.pp.tests.test_configure_script_yaml.test_combinedyaml_exists()
+

Make sure combined yaml file exists

+
+ +
+
+fre.pp.tests.test_configure_script_yaml.test_configure_script()
+

Tests success of confgure yaml script +Creates rose-suite, regrid rose-app, remap rose-app +TO-DO: will break this up for better tests

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.tests.test_rose_quoting.html b/fre.pp.tests.test_rose_quoting.html new file mode 100644 index 00000000..057091c5 --- /dev/null +++ b/fre.pp.tests.test_rose_quoting.html @@ -0,0 +1,120 @@ + + + + + + + + + fre.pp.tests.test_rose_quoting module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.tests.test_rose_quoting module

+
+
+fre.pp.tests.test_rose_quoting.test_boolean()
+
+ +
+
+fre.pp.tests.test_rose_quoting.test_string()
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.validate.html b/fre.pp.validate.html new file mode 100644 index 00000000..16202280 --- /dev/null +++ b/fre.pp.validate.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.pp.validate module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.validate module

+

fre pp validate

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.pp.wrapper.html b/fre.pp.wrapper.html new file mode 100644 index 00000000..ff38405c --- /dev/null +++ b/fre.pp.wrapper.html @@ -0,0 +1,113 @@ + + + + + + + + + fre.pp.wrapper module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.pp.wrapper module

+

frepp.py, a replacement for the frepp bash script located at: +https://gitlab.gfdl.noaa.gov/fre2/system-settings/-/blob/main/bin/frepp +Author: Carolyn.Whitlock

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.run.frerun.html b/fre.run.frerun.html new file mode 100644 index 00000000..c7aa3306 --- /dev/null +++ b/fre.run.frerun.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.run.frerun module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.run.frerun module

+

entry point for fre run subcommands

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.run.frerunexample.html b/fre.run.frerunexample.html new file mode 100644 index 00000000..fb15e4e6 --- /dev/null +++ b/fre.run.frerunexample.html @@ -0,0 +1,113 @@ + + + + + + + + + fre.run.frerunexample module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.run.frerunexample module

+

experimentation file for integrating one file’s functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.run.html b/fre.run.html new file mode 100644 index 00000000..4de56794 --- /dev/null +++ b/fre.run.html @@ -0,0 +1,122 @@ + + + + + + + + + fre.run package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.run package

+
+

Submodules

+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.test.fretest.html b/fre.test.fretest.html new file mode 100644 index 00000000..59729a4a --- /dev/null +++ b/fre.test.fretest.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.test.fretest module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.test.fretest module

+

entry point for fre test subcommands

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.test.fretestexample.html b/fre.test.fretestexample.html new file mode 100644 index 00000000..cf915a27 --- /dev/null +++ b/fre.test.fretestexample.html @@ -0,0 +1,113 @@ + + + + + + + + + fre.test.fretestexample module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.test.fretestexample module

+

experimentation file for integrating one file’s functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.test.html b/fre.test.html new file mode 100644 index 00000000..a3bd3d25 --- /dev/null +++ b/fre.test.html @@ -0,0 +1,122 @@ + + + + + + + + + fre.test package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.test package

+
+

Submodules

+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.html b/fre.tests.html new file mode 100644 index 00000000..688cbeb5 --- /dev/null +++ b/fre.tests.html @@ -0,0 +1,228 @@ + + + + + + + + + fre.tests package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests package

+
+

Submodules

+
+ +
+
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_app_cli.html b/fre.tests.test_fre_app_cli.html new file mode 100644 index 00000000..07c42362 --- /dev/null +++ b/fre.tests.test_fre_app_cli.html @@ -0,0 +1,171 @@ + + + + + + + + + fre.tests.test_fre_app_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_app_cli module

+

test “fre app” calls

+
+
+fre.tests.test_fre_app_cli.test_cli_fre_app(capfd)
+

fre app

+
+ +
+
+fre.tests.test_fre_app_cli.test_cli_fre_app_gen_time_averages(capfd)
+

fre cmor run

+
+ +
+
+fre.tests.test_fre_app_cli.test_cli_fre_app_gen_time_averages_help(capfd)
+

fre cmor run –help

+
+ +
+
+fre.tests.test_fre_app_cli.test_cli_fre_app_gen_time_averages_opt_dne(capfd)
+

fre cmor run optionDNE

+
+ +
+
+fre.tests.test_fre_app_cli.test_cli_fre_app_help(capfd)
+

fre app –help

+
+ +
+
+fre.tests.test_fre_app_cli.test_cli_fre_app_opt_dne(capfd)
+

fre app optionDNE

+
+ +
+
+fre.tests.test_fre_app_cli.test_cli_fre_app_regrid(capfd)
+

fre cmor run

+
+ +
+
+fre.tests.test_fre_app_cli.test_cli_fre_app_regrid_help(capfd)
+

fre cmor run –help

+
+ +
+
+fre.tests.test_fre_app_cli.test_cli_fre_app_regrid_opt_dne(capfd)
+

fre cmor run optionDNE

+
+ +
+
+fre.tests.test_fre_app_cli.test_cli_fre_app_regrid_test_case_1(capfd)
+

fre cmor run –help

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_catalog_cli.html b/fre.tests.test_fre_catalog_cli.html new file mode 100644 index 00000000..9c8c9bf2 --- /dev/null +++ b/fre.tests.test_fre_catalog_cli.html @@ -0,0 +1,151 @@ + + + + + + + + + fre.tests.test_fre_catalog_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_catalog_cli module

+

test “fre catalog” calls

+
+
+fre.tests.test_fre_catalog_cli.test_cli_fre_catalog()
+

fre catalog

+
+ +
+
+fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_builder()
+

fre catalog builder

+
+ +
+
+fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_builder_help()
+

fre catalog builder –help

+
+ +
+
+fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_help()
+

fre catalog –help

+
+ +
+
+fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_merge()
+
+ +
+
+fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_merge_help()
+
+ +
+
+fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_opt_dne()
+

fre catalog optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_check_cli.html b/fre.tests.test_fre_check_cli.html new file mode 100644 index 00000000..9e19e93e --- /dev/null +++ b/fre.tests.test_fre_check_cli.html @@ -0,0 +1,129 @@ + + + + + + + + + fre.tests.test_fre_check_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_check_cli module

+

test “fre check” calls

+
+
+fre.tests.test_fre_check_cli.test_cli_fre_check()
+

fre check

+
+ +
+
+fre.tests.test_fre_check_cli.test_cli_fre_check_help()
+

fre check –help

+
+ +
+
+fre.tests.test_fre_check_cli.test_cli_fre_check_opt_dne()
+

fre check optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_cli.html b/fre.tests.test_fre_cli.html new file mode 100644 index 00000000..a10d1de9 --- /dev/null +++ b/fre.tests.test_fre_cli.html @@ -0,0 +1,129 @@ + + + + + + + + + fre.tests.test_fre_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_cli module

+

test “fre” calls

+
+
+fre.tests.test_fre_cli.test_cli_fre()
+

fre

+
+ +
+
+fre.tests.test_fre_cli.test_cli_fre_help()
+

fre –help

+
+ +
+
+fre.tests.test_fre_cli.test_cli_fre_option_dne()
+

fre optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_cmor_cli.html b/fre.tests.test_fre_cmor_cli.html new file mode 100644 index 00000000..6a1233a5 --- /dev/null +++ b/fre.tests.test_fre_cmor_cli.html @@ -0,0 +1,159 @@ + + + + + + + + + fre.tests.test_fre_cmor_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_cmor_cli module

+

test “fre cmor” calls

+
+
+fre.tests.test_fre_cmor_cli.test_cli_fre_cmor()
+

fre cmor

+
+ +
+
+fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_help()
+

fre cmor –help

+
+ +
+
+fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_opt_dne()
+

fre cmor optionDNE

+
+ +
+
+fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run()
+

fre cmor run

+
+ +
+
+fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run_case1()
+

fre cmor run, test-use case

+
+ +
+
+fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run_case2()
+

fre cmor run, test-use case

+
+ +
+
+fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run_help()
+

fre cmor run –help

+
+ +
+
+fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run_opt_dne()
+

fre cmor run optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_list_cli.html b/fre.tests.test_fre_list_cli.html new file mode 100644 index 00000000..941944ec --- /dev/null +++ b/fre.tests.test_fre_list_cli.html @@ -0,0 +1,129 @@ + + + + + + + + + fre.tests.test_fre_list_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_list_cli module

+

test “fre list” calls

+
+
+fre.tests.test_fre_list_cli.test_cli_fre_list()
+

fre list

+
+ +
+
+fre.tests.test_fre_list_cli.test_cli_fre_list_help()
+

fre list –help

+
+ +
+
+fre.tests.test_fre_list_cli.test_cli_fre_list_opt_dne()
+

fre list optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_make_cli.html b/fre.tests.test_fre_make_cli.html new file mode 100644 index 00000000..209e7bdc --- /dev/null +++ b/fre.tests.test_fre_make_cli.html @@ -0,0 +1,141 @@ + + + + + + + + + fre.tests.test_fre_make_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_make_cli module

+

test “fre make” calls

+
+
+fre.tests.test_fre_make_cli.test_cli_fre_make()
+

fre make

+
+ +
+
+fre.tests.test_fre_make_cli.test_cli_fre_make_create_checkout_baremetal()
+

fre make create-checkout -y am5.yaml -p ncrc5.intel23 -t debug

+
+ +
+
+fre.tests.test_fre_make_cli.test_cli_fre_make_create_checkout_container()
+

fre make create-checkout -y am5.yaml -p hpcme.2023 -t debug

+
+ +
+
+fre.tests.test_fre_make_cli.test_cli_fre_make_help()
+

fre make –help

+
+ +
+
+fre.tests.test_fre_make_cli.test_cli_fre_make_opt_dne()
+

fre make optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_pp_cli.html b/fre.tests.test_fre_pp_cli.html new file mode 100644 index 00000000..259f61bc --- /dev/null +++ b/fre.tests.test_fre_pp_cli.html @@ -0,0 +1,273 @@ + + + + + + + + + fre.tests.test_fre_pp_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_pp_cli module

+

test “fre pp” calls

+
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp()
+

fre pp

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_checkout()
+

fre pp checkout

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_checkout_help()
+

fre pp checkout –help

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_checkout_opt_dne()
+

fre pp checkout optionDNE

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_xml()
+

fre pp configure-xml

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_xml_help()
+

fre pp configure-xml –help

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_xml_opt_dne()
+

fre pp configure-xml optionDNE

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_yaml()
+

fre pp configure-yaml

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_yaml_help()
+

fre pp configure-yaml –help

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_yaml_opt_dne()
+

fre pp configure-yaml optionDNE

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_help()
+

fre pp –help

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_install()
+

fre pp install

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_install_help()
+

fre pp install –help

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_install_opt_dne()
+

fre pp install optionDNE

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_opt_dne()
+

fre pp optionDNE

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_run()
+

fre pp run

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_run_help()
+

fre pp run –help

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_run_opt_dne()
+

fre pp run optionDNE

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_status()
+

fre pp status

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_status_help()
+

fre pp status –help

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_status_opt_dne()
+

fre pp status optionDNE

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_validate()
+

fre pp validate

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_validate_help()
+

fre pp validate –help

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_validate_opt_dne()
+

fre pp validate optionDNE

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_wrapper()
+

fre pp wrapper

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_wrapper_help()
+

fre pp wrapper –help

+
+ +
+
+fre.tests.test_fre_pp_cli.test_cli_fre_pp_wrapper_opt_dne()
+

fre pp wrapper optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_run_cli.html b/fre.tests.test_fre_run_cli.html new file mode 100644 index 00000000..c682341c --- /dev/null +++ b/fre.tests.test_fre_run_cli.html @@ -0,0 +1,129 @@ + + + + + + + + + fre.tests.test_fre_run_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_run_cli module

+

test “fre run” calls

+
+
+fre.tests.test_fre_run_cli.test_cli_fre_run()
+

fre run

+
+ +
+
+fre.tests.test_fre_run_cli.test_cli_fre_run_help()
+

fre run –help

+
+ +
+
+fre.tests.test_fre_run_cli.test_cli_fre_run_opt_dne()
+

fre run optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_test_cli.html b/fre.tests.test_fre_test_cli.html new file mode 100644 index 00000000..a11a5988 --- /dev/null +++ b/fre.tests.test_fre_test_cli.html @@ -0,0 +1,129 @@ + + + + + + + + + fre.tests.test_fre_test_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_test_cli module

+

test “fre test” calls

+
+
+fre.tests.test_fre_test_cli.test_cli_fre_test()
+

fre test

+
+ +
+
+fre.tests.test_fre_test_cli.test_cli_fre_test_help()
+

fre test –help

+
+ +
+
+fre.tests.test_fre_test_cli.test_cli_fre_test_opt_dne()
+

fre test optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.tests.test_fre_yamltools_cli.html b/fre.tests.test_fre_yamltools_cli.html new file mode 100644 index 00000000..b21be134 --- /dev/null +++ b/fre.tests.test_fre_yamltools_cli.html @@ -0,0 +1,129 @@ + + + + + + + + + fre.tests.test_fre_yamltools_cli module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.tests.test_fre_yamltools_cli module

+

test “fre yamltools” calls

+
+
+fre.tests.test_fre_yamltools_cli.test_cli_fre_yamltools()
+

fre yamltools

+
+ +
+
+fre.tests.test_fre_yamltools_cli.test_cli_fre_yamltools_help()
+

fre yamltools –help

+
+ +
+
+fre.tests.test_fre_yamltools_cli.test_cli_fre_yamltools_opt_dne()
+

fre yamltools optionDNE

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.yamltools.combine_yamls.html b/fre.yamltools.combine_yamls.html new file mode 100644 index 00000000..8d6716a7 --- /dev/null +++ b/fre.yamltools.combine_yamls.html @@ -0,0 +1,231 @@ + + + + + + + + + fre.yamltools.combine_yamls module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.yamltools.combine_yamls module

+

Script combines the model yaml with the compile, platform, and experiment yamls.

+
+
+fre.yamltools.combine_yamls.combined_compile_existcheck(combined, yml, platform, target)
+

Checks for if combined compile yaml exists already. +If not, combine model, compile, and platform yamls.

+
+ +
+
+fre.yamltools.combine_yamls.consolidate_yamls(yamlfile, experiment, platform, target, use)
+
+ +
+
+fre.yamltools.combine_yamls.experiment_check(mainyaml_dir, comb, experiment)
+

Check that the experiment given is an experiment listed in the model yaml. +Extract experiment specific information and file paths. +Arguments: +mainyaml_dir : model yaml file +comb : combined yaml file name +experiment : experiment name

+
+ +
+
+fre.yamltools.combine_yamls.get_combined_compileyaml(comb)
+

Combine the model, compile, and platform yamls +Arguments: +comb : combined yaml object

+
+ +
+
+fre.yamltools.combine_yamls.get_combined_ppyaml(comb)
+

Combine the model, experiment, and analysis yamls +Arguments: +comb : combined yaml object

+
+ +
+
+fre.yamltools.combine_yamls.get_compile_paths(mainyaml_dir, comb)
+

Extract compile and platform paths from model yaml

+
+ +
+
+class fre.yamltools.combine_yamls.init_compile_yaml(yamlfile, platform, target)
+

Bases: object

+
+
+clean_yaml()
+

Clean the yaml; remove unnecessary sections in +final combined yaml.

+
+ +
+
+combine_compile()
+

Combine compile yaml with the defined combined.yaml

+
+ +
+
+combine_model()
+

Create the combined.yaml and merge it with the model yaml

+
+ +
+
+combine_platforms()
+

Combine platforms yaml with the defined combined.yaml

+
+ +
+ +
+
+class fre.yamltools.combine_yamls.init_pp_yaml(yamlfile, experiment, platform, target)
+

Bases: object

+
+
+clean_yaml()
+

Clean the yaml; remove unnecessary sections in +final combined yaml.

+
+ +
+
+combine_analysis()
+

Combine analysis yamls with the defined combined.yaml

+
+ +
+
+combine_experiment()
+

Combine experiment yamls with the defined combined.yaml

+
+ +
+
+combine_model()
+

Create the combined.yaml and merge it with the model yaml

+
+ +
+ +
+
+fre.yamltools.combine_yamls.join_constructor(loader, node)
+

Allows FRE properties defined +in main yaml to be concatenated.

+
+ +
+
+fre.yamltools.combine_yamls.yaml_load(yamlfile)
+

Load the yamlfile

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.yamltools.freyamltools.html b/fre.yamltools.freyamltools.html new file mode 100644 index 00000000..b680817b --- /dev/null +++ b/fre.yamltools.freyamltools.html @@ -0,0 +1,111 @@ + + + + + + + + + fre.yamltools.freyamltools module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.yamltools.freyamltools module

+

fre yamltools

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.yamltools.freyamltoolsexample.html b/fre.yamltools.freyamltoolsexample.html new file mode 100644 index 00000000..74fde119 --- /dev/null +++ b/fre.yamltools.freyamltoolsexample.html @@ -0,0 +1,113 @@ + + + + + + + + + fre.yamltools.freyamltoolsexample module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.yamltools.freyamltoolsexample module

+

experimentation file for integrating one file’s functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fre.yamltools.html b/fre.yamltools.html new file mode 100644 index 00000000..08e37f91 --- /dev/null +++ b/fre.yamltools.html @@ -0,0 +1,175 @@ + + + + + + + + + fre.yamltools package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/fre.yamltools.tests.html b/fre.yamltools.tests.html new file mode 100644 index 00000000..fa7ba26b --- /dev/null +++ b/fre.yamltools.tests.html @@ -0,0 +1,134 @@ + + + + + + + + + fre.yamltools.tests package — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/fre.yamltools.tests.test_combine_yamls.html b/fre.yamltools.tests.test_combine_yamls.html new file mode 100644 index 00000000..ac6c2851 --- /dev/null +++ b/fre.yamltools.tests.test_combine_yamls.html @@ -0,0 +1,181 @@ + + + + + + + + + fre.yamltools.tests.test_combine_yamls module — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

fre.yamltools.tests.test_combine_yamls module

+

tests routines in fre.yamltools.combine_yamls

+
+
+fre.yamltools.tests.test_combine_yamls.test_analysisyaml_exists()
+

Make sure experiment yaml file exists

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_combined_compileyaml_combinefail()
+

Check to test if compile yaml is incorrect/does not exist, +the combine fails. (compile yaml path misspelled)

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_combined_compileyaml_validatefail()
+

Check if the schema is validating correctly +Branch should be string

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_combined_compileyaml_validation()
+

Validate the combined compile yaml

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_combined_ppyaml_validation()
+

Validate the combined compile yaml

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_compileyaml_exists()
+

Make sure experiment yaml file exists

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_expyaml_exists()
+

Make sure experiment yaml file exists

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_merged_compile_yamls()
+

Check for the creation of the combined-[experiment] yaml +Check that the model yaml was merged into the combined yaml

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_merged_pp_yamls()
+

Check for the creation of the combined-[experiment] yaml +Check that the model yaml was merged into the combined yaml

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_modelyaml_exists()
+

Make sure main yaml file exists

+
+ +
+
+fre.yamltools.tests.test_combine_yamls.test_platformyaml_exists()
+

Make sure experiment yaml file exists

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 00000000..ae99beff --- /dev/null +++ b/genindex.html @@ -0,0 +1,1388 @@ + + + + + + + + Index — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ A + | C + | D + | E + | F + | G + | I + | J + | L + | M + | P + | Q + | R + | S + | T + | U + | V + | W + | Y + +
+

A

+ + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + +
+ +

F

+ + + +
    +
  • + fre + +
  • +
  • + fre.app + +
  • +
  • + fre.app.freapp + +
  • +
  • + fre.app.generate_time_averages + +
  • +
  • + fre.app.generate_time_averages.cdoTimeAverager + +
  • +
  • + fre.app.generate_time_averages.frenctoolsTimeAverager + +
  • +
  • + fre.app.generate_time_averages.frepytoolsTimeAverager + +
  • +
  • + fre.app.generate_time_averages.generate_time_averages + +
  • +
  • + fre.app.generate_time_averages.tests + +
  • +
  • + fre.app.generate_time_averages.tests.test_generate_time_averages + +
  • +
  • + fre.app.generate_time_averages.timeAverager + +
  • +
  • + fre.app.mask_atmos_plevel + +
  • +
  • + fre.app.regrid_xy + +
  • +
  • + fre.app.regrid_xy.regrid_xy + +
  • +
  • + fre.catalog + +
  • +
  • + fre.catalog.frecatalog + +
  • +
  • + fre.catalog.tests + +
  • +
  • + fre.catalog.tests.test_fre_catalog + +
  • +
  • + fre.check + +
  • +
  • + fre.check.frecheck + +
  • +
  • + fre.check.frecheckexample + +
  • +
  • + fre.cmor + +
  • +
  • + fre.cmor.cmor_mixer + +
  • +
  • + fre.cmor.frecmor + +
  • +
  • + fre.cmor.tests + +
  • +
  • + fre.cmor.tests.test_cmor_run_subtool + +
  • +
  • + fre.fre + +
  • +
  • + fre.lazy_group + +
  • +
  • + fre.list + +
  • +
  • + fre.list.frelist + +
  • +
  • + fre.list.frelistexample + +
  • +
  • + fre.make + +
  • +
  • + fre.make.createCheckout + +
  • +
  • + fre.make.createCompile + +
  • +
  • + fre.make.createDocker + +
  • +
  • + fre.make.createMakefile + +
  • +
  • + fre.make.fremake + +
  • +
  • + fre.make.runFremake + +
  • +
  • + fre.make.tests + +
  • +
  • + fre.make.tests.test_create_makefile + +
  • +
+ +

G

+ + + +
+ +

I

+ + + +
+ +

J

+ + +
+ +

L

+ + + +
+ +

M

+ + +
+ +

P

+ + + +
+ +

Q

+ + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + +
+ +

V

+ + + +
+ +

W

+ + +
+ +

Y

+ + + +
+ + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..3eaf7d65 --- /dev/null +++ b/index.html @@ -0,0 +1,164 @@ + + + + + + + + + Welcome to Fre-Cli’s documentation! — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/lib/docs/conf.py b/lib/docs/conf.py new file mode 100644 index 00000000..32399782 --- /dev/null +++ b/lib/docs/conf.py @@ -0,0 +1,29 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'Fre-Cli' +copyright = '2024, Bennett Chang' +author = 'Bennett Chang' +release = '1.0' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ['sphinx.ext.autodoc'] + +templates_path = ['_templates'] +exclude_patterns = [] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'renku' +#html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] diff --git a/lib/fre/README.md b/lib/fre/README.md new file mode 100644 index 00000000..4570b317 --- /dev/null +++ b/lib/fre/README.md @@ -0,0 +1,43 @@ + +* Refer to fre-cli [README.md](https://github.com/NOAA-GFDL/fre-cli/blob/main/README.md) for usage and tips. + + diff --git a/lib/fre/__init__.py b/lib/fre/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/app/__init__.py b/lib/fre/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/app/freapp.py b/lib/fre/app/freapp.py new file mode 100644 index 00000000..545356e4 --- /dev/null +++ b/lib/fre/app/freapp.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +''' fre app calls ''' + +import time + +import click + +from .mask_atmos_plevel import mask_atmos_plevel_subtool +from .generate_time_averages.generate_time_averages import generate +from .regrid_xy.regrid_xy import _regrid_xy + +@click.group(help=click.style(" - access fre app subcommands", fg=(250,154,90))) +def app_cli(): + ''' entry point to fre app click commands ''' + +@app_cli.command() +@click.option("-i", "--input_dir", + type = str, + help = "`inputDir` / `input_dir` (env var) specifies input directory to regrid, " + \ + "typically an untarredv history file archive" , + required = True) +@click.option("-o", "--output_dir", + type = str, + help = "`outputDir` / `output_dir` (env var) specifies target location for output" + \ + " regridded files", + required = True) +@click.option("-b", "--begin", + type = str, + help = "`begin` / `begin` (env var) ISO8601 datetime format specification for" + \ + " starting date of data, part of input target file name", + required = True) +@click.option("-tmp", "--tmp_dir", + type = str, + help = "`TMPDIR` / `tmp_dir` (env var) temp directory for location of file " + \ + "read/writes", + required = True) +@click.option("-rd", "--remap_dir", + type = str, + help = "`fregridRemapDir` / `remap_dir` (env var) directory containing remap file" + \ + " for regridding", + required = True) +@click.option("-s", "--source", + type = str, + help = "`source` / `source` (env var) source name for input target file name " + \ + "within input directory to target for regridding. the value for `source` " + \ + "must be present in at least one component's configuration fields", + required = True) +@click.option("-g", "--grid_spec", + type = str, + help = "`gridSpec` / `grid_spec` (env var) file containing mosaic for regridding", + required = True) +@click.option("-xy", "--def_xy_interp", + type = str, + help = "`defaultxyInterp` / `def_xy_interp` (env var) default lat/lon resolution " + \ + "for output regridding. (change me? TODO)", + required = True) +@click.pass_context +def regrid(context, + input_dir, output_dir, begin, tmp_dir, + remap_dir, source, grid_spec, def_xy_interp ): + ''' regrid target netcdf file ''' + context.forward(_regrid_xy) + +@app_cli.command() +@click.option("-i", "--infile", + type = str, + help = "Input NetCDF file containing pressure-level output to be masked", + required = True) +@click.option("-o", "--outfile", + type = str, + help = "Output file", + required = True) +@click.option("-p", "--psfile", # surface pressure... ps? TODO + help = "Input NetCDF file containing surface pressure (ps)", + required = True) +@click.pass_context +def mask_atmos_plevel(context, infile, outfile, psfile): + # pylint: disable = unused-argument + """Mask out pressure level diagnostic output below land surface""" + context.forward(mask_atmos_plevel_subtool) + + +@app_cli.command() +@click.option("-i", "--inf", + type = str, + required = True, + help = "Input file name") +@click.option("-o", "--outf", + type = str, + required = True, + help = "Output file name") +@click.option("-p", "--pkg", + type = click.Choice(["cdo","fre-nctools","fre-python-tools"]), + default = "cdo", + help = "Time average approach") +@click.option("-v", "--var", + type = str, + default = None, + help = "Specify variable to average") +@click.option("-u", "--unwgt", + is_flag = True, + default = False, + help = "Request unweighted statistics") +@click.option("-a", "--avg_type", + type = click.Choice(["month","seas","all"]), + default = "all", + help = "Type of time average to generate. \n \ + currently, fre-nctools and fre-python-tools pkg options\n \ + do not support seasonal and monthly averaging.\n") +@click.option("-s", "--stddev_type", + type = click.Choice(["samp","pop","samp_mean","pop_mean"]), + default = "samp", + help = "Compute standard deviations for time-averages as well") +@click.pass_context +def gen_time_averages(context, inf, outf, pkg, var, unwgt, avg_type, stddev_type): + # pylint: disable = unused-argument + """ + generate time averages for specified set of netCDF files. + Example: generate-time-averages.py /path/to/your/files/ + """ + start_time = time.perf_counter() + context.forward(generate) + # need to change to a click.echo, not sure if echo supports f strings + print(f'Finished in total time {round(time.perf_counter() - start_time , 2)} second(s)') + +if __name__ == "__main__": + app_cli() diff --git a/lib/fre/app/generate_time_averages/README.md b/lib/fre/app/generate_time_averages/README.md new file mode 100644 index 00000000..49991e13 --- /dev/null +++ b/lib/fre/app/generate_time_averages/README.md @@ -0,0 +1,8 @@ +From an input netCDF file, output a time-averaged netCDF file. + +Will average all available information on a given variable across time, +but can also handle monthly+seasonal averaging. + +To run time-averaging tests, return to root directory and call just those +tests with `python -m pytest tests/test_generate_time_averages.py`, or run +all tests with `python -m pytests tests/` \ No newline at end of file diff --git a/lib/fre/app/generate_time_averages/__init__.py b/lib/fre/app/generate_time_averages/__init__.py new file mode 100644 index 00000000..37410fef --- /dev/null +++ b/lib/fre/app/generate_time_averages/__init__.py @@ -0,0 +1,3 @@ +'''required for generate_time_averages module import functionality''' +__all__ = ['generate_time_averages', 'timeAverager', + 'frenctoolsTimeAverager', 'cdoTimeAverager', 'frepytoolsTimeAverager'] diff --git a/lib/fre/app/generate_time_averages/cdoTimeAverager.py b/lib/fre/app/generate_time_averages/cdoTimeAverager.py new file mode 100644 index 00000000..d288bebb --- /dev/null +++ b/lib/fre/app/generate_time_averages/cdoTimeAverager.py @@ -0,0 +1,85 @@ +''' class using (mostly) cdo functions for time-averages ''' +from .timeAverager import timeAverager + +class cdoTimeAverager(timeAverager): + ''' + class inheriting from abstract base class timeAverager + generates time-averages using cdo (mostly, see weighted approach) + ''' + + def generate_timavg(self, infile=None, outfile=None): + ''' use cdo package routines via python bindings ''' + assert self.pkg=="cdo" + if __debug__: + print(locals()) #input argument details + + if all([self.avg_type!='all',self.avg_type!='seas',self.avg_type!='month', + self.avg_type is not None]): + print('ERROR, avg_type requested unknown.') + return 1 + + if self.var is not None: + print(f'WARNING: variable specification (var={self.var})' + \ + f' not currently supported for cdo time averaging. ignoring!') + + import cdo + print(f'python-cdo version is {cdo.__version__}') + from cdo import Cdo + + _cdo=Cdo() + + wgts_sum=0 + if not self.unwgt: #weighted case, cdo ops alone don't support a weighted time-average. + from netCDF4 import Dataset + import numpy + + nc_fin = Dataset(infile, 'r') + + time_bnds=nc_fin['time_bnds'][:].copy() + wgts = ( numpy.moveaxis(time_bnds,0,-1)[1][:].copy() - \ + numpy.moveaxis(time_bnds,0,-1)[0][:].copy() ) + wgts_sum=sum(wgts) + if __debug__: + print(f'wgts_sum={wgts_sum}') + + if self.avg_type == 'all': + if self.stddev_type is None: + print('time average over all time requested.') + if self.unwgt: + _cdo.timmean(input=infile, output=outfile, returnCdf=True) + else: + _cdo.divc( str(wgts_sum), input="-timsum -muldpm "+infile, output=outfile) + + print('done averaging over all time.') + else: + print('time standard-deviation (N-1) over all time requested.') + _cdo.timstd1(input=infile, output=outfile, returnCdf=True) + print('done computing standard-deviation over all time.') + + elif self.avg_type == 'seas': + if self.stddev_type is None: + print('seasonal time-averages requested.') + _cdo.yseasmean(input=infile, output=outfile, returnCdf=True) + print('done averaging over seasons.') + else: + print('seasonal time standard-deviation (N-1) requested.') + _cdo.yseasstd1(input=infile, output=outfile, returnCdf=True) + print('done averaging over seasons.') + + elif self.avg_type == 'month': + + if self.stddev_type is None: + print('monthly time-averages requested.') + _cdo.ymonmean(input=infile, output=outfile, returnCdf=True) + print('done averaging over months.') + else: + print('monthly time standard-deviation (N-1) requested.') + _cdo.ymonstd1(input=infile, output=outfile, returnCdf=True) + print('done averaging over months.') + + else: + print(f'problem: unknown avg_type={self.avg_type}') + return 1 + + print('done averaging') + return 0 diff --git a/lib/fre/app/generate_time_averages/frenctoolsTimeAverager.py b/lib/fre/app/generate_time_averages/frenctoolsTimeAverager.py new file mode 100644 index 00000000..32b9c2bb --- /dev/null +++ b/lib/fre/app/generate_time_averages/frenctoolsTimeAverager.py @@ -0,0 +1,55 @@ +''' class for utilizing timavg.csh (aka script to TAVG fortran exe) in frenc-tools ''' +from .timeAverager import timeAverager + +class frenctoolsTimeAverager(timeAverager): + ''' + class inheriting from abstract base class timeAverager + generates time-averages using fre-nctools + ''' + + def generate_timavg(self, infile=None, outfile=None): + ''' use fre-nctool's CLI timavg.csh with subprocess call ''' + assert self.pkg=="fre-nctools" + if __debug__: + print(locals()) #input argument details + + exitstatus=1 + if self.avg_type!='all': + print(f'ERROR: avg_type={self.avg_type} not supported by this class at this time.') + return exitstatus + + if self.unwgt: + print('WARNING: unwgt=True unsupported by frenctoolsAverager. ignoring!!!') + + if self.stddev_type is not None: + print('WARNING: stddev_type arg unsupported by frenctoolsTimeAverager. ignoring!!!') + + if self.var is not None: + print(f'WARNING: variable specification (var={self.var})' + \ + f' not currently supported for frenctols time averaging. ignoring!') + + if infile is None: + print('ERROR: I need an input file, specify a value for the infile argument') + return exitstatus + + if outfile is None: + outfile='frenctoolsTimeAverage_'+infile + print(f'WARNING: no output filename given, setting outfile={outfile}') + + from subprocess import Popen, PIPE + + precision='-r8' + timavgcsh_command=['timavg.csh', precision, '-mb','-o', outfile, infile] + exitstatus=1 + with Popen(timavgcsh_command, + stdout=PIPE, stderr=PIPE, shell=False) as subp: + output=subp.communicate()[0] + print(f'output={output}') + + if subp.returncode < 0: + print('error') + else: + print('success') + exitstatus=0 + + return exitstatus diff --git a/lib/fre/app/generate_time_averages/frepytoolsTimeAverager.py b/lib/fre/app/generate_time_averages/frepytoolsTimeAverager.py new file mode 100644 index 00000000..1e1e6638 --- /dev/null +++ b/lib/fre/app/generate_time_averages/frepytoolsTimeAverager.py @@ -0,0 +1,249 @@ +''' class for python-native routine usuing netCDF4 and numpy to crunch time-averages ''' + +import math +import numpy +from netCDF4 import Dataset + +from .timeAverager import timeAverager + +class frepytoolsTimeAverager(timeAverager): + ''' + class inheriting from abstract base class timeAverager + generates time-averages using a python-native approach + avoids using other third party statistics functions by design. + ''' + + def generate_timavg(self, infile=None, outfile=None): + ''' frepytools approach in a python-native manner. + deliberately avoids pre-packaged routines. ''' + assert self.pkg=="fre-python-tools" + if __debug__: + print(locals()) #input argument details + + if __debug__: + print('calling generate_frepythontools_timavg for file: ' + infile) + + if self.avg_type != 'all': + print(f'ERROR: avg_type={self.avg_type} not supported at this time.') + return 1 + + # (TODO) file I/O should be a sep function, no? make tests, extend + nc_fin = Dataset(infile, 'r') + if nc_fin.file_format != 'NETCDF4': + print(f'INFO: input file is not netCDF4 format, is {nc_fin.file_format}') + + # (TODO) make this a sep function, make tests, extend + # identifying the input variable, two approaches + # user inputs target variable OR + # attempt to determine target var w/ bronx convention + if self.var is not None: + targ_var = self.var + else: # this can be replaced w/ a regex search maybe + targ_var = infile.split('/').pop().split('.')[-2] + + if __debug__: + print(f'targ_var={targ_var}') + + # (TODO) make this a sep function, make tests, extend + # check for the variable we're hoping is in the file + time_bnds = None + nc_fin_vars = nc_fin.variables + for key in nc_fin_vars: + if str(key) == targ_var: # found our variable, grab bounds + time_bnds = nc_fin['time_bnds'][:].copy() + break + if time_bnds is None: + print('requested variable not found. exit.') + return 1 + + + # (TODO) determine what we need to worry about with masks and account for it + # check for mask, adjust accordingly + #is_masked = ma.is_masked(val_array) + + # (TODO) make this a sep function, make tests, extend + # read in sizes of specific axes / compute weights + # weights can be encoded as a class member, whose existence + # depends on the user specifying unwgt=True, if vect_wgts=None, set the avg + # and stddev gen functions to the appropriate behavior (TODO) + fin_dims = nc_fin.dimensions + num_time_bnds = fin_dims['time'].size + if not self.unwgt: #compute sum of weights + wgts = ( numpy.moveaxis( time_bnds,0,-1 )[1][:].copy() - \ + numpy.moveaxis( time_bnds,0,-1 )[0][:].copy() ) + wgts_sum=sum(wgts) + if __debug__: + print(f'wgts_sum={wgts_sum}') + + + # initialize arrays, is there better practice for reserving the memory necessary + # for holding the day? is something that does more write-on-demand possible like + # reading data on-demand? (TODO) + num_lat_bnds=fin_dims['lat'].size + print(f'num_lat_bnds={num_lat_bnds}') + num_lon_bnds=fin_dims['lon'].size + print(f'num_lon_bnds={num_lon_bnds}') + avgvals=numpy.zeros((1,num_lat_bnds,num_lon_bnds),dtype=float) + if self.stddev_type is not None: + print('computing std. deviations') + stddevs=numpy.zeros((1,num_lat_bnds,num_lon_bnds),dtype=float) + + # this loop behavior 100% should be re-factored into generator functions. + # they should be slightly faster, and much more readable. (TODO) + # the average/stddev cpu settings should also be genfunctions, their behavior + # (e.g. stddev_pop v stddev_samp) should be set given user inputs. (TODO) + # the computations can lean on numpy.stat more- i imagine it's faster (TODO) + # parallelism via multiprocessing shouldn't be too bad- explore an alt [dask] too (TODO) + # compute average, for each lat/lon coordinate over time record in file + if not self.unwgt: #weighted case + print('computing weighted statistics') + for lat in range(num_lat_bnds): + lon_val_array=numpy.moveaxis( nc_fin[targ_var][:],0,-1)[lat].copy() + + for lon in range(num_lon_bnds): + tim_val_array= lon_val_array[lon].copy() + avgvals[0][lat][lon]=sum( (tim_val_array[tim] * wgts[tim] ) + for tim in range(num_time_bnds) ) / wgts_sum + + if self.stddev_type is not None: # implement stddeviation types TO DO + stddevs[0][lat][lon]=math.sqrt( + sum( wgts[tim] * + (tim_val_array[tim]-avgvals[0][lat][lon]) ** 2 + for tim in range(num_time_bnds) ) + / wgts_sum ) + del tim_val_array + del lon_val_array + else: #unweighted case + print('computing unweighted statistics') + for lat in range(num_lat_bnds): + lon_val_array=numpy.moveaxis( nc_fin[targ_var][:],0,-1)[lat].copy() + + for lon in range(num_lon_bnds): + tim_val_array= lon_val_array[lon].copy() + avgvals[0][lat][lon]=sum( # no time sum needed here, b.c. unweighted, so sum + tim_val_array[tim] for tim in range(num_time_bnds) + ) / num_time_bnds + + if self.stddev_type is not None: + + stddevs[0][lat][lon]=math.sqrt( + sum( + (tim_val_array[tim]-avgvals[0][lat][lon]) ** 2 + for tim in range(num_time_bnds) + ) / ( (num_time_bnds - 1. ) ) + ) + del tim_val_array + del lon_val_array + + + + # write output file + # (TODO) make this a sep function, make tests, extend, + # (TODO) consider compression particular;y for NETCDF file writing + # consider this approach instead: + # with Dataset( outfile, 'w', format='NETCDF4', persist=True ) as nc_fout: + nc_fout= Dataset( outfile, 'w', format=nc_fin.file_format, persist=True ) + + # (TODO) make this a sep function, make tests, extend + # write file global attributes + print('------- writing output attributes. --------') + unwritten_ncattr_list=[] + try: + nc_fout.setncatts(nc_fin.__dict__) #this copies the global attributes exactly. + except: # if the first way doesn't work... + print('could not copy ncatts from input file. trying to copy one-by-one') + fin_ncattrs=nc_fin.ncattrs() + for ncattr in fin_ncattrs: + print(f'\n_________\nncattr={ncattr}') + try: + nc_fout.setncattr(ncattr, nc_fin.getncattr(ncattr)) + except: + print(f'could not get nc file attribute: {ncattr}. moving on.') + unwritten_ncattr_list.append(ncattr) + if len(unwritten_ncattr_list)>0: + print(f'WARNING: Some global attributes ({unwritten_ncattr_list}) were not written.') + print('------- DONE writing output attributes. --------') + ## + + # (TODO) make this a sep function, make tests, extend + # write file dimensions + print('\n ------ writing output dimensions. ------ ') + unwritten_dims_list=[] + for key in fin_dims: + try: + if key=='time': + # this strongly influences the final data structure shape of the averages. + # if set to None, and lets say you try to write + # e.g. the original 'time_bnds' (which has 60 time steps) + # the array holding the avg. value will suddently have 60 time steps + # even though only 1 is needed, 59 time steps will have no data + #nc_fout.createDimension( dimname=key, size=None ) + nc_fout.createDimension( dimname=key, size=1) + else: + nc_fout.createDimension( dimname=key, size=fin_dims[key].size ) + except: + print(f'problem. cannot read/write dimension {key}') + unwritten_dims_list.append(key) + if len(unwritten_dims_list)>0: + print(f'WARNING: Some dimensions ({unwritten_dims_list}) were not written.') + print('------ DONE writing output dimensions. ------- \n') + ## + + + # (TODO) make this a sep function, make tests, extend + # first write the data we care most about- those we computed + # copying metadata, not fully correct + # but not far from wrong according to CF + # cell_methods must be changed TO DO + print(f'\n------- writing data for data {targ_var} -------- ') + nc_fout.createVariable(targ_var, nc_fin[targ_var].dtype, nc_fin[targ_var].dimensions) + nc_fout.variables[targ_var].setncatts(nc_fin[targ_var].__dict__) + + + nc_fout.variables[targ_var][:]=avgvals + if self.stddev_type is not None: + stddev_varname=targ_var+'_'+self.stddev_type+'_stddev' + nc_fout.createVariable( + stddev_varname, nc_fin[targ_var].dtype, nc_fin[targ_var].dimensions ) + nc_fout.variables[stddev_varname].setncatts(nc_fin[targ_var].__dict__) + nc_fout.variables[stddev_varname][:]=stddevs + print('---------- DONE writing output variables. ---------') + ## + + # (TODO) make this a sep function, make tests, extend + # write OTHER output variables (aka data) #prev code. + print('\n------- writing other output variables. -------- ') + unwritten_var_list=[] + unwritten_var_ncattr_dict={} + for var in nc_fin_vars: + if var != targ_var: + print(f'\nattempting to create output variable: {var}') + #print(f'is it a time variable? {self.var_is_time(nc_fin.variables[var])}') + nc_fout.createVariable(var, nc_fin[var].dtype, nc_fin[var].dimensions) + nc_fout.variables[var].setncatts(nc_fin[var].__dict__) + try: + nc_fout.variables[var][:] = nc_fin[var][:] + except: + print(f'could not write var={var}. i bet its the shape!') + print(f'nc_fin[var].shape={nc_fin[var].shape}') + #print(f'len(nc_fout.variables[{var}])={len(nc_fout.variables[var])}') + nc_fout.variables[var][:] = [ nc_fin[var][0] ] + print(f'time variable? {self.var_has_time_units(nc_fin.variables[var])}') + else: + continue + + if len(unwritten_var_list)>0: + print(f'WARNING: some variables\' data ({unwritten_var_list}) was not written.') + if len(unwritten_var_ncattr_dict)>0: + print('WARNING: some variables\' metadata was not successfully written.') + print(f'WARNING: relevant variable/attr pairs: \n{unwritten_var_ncattr_dict}') + print('---------- DONE writing output variables. ---------') + ## + + + nc_fout.close() + #close input file + nc_fin.close() + print(f'wrote ouput file: {outfile}') + + return 0 diff --git a/lib/fre/app/generate_time_averages/generate_time_averages.py b/lib/fre/app/generate_time_averages/generate_time_averages.py new file mode 100644 index 00000000..1c4bbf69 --- /dev/null +++ b/lib/fre/app/generate_time_averages/generate_time_averages.py @@ -0,0 +1,61 @@ +''' tools for generating time averages from various packages ''' +import click + +def generate_time_average(infile=None, outfile=None, + pkg=None, var=None, + unwgt=False, avg_type=None, stddev_type=None): + ''' steering function to various averaging functions above''' + if __debug__: + print(locals()) #input argument details + exitstatus=1 + + #needs a case statement. better yet, smarter way to do this? (TODO) + myavger=None + if pkg == 'cdo' : + from .cdoTimeAverager import cdoTimeAverager + myavger=cdoTimeAverager(pkg = pkg, var=var, + unwgt = unwgt, + avg_type = avg_type, stddev_type = stddev_type) + + elif pkg == 'fre-nctools' : + from .frenctoolsTimeAverager import frenctoolsTimeAverager + myavger=frenctoolsTimeAverager(pkg = pkg, var=var, + unwgt = unwgt , + avg_type = avg_type, stddev_type = stddev_type) + + elif pkg == 'fre-python-tools': + from .frepytoolsTimeAverager import frepytoolsTimeAverager + myavger=frepytoolsTimeAverager(pkg = pkg, var=var, + unwgt = unwgt, + avg_type = avg_type, stddev_type = stddev_type) + + else : + print('requested package unknown. exit.') + return exitstatus + + if __debug__: + print(f'myavger.__repr__={myavger.__repr__}') + if myavger is not None: + exitstatus=myavger.generate_timavg(infile=infile, outfile=outfile) + else: + print('ERROR: averager is None, check generate_time_average in generate_time_averages.py!') + + return exitstatus + +@click.command() +def generate(inf, outf, pkg, var, unwgt, avg_type, stddev_type): + ''' click entrypoint to time averaging routine ''' + exitstatus=generate_time_average( inf, outf, + pkg, var, + unwgt, + avg_type, stddev_type) + if exitstatus!=0: + print(f'WARNING: exitstatus={exitstatus} != 0. Something exited poorly!') + else: + print('time averaging finished successfully') + +if __name__ == '__main__': + import time + start_time=time.perf_counter() + generate(inf, outf, pkg, var, unwgt, avg_type, stddev_type) + print(f'Finished in total time {round(time.perf_counter() - start_time , 2)} second(s)') diff --git a/lib/fre/app/generate_time_averages/tests/.unused_tests/test_multiply_duration.py b/lib/fre/app/generate_time_averages/tests/.unused_tests/test_multiply_duration.py new file mode 100644 index 00000000..704907b5 --- /dev/null +++ b/lib/fre/app/generate_time_averages/tests/.unused_tests/test_multiply_duration.py @@ -0,0 +1,17 @@ +from fre_python_tools.utilities.multiply_duration import multiply_duration +import metomi.isodatetime.parsers as parse + +def test_month(): + '''1 month x 2 = 2 months''' + two_months = parse.DurationParser().parse('P2M') + assert multiply_duration('P1M', 2) == two_months + +def test_minutes(): + '''12 minutes x 5 = 1 hour''' + hour = parse.DurationParser().parse('PT1H') + assert multiply_duration('PT12M', 5) == hour + +def test_fail(): + '''10 minutes x 5 != 1 hour''' + hour = parse.DurationParser().parse('PT1H') + assert multiply_duration('PT10M', 5) != hour diff --git a/lib/fre/app/generate_time_averages/tests/.unused_tests/test_subtract_durations.py b/lib/fre/app/generate_time_averages/tests/.unused_tests/test_subtract_durations.py new file mode 100644 index 00000000..f495745d --- /dev/null +++ b/lib/fre/app/generate_time_averages/tests/.unused_tests/test_subtract_durations.py @@ -0,0 +1,17 @@ +from fre_python_tools.utilities.subtract_durations import subtract_durations +import metomi.isodatetime.parsers as parse + +def test_months(): + '''13 months - 3 months = 10 months''' + ten_months = parse.DurationParser().parse('P10M') + assert subtract_durations('P13M', 'P3M') == ten_months + +def test_hour(): + '''2 hours minus 30 minutes = 90 minutes''' + ninety_mins = parse.DurationParser().parse('PT90M') + assert subtract_durations('PT2H', 'PT30M') == ninety_mins + +def test_fail(): + '''2 hours minus 60 minutes != 90 minutes''' + ninety_mins = parse.DurationParser().parse('PT90M') + assert subtract_durations('PT2H', 'PT60M') != ninety_mins diff --git a/lib/fre/app/generate_time_averages/tests/__init__.py b/lib/fre/app/generate_time_averages/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/app/generate_time_averages/tests/test_generate_time_averages.py b/lib/fre/app/generate_time_averages/tests/test_generate_time_averages.py new file mode 100644 index 00000000..4ce3963d --- /dev/null +++ b/lib/fre/app/generate_time_averages/tests/test_generate_time_averages.py @@ -0,0 +1,308 @@ +''' for testing fre app generate-time-averages ''' +import pathlib as pl +import pytest + +def run_avgtype_pkg_calculations(infile=None,outfile=None, pkg=None, avg_type=None, unwgt=None, + stddev_type=None): + ''' test-harness function, called by other test functions. ''' + assert all( [infile is not None, outfile is not None, + pkg is not None, avg_type is not None, + unwgt is not None] ) + if pl.Path(outfile).exists(): + print('output test file exists. deleting before remaking.') + pl.Path(outfile).unlink() #delete file so we check that it can be recreated + from fre.app.generate_time_averages import generate_time_averages as gtas + gtas.generate_time_average(infile = infile, outfile = outfile, + pkg = pkg, unwgt = unwgt, + avg_type = avg_type, stddev_type = stddev_type) + return pl.Path(outfile).exists() + +### preamble tests. if these fail, none of the others will succeed. ----------------- +time_avg_file_dir=str(pl.Path.cwd())+'/fre/app/generate_time_averages/tests/time_avg_test_files/' +test_file_name='atmos.197901-198312.LWP.nc' + +def test_time_avg_file_dir_exists(): + ''' look for input test file directory ''' + assert pl.Path(time_avg_file_dir).exists() + +def test_time_avg_input_file_exists(): + ''' look for input test file ''' + assert pl.Path( time_avg_file_dir + test_file_name ).exists() + +### cdo avgs, unweighted, all/seasonal/monthly ------------------------ +def test_monthly_cdo_time_unwgt_avgs(): + ''' generates an unweighted monthly time averaged file using cdo ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'ymonmean_unwgt_'+test_file_name), + pkg='cdo',avg_type='month',unwgt=True ) + +def test_seasonal_cdo_time_unwgt_avgs(): + ''' generates an unweighted seasonal time averaged file using cdo ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'yseasmean_unwgt_'+test_file_name), + pkg='cdo',avg_type='seas',unwgt=True ) + +def test_cdo_time_unwgt_avgs(): + ''' generates an unweighted time averaged file using cdo ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'timmean_unwgt_'+test_file_name), + pkg='cdo',avg_type='all',unwgt=True ) + + +#### cdo avgs, weighted, all/seasonal/monthly ------------------------ +## (TODO) WRITE THESE VERSIONS FOR CDOTIMEAVERAGER CLASS THEN MAKE THESE TESTS +##def test_monthly_cdo_time_avgs(): +##def test_seasonal_cdo_time_avgs(): + +def test_cdo_time_avgs(): + ''' generates a weighted time averaged file using cdo ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'timmean_'+test_file_name), + pkg='cdo',avg_type='all',unwgt=False ) + + +### cdo stddevs, unweighted, all/seasonal/monthly ------------------------ +def test_monthly_cdo_time_unwgt_stddevs(): + ''' generates a monthly time averaged file using cdo ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'ymonstddev1_unwgt_'+test_file_name), + pkg='cdo',avg_type='month',stddev_type='samp', unwgt=True ) + +def test_seasonal_cdo_time_unwgt_stddevs(): + ''' generates a seasonal time averaged file using cdo ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'yseasstddev1_unwgt_'+test_file_name), + pkg='cdo',avg_type='seas',stddev_type='samp',unwgt=True ) + +def test_cdo_time_unwgt_stddevs(): + ''' generates a time averaged file using cdo ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'yseasmean_unwgt_'+test_file_name), + pkg='cdo',avg_type='all',stddev_type='samp', unwgt=True ) + + +#### cdo stddevs, weighted, all/seasonal/monthly ----------------------- +## (TODO) WRITE THESE VERSIONS FOR CDOTIMEAVERAGER CLASS THEN MAKE THESE TESTS +#def test_monthly_cdo_time_stddevs(): +#def test_seasonal_cdo_time_stddevs(): +#def test_cdo_time_stddevs(): + +## frepythontools avgs+stddevs, weighted+unweighted, all ------------------------ +def test_fre_python_tools_time_avgs(): + ''' generates a time averaged file using fre_python_tools's version ''' + ''' weighted average, no std deviation ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'frepytools_timavg_'+test_file_name), + pkg='fre-python-tools',avg_type='all', unwgt=False ) + +def test_fre_python_tools_time_unwgt_avgs(): + ''' generates a time averaged file using fre_python_tools's version ''' + ''' weighted average, no std deviation ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'frepytools_unwgt_timavg_'+test_file_name), + pkg='fre-python-tools',avg_type='all', unwgt=True ) + +def test_fre_python_tools_time_avgs_stddevs(): + ''' generates a time averaged file using fre_python_tools's version ''' + ''' weighted average, no std deviation ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'frepytools_stddev_'+test_file_name), + pkg='fre-python-tools',avg_type='all', stddev_type='samp', unwgt=False ) + +def test_fre_python_tools_time_unwgt_avgs_stddevs(): + ''' generates a time averaged file using fre_python_tools's version ''' + ''' weighted average, no std deviation ''' + assert run_avgtype_pkg_calculations( + infile = (time_avg_file_dir+test_file_name), + outfile = (time_avg_file_dir+'frepytools_unwgt_stddev_'+test_file_name), + pkg='fre-python-tools',avg_type='all', stddev_type='samp', unwgt=True ) + +## (TODO) WRITE THESE VERSIONS FOR FREPYTOOLSTIMEAVERAGER CLASS THEN MAKE THESE TESTS +#def test_monthly_fre_python_tools_time_avgs(): +#def test_monthly_fre_python_tools_time_unwgt_avgs(): +#def test_monthly_fre_python_tools_time_avgs_stddevs(): +#def test_monthly_fre_python_tools_time_unwgt_avgs_stddevs(): +# +#def test_seasonal_fre_python_tools_time_avgs(): +#def test_seasonal_fre_python_tools_time_unwgt_avgs(): +#def test_seasonal_fre_python_tools_time_avgs_stddevs(): +#def test_seasonal_fre_python_tools_time_unwgt_avgs_stddevs(:) + + + +## this will only work at GFDL. dev convenience only. +#alt_str_fre_nctools_inf= \ +# 'tests/time_avg_test_files/fre_nctools_timavg_CLI_test_r8_b_atmos_LWP_1979_5y.nc' +#def test_fre_nctools_time_avgs(): +# ''' generates a time averaged file using fre_python_tools's version ''' +# ''' weighted average, no std deviation ''' +# infile =time_avg_file_dir+test_file_name +# all_outfile=time_avg_file_dir+'frenctools_timavg_'+test_file_name +# +# if pl.Path(all_outfile).exists(): +# print('output test file exists. deleting before remaking.') +# pl.Path(all_outfile).unlink() #delete file so we check that it can be recreated +# +# from fre_python_tools.generate_time_averages import generate_time_averages as gtas +# gtas.generate_time_average(infile = infile, outfile = all_outfile, +# pkg='fre-nctools', unwgt=False, avg_type='all') +# assert pl.Path(all_outfile).exists() + + +# Numerics-based tests. these have room for improvemnent for sure (TODO) +# compare frepytools, cdo time-average output to fre-nctools where possible +var='LWP' +str_fre_nctools_inf=time_avg_file_dir+'frenctools_timavg_'+test_file_name # this is now in the repo +str_fre_pytools_inf=time_avg_file_dir+'frepytools_timavg_'+test_file_name +str_cdo_inf=time_avg_file_dir+'timmean_'+test_file_name +str_unwgt_fre_pytools_inf=time_avg_file_dir+'frepytools_unwgt_timavg_'+test_file_name +str_unwgt_cdo_inf=time_avg_file_dir+'timmean_unwgt_'+test_file_name + + +def test_compare_fre_python_tools_to_fre_nctools(): + ''' compares fre_python_tools pkg answer to fre_nctools pkg answer ''' + import numpy as np + import netCDF4 as nc + fre_pytools_inf=nc.Dataset(str_fre_pytools_inf,'r') + + try: + fre_nctools_inf=nc.Dataset(str_fre_nctools_inf,'r') + except: + print('fre-nctools input file not found. \ + probably because you are not at GFDL! run the shell script \ + example if you would like to see this pass. otherwise, \ + i will error right after this message.') + try: + fre_nctools_inf=nc.Dataset(alt_str_fre_nctools_inf,'r') + except: + print('fre-nctools output does not exist. create it first!') + assert False + + fre_pytools_timavg=fre_pytools_inf[var][:].copy() + fre_nctools_timavg=fre_nctools_inf[var][:].copy() + assert all([ len(fre_pytools_timavg)==len(fre_nctools_timavg), + len(fre_pytools_timavg[0])==len(fre_nctools_timavg[0]), + len(fre_pytools_timavg[0][0])==len(fre_nctools_timavg[0][0]) ]) + + diff_pytools_nctools_timavg=fre_pytools_timavg-fre_nctools_timavg + for lat in range(0,len(diff_pytools_nctools_timavg[0])): + for lon in range(0,len(diff_pytools_nctools_timavg[0][0])): + print(f'lat={lat},lon={lon}') + #diff_at_latlon=diff_pytools_nctools_timavg[0][lat][lon] + #print(f'diff_pytools_nctools_timavg[0][lat][lon]={diff_at_latlon}') + if lon>10: break + break + + non_zero_count=np.count_nonzero(diff_pytools_nctools_timavg[:]) + #assert (non_zero_count == 0.) # bad way to check for zero. + assert not( (non_zero_count > 0.) or (non_zero_count < 0.) ) + +@pytest.mark.skip(reason='test fails b.c. cdo cannot bitwise-reproduce fre-nctools answer') +def test_compare_fre_python_tools_to_cdo(): + ''' compares fre_python_tools pkg answer to cdo pkg answer ''' + import numpy as np + import netCDF4 as nc + fre_pytools_inf=nc.Dataset(str_fre_pytools_inf,'r') + + try: + cdo_inf=nc.Dataset(str_cdo_inf,'r') + except: + print('cdo input file not found. run cdo tests first.') + assert False + + fre_pytools_timavg=fre_pytools_inf[var][:].copy() + cdo_timavg=cdo_inf[var][:].copy() + + assert all([ len(fre_pytools_timavg)==len(cdo_timavg), + len(fre_pytools_timavg[0])==len(cdo_timavg[0]), + len(fre_pytools_timavg[0][0])==len(cdo_timavg[0][0]) ]) + + diff_pytools_cdo_timavg=fre_pytools_timavg-cdo_timavg + for lat in range(0,len(diff_pytools_cdo_timavg[0])): + for lon in range(0,len(diff_pytools_cdo_timavg[0][0])): + print(f'lat={lat},lon={lon}') + print(f'diff_pytools_cdo_timavg[0][lat][lon]={diff_pytools_cdo_timavg[0][lat][lon]}') + if lon>10: break + break + + non_zero_count=np.count_nonzero(diff_pytools_cdo_timavg[:]) + assert not( (non_zero_count > 0.) or (non_zero_count < 0.) ) + + +def test_compare_unwgt_fre_python_tools_to_unwgt_cdo(): + ''' compares fre_python_tools pkg answer to cdo pkg answer ''' + import numpy as np + import netCDF4 as nc + fre_pytools_inf=nc.Dataset(str_unwgt_fre_pytools_inf,'r') + + try: + cdo_inf=nc.Dataset(str_unwgt_cdo_inf,'r') + except: + print('cdo input file not found. run cdo tests first.') + assert False + + fre_pytools_timavg=fre_pytools_inf[var][:].copy() + cdo_timavg=cdo_inf[var][:].copy() + assert all([ len(fre_pytools_timavg)==len(cdo_timavg), + len(fre_pytools_timavg[0])==len(cdo_timavg[0]), + len(fre_pytools_timavg[0][0])==len(cdo_timavg[0][0]) ]) + + diff_pytools_cdo_timavg=fre_pytools_timavg-cdo_timavg + for lat in range(0,len(diff_pytools_cdo_timavg[0])): + for lon in range(0,len(diff_pytools_cdo_timavg[0][0])): + print(f'lat={lat},lon={lon}') + print(f'diff_pytools_cdo_timavg[0][lat][lon]={diff_pytools_cdo_timavg[0][lat][lon]}') + if lon>10: break + break + + non_zero_count=np.count_nonzero(diff_pytools_cdo_timavg[:]) + assert not( (non_zero_count > 0.) or (non_zero_count < 0.) ) + +@pytest.mark.skip(reason='test fails b.c. cdo cannot bitwise-reproduce fre-nctools answer') +def test_compare_cdo_to_fre_nctools(): + ''' compares cdo pkg answer to fre_nctools pkg answer ''' + import numpy as np + import netCDF4 as nc + cdo_inf=nc.Dataset(str_cdo_inf,'r') + + try: + fre_nctools_inf=nc.Dataset(str_fre_nctools_inf,'r') + except: + print('fre-nctools input file not found. \ + probably because you are not at GFDL! run the shell script \ + example if you would like to see this pass. otherwise, \ + i will error right after this message.') + alt_str_fre_nctools_inf = \ + 'tests/time_avg_test_files/fre_nctools_timavg_CLI_test_r8_b_atmos_LWP_1979_5y.nc' + try: + fre_nctools_inf=nc.Dataset(alt_str_fre_nctools_inf,'r') + except: + print('fre-nctools output does not exist. create it first!') + assert False + + cdo_timavg=cdo_inf[var][:].copy() + fre_nctools_timavg=fre_nctools_inf[var][:].copy() + assert all([ len(cdo_timavg)==len(fre_nctools_timavg), + len(cdo_timavg[0])==len(fre_nctools_timavg[0]), + len(cdo_timavg[0][0])==len(fre_nctools_timavg[0][0]) ]) + + diff_cdo_nctools_timavg=cdo_timavg-fre_nctools_timavg + for lat in range(0,len(diff_cdo_nctools_timavg[0])): + for lon in range(0,len(diff_cdo_nctools_timavg[0][0])): + print(f'lat={lat},lon={lon}') + print(f'diff_cdo_nctools_timavg[0][lat][lon]={diff_cdo_nctools_timavg[0][lat][lon]}') + if lon>10: break + break + + non_zero_count=np.count_nonzero(diff_cdo_nctools_timavg[:]) + assert not( (non_zero_count > 0.) or (non_zero_count < 0.) ) diff --git a/lib/fre/app/generate_time_averages/tests/time_avg_test_files/README.md b/lib/fre/app/generate_time_averages/tests/time_avg_test_files/README.md new file mode 100644 index 00000000..e25c76f4 --- /dev/null +++ b/lib/fre/app/generate_time_averages/tests/time_avg_test_files/README.md @@ -0,0 +1 @@ +this directory is for tests/examples/sample files, etc. diff --git a/lib/fre/app/generate_time_averages/tests/time_avg_test_files/atmos.197901-198312.LWP.nc b/lib/fre/app/generate_time_averages/tests/time_avg_test_files/atmos.197901-198312.LWP.nc new file mode 100644 index 00000000..e2aa57b2 Binary files /dev/null and b/lib/fre/app/generate_time_averages/tests/time_avg_test_files/atmos.197901-198312.LWP.nc differ diff --git a/lib/fre/app/generate_time_averages/tests/time_avg_test_files/frenctools_timavg_atmos.197901-198312.LWP.nc b/lib/fre/app/generate_time_averages/tests/time_avg_test_files/frenctools_timavg_atmos.197901-198312.LWP.nc new file mode 100644 index 00000000..049d668e Binary files /dev/null and b/lib/fre/app/generate_time_averages/tests/time_avg_test_files/frenctools_timavg_atmos.197901-198312.LWP.nc differ diff --git a/lib/fre/app/generate_time_averages/timeAverager.py b/lib/fre/app/generate_time_averages/timeAverager.py new file mode 100644 index 00000000..803febfe --- /dev/null +++ b/lib/fre/app/generate_time_averages/timeAverager.py @@ -0,0 +1,68 @@ +''' core class structure for this module.''' + +class timeAverager: + ''' + abstract base class for generating time averages + related statistical quantities + this class must be inherited by another for functionality. + ''' + pkg: str + var: str + unwgt: bool + avg_type: str + stddev_type: str + + def __init__(self): + ''' init method 1, no inputs given ''' + self.pkg = None + self.var = None + self.unwgt = False + self.avg_type = "all" #see argparser for options + self.stddev_type = None #see argparser for options + + def __init__(self, pkg, var, unwgt, + avg_type, stddev_type): + ''' init method 2, all inputs specified ''' + self.pkg = pkg + self.var = var + self.unwgt = unwgt + self.avg_type = avg_type + self.stddev_type = stddev_type + + def __repr__(self): + ''' return text representation of object ''' + return f'{type(self).__name__}( pkg={self.pkg}, \ + unwgt={self.unwgt}, \ + var={self.var}, \ + avg_type={self.avg_type}, \ + stddev_type={self.stddev_type})' + + def var_has_time_units(self, an_nc_var=None): + ''' checks if variable's units are of time ''' + try: + var_units=an_nc_var.units + units_is_time = False + units_is_time = any( [ var_units == 'seconds' , 'seconds since' in var_units , + var_units == 'minutes' , 'minutes since' in var_units , + var_units == 'hours' , 'hours since' in var_units , + var_units == 'days' , 'days since' in var_units , + var_units == 'months' , 'months since' in var_units , + var_units == 'years' , 'years since' in var_units ] ) + return units_is_time + except: + print('variable does not have units') + print('PROBABLY not time.') + return False + + #def var_has_time_dims(self, an_nc_var=None): + #try: + # var_dims=an_nc_var.dimensions + #for dim in an_nc_var.dimensions: + # if dim == + + + + + def generate_timavg(self, infile=None, outfile=None): + '''# this is a hint: this is to be defined by classes inheriting from the abstract one + this function is never to be fully defined here by design.''' + raise NotImplementedError() diff --git a/lib/fre/app/mask_atmos_plevel.py b/lib/fre/app/mask_atmos_plevel.py new file mode 100644 index 00000000..0e7b2c28 --- /dev/null +++ b/lib/fre/app/mask_atmos_plevel.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +''' +This script contains the refineDiags that produce data at the same +frequency as the input data (no reduction) such as surface albedo, +masking fields,... +It can accept any file and will only compute refineDiags in fields +are present. +''' + +import os +import netCDF4 as nc +import xarray as xr +import click + +@click.command() +def mask_atmos_plevel_subtool(infile, outfile, psfile): + ''' click entry point to fre cmor mask-atmos-plevel''' + + # Error if outfile exists + if os.path.exists(outfile): + raise FileExistsError(f"ERROR: Output file {outfile} already exists") + + # Open ps dataset + if not os.path.exists(psfile): + raise FileNotFoundError(f"ERROR: Input surface pressure file {psfile} does not exist") + ds_ps = xr.open_dataset(psfile) + + # Exit with message if "ps" not available + if "ps" not in list(ds_ps.variables): + raise ValueError(f"ERROR: File {infile} does not contain surface pressure. exit.") + + # Open input dataset + if not os.path.exists(infile): + raise FileNotFoundError(f"ERROR: Input file {infile} does not exist") + ds_in = xr.open_dataset(infile) + + # The trigger for atmos masking is a variable attribute "needs_atmos_masking = True". + # In the future this will be set within the model, but for now and testing, + # we'll add the attribute for variables that end with "_unmsk". + ds_in = preprocess(ds_in) + + ds_out = xr.Dataset() + + # Process all variables with attribute "needs_atmos_masking = True" + for var in list(ds_in.variables): + if 'needs_atmos_masking' in ds_in[var].attrs: + del ds_in[var].attrs['needs_atmos_masking'] + ds_out[var] = mask_field_above_surface_pressure(ds_in, var, ds_ps) + else: + continue + + # Write the output file if anything was done + if ds_out.variables: + print(f"Modifying variables: {list(ds_out.variables)}\n appending into new file {outfile}") + write_dataset(ds_out, ds_in, outfile) + else: + print(f"No variables modified, so not writing output file {outfile}") + + + +def preprocess(ds): + """add needs_atmos_masking attribute if var ends with _unmsk""" + for var in list(ds.variables): + if var.endswith('_unmsk'): + ds[var].attrs['needs_atmos_masking'] = True + return ds + + +def mask_field_above_surface_pressure(ds, var, ds_ps): + """mask data with pressure larger than surface pressure""" + plev = pressure_coordinate(ds, var) + + # broadcast pressure coordinate and surface pressure to + # the dimensions of the variable to mask + plev_extended, _ = xr.broadcast(plev, ds[var]) + ps_extended, _ = xr.broadcast(ds_ps["ps"], ds[var]) + + # masking do not need looping + masked = xr.where(plev_extended > ps_extended, 1.0e20, ds[var]) + + # copy attributes, but it doesn't include the missing values + attrs = ds[var].attrs.copy() + + # add the missing values back + attrs['missing_value'] = 1.0e20 + attrs['_FillValue'] = 1.0e20 + masked.attrs = attrs + + # transpose dims like the original array + masked = masked.transpose(*ds[var].dims) + print(f"Processed {var}") + + return masked + + +def pressure_coordinate(ds, varname):#, verbose=False): + """check if dataArray has pressure coordinate fitting requirements + and return it""" + + pressure_coord = None + for dim in list(ds[varname].dims): + if dim in list(ds.variables): # dim needs to have values in file + if ds[dim].attrs["long_name"] == "pressure": + pressure_coord = ds[dim] + elif ("coordinates" in ds.attrs) and (ds[dim].attrs["units"] == "Pa"): + pressure_coord = ds[dim] + return pressure_coord + + +def write_dataset(ds, template, outfile): + """prepare the dataset and dump into netcdf file""" + + # copy global attributes + ds.attrs = template.attrs.copy() + + # copy all variables and their attributes + # except those already processed + for var in list(template.variables): + if var in list(ds.variables): + continue + ds[var] = template[var] + ds[var].attrs = template[var].attrs.copy() + # write to file + ds.to_netcdf(outfile, unlimited_dims="time") + + + +def set_netcdf_encoding(ds, pressure_vars): + """set preferred options for netcdf encoding""" + all_vars = list(ds.variables) + encoding = {} + #for var in do_not_encode_vars + pressure_vars: #what was here in first place + for var in pressure_vars: #remove unused variable + if var in all_vars: + encoding.update( { var : + { '_FillValue':None} } ) + return encoding + + +def post_write(filename, var_with_bounds, bounds_variables): + """fix a posteriori attributes that xarray.to_netcdf + did not do properly using low level netcdf lib""" + f = nc.Dataset(filename, "a") + for var, bndvar in zip(var_with_bounds, bounds_variables): + f.variables[var].setncattr("bounds", bndvar) + f.close() diff --git a/lib/fre/app/regrid_xy/README.md b/lib/fre/app/regrid_xy/README.md new file mode 100644 index 00000000..30bf9b22 --- /dev/null +++ b/lib/fre/app/regrid_xy/README.md @@ -0,0 +1,55 @@ +# ABOUT + +`regrid_xy.py` remaps scalar and/or vector fields from one kind of lat/lon grid to another. It can remap between different grids of the same type (e.g. spherical), and between grids of different types (e.g. spherical to tripolar). By default, it uses an O(1) conservative interpolation scheme to accomplish the regridding, except under certain conditions [defined within `fregrid`](https://github.com/NOAA-GFDL/FRE-NCtools/blob/master/tools/fregrid/fregrid.c#L915-L920) the underlying CLI tool which does the heavy lifting. + +requires `fre-nctools` and `fregrid` to be in one's `PATH` variable, and `python3` (tested/developed with python 3.9.16). there should be `netCDF4` and `metomi` python modules in one's python environment for imports. `pytest` and `nccmp` is required for tests. `pylint` recommended for future developers working on this tool. + + +# INPUT PARAMETERS (mandatory, env vars) +format here is: +config field name / python variable name (type) explanation + +the following are required to be specified: +_______________________________________ +`inputDir` / `input_dir` (env var) specifies input directory to regrid, typically an untarredv history file archive +_______________________________________ +`source` / `source` (env var) source name for input target file name within input directory to target for regridding. the value for `source` must be present in at least one component's configuration fields +_______________________________________ +`begin` / `begin` (env var) ISO8601 datetime format specification for starting date of data, part of input target file name +_______________________________________ +`outputDir` / `output_dir` (env var) specifies target location for output regridded files +_______________________________________ +`TMPDIR` / `tmp_dir` (env var) temp directory for location of file read/writes +_______________________________________ +`fregridRemapDir` / `remap_dir` (env var) directory containing remap file for regridding +_______________________________________ +`gridSpec` / `grid_spec` (env var) file containing mosaic for regridding +_______________________________________ +`defaultxyInterp` / `def_xy_interp` (env var) default lat/lon resolution for output regridding. (change me? TODO) + + +# INPUT PARAMETERS (configuration fields, mandatory) +the following parameters are REQUIRED to be specified on a per-component basis wtihin `app/regrid-xy/rose-app.conf`. A component's input parameters are delineated with a `[component_name]` +_______________________________________ +`inputRealm` / `input_realm` (config field) realm within model from which the input component/source files are derived +_______________________________________ +`interpMethod` / `interp_method` (config field) interpolation method to use for regridding, it may be changed if it is specified in the target source file's attributes +_______________________________________ +`inputGrid` / `input_grid` (config field) current grid type of input source files. +_______________________________________ +`sources` / N/A (config field) + +# INPUT PARAMETERS (configuration fields, optional) +the following parameters are OPTIONAL for specifying on a per-component basis within `app/regrid-xy/rose-app.conf`. +_______________________________________ +`outputGridType` / `output_grid_type` (config field) used only for output dir to specify grid type, but does not determine actual output grid type for regridding to fregrid +_______________________________________ +`fregridRemapFile` / `fregrid_remap_file` (config field) remap file name to use for regridding +_______________________________________ +`fregridMoreOptions` / `more_options` (config field) field for specifying additional options to `fregrid` that have not-yet been officially implemented. use with caution! +_______________________________________ +`variables` / `regrid_vars` (config field) list of variable data to regrid within target source files. if unspecified, all variables within the target file of dimension 2 or greater will be regridded. +_______________________________________ +`outputGridLat` / `output_grid_lat` (config field) latitude resolution for regridded output, also used for remap file targeting if there is no remap file specified. +_______________________________________ +`outputGridLon` / `output_grid_lon` (config field) latitude resolution for regridded output, also used for remap file targeting if there is no remap file specified. diff --git a/lib/fre/app/regrid_xy/__init__.py b/lib/fre/app/regrid_xy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/app/regrid_xy/regrid_xy.py b/lib/fre/app/regrid_xy/regrid_xy.py new file mode 100644 index 00000000..8721b36a --- /dev/null +++ b/lib/fre/app/regrid_xy/regrid_xy.py @@ -0,0 +1,486 @@ +""" + remaps scalar and/or vector fields. It is capable of remapping + from a spherical grid onto a different one, e.g. spherical, + or tripolar. By default, it does so using a conservative scheme. + most valid input args to fregrid are valid in this script +""" + +import subprocess +import shutil +import os +from pathlib import Path + +#3rd party +import metomi.rose.config as rose_cfg +from netCDF4 import Dataset +import click + +FREGRID_SHARED_FILES='/home/fms/shared_fregrid_remap_files' + +def truncate_date(date, freq): + """ truncates iso freq to iso date time """ + freq_in_date_format=freq_to_date_format(freq) + + #in the shell version, this line simply gets run. + #we will simply print this command to screen for now. TO DO (maybe we can work around it?) + #not clear to me why piping to tr is necessary, doesnt seem to change output at all + #print('cylc date --template '+freq_in_date_format+' '+date+' | tr -d T') + #output =subprocess.Popen(["cylc", "date", "--template", freq_in_date_format, date, + # "|","tr","-d","T"], + # stdout=subprocess.PIPE) + output =subprocess.Popen(["cylc", "cycle-point", "--template", freq_in_date_format, date], + stdout=subprocess.PIPE) + bytedate = output.communicate()[0] + date=str(bytedate.decode()) + + #remove trailing newline + date=date[:(len(date)-1)] + + #check for and remove 'T' if present + if not date.isnumeric(): + date=date[:8]+date[-2:] + return date + +def freq_to_date_format(iso_freq): + """ + Print legacy Bronx-like date template format given a frequency (ISO 8601 duration) + """ + if iso_freq=='P1Y': + return 'CCYY' + if iso_freq=='P1M': + return 'CCYYMM' + if iso_freq=='P1D': + return 'CCYYMMDD' + if (iso_freq[:2]=='PT') and (iso_freq[-1:]=='H'): + return 'CCYYMMDDThh' + raise ValueError(f'ERROR: Unknown Frequency {iso_freq}') + +def test_import(): + """for quickly testing import within pytest""" + return 1 + +def safe_rose_config_get(config, section, field): + """read optional variables from rose configuration, and don't error on None value""" + config_dict = config.get( [section,field] ) + return None if config_dict is None else config_dict.get_value() + + +def get_mosaic_file_name(grid_spec_file, mosaic_type): + """read string from a numpy masked array WHY""" + grid_spec_nc = Dataset(grid_spec_file, 'r') + masked_data = grid_spec_nc[mosaic_type][:].copy() # maskedArray + grid_spec_nc.close() + unmasked_data = masked_data[~masked_data.mask] + file_name = ''.join([ one_char.decode() for one_char in unmasked_data ]) + return file_name + + +def get_mosaic_grid_file_name(input_mosaic): + """get mosaic grid file name from NESTED numpy masked array WHY""" + input_mosaic_nc = Dataset(input_mosaic,'r') + masked_data = input_mosaic_nc['gridfiles'][0].copy() + input_mosaic_nc.close() + unmasked_data = masked_data[~masked_data.mask] + grid_file_name = ''.join([ one_char.decode() for one_char in unmasked_data ]) + return grid_file_name + + +def check_interp_method( nc_variable, interp_method): + """print warning if optional interp_method clashes with nc file attribute field, if present""" + attr_list=nc_variable.ncattrs() + if 'interp_method' not in attr_list: + pass + elif nc_variable.interp_method != interp_method: + print(f'WARNING: interp_method={interp_method} differs from what is in target_file') + print(f'WARNING: interp_method used may not be {interp_method}!') + + +def check_per_component_settings(component_list, rose_app_cfg): + """for a source file ref'd by multiple components check per-component + settings for uniqueness. output list of bools of same length to check + in componenet loop""" + do_regridding = [True] #first component will always be run + curr_out_grid_type_list = [safe_rose_config_get( \ + rose_app_cfg, component_list[0], 'outputGridType')] + for i in range( 1, len(component_list) ): + next_comp=component_list[i] + next_out_grid_type=safe_rose_config_get( rose_app_cfg, next_comp, 'outputGridType') + if next_out_grid_type not in curr_out_grid_type_list: + do_regridding.append(True) + curr_out_grid_type_list.append(next_out_grid_type) + else: + do_regridding.append(False) + if len(do_regridding) != len(component_list) : + raise ValueError('problem with checking per-component settings for uniqueness') + return do_regridding + + + + +def make_component_list(config, source): + """make list of relevant component names where source file appears in sources""" + comp_list=[] #will not contain env, or command + try: + for keys, sub_node in config.walk(): + sources = sub_node.get_value(keys=['sources'])#.split(' ') + if any( [ len(keys) < 1, + keys[0] in ['env', 'command'], + keys[0] in comp_list, + sources is None ] ): + continue + sources = sources.split(' ') + if source in sources: + comp_list.append(keys[0]) + except Exception as exc: + raise ValueError(f'config = {config} may be an empty file... check the config') \ + from exc + return comp_list + + +def make_regrid_var_list(target_file, interp_method = None): + """create default list of variables to be regridded within target file.""" + fin = Dataset(target_file,'r') + all_fin_vars = fin.variables + regrid_vars = [] + for var_name in all_fin_vars: + if var_name in ['average_T1','average_T2', + 'average_DT','time_bnds' ]: + continue + if len(all_fin_vars[var_name].shape) < 2 : + continue + regrid_vars.append(var_name) + if interp_method is not None: + check_interp_method( all_fin_vars[var_name] , interp_method) + + fin.close() + return regrid_vars + + +def regrid_xy(input_dir = None, output_dir = None, begin = None, tmp_dir = None, + remap_dir = None, source = None, grid_spec = None, def_xy_interp = None + ): + """ + calls fre-nctools' fregrid to regrid net cdf files + """ + + ## rose config load check + config_name = os.getcwd() + config_name += '/rose-app-run.conf' + #config_name += '/rose-app.conf' + print(f'config_name = {config_name}') + try: + rose_app_config = rose_cfg.load(config_name) + except Exception as exc: + raise ValueError(f'config_name = {config_name} not found.') \ + from exc + + + # mandatory arguments- code exits if any of these are not present + #input_dir = os.getenv( 'inputDir' ) + #output_dir = os.getenv( 'outputDir' ) + #begin = os.getenv( 'begin' ) + #tmp_dir = os.getenv( 'TMPDIR' ) + #remap_dir = os.getenv( 'fregridRemapDir' ) + #source = os.getenv( 'source' ) + #grid_spec = os.getenv( 'gridSpec' ) + #def_xy_interp = os.getenv( 'defaultxyInterp' ) + if None in [ input_dir , output_dir , + begin , tmp_dir , + remap_dir , source , + grid_spec , def_xy_interp ]: + print( f'input_dir = { input_dir }\n' + \ + f'output_dir = { output_dir }\n' + \ + f'begin = { begin }\n' + \ + f'tmp_dir = { tmp_dir }\n' + \ + f'remap_dir = { remap_dir }\n' + \ + f'source = { source }\n' + \ + f'grid_spec = { grid_spec }\n' + \ + f'def_xy_interp = { def_xy_interp }' ) + raise ValueError(f'a mandatory input argument to regrid_xy is None... \n {locals()}') + + + def_xy_interp = def_xy_interp.split(',') + def_xy_interp[0] = def_xy_interp[0].replace('"', '') + def_xy_interp[1] = def_xy_interp[1].replace('"', '') + if any( [ def_xy_interp == [] or len(def_xy_interp) != 2 ] ): + raise ValueError( + f'default xy interpolation has invalid format: \n def_xy_interp = {def_xy_interp}') + + # input dir must exist + if not Path( input_dir ).exists(): + raise OSError(f'input_dir={input_dir} \n does not exist') + + # tmp_dir check + if not Path( tmp_dir ).exists(): + raise OSError(f'tmp_dir={tmp_dir} \n does not exist.') + + # output dir check + Path( output_dir ).mkdir( parents = True, exist_ok = True ) + if not Path( output_dir ).exists() : + raise OSError('the following does not exist and/or could not be created:' + + f'output_dir=\n{output_dir}') + + # work/ dir check + work_dir = tmp_dir + 'work/' + Path( work_dir ).mkdir( exist_ok = True ) + if not Path( work_dir ).exists(): + raise OSError('the following does not exist and/or could not be created:' + + f'work_dir=\n{work_dir}') + + + # fregrid remap dir check + Path(remap_dir).mkdir( exist_ok = True ) + if not Path( remap_dir ).exists(): + raise OSError(f'{remap_dir} could not be created') + + + # grid_spec file management + #starting_dir = os.getcwd() + #os.chdir(work_dir) # i hate it + if '.tar' in grid_spec: + untar_sp = \ + subprocess.run( ['tar', '-xvf', grid_spec, '-C', input_dir], + check = False , capture_output = True ) + if untar_sp.returncode != 0: + raise OSError( + f'untarring of {grid_spec} file failed, ' + \ + f'ret_code={untar_sp.returncode}, stderr={untar_sp.stderr}') + if Path( input_dir+'mosaic.nc' ).exists(): + grid_spec_file=input_dir+'mosaic.nc' + elif Path( input_dir+'grid_spec.nc' ).exists(): + grid_spec_file=input_dir+'grid_spec.nc' + else: + raise ValueError(f'grid_spec_file cannot be determined from grid_spec={grid_spec}') + else: + try: #attempt to copy directly from uncompressed grid_spec location + grid_spec_file=input_dir+grid_spec.split('/').pop() + shutil.copy(grid_spec, grid_spec_file ) + except Exception as exc: + raise OSError(f'grid_spec={grid_spec} could not be copied.') \ + from exc + + # ------------------------------------------------------------------ component loop + component_list = make_component_list(rose_app_config, source) + if len(component_list) == 0: + raise ValueError('component list empty- source file not found in any source file list!') + if len(component_list) > 1: # check settings for uniqueness + do_regridding = \ + check_per_component_settings( \ + component_list, rose_app_config) + print(f'component_list = {component_list}') + print(f'do_regridding = {do_regridding}') + else: + do_regridding=[True] + + for component in component_list: + if not do_regridding[ + component_list.index(component) ]: + continue + print(f'\n\nregridding \nsource={source} for \ncomponent={component}\n') + + # mandatory per-component inputs, will error if nothing in rose config + input_realm, interp_method, input_grid = None, None, None + try: + input_realm = rose_app_config.get( [component, 'inputRealm'] ).get_value() + interp_method = rose_app_config.get( [component, 'interpMethod'] ).get_value() + input_grid = rose_app_config.get( [component, 'inputGrid'] ).get_value() + except Exception as exc: + raise ValueError('at least one of the following are None: \n' + \ + f'input_grid=\n{input_grid}\n,input_realm=\n' + \ + f'{input_realm}\n,/interp_method=\n{interp_method}') \ + from exc + print(f'input_grid = {input_grid }\n' + \ + f'input_realm = {input_realm }\n' + \ + f'interp_method = {interp_method }' ) + + #target input variable resolution + is_tiled = 'cubedsphere' in input_grid + target_file = input_dir + target_file += f"/{truncate_date(begin,'P1D')}.{source}.tile1.nc" \ + if is_tiled \ + else f"/{truncate_date(begin,'P1D')}.{source}.nc" + if not Path( target_file ).exists(): + raise OSError(f'regrid_xy target does not exist. \ntarget_file={target_file}') + print(f'target_file={target_file}') #DELETE + + + # optional per-component inputs + output_grid_type = safe_rose_config_get( rose_app_config, component, 'outputGridType') + remap_file = safe_rose_config_get( rose_app_config, component, 'fregridRemapFile') + more_options = safe_rose_config_get( rose_app_config, component, 'fregridMoreOptions') + regrid_vars = safe_rose_config_get( rose_app_config, component, 'variables') + output_grid_lon = safe_rose_config_get( rose_app_config, component, 'outputGridLon') + output_grid_lat = safe_rose_config_get( rose_app_config, component, 'outputGridLat') + + # if no input args specified, grab the defaul values + if any( [ output_grid_lon is None, + output_grid_lat is None ] ): + output_grid_lon = def_xy_interp[0] + output_grid_lat = def_xy_interp[1] + + print( f'output_grid_type = {output_grid_type }\n' + \ + f'remap_file = {remap_file }\n' + \ + f'more_options = {more_options }\n' + \ + f'output_grid_lon = {output_grid_lon }\n' + \ + f'output_grid_lat = {output_grid_lat }\n' + \ + f'regrid_vars = {regrid_vars }\n' ) + + + + # prepare to create input_mosaic via ncks call + if input_realm in ['atmos', 'aerosol']: + mosaic_type = 'atm_mosaic_file' + elif input_realm == 'ocean': + mosaic_type = 'ocn_mosaic_file' + elif input_realm == 'land': + mosaic_type = 'lnd_mosaic_file' + else: + raise ValueError(f'input_realm={input_realm} not recognized.') + + # this is just to get the grid_file name + #print(f'mosaic_type = {mosaic_type}') + #print(f'grid_spec_file = {grid_spec_file}') + #print(f'input_mosaic = get_mosaic_file_name(grid_spec_file, mosaic_type)') + + # assume input_mosaic near input grid_spec, where intially specified. + input_mosaic = input_dir + get_mosaic_file_name(grid_spec_file, mosaic_type) + #print(f'input_mosaic = {input_mosaic}') + + ## this is to get the tile1 filename? + mosaic_grid_file = input_dir + get_mosaic_grid_file_name(input_mosaic) + #print(f'mosaic_grid_file = {mosaic_grid_file}') + + # need source file dimenions for lat/lon + source_nx = str(int(Dataset(mosaic_grid_file).dimensions['nx'].size / 2 )) + source_ny = str(int(Dataset(mosaic_grid_file).dimensions['ny'].size / 2 )) + print(f'source_[nx,ny] = ({source_nx},{source_ny})') + Dataset(mosaic_grid_file).close() + + + if remap_file is not None: + try: + shutil.copy( remap_file, + input_dir+remap_file.split('/').pop() ) + except Exception as exc: + raise OSError('remap_file={remap_file} could not be copied to local dir') \ + from exc + else: + print('remap_file was not specified nor found. looking for default one') + remap_file= f'fregrid_remap_file_{output_grid_lon}_by_{output_grid_lat}.nc' \ + if \ + all( [output_grid_lon is not None, + output_grid_lat is not None]) \ + else \ + f'fregrid_remap_file_{def_xy_interp(0)}_by_{def_xy_interp(1)}.nc' + remap_cache_file = \ + f'{remap_dir}/{input_grid}/{input_realm}/' + \ + f'{source_nx}-by-{source_ny}/{interp_method}/{remap_file}' + central_remap_cache_file = \ + f'{FREGRID_SHARED_FILES}/{input_grid}/' + \ + f'{source_nx}_by_{source_ny}/{remap_file}' + + print(f'remap_file = {remap_file }' + \ + f'remap_cache_file = {remap_cache_file }' + \ + f'central_remap_cache_file = {central_remap_cache_file}' ) + + if Path( remap_cache_file ).exists(): + print(f'NOTE: using cached remap file {remap_cache_file}') + shutil.copy(remap_cache_file, + work_dir+remap_cache_file.split('/').pop()) + elif Path( central_remap_cache_file ).exists(): + print(f'NOTE: using centrally cached remap file {remap_cache_file}') + shutil.copy(central_remap_cache_file, + work_dir+central_remap_cache_file.split('/').pop()) + + + + # if no variables in config, find the interesting ones to regrid + if regrid_vars is None: + regrid_vars=make_regrid_var_list( target_file , interp_method) + print(f'regrid_vars = {regrid_vars}') #DELETE + + #check if there's anything worth regridding + if len(regrid_vars) < 1: + raise ValueError('make_regrid_var_list found no vars to regrid. and no vars given. exit') + print(f'regridding {len(regrid_vars)} variables: {regrid_vars}') + regrid_vars_str=','.join(regrid_vars) # fregrid needs comma-demarcated list of vars + + + + # massage input file argument to fregrid. + input_file = target_file.replace('.tile1.nc','') \ + if '.tile1' in target_file \ + else target_file + input_file=input_file.split('/').pop() + + # create output file argument... + output_file = target_file.replace('.tile1','') \ + if 'tile1' in target_file \ + else target_file + output_file = work_dir + output_file.split('/').pop() + + fregrid_command = [ + 'fregrid', + '--debug', + '--standard_dimension', + '--input_mosaic', f'{input_mosaic}', + '--input_dir', f'{input_dir}', + '--input_file', f'{input_file}', + '--associated_file_dir', f'{input_dir}', + '--interp_method', f'{interp_method}', + '--remap_file', f'{remap_file}', + '--nlon', f'{str(output_grid_lon)}', + '--nlat', f'{str(output_grid_lat)}', + '--scalar_field', f'{regrid_vars_str}', + '--output_file', f'{output_file}'] + if more_options is not None: + fregrid_command.append(f'{more_options}') + + print(f"\n\nabout to run the following command: \n{' '.join(fregrid_command)}\n") + fregrid_proc = subprocess.run( fregrid_command, check = False )#i hate it + fregrid_rc =fregrid_proc.returncode + print(f'fregrid_result.returncode()={fregrid_rc}') + + + # output wrangling + + # copy the remap file to the cache location + if not Path( remap_cache_file ).exists(): + remap_cache_file_dir='/'.join(remap_cache_file.split('/')[0:-1]) + Path( remap_cache_file_dir ).mkdir( parents = True , exist_ok = True) + print(f'copying \nremap_file={remap_file} to') + print(f'remap_cache_file_dir={remap_cache_file_dir}') + shutil.copy(remap_file, remap_cache_file_dir) + + # more output wrangling + final_output_dir = output_dir \ + if output_grid_type is None \ + else output_dir + '/' + output_grid_type + Path( final_output_dir ).mkdir( exist_ok = True) + + print(f'TRYING TO COPY {output_file} TO {final_output_dir}') + shutil.copy(output_file, final_output_dir) + + continue # end of comp loop, exit or next one. + + #os.chdir(starting_dir) # not clear this is necessary. + print('done running regrid_xy()') + return 0 + + +@click.command() +def _regrid_xy( input_dir, output_dir, begin, tmp_dir, + remap_dir, source, grid_spec, def_xy_interp ): + """ click entrypoint """ + click.echo(f'(_regrid_xy) locals={locals()}') + click.echo( '(_regrid_xy) click entrypoint hit- calling regrid_xy()') + return regrid_xy( input_dir, output_dir, begin, tmp_dir, + remap_dir, source, grid_spec, def_xy_interp ) + + +def main(): + """steering, local test/debug""" + return regrid_xy() + +if __name__=='__main__': + main() diff --git a/lib/fre/app/regrid_xy/tests/test_inputs_outputs/input_directory.tar.gz b/lib/fre/app/regrid_xy/tests/test_inputs_outputs/input_directory.tar.gz new file mode 100644 index 00000000..003d654a Binary files /dev/null and b/lib/fre/app/regrid_xy/tests/test_inputs_outputs/input_directory.tar.gz differ diff --git a/lib/fre/app/regrid_xy/tests/test_regrid_xy.py b/lib/fre/app/regrid_xy/tests/test_regrid_xy.py new file mode 100644 index 00000000..fa511b66 --- /dev/null +++ b/lib/fre/app/regrid_xy/tests/test_regrid_xy.py @@ -0,0 +1,494 @@ +""" test regrid_xy functioning as an importable python module """ +from pathlib import Path +import subprocess +import os + +import pytest + +import fre.app.regrid_xy.regrid_xy as rgxy + +# directories for tests +TEST_DIR = os.getcwd() + "/fre/app/regrid_xy/tests/test_inputs_outputs/" + +TAR_IN_DIR = TEST_DIR + 'input_directory.tar.gz' #contains in-dir +IN_DIR = TEST_DIR + 'in-dir/' +WORK_DIR = TEST_DIR + 'work/' + +TEST_OUT_DIR = TEST_DIR + 'out-dir/' +REMAP_DIR = TEST_DIR + 'remap-dir/' + +ALL_TEST_OUT_DIR = TEST_DIR + "out-test-dir/" +REMAP_TEST_DIR = TEST_DIR + 'remap-test-dir/' + +# official data/mosaic stuff +GOLD_GRID_SPEC = "/archive/gold/datasets/OM4_05/mosaic_c96.v20180227.tar" +GOLD_GRID_SPEC_NO_TAR = IN_DIR + "mosaic.nc" +TEST_CDL_GRID_FILE = IN_DIR + "C96_mosaic.cdl" + + +## for rose configuration +COMPONENT='atmos' +NLON=288 +NLAT=180 +INPUT_GRID='cubedsphere' +INPUT_REALM='atmos' +INTERP_METHOD='conserve_order1' +YYYYMMDD='20030101' +SOURCE='atmos_static_cmip' # generally a list, but for tests, only one + # note that components will not always contain a + # a source file of the same name +SOURCES_XY = '96-by-96' + +WORK_YYYYMMDD_DIR = WORK_DIR + f'{YYYYMMDD}.nc/' +TEST_NC_GRID_FILE = WORK_YYYYMMDD_DIR + "C96_mosaic.nc" # output of first ncgen test + +def test_setup_clean_up(capfd): + """ cleanup i/o directories is present for clean regrid_xy testing """ + try: + Path(IN_DIR).unlink() + except OSError: + pass + try: + Path(TEST_NC_GRID_FILE).unlink() + except OSError: + pass + try: + Path(WORK_DIR).unlink() + except OSError: + pass + try: + Path(TEST_OUT_DIR).unlink() + except OSError: + pass + try: + Path(ALL_TEST_OUT_DIR).unlink() + except OSError: + pass + try: + Path(REMAP_DIR).unlink() + except OSError: + pass + try: + Path(REMAP_TEST_DIR).unlink() + except OSError: + pass + assert True + out, err = capfd.readouterr() + + +def test_setup_global_work_dirs(capfd): + """ create i/o directories for regrid_xy testing """ + Path(WORK_YYYYMMDD_DIR).mkdir(parents = True, exist_ok = True) + assert Path(WORK_YYYYMMDD_DIR).exists() + + Path(REMAP_TEST_DIR).mkdir(exist_ok = True) + assert Path(REMAP_TEST_DIR).exists() + + Path(ALL_TEST_OUT_DIR).mkdir(exist_ok = True) + assert Path(ALL_TEST_OUT_DIR).exists() + + Path(TEST_OUT_DIR).mkdir(exist_ok = True) + assert Path(TEST_OUT_DIR).exists() + + Path(REMAP_DIR).mkdir(exist_ok = True) + assert Path(REMAP_DIR).exists() + out, err = capfd.readouterr() + + +def test_untar_inputs(capfd): + """ untar input directory tarball to create test inputs """ + ex = ["tar", "-C", TEST_DIR, "-zxvf", TAR_IN_DIR] + sp = subprocess.run( ex , check = True ) + assert all ( [ sp.returncode == 0, + Path(IN_DIR).exists() ] ) + out, err = capfd.readouterr() + + +#@pytest.mark.skip(reason='debug') +def test_make_ncgen3_nc_inputs(capfd): + """ + set-up test: ncgen3 netcdf file inputs for later steps + if the output exists, it will not bother remaking it + """ + + ncgen3_output = TEST_NC_GRID_FILE + ncgen3_input = TEST_CDL_GRID_FILE + if Path(ncgen3_output).exists(): + assert True + else: + assert Path(TEST_DIR).exists() + assert Path(ncgen3_input).exists() + ex = [ 'ncgen3', '-k', 'netCDF-4', '-o', ncgen3_output , ncgen3_input ] + print (' '.join(ex)) + sp = subprocess.run( ex , check = True ) + + assert all( [ sp.returncode == 0, + Path(ncgen3_output).exists() ] ) + out, err = capfd.readouterr() + + +#@pytest.mark.skip(reason='debug') +def test_make_ncgen_tile_nc_inputs(capfd): + """ + set-up test: ncgen netcdf tile file inputs for later steps + if the output exists, it will not bother remaking it + """ + ncgen_tile_i_nc_output_exists = [ Path( WORK_YYYYMMDD_DIR + \ + f'{YYYYMMDD}.{SOURCE}.tile{i}.nc' ).exists() \ + for i in range(1, 6+1) ] + if all( ncgen_tile_i_nc_output_exists ): + assert True + else: + for i in range(1, 6+1): + ncgen_tile_i_nc_output = WORK_YYYYMMDD_DIR + f'{YYYYMMDD}.{SOURCE}.tile{i}.nc' + ncgen_tile_i_cdl_input = IN_DIR + f'{YYYYMMDD}.{SOURCE}.tile{i}.cdl' + assert Path(ncgen_tile_i_cdl_input).exists() + ex = [ 'ncgen', '-o', ncgen_tile_i_nc_output, ncgen_tile_i_cdl_input ] + print (' '.join(ex)) + sp = subprocess.run( ex , check = True ) + + assert all( [sp.returncode == 0, + Path(ncgen_tile_i_nc_output).exists()] ) + out, err = capfd.readouterr() + + +#@pytest.mark.skip(reason='debug') +def test_make_ncgen_grid_spec_nc_inputs(capfd): + """ + set-up test: ncgen netcdf grid spec tile file inputs for later steps + if the output exists, it will not bother remaking it + """ + ncgen_grid_spec_i_nc_output_exists = [ Path( WORK_YYYYMMDD_DIR + \ + f'{YYYYMMDD}.grid_spec.tile{i}.nc').exists() \ + for i in range(1, 6+1) ] + if all( ncgen_grid_spec_i_nc_output_exists ): + assert True + else: + for i in range(1, 6+1): + ncgen_grid_spec_i_nc_output = WORK_YYYYMMDD_DIR + f'{YYYYMMDD}.grid_spec.tile{i}.nc' + ncgen_grid_spec_i_cdl_input = IN_DIR + f'{YYYYMMDD}.grid_spec.tile{i}.cdl' + ex = [ 'ncgen', '-o', ncgen_grid_spec_i_nc_output, ncgen_grid_spec_i_cdl_input ] + print (' '.join(ex)) + sp = subprocess.run( ex , check = True ) + + assert all( [sp.returncode == 0, + Path(ncgen_grid_spec_i_nc_output).exists()] ) + out, err = capfd.readouterr() + + +#@pytest.mark.skip(reason='debug') +def test_make_hgrid_gold_input(capfd): + """ + set-up test: make C96 gold input via make_hgrid for later steps + if the output exists in the desired location, it will not bother remaking it + """ + grid_i_file_targ_loc_exists = [ Path( WORK_YYYYMMDD_DIR + f'C96_grid.tile{i}.nc').exists() \ + for i in range(1,6+1) ] + if all( grid_i_file_targ_loc_exists ): + assert True + else: + # remake gold grid file locally, then move + ex = [ "make_hgrid", + "--grid_type", "gnomonic_ed", + "--nlon", "192", + "--grid_name", "C96_grid" ] + print (' '.join(ex)) + sp = subprocess.run( ex , check = True ) + + assert sp.returncode == 0 + out, err = capfd.readouterr() + + # now move the files... + for i in range(1, 6+1): + grid_i_file_curr_loc = f'C96_grid.tile{i}.nc' + grid_i_file_targ_loc = WORK_YYYYMMDD_DIR + f'C96_grid.tile{i}.nc' + ex = [ 'mv', '-f', grid_i_file_curr_loc, grid_i_file_targ_loc ] + print (' '.join(ex)) + sp = subprocess.run( ex , check = True ) + + assert all( [sp.returncode == 0, + Path(grid_i_file_targ_loc).exists()] ) + out, err = capfd.readouterr() + + +#@pytest.mark.skip(reason='debug') +def test_make_fregrid_comparison_input(capfd): + """ + set-up test: use fregrid to regrid for later comparison to regrid_xy output + if the output exists in the desired location, it will not bother remaking it + """ + + fregrid_input_mosaic_arg = TEST_NC_GRID_FILE + fregrid_input_dir_arg = WORK_YYYYMMDD_DIR + fregrid_input_file_arg = f'{YYYYMMDD}.{SOURCE}' + fregrid_assoc_file_dir_arg = WORK_YYYYMMDD_DIR + + fregrid_remap_file = f'fregrid_remap_file_{NLON}_by_{NLAT}.nc' + fregrid_remap_file_arg = REMAP_TEST_DIR + fregrid_remap_file + + fregrid_nlat_arg = str(NLAT) + fregrid_nlon_arg = str(NLON) + fregrid_vars_arg = 'grid_xt,grid_yt,orog' + + + fregrid_output_file_arg = ALL_TEST_OUT_DIR + fregrid_input_file_arg + '.nc' + + ex = [ 'fregrid', '--standard_dimension', + '--input_mosaic', fregrid_input_mosaic_arg, + '--input_dir', fregrid_input_dir_arg, + '--input_file', fregrid_input_file_arg, + '--associated_file_dir', fregrid_assoc_file_dir_arg, + '--remap_file', fregrid_remap_file_arg, + '--nlon', fregrid_nlon_arg, + '--nlat', fregrid_nlat_arg, + '--scalar_field', fregrid_vars_arg, + '--output_file', fregrid_output_file_arg ] + print (' \n'.join(ex)) + sp = subprocess.run( ex , check = True ) + + assert all( [ sp.returncode == 0, + Path(fregrid_remap_file_arg).exists(), + Path(fregrid_output_file_arg).exists() ] ) + out, err = capfd.readouterr() + + +#@pytest.mark.skip(reason='debug') +def test_import_regrid_xy(capfd): + """ + check import of regrid_xy as a module + """ + assert all( [ rgxy is not None, + rgxy.test_import() == 1 ] ) + out, err = capfd.readouterr() + +#@pytest.mark.skip(reason='debug') +def test_success_tar_grid_spec_regrid_xy(capfd): + """ + checks for success of regrid_xy with rose app-app run + """ + # this will only work at GFDL for now. + if not Path(GOLD_GRID_SPEC).exists(): + assert True + else: + # for the time being, still a little dependent on rose for configuration value passing + if Path(os.getcwd()+'/rose-app-run.conf').exists(): + Path(os.getcwd()+'/rose-app-run.conf').unlink() + + with open(os.getcwd()+'/rose-app-run.conf','a',encoding='utf-8') as rose_app_run_config: + rose_app_run_config.write( '[command]\n' ) + rose_app_run_config.write( 'default=regrid-xy\n' ) + rose_app_run_config.write( '\n' ) + rose_app_run_config.write( f'[{COMPONENT}]\n' ) + rose_app_run_config.write( f'sources={SOURCE}\n' ) + rose_app_run_config.write( f'inputGrid={INPUT_GRID}\n' ) + rose_app_run_config.write( f'inputRealm={INPUT_REALM}\n' ) + rose_app_run_config.write( f'interpMethod={INTERP_METHOD}\n' ) + rose_app_run_config.write( f'outputGridLon={NLON}\n' ) + rose_app_run_config.write( f'outputGridLat={NLAT}\n' ) + rose_app_run_config.write( '\n' ) + assert Path('./rose-app-run.conf').exists() + + rgxy_returncode = rgxy.regrid_xy( + input_dir = WORK_YYYYMMDD_DIR, + output_dir = TEST_OUT_DIR, + begin = f'{YYYYMMDD}T000000', + tmp_dir = TEST_DIR, + remap_dir = REMAP_DIR, + source = SOURCE, + grid_spec = GOLD_GRID_SPEC, + def_xy_interp = f'"{NLON},{NLAT}"' + ) + + # uhm.... + assert rgxy_returncode == 0 + assert Path( REMAP_DIR + \ + f'{INPUT_GRID}/{INPUT_REALM}/96-by-96/{INTERP_METHOD}/' + \ + f'fregrid_remap_file_{NLON}_by_{NLAT}.nc' \ + ).exists() + assert Path( TEST_OUT_DIR ).exists() + assert Path( TEST_OUT_DIR + f'{YYYYMMDD}.{SOURCE}.nc' ).exists() + assert Path( WORK_DIR ).exists() + assert Path( WORK_DIR + f'{YYYYMMDD}.{SOURCE}.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'basin_codes.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile2.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile3.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile4.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile5.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile6.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile1XC96_mosaic_tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile1Xocean_mosaic_tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile2XC96_mosaic_tile2.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile2Xocean_mosaic_tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile3XC96_mosaic_tile3.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile3Xocean_mosaic_tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile4XC96_mosaic_tile4.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile4Xocean_mosaic_tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile5XC96_mosaic_tile5.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile5Xocean_mosaic_tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile6XC96_mosaic_tile6.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic_tile6Xocean_mosaic_tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'hash.md5' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'land_mask_tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'land_mask_tile2.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'land_mask_tile3.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'land_mask_tile4.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'land_mask_tile5.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'land_mask_tile6.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'mosaic.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'ocean_hgrid.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'ocean_mask.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'ocean_mosaic.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'ocean_static.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'ocean_topog.nc' ).exists() + out, err = capfd.readouterr() + assert True + + + + +#@pytest.mark.skip(reason='debug') +def test_success_no_tar_grid_spec_regrid_xy(capfd): + """ + checks for success of regrid_xy with rose app-app run + """ + # for the time being, still a little dependent on rose for configuration value passing + if Path(os.getcwd()+'/rose-app-run.conf').exists(): + Path(os.getcwd()+'/rose-app-run.conf').unlink() + + with open(os.getcwd()+'/rose-app-run.conf','a',encoding='utf-8') as rose_app_run_config: + rose_app_run_config.write( '[command]\n' ) + rose_app_run_config.write( 'default=regrid-xy\n' ) + rose_app_run_config.write( '\n' ) + rose_app_run_config.write( f'[{COMPONENT}]\n' ) + rose_app_run_config.write( f'sources={SOURCE}\n' ) + rose_app_run_config.write( f'inputGrid={INPUT_GRID}\n' ) + rose_app_run_config.write( f'inputRealm={INPUT_REALM}\n' ) + rose_app_run_config.write( f'interpMethod={INTERP_METHOD}\n' ) + rose_app_run_config.write( f'outputGridLon={NLON}\n' ) + rose_app_run_config.write( f'outputGridLat={NLAT}\n' ) + rose_app_run_config.write( '\n' ) + assert Path('./rose-app-run.conf').exists() + + rgxy_returncode = rgxy.regrid_xy( + input_dir = WORK_YYYYMMDD_DIR, + output_dir = TEST_OUT_DIR, + begin = f'{YYYYMMDD}T000000', + tmp_dir = TEST_DIR, + remap_dir = REMAP_DIR, + source = SOURCE, + grid_spec = GOLD_GRID_SPEC_NO_TAR, + def_xy_interp = f'"{NLON},{NLAT}"' + ) + + # uhm.... + assert rgxy_returncode == 0 + assert Path( REMAP_DIR + \ + f'{INPUT_GRID}/{INPUT_REALM}/96-by-96/{INTERP_METHOD}/' + \ + f'fregrid_remap_file_{NLON}_by_{NLAT}.nc' \ + ).exists() + assert Path( TEST_OUT_DIR ).exists() + assert Path( TEST_OUT_DIR + f'{YYYYMMDD}.{SOURCE}.nc' ).exists() + assert Path( WORK_DIR ).exists() + assert Path( WORK_DIR + f'{YYYYMMDD}.{SOURCE}.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR ).exists() + + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile1.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile2.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile3.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile4.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile5.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_grid.tile6.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'C96_mosaic.nc' ).exists() + assert Path( WORK_YYYYMMDD_DIR + 'mosaic.nc' ).exists() + out, err = capfd.readouterr() + + + + +@pytest.mark.skip(reason='debug') +def test_failure_wrong_datetime_regrid_xy(capfd): + """ + checks for failure of regrid_xy with rose app-run when fed an + invalid date for begin + """ + # for the time being, still a little dependent on rose for configuration value passing + if Path(os.getcwd()+'/rose-app-run.conf').exists(): + Path(os.getcwd()+'/rose-app-run.conf').unlink() + + with open(os.getcwd()+'/rose-app-run.conf','a',encoding='utf-8') as rose_app_run_config: + rose_app_run_config.write( '[command]\n' ) + rose_app_run_config.write( 'default=regrid-xy\n' ) + rose_app_run_config.write( '\n' ) + rose_app_run_config.write( f'[{COMPONENT}]\n' ) + rose_app_run_config.write( f'sources={SOURCE}\n' ) + rose_app_run_config.write( f'inputGrid={INPUT_GRID}\n' ) + rose_app_run_config.write( f'inputRealm={INPUT_REALM}\n' ) + rose_app_run_config.write( f'interpMethod={INTERP_METHOD}\n' ) + rose_app_run_config.write( f'outputGridLon={NLON}\n' ) + rose_app_run_config.write( f'outputGridLat={NLAT}\n' ) + rose_app_run_config.write( '\n' ) + assert Path('./rose-app-run.conf').exists() + + try: + rgxy_returncode = rgxy.regrid_xy( + input_dir = WORK_YYYYMMDD_DIR, + output_dir = TEST_OUT_DIR, + begin = '99999999T999999', + tmp_dir = TEST_DIR, + remap_dir = REMAP_DIR, + source = SOURCE, + grid_spec = GOLD_GRID_SPEC, + def_xy_interp = f'"{NLON},{NLAT}"' + ) + except: + # yay good job + assert True + + out, err = capfd.readouterr() + + + +#@pytest.mark.skip(reason='debug') + + +def test_nccmp1_regrid_xy(capfd): + """ + This test compares the output of make_hgrid and fregrid, which are expected to be identical + """ + fregrid_remap_file=f'fregrid_remap_file_{NLON}_by_{NLAT}.nc' + + nccmp_arg1 = REMAP_DIR + INPUT_GRID + '/' + INPUT_REALM + '/' + \ + SOURCES_XY + '/' + INTERP_METHOD + '/' + fregrid_remap_file + nccmp_arg2 = REMAP_TEST_DIR + fregrid_remap_file + nccmp= [ 'nccmp', '-m', '--force', nccmp_arg1, nccmp_arg2 ] + print (' '.join(nccmp)) + sp = subprocess.run( nccmp, check = True) + assert sp.returncode == 0 + out, err = capfd.readouterr() + + +def test_nccmp2_regrid_xy(capfd): + """ + This test compares the regridded source file output(s), which are expected to be identical + """ + nccmp_arg1 = TEST_OUT_DIR + f'{YYYYMMDD}.{SOURCE}.nc' + nccmp_arg2 = ALL_TEST_OUT_DIR + f'{YYYYMMDD}.{SOURCE}.nc' + nccmp= [ 'nccmp', '-m', '--force', nccmp_arg1, nccmp_arg2 ] + print (' '.join(nccmp)) + sp = subprocess.run( nccmp, check = True) + assert sp.returncode == 0 + out, err = capfd.readouterr() + + +@pytest.mark.skip(reason='TODO') +def test_regrid_one_for_two_comps(capfd): + """ + this test will compare regridding settings for a single source file ref'd in two + diff components and regrid that source file twice if the settings are different, + and only once if the settings are the same. + """ + assert False + out, err = capfd.readouterr() diff --git a/lib/fre/catalog/__init__.py b/lib/fre/catalog/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/catalog/frecatalog.py b/lib/fre/catalog/frecatalog.py new file mode 100644 index 00000000..58149e5e --- /dev/null +++ b/lib/fre/catalog/frecatalog.py @@ -0,0 +1,62 @@ +''' +entry point for fre catalog subcommands +''' + +import click +#import catalogbuilder +from catalogbuilder.scripts import gen_intake_gfdl +from catalogbuilder.scripts import test_catalog +from catalogbuilder.scripts import combine_cats + + +@click.group(help=click.style(" - access fre catalog subcommands", fg=(64,94,213))) +def catalog_cli(): + ''' entry point for click into fre catalog cli calls ''' + + + +@catalog_cli.command() +#TODO arguments dont have help message. So consider changing arguments to options? +@click.argument('input_path', required = False, nargs = 1) +#, help = 'The directory path with the datasets to be cataloged. E.g a GFDL PP path till /pp') +@click.argument('output_path', required = False, nargs = 1) +#, help = 'Specify output filename suffix only. e.g. catalog') +@click.option('--config', required = False, type = click.Path(exists = True), nargs = 1, + help = 'Path to your yaml config, Use the config_template in intakebuilder repo') +@click.option('--filter_realm', nargs = 1) +@click.option('--filter_freq', nargs = 1) +@click.option('--filter_chunk', nargs = 1) +@click.option('--overwrite', is_flag = True, default = False) +@click.option('--append', is_flag = True, default = False) +@click.option('--slow', is_flag = True, default = False, + help = "Open NetCDF files to retrieve additional vocabulary (standard_name and intrafile static variables") +@click.pass_context +def builder(context, input_path = None, output_path = None, config = None, filter_realm = None, + filter_freq = None, filter_chunk = None, overwrite = False, append = False, slow = False): + # pylint: disable=unused-argument + """ - Generate .csv and .json files for catalog """ + context.forward(gen_intake_gfdl.create_catalog_cli) + +@catalog_cli.command() +@click.argument('json_path', nargs = 1 , required = True) +@click.argument('json_template_path', nargs = 1 , required = False) +@click.option('-tf', '--test-failure', is_flag=True, default = False, + help="Errors are only printed. Program will not exit.") +@click.pass_context +def validate(context, json_path, json_template_path, test_failure): + # pylint: disable=unused-argument + """ - Validate a catalog against catalog schema """ + context.forward(test_catalog.main) + +@catalog_cli.command() +@click.option('--input', required = True, multiple = True, + help = 'Catalog json files to be merged, space-separated') +@click.option('--output', required = True, nargs = 1, + help = 'Merged catalog') +@click.pass_context +def merge(context, input, output): + """ - Merge two or more more catalogs into one """ + context.invoke(combine_cats.combine_cats, inputfiles=input, output_path=output) + +if __name__ == "__main__": + catalog_cli() diff --git a/lib/fre/catalog/tests/__init__.py b/lib/fre/catalog/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/catalog/tests/test_fre_catalog.py b/lib/fre/catalog/tests/test_fre_catalog.py new file mode 100644 index 00000000..7bc36637 --- /dev/null +++ b/lib/fre/catalog/tests/test_fre_catalog.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 + +def test_fre_catalog_import(): + from fre import catalog + assert catalog is not None diff --git a/lib/fre/check/__init__.py b/lib/fre/check/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/check/frecheck.py b/lib/fre/check/frecheck.py new file mode 100644 index 00000000..9640d138 --- /dev/null +++ b/lib/fre/check/frecheck.py @@ -0,0 +1,20 @@ +''' fre check ''' + +import click + +from .frecheckexample import check_test_function + +@click.group(help=click.style(" - access fre check subcommands", fg=(162,91,232))) +def check_cli(): + ''' entry point to fre check click commands ''' + +@check_cli.command() +@click.option('--uppercase', '-u', is_flag=True, help = 'Print statement in uppercase.') +@click.pass_context +def function(context, uppercase): + # pylint: disable=unused-argument + """ - Execute fre check test """ + context.forward(check_test_function) + +if __name__ == "__main__": + check_cli() diff --git a/lib/fre/check/frecheckexample.py b/lib/fre/check/frecheckexample.py new file mode 100644 index 00000000..daac462a --- /dev/null +++ b/lib/fre/check/frecheckexample.py @@ -0,0 +1,18 @@ +""" +experimentation file for integrating one file's functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL +""" + +import click + +@click.command() +def check_test_function(uppercase=None): + """Execute fre list testfunction2.""" + statement = "testingtestingtestingtesting" + if uppercase: + statement = statement.upper() + click.echo(statement) + +if __name__ == '__main__': + check_test_function() diff --git a/lib/fre/cmor/README.md b/lib/fre/cmor/README.md new file mode 100644 index 00000000..865c1ee7 --- /dev/null +++ b/lib/fre/cmor/README.md @@ -0,0 +1,102 @@ +UNDER CONSTRUCTION: old usage notes at the top of `cmor_mixer.py`, re-rigged for markdown and CMIP7. + +at PP/AN, module load the latest `fre-cli` that's been pushed to the main branch: +``` +> module load fre/canopy +> which fre + /home/fms/local/opt/fre-commands/canopy/bin/fre +``` + +alternatively, with access to conda: +``` +> conda activate /nbhome/fms/conda/envs/fre-cli +> which fre + /nbhome/fms/conda/envs/fre-cli/bin/fre +``` + +this subtool's help, and command-specific `run` help: +``` +> fre cmor --help + Usage: fre cmor [OPTIONS] COMMAND [ARGS]... + + - access fre cmor subcommands + + Options: + --help Show this message and exit. + + Commands: + run Rewrite climate model output + + +# subtool command-specific help, e.g. for run +> fre cmor run --help +Usage: fre cmor run [OPTIONS] + + Rewrite climate model output files with CMIP-compliant metadata for down- + stream publishing + +Options: + -d, --indir TEXT directory containing netCDF files. keys specified + in json_var_list are local variable names used for + targeting specific files in this directory + [required] + -l, --varlist TEXT path pointing to a json file containing directory + of key/value pairs. the keys are the 'local' names + used in the filename, and the values pointed to by + those keys are strings representing the name of the + variable contained in targeted files. the key and + value are often the same, but it is not required. + [required] + -r, --table_config TEXT json file containing CMIP-compliant per- + variable/metadata for specific MIP table. The MIP + table can generally be identified by the specific + filename (e.g. 'Omon') [required] + -p, --exp_config TEXT json file containing metadata dictionary for + CMORization. this metadata is effectively appended + to the final output file's header [required] + -o, --outdir TEXT directory root that will contain the full output + and output directory structure generated by the + cmor module upon request. [required] + -v, --opt_var_name TEXT optional, specify a variable name to specifically + process only filenames matching that variable name. + I.e., this string help target local_vars, not + target_vars. + --help Show this message and exit. +``` + + +the tool requires configuration in the form of variable tables and conventions to work appropriately +clone the following repository and list the following directory contents to get a sense of what +the code needs from you to work. a few examples shown in the output below. the CV file is of particular interest/necessity +``` +> git clone https://github.com/PCMDI/cmip6-cmor-tables.git fre/tests/test_files/cmip6-cmor-tables +> ls fre/tests/test_files/cmip6-cmor-tables/Tables +... + CMIP6_CV.json + CMIP6_formula_terms.json + CMIP6_grids.json + CMIP6_coordinate.json + CMIP6_input_example.json +... + CMIP6_3hr.json +... + CMIP6_Efx.json +... + CMIP6_IyrGre.json +... +``` + + +Simple example call(s) using fre-cli in the root directory of this repository note the line-continuation character at the end for readability, +you may wish to avoid it when copy/pasting. +``` +> fre cmor run \ + -d fre/tests/test_files/ocean_sos_var_file \ + -l fre/tests/test_files/varlist \ + -r fre/tests/test_files/cmip6-cmor-tables/Tables/CMIP6_Omon.json \ + -p fre/tests/test_files/CMOR_input_example.json \ + -o fre/tests/test_files/outdir +``` + + + diff --git a/lib/fre/cmor/__init__.py b/lib/fre/cmor/__init__.py new file mode 100644 index 00000000..465ad02c --- /dev/null +++ b/lib/fre/cmor/__init__.py @@ -0,0 +1,2 @@ +''' for fre.cmor imports ''' +from .cmor_mixer import cmor_run_subtool diff --git a/lib/fre/cmor/cmor_mixer.py b/lib/fre/cmor/cmor_mixer.py new file mode 100644 index 00000000..0f4ef243 --- /dev/null +++ b/lib/fre/cmor/cmor_mixer.py @@ -0,0 +1,652 @@ +''' +python module housing the metadata processing routines utilizing the cmor module, in addition to +click API entry points +see README.md for additional information on `fre cmor run` (cmor_mixer.py) usage +''' + +import os +import glob +import json +import subprocess +from pathlib import Path + +import netCDF4 as nc +import click +import cmor + +# ----- \start consts +DEBUG_MODE_RUN_ONE = True + +# ----- \end consts + +### ------ helper functions ------ ### +def copy_nc(in_nc, out_nc): + ''' + copy target input netcdf file in_nc to target out_nc. I have to think this is not a trivial copy + operation, as if it were, using shutil's copy would be sufficient. accepts two arguments + in_nc: string, path to an input netcdf file we wish to copy + out_nc: string, an output path to copy the targeted input netcdf file to + ''' + print(f'(copy_nc) in_nc: {in_nc}\n' + f' out_nc: {out_nc}') + + # input file + dsin = nc.Dataset(in_nc) + + # output file, same exact data_model as input file. + # note- totally infuriating... + # the correct value for the format arg is netCDF4.Dataset.data_model + # and NOT netCDF4.Dataset.disk_format + dsout = nc.Dataset(out_nc, "w", + format = dsin.data_model) + + #Copy dimensions + for dname, the_dim in dsin.dimensions.items(): + dsout.createDimension( dname, + len(the_dim) if not the_dim.isunlimited() else None ) + + # Copy variables and attributes + for v_name, varin in dsin.variables.items(): + out_var = dsout.createVariable(v_name, varin.datatype, varin.dimensions) + out_var.setncatts({k: varin.getncattr(k) for k in varin.ncattrs()}) + out_var[:] = varin[:] + dsout.setncatts({a:dsin.getncattr(a) for a in dsin.ncattrs()}) + + # close up + dsin.close() + dsout.close() + + +def get_var_filenames(indir, var_filenames = None, local_var = None): + ''' + appends files ending in .nc located within indir to list var_filenames accepts three arguments + indir: string, representing a path to a directory containing files ending in .nc extension + var_filenames: list of strings, empty or non-empty, to append discovered filenames to. the + object pointed to by the reference var_filenames is manipulated, and so need + not be returned. + local_var: string, optional, if not None, will be used for ruling out filename targets + ''' + if var_filenames is None: + var_filenames = [] + filename_pattern='.nc' if local_var is None else f'.{local_var}.nc' + print(f'(get_var_filenames) filename_pattern={filename_pattern}') + var_filenames_all=glob.glob(f'{indir}/*{filename_pattern}') + print(f'(get_var_filenames) var_filenames_all={var_filenames_all}') + for var_file in var_filenames_all: + var_filenames.append( Path(var_file).name ) + print(f"(get_var_filenames) var_filenames = {var_filenames}") + if len(var_filenames) < 1: + raise ValueError(f'target directory had no files with .nc ending. indir =\n {indir}') + var_filenames.sort() + + +def get_iso_datetimes(var_filenames, iso_datetime_arr = None): + ''' + appends iso datetime strings found amongst filenames to iso_datetime_arr. + var_filenames: non-empty list of strings representing filenames. some of which presumably + contain datetime strings + iso_datetime_arr: list of strings, empty or non-empty, representing datetimes found in + var_filenames entries. the objet pointed to by the reference + iso_datetime_arr is manipulated, and so need-not be returned + ''' + if iso_datetime_arr is None: + iso_datetime_arr = [] + for filename in var_filenames: + iso_datetime = filename.split(".")[1] + if iso_datetime not in iso_datetime_arr: + iso_datetime_arr.append( + filename.split(".")[1] ) + iso_datetime_arr.sort() + #print(f"(get_iso_datetimes) Available dates: {iso_datetime_arr}") + if len(iso_datetime_arr) < 1: + raise ValueError('(get_iso_datetimes) ERROR: iso_datetime_arr has length 0!') + +def check_dataset_for_ocean_grid(ds): + ''' + checks netCDF4.Dataset ds for ocean grid origin, and throws an error if it finds one. accepts + one argument. this function has no return. + ds: netCDF4.Dataset object containing variables with associated dimensional information. + ''' + if "xh" in list(ds.variables.keys()): + raise NotImplementedError( + "(check_dataset_for_ocean_grid) 'xh' found in var_list. ocean grid req'd but not yet unimplemented. stop.") + + +def get_vertical_dimension(ds,target_var): + ''' + determines the vertical dimensionality of target_var within netCDF4 Dataset ds. accepts two + arguments and returns an object represnting the vertical dimensions assoc with the target_var. + ds: netCDF4.Dataset object containing variables with associated dimensional information. + target_var: string, representating a variable contained within the netCDF4.Dataset ds + + ''' + vert_dim = 0 + for name, variable in ds.variables.items(): + # not the var we are looking for? move on. + if name != target_var: + continue + dims = variable.dimensions + for dim in dims: + # if it is not a vertical axis, move on. + print(f'(get_vertical_dimension) dim={dim}') + if dim == 'landuse': + continue + if not (ds[dim].axis and ds[dim].axis == "Z"): + continue + vert_dim = dim + return vert_dim + +def create_tmp_dir(outdir): + ''' + creates a tmp_dir based on targeted output directory root. returns the name of the tmp dir. + accepts one argument: + outdir: string, representing the final output directory root for the cmor modules netcdf + file output. tmp_dir will be slightly different depending on the output directory + targeted + ''' + print(f"(create_tmp_dir) outdir = {outdir}") + tmp_dir = None + if any( [ outdir == "/local2", + outdir.find("/work") != -1, + outdir.find("/net" ) != -1 ] ): + print(f'(create_tmp_dir) using /local /work /net ( tmp_dir = {outdir}/ )') + tmp_dir = str( Path("{outdir}/").resolve() ) + else: + print(f'(create_tmp_dir) NOT using /local /work /net (tmp_dir = {outdir}/tmp/ )') + tmp_dir = str( Path(f"{outdir}/tmp/").resolve() ) + try: + os.makedirs(tmp_dir, exist_ok=True) + except Exception as exc: + raise OSError('(create_tmp_dir) problem creating temp output directory. stop.') from exc + return tmp_dir + + + +### ------ BULK ROUTINES ------ ### +def rewrite_netcdf_file_var ( proj_table_vars = None, + local_var = None, + netcdf_file = None, + target_var = None, + json_exp_config = None, + json_table_config = None):#, tmp_dir = None ): + ''' + rewrite the input netcdf file nc_fl containing target_var in a CMIP-compliant manner. + accepts six arguments, all required: + proj_table_vars: json dictionary object, variable table read from json_table_config. + local_var: string, variable name used for finding files locally containing target_var, + this argument is often equal to target_var. + netcdf_file: string, representing path to intput netcdf file. + target_var: string, representing the variable name attached to the data object in the netcdf file. + json_exp_config: string, representing path to json configuration file holding metadata for appending to output + this argument is most used for making sure the right grid label is getting attached to the right output + json_table_config: string, representing path to json configuration file holding variable names for a given table. + proj_table_vars is read from this file, but both are passed anyways. + ''' + print('\n\n-------------------------- START rewrite_netcdf_file_var call -----') + print( "(rewrite_netcdf_file_var) input data: " ) + print(f" local_var = {local_var}" ) + print(f" target_var = {target_var}") + + + # open the input file + print(f"(rewrite_netcdf_file_var) opening {netcdf_file}" ) + ds = nc.Dataset(netcdf_file,'a') + + + # ocean grids are not implemented yet. + print( '(rewrite_netcdf_file_var) checking input netcdf file for oceangrid condition') + check_dataset_for_ocean_grid(ds) + + + # figure out the dimension names programmatically TODO + # Define lat and lon dimensions + # Assume input file is lat/lon grid + lat = ds["lat"][:] + lon = ds["lon"][:] + lat_bnds = ds["lat_bnds"][:] + lon_bnds = ds["lon_bnds"][:] + + ## Define time + #time = ds["time"][:] + + # read in time_coords + units + time_coords = ds["time"][:] + time_coord_units = ds["time"].units + print(f"(rewrite_netcdf_file_var) time_coord_units = {time_coord_units}") + + # read in time_bnds , if present + time_bnds = [] + try: + time_bnds = ds["time_bnds"][:] + #print(f"(rewrite_netcdf_file_var) time_bnds = {time_bnds}") + except ValueError: + print( "(rewrite_netcdf_file_var) WARNING grabbing time_bnds didnt work... moving on") + + + # read the input variable data, i believe + var = ds[target_var][:] + + # determine the vertical dimension by looping over netcdf variables + vert_dim = get_vertical_dimension(ds, target_var) + print(f"(rewrite_netcdf_file_var) Vertical dimension of {target_var}: {vert_dim}") + + # grab var_dim + var_dim = len(var.shape) + print(f"(rewrite_netcdf_file_var) var_dim = {var_dim}, local_var = {local_var}") + + # Check var_dim + if var_dim not in [3, 4]: + raise ValueError(f"var_dim == {var_dim} != 3 nor 4. stop.") + + # Check var_dim and vert_dim and assign lev if relevant. + # error if vert_dim wrong given var_dim + lev = None + if var_dim == 4: + if vert_dim not in [ "plev30", "plev19", "plev8", + "height2m", "level", "lev", "levhalf"] : + raise ValueError(f'var_dim={var_dim}, vert_dim = {vert_dim} is not supported') + lev = ds[vert_dim] + + + # now we set up the cmor module object + # initialize CMOR + cmor.setup( + netcdf_file_action = cmor.CMOR_PRESERVE, + set_verbosity = cmor.CMOR_QUIET, #default is CMOR_NORMAL + exit_control = cmor.CMOR_NORMAL, + logfile = None, + create_subdirectories = 1 + ) + + # read experiment configuration file + print(f"(rewrite_netcdf_file_var) cmor is opening: json_exp_config = {json_exp_config}") + cmor.dataset_json(json_exp_config) + + # load CMOR table + print(f"(rewrite_netcdf_file_var) cmor is opening json_table_config = {json_table_config}") + cmor.load_table(json_table_config) + + units = proj_table_vars["variable_entry"] [target_var] ["units"] + print(f"(rewrite_netcdf_file_var) units={units}") + + cmor_lat = cmor.axis("latitude", coord_vals = lat, cell_bounds = lat_bnds, units = "degrees_N") + cmor_lon = cmor.axis("longitude", coord_vals = lon, cell_bounds = lon_bnds, units = "degrees_E") + try: + print( f"(rewrite_netcdf_file_var) Executing cmor.axis('time', \n" + f" coord_vals = \n{time_coords}, \n" + f" cell_bounds = time_bnds, units = {time_coord_units}) ") + cmor_time = cmor.axis("time", coord_vals = time_coords, + cell_bounds = time_bnds, units = time_coord_units) + except ValueError as exc: + print(f"(rewrite_netcdf_file_var) WARNING exception raised... exc={exc}\n" + " cmor_time = cmor.axis('time', \n" + " coord_vals = time_coords, units = time_coord_units)") + cmor_time = cmor.axis("time", coord_vals = time_coords, units = time_coord_units) + + # initializations + save_ps = False + ps = None + ierr_ap, ierr_b = None, None + ips = None + + # set axes for 3-dim case + if var_dim == 3: + axes = [cmor_time, cmor_lat, cmor_lon] + print(f"(rewrite_netcdf_file_var) axes = {axes}") + # set axes for 4-dim case + elif var_dim == 4: + + if vert_dim in ["plev30", "plev19", "plev8", "height2m"]: + cmor_lev = cmor.axis( vert_dim, + coord_vals = lev[:], units = lev.units ) + + elif vert_dim in ["level", "lev", "levhalf"]: + # find the ps file nearby + ps_file = netcdf_file.replace(f'.{target_var}.nc', '.ps.nc') + ds_ps = nc.Dataset(ps_file) + ps = ds_ps['ps'][:].copy() + ds_ps.close() + + # assign lev_half specifics + if vert_dim == "levhalf": + cmor_lev = cmor.axis( "alternate_hybrid_sigma_half", + coord_vals = lev[:], + units = lev.units ) + ierr_ap = cmor.zfactor( zaxis_id = cmor_lev, + zfactor_name = "ap_half", + axis_ids = [cmor_lev, ], + zfactor_values = ds["ap_bnds"][:], + units = ds["ap_bnds"].units ) + ierr_b = cmor.zfactor( zaxis_id = cmor_lev, + zfactor_name = "b_half", + axis_ids = [cmor_lev, ], + zfactor_values = ds["b_bnds"][:], + units = ds["b_bnds"].units ) + else: + cmor_lev = cmor.axis( "alternate_hybrid_sigma", + coord_vals = lev[:], + units = lev.units, + cell_bounds = ds[vert_dim+"_bnds"] ) + ierr_ap = cmor.zfactor( zaxis_id = cmor_lev, + zfactor_name = "ap", + axis_ids = [cmor_lev, ], + zfactor_values = ds["ap"][:], + zfactor_bounds = ds["ap_bnds"][:], + units = ds["ap"].units ) + ierr_b = cmor.zfactor( zaxis_id = cmor_lev, + zfactor_name = "b", + axis_ids = [cmor_lev, ], + zfactor_values = ds["b"][:], + zfactor_bounds = ds["b_bnds"][:], + units = ds["b"].units ) + + print(f'(rewrite_netcdf_file_var) ierr_ap after calling cmor_zfactor: {ierr_ap}\n' + f'(rewrite_netcdf_file_var) ierr_b after calling cmor_zfactor: {ierr_b}' ) + ips = cmor.zfactor( zaxis_id = cmor_lev, + zfactor_name = "ps", + axis_ids = [cmor_time, cmor_lat, cmor_lon], + units = "Pa" ) + save_ps = True + # assign axes at end of 4-dim case + axes = [cmor_time, cmor_lev, cmor_lat, cmor_lon] + + + + + # read positive attribute and create cmor_var? can this return none? TODO + positive = proj_table_vars["variable_entry"] [target_var] ["positive"] + print(f"(rewrite_netcdf_file_var) positive = {positive}") + cmor_var = cmor.variable(target_var, units, axes, positive = positive) + + # Write the output to disk + #var = ds[target_var][:] #was this ever needed? why? + cmor.write(cmor_var, var) + if save_ps: + if any( [ ips is None, ps is None ] ): + print( '(rewrite_netcdf_file_var) WARNING: ps or ips is None!, but save_ps is True!\n' + f' ps = {ps}, ips = {ips}\n' + ' skipping ps writing!' ) + else: + cmor.write(ips, ps, store_with = cmor_var) + cmor.close(ips, file_name = True, preserve = False) + filename = cmor.close(cmor_var, file_name = True, preserve = False) + print(f"(rewrite_netcdf_file_var) returned by cmor.close: filename = {filename}") + #cmor.close() + ds.close() + + print('-------------------------- END rewrite_netcdf_file_var call -----\n\n') + return filename + + +def cmorize_target_var_files( indir = None, target_var = None, local_var = None, + iso_datetime_arr = None, name_of_set = None, + json_exp_config = None, outdir = None, + proj_table_vars = None, json_table_config = None ): + ''' processes a target directory/file + this routine is almost entirely exposed data movement before/after calling + rewrite_netcdf_file_var it is also the most hopelessly opaque routine in this entire dang macro. + this badboy right here accepts... lord help us... !!!NINE!!! arguments, NINE. + indir: string, path to target directories containing netcdf files to cmorize + target_var: string, name of variable inside the netcdf file to cmorize + local_var: string, value of the variable name in the filename, right before the .nc + extension. often identical to target_var but not always. + iso_datetime_arr: list of strings, each one a unique ISO datetime string found in targeted + netcdf filenames + name_of_set: string, representing the post-processing component (GFDL convention) of the + targeted files. + json_exp_config: see cmor_run_subtool arg desc + outdir: string, path to output directory root to move the cmor module output to, including + the whole directory structure + proj_table_vars: an opened json file object, read from json_table_config + json_table_config: see cmor_run_subtool arg desc + + ''' + print('\n\n-------------------------- START cmorize_target_var_files call -----') + print(f"(cmorize_target_var_files) local_var = {local_var} to be used for file-targeting.\n" + f" target_var = {target_var} to be used for reading the data \n" + " from the file\n" + f" outdir = {outdir}") + + + #determine a tmp dir for working on files. + tmp_dir = create_tmp_dir( outdir ) + '/' + print(f'(cmorize_target_var_files) will use tmp_dir={tmp_dir}') + + + # loop over sets of dates, each one pointing to a file + nc_fls = {} + for i, iso_datetime in enumerate(iso_datetime_arr): + + # why is nc_fls a filled list/array/object thingy here? see above line + nc_fls[i] = f"{indir}/{name_of_set}.{iso_datetime}.{local_var}.nc" + print(f"(cmorize_target_var_files) input file = {nc_fls[i]}") + if not Path(nc_fls[i]).exists(): + print ("(cmorize_target_var_files) input file(s) not found. Moving on.") + continue + + + # create a copy of the input file with local var name into the work directory + nc_file_work = f"{tmp_dir}{name_of_set}.{iso_datetime}.{local_var}.nc" + + print(f"(cmorize_target_var_files) nc_file_work = {nc_file_work}") + copy_nc( nc_fls[i], nc_file_work) + + # if the ps file exists, we'll copy it to the work directory too + nc_ps_file = nc_fls[i].replace(f'.{local_var}.nc', '.ps.nc') + nc_ps_file_work = nc_file_work.replace(f'.{local_var}.nc', '.ps.nc') + if Path(nc_ps_file).exists(): + print(f"(cmorize_target_var_files) nc_ps_file_work = {nc_ps_file_work}") + copy_nc(nc_ps_file, nc_ps_file_work) + + + # TODO think of better way to write this kind of conditional data movement... + # now we have a file in our targets, point CMOR to the configs and the input file(s) + make_cmor_write_here = None + print( Path( tmp_dir ) ) + print( Path( os.getcwd() ) ) + if Path( tmp_dir ).is_absolute(): + print(f'tmp_dir is absolute') + make_cmor_write_here = tmp_dir + elif Path( tmp_dir ).exists(): # relative to where we are + print(f'tmp_dir is relative to CWD!') + make_cmor_write_here = os.getcwd() + '/'+tmp_dir # unavoidable, cmor module FORCES write to CWD + assert make_cmor_write_here is not None + + gotta_go_back_here=os.getcwd() + try: + print(f"cd'ing to \n {make_cmor_write_here}" ) + os.chdir( make_cmor_write_here ) + except: + raise OSError(f'could not chdir to {make_cmor_write_here}') + + print ("(cmorize_target_var_files) calling rewrite_netcdf_file_var") + local_file_name = rewrite_netcdf_file_var( proj_table_vars , + local_var , + nc_file_work , + target_var , + json_exp_config , + json_table_config ) + os.chdir( gotta_go_back_here ) + + + # now that CMOR has rewritten things... we can take our post-rewriting actions + # the final output filename will be... + print(f'(cmorize_target_var_files) local_file_name={local_file_name}') + filename =f"{outdir}/{local_file_name}" + print(f"(cmorize_target_var_files) filename = {filename}") + + # the final output file directory will be... + filedir = Path(filename).parent + print(f"(cmorize_target_var_files) filedir = {filedir}") + try: + print(f'(cmorize_target_var_files) attempting to create filedir={filedir}') + os.makedirs(filedir) + except FileExistsError: + print(f'(cmorize_target_var_files) WARNING: directory {filedir} already exists!') + + # hmm.... this is making issues for pytest + mv_cmd = f"mv {tmp_dir}/{local_file_name} {filedir}" + print(f"(cmorize_target_var_files) moving files...\n {mv_cmd}") + subprocess.run(mv_cmd, shell = True, check = True) + + # ------ refactor this into function? #TODO + # ------ what is the use case for this logic really?? + filename_no_nc = filename[:filename.rfind(".nc")] + chunk_str = filename_no_nc[-6:] + if not chunk_str.isdigit(): + print(f'(cmorize_target_var_files) WARNING: chunk_str is not a digit: ' + f'chunk_str = {chunk_str}') + filename_corr = "{filename[:filename.rfind('.nc')]}_{iso_datetime}.nc" + mv_cmd = f"mv {filename} {filename_corr}" + print(f"(cmorize_target_var_files) moving files, strange chunkstr logic...\n {mv_cmd}") + subprocess.run(mv_cmd, shell = True, check = True) + # ------ end refactor this into function? + + # delete files in work dirs + if Path(nc_file_work).exists(): + Path(nc_file_work).unlink() + + if Path(nc_ps_file_work).exists(): + Path(nc_ps_file_work).unlink() + + if DEBUG_MODE_RUN_ONE: + print(f'WARNING: DEBUG_MODE_RUN_ONE is True!!!!') + print(f'WARNING: done processing one file!!!') + break + + + + +def cmor_run_subtool( indir = None, + json_var_list = None, + json_table_config = None, + json_exp_config = None , + outdir = None, opt_var_name = None + ): + ''' + primary steering function for the cmor_mixer tool, i.e essentially main. Accepts six args: + indir: string, directory containing netCDF files. keys specified in json_var_list are local + variable names used for targeting specific files + json_var_list: string, path pointing to a json file containing directory of key/value + pairs. the keys are the "local" names used in the filename, and the + values pointed to by those keys are strings representing the name of the + variable contained in targeted files. the key and value are often the same, + but it is not required. + json_table_config: json file containing CMIP-compliant per-variable/metadata for specific + MIP table. The MIP table can generally be identified by the specific + filename (e.g. "Omon") + json_exp_config: json file containing metadata dictionary for CMORization. this metadata is effectively + appended to the final output file's header + outdir: string, directory root that will contain the full output and output directory + structure generated by the cmor module upon request. + opt_var_name: string, optional, specify a variable name to specifically process only filenames matching + that variable name. I.e., this string help target local_vars, not target_vars. + ''' + # check req'd inputs + if None in [indir, json_var_list, json_table_config, json_exp_config, outdir]: + raise ValueError(f'(cmor_run_subtool) all input arguments except opt_var_name are required!\n' + ' [indir, json_var_list, json_table_config, json_exp_config, outdir] = \n' + f' [{indir}, {json_var_list}, {json_table_config}, ' + ' {json_exp_config}, {outdir}]' ) + + # open CMOR table config file + print( '(cmor_run_subtool) getting table variables from json_table_config = \n' + f' {json_table_config}' ) + try: + with open( json_table_config, "r", encoding = "utf-8") as table_config_file: + proj_table_vars=json.load(table_config_file) + + except Exception as exc: + raise FileNotFoundError( + f'ERROR: json_table_config file cannot be opened.\n' + f' json_table_config = {json_table_config}' ) from exc + + # now resolve the json_table_config path after confirming it can be open + json_table_config= str( Path(json_table_config).resolve() ) + + # open input variable list + print('(cmor_run_subtool) opening variable list json_var_list') + try: + with open( json_var_list, "r", encoding = "utf-8" ) as var_list_file: + var_list = json.load( var_list_file ) + + except Exception as exc: + raise FileNotFoundError( + f'ERROR: json_var_list file cannot be opened.\n' + f' json_var_list = {json_var_list}' ) from exc + + # make sure the exp config exists too while we're at it... + if Path(json_exp_config).exists(): # if so, resolve to absolute path + json_exp_config = str( Path( json_exp_config).resolve() ) + else: + raise FileNotFoundError( + f'ERROR: json_exp_config file cannot be opened.\n' + f' json_exp_config = {json_exp_config}' ) + + # loop over entries in the json_var_list, read into var_list + for local_var in var_list: + + # if its not in the table configurations variable_entry list, skip + if var_list[local_var] not in proj_table_vars["variable_entry"]: + print(f"(cmor_run_subtool) WARNING: skipping local_var = {local_var} /\n" + f" target_var = {var_list[local_var]}\n" + " ... target_var not found in CMOR variable group") + continue + + if all( [ opt_var_name is not None, + local_var != opt_var_name ] ): + print(f'(cmor_run_subtool) WARNING: skipping local_var={local_var} as it is not equal\n' + ' to the opt_var_name argument.') + continue + + # it is in there, get the name of the data inside the netcdf file. + target_var=var_list[local_var] # often equiv to local_var but not necessarily. + if local_var != target_var: + print(f'(cmor_run_subtool) WARNING: local_var == {local_var} \n' + f' != {target_var} == target_var\n' + f' i am expecting {local_var} to be in the filename, and i expect the variable\n' + f' in that file to be {target_var}') + + + # examine input directory to obtain a list of input file targets + var_filenames = [] + get_var_filenames(indir, var_filenames, local_var) + print(f"(cmor_run_subtool) found filenames = \n {var_filenames}") + + # examine input files to obtain target date ranges + iso_datetime_arr = [] + get_iso_datetimes(var_filenames, iso_datetime_arr) + print(f"(cmor_run_subtool) found iso datetimes = \n {iso_datetime_arr}") + + # name_of_set == component label... + # which is not relevant for CMOR/CMIP... or is it? + name_of_set = var_filenames[0].split(".")[0] + print(f"(cmor_run_subtool) setting name_of_set = {name_of_set}") + + + + print(f'(cmor_run_subtool) ..............beginning CMORization for {local_var}/\n' + f' {target_var}..........') + cmorize_target_var_files( + indir, target_var, local_var, iso_datetime_arr, # OK + name_of_set, json_exp_config, + outdir, + proj_table_vars, json_table_config # a little redundant + ) + + if DEBUG_MODE_RUN_ONE: + print(f'WARNING: DEBUG_MODE_RUN_ONE is True. breaking var_list loop') + break + return 0 + + +@click.command() +def _cmor_run_subtool(indir = None, + json_var_list = None, json_table_config = None, json_exp_config = None, + outdir = None, opt_var_name = None): + ''' entry point to fre cmor run for click. see cmor_run_subtool for argument descriptions.''' + return cmor_run_subtool(indir, json_var_list, json_table_config, json_exp_config, outdir, opt_var_name) + + +if __name__ == '__main__': + cmor_run_subtool() diff --git a/lib/fre/cmor/frecmor.py b/lib/fre/cmor/frecmor.py new file mode 100644 index 00000000..e882186a --- /dev/null +++ b/lib/fre/cmor/frecmor.py @@ -0,0 +1,66 @@ +''' fre cmor ''' + +import click + +from .cmor_mixer import _cmor_run_subtool + +@click.group(help=click.style(" - access fre cmor subcommands", fg=(232,91,204))) +def cmor_cli(): + ''' entry point to fre cmor click commands ''' + +@cmor_cli.command() +@click.option("-d", "--indir", + type=str, + help="directory containing netCDF files. keys specified in json_var_list are local " + \ + "variable names used for targeting specific files in this directory", + required=True) +@click.option("-l", "--varlist", + type=str, + help="path pointing to a json file containing directory of key/value pairs. " + \ + "the keys are the \'local\' names used in the filename, and the values " + \ + "pointed to by those keys are strings representing the name of the variable " + \ + "contained in targeted files. the key and value are often the same, " + \ + "but it is not required.", + required=True) +@click.option("-r", "--table_config", + type=str, + help="json file containing CMIP-compliant per-variable/metadata for specific " + \ + "MIP table. The MIP table can generally be identified by the specific " + \ + "filename (e.g. \'Omon\')", + required=True) +@click.option("-p", "--exp_config", + type=str, + help="json file containing metadata dictionary for CMORization. this metadata is " + \ + "effectively appended to the final output file's header", + required=True) +@click.option("-o", "--outdir", + type=str, + help="directory root that will contain the full output and output directory " + \ + "structure generated by the cmor module upon request.", + required=True) +@click.option('-v', "--opt_var_name", + type = str, + help="optional, specify a variable name to specifically process only filenames " + \ + "matching that variable name. I.e., this string help target local_vars, not " + \ + "target_vars.", + required=False) +@click.pass_context +def run(context, indir, varlist, table_config, exp_config, outdir, opt_var_name): + # pylint: disable=unused-argument + """ + Rewrite climate model output files with CMIP-compliant metadata for down-stream publishing + """ + context.invoke( + _cmor_run_subtool, + indir = indir, + json_var_list = varlist, + json_table_config = table_config, + json_exp_config = exp_config, + outdir = outdir, + opt_var_name = opt_var_name + ) + # context.forward( + # _cmor_run_subtool() ) + +if __name__ == "__main__": + cmor_cli() diff --git a/lib/fre/cmor/tests/__init__.py b/lib/fre/cmor/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/cmor/tests/test_cmor_run_subtool.py b/lib/fre/cmor/tests/test_cmor_run_subtool.py new file mode 100644 index 00000000..043b5705 --- /dev/null +++ b/lib/fre/cmor/tests/test_cmor_run_subtool.py @@ -0,0 +1,249 @@ +''' tests for fre.cmor.cmor_run_subtool ''' +import subprocess +import shutil +from pathlib import Path +from datetime import date + +import git + +import fre + +# where are we? we're running pytest from the base directory of this repo +ROOTDIR = 'fre/tests/test_files' + +# setup- cmip/cmor variable table(s) +CLONE_CMIP_TABLE_URL = \ + 'https://github.com/PCMDI/cmip6-cmor-tables.git' +CLONE_REPO_PATH = \ + f'{ROOTDIR}/cmip6-cmor-tables' +TABLE_CONFIG = \ + f'{CLONE_REPO_PATH}/Tables/CMIP6_Omon.json' + +def test_setup_cmor_cmip_table_repo(): + ''' setup routine, if it doesnt exist, clone the repo holding CMOR/CMIP6 tables ''' + if Path(TABLE_CONFIG).exists(): + pass + else: + git.Repo.clone_from( + CLONE_CMIP_TABLE_URL, + CLONE_REPO_PATH ) + assert Path(TABLE_CONFIG).exists() + +# explicit inputs to tool +INDIR = f'{ROOTDIR}/ocean_sos_var_file' +VARLIST = f'{ROOTDIR}/varlist' +EXP_CONFIG = f'{ROOTDIR}/CMOR_input_example.json' +OUTDIR = f'{ROOTDIR}/outdir' +TMPDIR = f'{OUTDIR}/tmp' + +# determined by cmor_run_subtool +YYYYMMDD = date.today().strftime('%Y%m%d') +CMOR_CREATES_DIR = \ + 'CMIP6/CMIP6/ISMIP6/PCMDI/PCMDI-test-1-0/piControl-withism/r3i1p1f1/Omon/sos/gn' +FULL_OUTPUTDIR = \ + f"{OUTDIR}/{CMOR_CREATES_DIR}/v{YYYYMMDD}" +FULL_OUTPUTFILE = \ +f"{FULL_OUTPUTDIR}/sos_Omon_PCMDI-test-1-0_piControl-withism_r3i1p1f1_gn_199307-199807.nc" + +# FYI but helpful for tests +FILENAME = 'ocean_monthly_1x1deg.199301-199712.sos.nc' # unneeded, this is mostly for reference +FULL_INPUTFILE=f"{INDIR}/{FILENAME}" + +def test_setup_fre_cmor_run_subtool(capfd): + ''' checks for outputfile from prev pytest runs, removes it if it's present. + this routine also checks to make sure the desired input file is present''' + if Path(FULL_OUTPUTFILE).exists(): + Path(FULL_OUTPUTFILE).unlink() + if Path(OUTDIR).exists(): + shutil.rmtree(OUTDIR) + assert not any ( [ Path(FULL_OUTPUTFILE).exists(), + Path(OUTDIR).exists() ] ) + assert Path(FULL_INPUTFILE).exists() + _out, _err = capfd.readouterr() + +def test_fre_cmor_run_subtool_case1(capfd): + ''' fre cmor run, test-use case ''' + + #debug + #print( + # f"fre.cmor.cmor_run_subtool(" + # f"\'{INDIR}\'," + # f"\'{VARLIST}\'," + # f"\'{TABLE_CONFIG}\'," + # f"\'{EXP_CONFIG}\'," + # f"\'{OUTDIR}\'" + # ")" + #) + + # test call, where meat of the workload gets done + fre.cmor.cmor_run_subtool( + indir = INDIR, + json_var_list = VARLIST, + json_table_config = TABLE_CONFIG, + json_exp_config = EXP_CONFIG, + outdir = OUTDIR + ) + + assert all( [ Path(FULL_OUTPUTFILE).exists(), + Path(FULL_INPUTFILE).exists() ] ) + _out, _err = capfd.readouterr() + +def test_fre_cmor_run_subtool_case1_output_compare_data(capfd): + ''' I/O data-only comparison of test case1 ''' + print(f'FULL_OUTPUTFILE={FULL_OUTPUTFILE}') + print(f'FULL_INPUTFILE={FULL_INPUTFILE}') + + nccmp_cmd= [ "nccmp", "-f", "-d", + f"{FULL_INPUTFILE}", + f"{FULL_OUTPUTFILE}" ] + print(f"via subprocess, running {' '.join(nccmp_cmd)}") + result = subprocess.run( ' '.join(nccmp_cmd), + shell=True, + check=False, + capture_output=True + ) + # err_list has length two if end in newline + err_list = result.stderr.decode().split('\n') + expected_err = \ + "DIFFER : FILE FORMATS : NC_FORMAT_64BIT <> NC_FORMAT_NETCDF4_CLASSIC" + assert all( [result.returncode == 1, + len(err_list)==2, + '' in err_list, + expected_err in err_list ] ) + _out, _err = capfd.readouterr() + +def test_fre_cmor_run_subtool_case1_output_compare_metadata(capfd): + ''' I/O metadata-only comparison of test case1 ''' + print(f'FULL_OUTPUTFILE={FULL_OUTPUTFILE}') + print(f'FULL_INPUTFILE={FULL_INPUTFILE}') + + nccmp_cmd= [ "nccmp", "-f", "-m", "-g", + f"{FULL_INPUTFILE}", + f"{FULL_OUTPUTFILE}" ] + print(f"via subprocess, running {' '.join(nccmp_cmd)}") + result = subprocess.run( ' '.join(nccmp_cmd), + shell=True, + check=False + ) + + assert result.returncode == 1 + _out, _err = capfd.readouterr() + + +# FYI, but again, helpful for tests +FILENAME_DIFF = \ + 'ocean_monthly_1x1deg.199301-199712.sosV2.nc' +FULL_INPUTFILE_DIFF = \ + f"{INDIR}/{FILENAME_DIFF}" +VARLIST_DIFF = \ + f'{ROOTDIR}/varlist_local_target_vars_differ' +def test_setup_fre_cmor_run_subtool_case2(capfd): + ''' make a copy of the input file to the slightly different name. + checks for outputfile from prev pytest runs, removes it if it's present. + this routine also checks to make sure the desired input file is present''' + if Path(FULL_OUTPUTFILE).exists(): + Path(FULL_OUTPUTFILE).unlink() + assert not Path(FULL_OUTPUTFILE).exists() + + if Path(OUTDIR+'/CMIP6').exists(): + shutil.rmtree(OUTDIR+'/CMIP6') + assert not Path(OUTDIR+'/CMIP6').exists() + + + # VERY ANNOYING !!! FYI WARNING TODO + if Path(TMPDIR).exists(): + try: + shutil.rmtree(TMPDIR) + except OSError as exc: + print(f'WARNING: TMPDIR={TMPDIR} could not be removed.') + print( ' this does not matter that much, but is unfortunate.') + print( ' supicion: something the cmor module is using is not being closed') + + #assert not Path(TMPDIR).exists() # VERY ANNOYING !!! FYI WARNING TODO + + # VERY ANNOYING !!! FYI WARNING TODO + if Path(OUTDIR).exists(): + try: + shutil.rmtree(OUTDIR) + except OSError as exc: + print(f'WARNING: OUTDIR={OUTDIR} could not be removed.') + print( ' this does not matter that much, but is unfortunate.') + print( ' supicion: something the cmor module is using is not being closed') + + #assert not Path(OUTDIR).exists() # VERY ANNOYING !!! FYI WARNING TODO + + # make a copy of the usual test file. + if not Path(FULL_INPUTFILE_DIFF).exists(): + shutil.copy( + Path(FULL_INPUTFILE), + Path(FULL_INPUTFILE_DIFF) ) + assert Path(FULL_INPUTFILE_DIFF).exists() + _out, _err = capfd.readouterr() + +def test_fre_cmor_run_subtool_case2(capfd): + ''' fre cmor run, test-use case2 ''' + + #debug + #print( + # f"fre.cmor.cmor_run_subtool(" + # f"\'{INDIR}\'," + # f"\'{VARLIST_DIFF}\'," + # f"\'{TABLE_CONFIG}\'," + # f"\'{EXP_CONFIG}\'," + # f"\'{OUTDIR}\'" + # ")" + #) + + # test call, where meat of the workload gets done + fre.cmor.cmor_run_subtool( + indir = INDIR, + json_var_list = VARLIST_DIFF, + json_table_config = TABLE_CONFIG, + json_exp_config = EXP_CONFIG, + outdir = OUTDIR + ) + + # check we ran on the right input file. + assert all( [ Path(FULL_OUTPUTFILE).exists(), + Path(FULL_INPUTFILE_DIFF).exists() ] ) + _out, _err = capfd.readouterr() + +def test_fre_cmor_run_subtool_case2_output_compare_data(capfd): + ''' I/O data-only comparison of test case2 ''' + print(f'FULL_OUTPUTFILE={FULL_OUTPUTFILE}') + print(f'FULL_INPUTFILE_DIFF={FULL_INPUTFILE_DIFF}') + + nccmp_cmd= [ "nccmp", "-f", "-d", + f"{FULL_INPUTFILE_DIFF}", + f"{FULL_OUTPUTFILE}" ] + print(f"via subprocess, running {' '.join(nccmp_cmd)}") + result = subprocess.run( ' '.join(nccmp_cmd), + shell=True, + check=False, + capture_output=True + ) + + err_list = result.stderr.decode().split('\n')#length two if end in newline + expected_err="DIFFER : FILE FORMATS : NC_FORMAT_64BIT <> NC_FORMAT_NETCDF4_CLASSIC" + assert all( [result.returncode == 1, + len(err_list)==2, + '' in err_list, + expected_err in err_list ] ) + _out, _err = capfd.readouterr() + +def test_fre_cmor_run_subtool_case2_output_compare_metadata(capfd): + ''' I/O metadata-only comparison of test case2 ''' + print(f'FULL_OUTPUTFILE={FULL_OUTPUTFILE}') + print(f'FULL_INPUTFILE_DIFF={FULL_INPUTFILE_DIFF}') + + nccmp_cmd= [ "nccmp", "-f", "-m", "-g", + f"{FULL_INPUTFILE_DIFF}", + f"{FULL_OUTPUTFILE}" ] + print(f"via subprocess, running {' '.join(nccmp_cmd)}") + result = subprocess.run( ' '.join(nccmp_cmd), + shell=True, + check=False + ) + + assert result.returncode == 1 + _out, _err = capfd.readouterr() diff --git a/lib/fre/coveragerc b/lib/fre/coveragerc new file mode 100644 index 00000000..f95c3c41 --- /dev/null +++ b/lib/fre/coveragerc @@ -0,0 +1,3 @@ +[run] +omit = + */test_*py diff --git a/lib/fre/fre.py b/lib/fre/fre.py new file mode 100644 index 00000000..9dd65eb0 --- /dev/null +++ b/lib/fre/fre.py @@ -0,0 +1,41 @@ +""" +Main host file for FRE-CLI program scripts +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL +2023-2024 +principal click group for main/fre allows for subgroup functions to +be called via this script. I.e. 'fre' is the entry point +""" + +import click +from .lazy_group import LazyGroup + +@click.group( + cls = LazyGroup, + lazy_subcommands = {"pp": ".pp.frepp.pp_cli", + "catalog": ".catalog.frecatalog.catalog_cli", + "list": ".list.frelist.list_cli", + "check": ".check.frecheck.check_cli", + "run": ".run.frerun.run_cli", + "test": ".test.fretest.test_cli", + "yamltools": ".yamltools.freyamltools.yamltools_cli", + "make": ".make.fremake.make_cli", + "app": ".app.freapp.app_cli", + "cmor": ".cmor.frecmor.cmor_cli" }, + help = click.style( + "'fre' is the main CLI click group that houses the other tool groups as lazy subcommands.", + fg='cyan') +) + +@click.version_option( + package_name = "fre-cli", + message = click.style("%(package)s | %(version)s", + fg = (155,255,172) ) +) + +def fre(): + ''' entry point function to subgroup functions ''' + + +if __name__ == '__main__': + fre() diff --git a/lib/fre/gfdl_msd_schemas/.github/CODEOWNERS b/lib/fre/gfdl_msd_schemas/.github/CODEOWNERS new file mode 100644 index 00000000..22c95973 --- /dev/null +++ b/lib/fre/gfdl_msd_schemas/.github/CODEOWNERS @@ -0,0 +1,4 @@ +* @ceblanton @J-Lentz +FMS/diag_table.yaml @uramirez8707 +FMS/data_table.yaml @J-Lentz +FMS/field_table.yaml @J-Lentz diff --git a/lib/fre/gfdl_msd_schemas/FMS/data_table.json b/lib/fre/gfdl_msd_schemas/FMS/data_table.json new file mode 100644 index 00000000..f0683374 --- /dev/null +++ b/lib/fre/gfdl_msd_schemas/FMS/data_table.json @@ -0,0 +1,114 @@ +{ + "title": "Data table schema for data_override", + "type": "object", + "properties": { + "data_table": { + "type": "array", + "items": { + "type": "object", + "properties": { + "grid_name": { + "type": "string", + "enum": ["OCN", "LND", "ATM", "ICE"] + }, + "fieldname_in_model": { + "type": "string", + "minLength": 1 + }, + "override_file": { + "type": "array", + "items": { + "type": "object", + "properties": { + "file_name": { + "type": "string", + "minLength": 1 + }, + "fieldname_in_file": { + "type": "string", + "minLength": 1 + }, + "interp_method": { + "type": "string", + "enum": ["bilinear", "bicubic", "none"] + }, + "multi_file": { + "type": "array", + "items": { + "type": "object", + "properties": { + "next_file_name": { + "type": "string", + "minLength": 1 + }, + "prev_file_name": { + "type": "string", + "minLength": 1 + } + }, + "required": ["next_file_name", "prev_file_name"], + "additionalProperties": false + } + }, + "external_weights": { + "type": "array", + "items": { + "type": "object", + "properties": { + "file_name": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "enum": ["fregrid"] + } + }, + "required": ["file_name", "source"], + "additionalProperties": false + } + } + }, + "required": ["file_name", "fieldname_in_file", "interp_method"], + "additionalProperties": false + } + }, + "factor": { + "type": "number" + }, + "subregion": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["inside_region", "outside_region"] + }, + "lon_start": { + "type": "number" + }, + "lon_end": { + "type": "number" + }, + "lat_start": { + "type": "number" + }, + "lat_end": { + "type": "number" + } + }, + "required": ["type", "lon_start", "lon_end", "lat_start", "lat_end"], + "additionalProperties": false + } + } + }, + "required": ["grid_name", "fieldname_in_model", "factor"], + "additionalProperties": false + } + } + }, + "required": ["data_table"], + "additionalProperties": false +} diff --git a/lib/fre/gfdl_msd_schemas/FMS/diag_table.json b/lib/fre/gfdl_msd_schemas/FMS/diag_table.json new file mode 100644 index 00000000..a968ca6b --- /dev/null +++ b/lib/fre/gfdl_msd_schemas/FMS/diag_table.json @@ -0,0 +1,250 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["title", "base_date"], + "additionalProperties": false, + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "base_date": { + "type": "string", + "anyOf": [ + {"enum": ["$baseDate"]}, + {"pattern": "^([0-9]*[1-9][0-9]* ){3}([0-9]+ ){2}[0-9]+$"} + ] + }, + "diag_files": { + "type": "array", + "items": { + "type": "object", + "required": ["file_name", "freq", "time_units", "unlimdim"], + "additionalProperties": false, + "properties": { + "file_name": { + "type": "string", + "minLength": 1 + }, + "freq": { + "anyOf": [ + {"type": "string"}, + {"type": "number"} + ], + "pattern": "^(-1|0|((-1|[0-9]+) +(seconds|minutes|hours|days|months|years)( *, *)?)+)$" + }, + "time_units": { + "type": "string", + "enum": ["seconds", "minutes", "hours", "days", "months", "years"] + }, + "unlimdim": { + "type": "string", + "minLength": 1 + }, + "write_file": { + "type": "boolean" + }, + "global_meta": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "additionalProperties": { + "oneOf": [ + {"type": "string"}, + {"type": "number"}, + {"type": "boolean"} + ] + } + } + }, + "sub_region": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "required": ["grid_type", "corner1", "corner2", "corner3", "corner4"], + "properties": { + "grid_type": { + "type": "string", + "enum": ["indices", "latlon"] + }, + "corner1": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "number" + } + }, + "corner2": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "number" + } + }, + "corner3": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "number" + } + }, + "corner4": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "number" + } + }, + "tile": { + "type": "number" + } + } + }, + "new_file_freq": { + "type": "string", + "pattern": "^(0*[1-9][0-9]* (seconds|minutes|hours|days|months|years), )*0*[1-9][0-9]* (seconds|minutes|hours|days|months|years)$" + }, + "start_time": { + "type": "array", + "minItems": 6, + "maxItems": 6, + "items": { + "type": "integer", + "minimum": 0 + } + }, + "file_duration": { + "type": "string", + "pattern": "^(0*[1-9][0-9]* (seconds|minutes|hours|days|months|years), )*0*[1-9][0-9]* (seconds|minutes|hours|days|months|years)$" + }, + "is_ocean": { + "type": "boolean" + }, + "kind": { + "type": "string", + "enum": ["r4", "r8", "i4", "i8"] + }, + "module": { + "type": "string", + "minLength": 1 + }, + "reduction": { + "type": "string", + "pattern": "^average$|^min$|^max$|^none$|^rms$|^sum$|^diurnal[1-9]+|^pow[1-9]+" + }, + "varlist": { + "type": "array", + "items": { + "type": "object", + "required": ["var_name"], + "additionalProperties": false, + "properties": { + "kind": { + "type": "string", + "enum": ["r4", "r8", "i4", "i8"] + }, + "module": { + "type": "string", + "minLength": 1 + }, + "reduction": { + "type": "string", + "pattern": "^average$|^min$|^max$|^none$|^rms$|^sum$|^diurnal[1-9]+|^pow[1-9]+" + }, + "var_name": { + "type": "string", + "minLength": 1 + }, + "write_var": { + "type": "boolean" + }, + "output_name": { + "type": "string", + "minLength": 1 + }, + "long_name": { + "type": "string", + "minLength": 1 + }, + "attributes": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "additionalProperties": { + "oneOf": [ + {"type": "string"}, + {"type": "number"}, + {"type": "boolean"} + ] + } + } + }, + "zbounds": { + "type": "string" + } + } + } + } + }, + "allOf": [ + { + "if": { + "required": ["kind"] + }, + "else": { + "properties": { + "varlist": { + "type": "array", + "items": { + "type": "object", + "required": ["kind"] + } + } + } + } + }, + { + "if": { + "required": ["module"] + }, + "else": { + "properties": { + "varlist": { + "type": "array", + "items": { + "type": "object", + "required": ["module"] + } + } + } + } + }, + { + "if": { + "required": ["reduction"] + }, + "else": { + "properties": { + "varlist": { + "type": "array", + "items": { + "type": "object", + "required": ["reduction"] + } + } + } + } + } + ] + } + } + } +} diff --git a/lib/fre/gfdl_msd_schemas/FMS/field_table.json b/lib/fre/gfdl_msd_schemas/FMS/field_table.json new file mode 100644 index 00000000..64e069cb --- /dev/null +++ b/lib/fre/gfdl_msd_schemas/FMS/field_table.json @@ -0,0 +1,48 @@ +{ + "title": "Field table for field_manager", + "type": "object", + "properties": { + "field_table": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field_type": { + "type": "string" + }, + "modlist": { + "type": "array", + "items": { + "type": "object", + "properties": { + "model_type": { + "type": "string", + "enum": ["coupler_mod", "atmos_mod", "ocean_mod", "land_mod", "ice_mod"] + }, + "varlist": { + "type": "array", + "items": { + "type": "object", + "properties": { + "variable": { + "type": "string" + } + }, + "required": ["variable"], + "additionalProperties": true + } + } + }, + "required": ["model_type"], + "additionalProperties": false + } + } + }, + "required": ["field_type"], + "additionalProperties": false + } + } + }, + "required": ["field_table"], + "additionalProperties": false +} diff --git a/lib/fre/gfdl_msd_schemas/FRE/fre_make.json b/lib/fre/gfdl_msd_schemas/FRE/fre_make.json new file mode 100644 index 00000000..44a153f4 --- /dev/null +++ b/lib/fre/gfdl_msd_schemas/FRE/fre_make.json @@ -0,0 +1,212 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "The name of the experiment", + "type": "string" + }, + "platform": { + "description": "The platforms listed in the command", + "type": "string" + }, + "target": { + "description": "The targets listed in the command", + "type": "string" + }, + "build": { + "type": "object", + "additionalProperties": false, + "properties": { + "compileYaml": { + "description": "Path to the compile yaml.", + "type": "string" + }, + "platformYaml": { + "description": "Path to the platform yaml.", + "type": "string" + } + } + }, + "compile": { + "description": "The source code descriptions", + "$ref": "#/$defs/Compile" + }, + "platforms": { + "description": "FRE platforms", + "type": "array", + "items": {"$ref": "#/$defs/Platform"} + } + }, + "$defs": { + "Compile": { + "type": "object", + "properties": { + "experiment": { + "description": "The name of the model", + "type": "string" + }, + "container_addlibs": { + "description": "Libraries and packages needed for linking in the container", + "oneOf": [ + {"type": "null"}, + { + "type": "array", + "items": {"type": "string"} + } + ] + }, + "baremetal_linkerflags": { + "description": "Linker flags of libraries and packages needed for linking in the bare-metal build", + "oneOf": [ + {"type": "null"}, + { + "type": "array", + "items": {"type": "string"} + } + ] + }, + "src": { + "type": "array", + "items": {"$ref": "#/$defs/Src"} + } + } + }, + "Src": { + "type": "object", + "properties": { + "component": { + "description": "The name of the model component", + "type": "string" + }, + "repo": { + "anyOf": [ + { + "description": "The URL of the code repository", + "type": "array", + "items": { + "type": "string", + "format": "uri", + "qt-uri-protocols": [ + "https" + ], + "qt-uri-extensions": [ + ".git" + ] + } + }, + { + "description": "The URL of the code repository", + "type": "string", + "format": "uri", + "qt-uri-protocols": [ + "https" + ], + "qt-uri-extensions": [ + ".git" + ] + } + ] + }, + "cppdefs": { + "description": "String of CPPDEFs to include in compiling the component", + "type": "string" + }, + "branch": { + "anyOf": [ + { + "description": "The version of code to clone", + "type": "array", + "items": { + "type": "string" + } + }, + { + "description": "The version of code to clone", + "type": "string" + } + ] + }, + "otherFlags": { + "description": "String of Include flags necessary to retrieve other code needed", + "type": "string" + }, + "requires": { + "description": "list of componets that this component depends on", + "type": "array", + "items": {"type": "string"} + }, + "paths": { + "description": "A list of the paths in the component to compile", + "type": "array", + "items": {"type": "string"} + }, + "doF90Cpp": { + "description": "True if the preprocessor needs to be run", + "type": "boolean" + }, + "makeOverrides": { + "description": "Overrides openmp target for MOM6", + "type": "string" + } + } + }, + "Platform": { + "type": "object", + "properties": { + "name": { + "description": "The name of the platform", + "type": "string" + }, + "compiler": { + "description": "The compiler used to build the model", + "type": "string" + }, + "modulesInit": { + "description": "Array of commands to run before loading modules", + "type": "array", + "items": {"type": "string"} + }, + "modules": { + "description": "List (array) of modules to load", + "type": "array", + "items": {"type": "string"} + }, + "fc": { + "description": "The Fortran compiler", + "type": "string" + }, + "cc": { + "description": "The C compiler", + "type": "string" + }, + "mkTemplate": { + "description": "Path to the mk template file", + "type": "string" + }, + "modelRoot": { + "description": "Path to the root for all model install files", + "type": "string" + }, + "RUNenv": { + "description": "Commands needed at the beginning of a RUN in dockerfile", + "type": ["array"], + "items": {"type": "string"} + }, + "container": { + "description": "True/False if using container to compile", + "type": "boolean" + }, + "containerBuild": { + "description": "Program used to build the container", + "type": "string" + }, + "containerRun": { + "description": "Program used to run the container", + "type": "string" + } + } + } + } +} diff --git a/lib/fre/gfdl_msd_schemas/README.md b/lib/fre/gfdl_msd_schemas/README.md new file mode 100644 index 00000000..7fd70f3a --- /dev/null +++ b/lib/fre/gfdl_msd_schemas/README.md @@ -0,0 +1,26 @@ +# About +This repository contains schemas for various types of YAML files that are used +by FMS and FRE at GFDL. These schemas are written in the +[JSON Schema](https://json-schema.org/) format, and are intended for use with +the `validate_schema.py` tool in +[fms_yaml_tools](https://github.com/NOAA-GFDL/fms_yaml_tools). + +# Inventory +* `FMS/data_table.json` +* `FMS/diag_table.json` +* `FMS/field_table.json` + +# Disclaimer + +The United States Department of Commerce (DOC) GitHub project code is provided +on an 'as is' basis and the user assumes responsibility for its use. DOC has +relinquished control of the information and no longer has responsibility to +protect the integrity, confidentiality, or availability of the information. Any +claims against the Department of Commerce stemming from the use of its GitHub +project will be governed by all applicable Federal law. Any reference to +specific commercial products, processes, or services by service mark, +trademark, manufacturer, or otherwise, does not constitute or imply their +endorsement, recommendation or favoring by the Department of Commerce. The +Department of Commerce seal and logo, or the seal and logo of a DOC bureau, +shall not be used in any manner to imply endorsement of any commercial product +or activity by DOC or the United States Government. diff --git a/lib/fre/lazy_group.py b/lib/fre/lazy_group.py new file mode 100644 index 00000000..1ceefca7 --- /dev/null +++ b/lib/fre/lazy_group.py @@ -0,0 +1,43 @@ +''' +for lazy-style loading of commands and subcommands while using click +see https://click.palletsprojects.com/en/8.1.x/complex/ +''' + +import importlib +import click + +class LazyGroup(click.Group): + ''' class defining lazygroup command/subcommand loading ''' + def __init__(self, *args, lazy_subcommands=None, **kwargs): + super().__init__(*args, **kwargs) + # lazy_subcommands is a map of the form: + # + # {command-name} -> {module-name}.{command-object-name} + # + self.lazy_subcommands = lazy_subcommands or {} + + def list_commands(self, ctx): + base = super().list_commands(ctx) + lazy = sorted(self.lazy_subcommands.keys()) + return base + lazy + + def get_command(self, ctx, cmd_name): + if cmd_name in self.lazy_subcommands: + return self._lazy_load(cmd_name) + return super().get_command(ctx, cmd_name) + + def _lazy_load(self, cmd_name): + # lazily loading a command, first get the module name and attribute name + import_path = self.lazy_subcommands[cmd_name] + modname, cmd_object_name = import_path.rsplit(".", 1) + # do the import + mod = importlib.import_module(modname, package="fre") + # get the Command object from that module + cmd_object = getattr(mod, cmd_object_name) + # check the result to make debugging easier + if not isinstance(cmd_object, click.BaseCommand): + raise ValueError( + f"Lazy loading of {import_path} failed by returning " + "a non-command object" + ) + return cmd_object diff --git a/lib/fre/list/__init__.py b/lib/fre/list/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/list/frelist.py b/lib/fre/list/frelist.py new file mode 100644 index 00000000..fea3b6ea --- /dev/null +++ b/lib/fre/list/frelist.py @@ -0,0 +1,20 @@ +''' fre list ''' + +import click + +from .frelistexample import list_test_function + +@click.group(help=click.style(" - access fre list subcommands", fg=(232,204,91))) +def list_cli(): + ''' entry point to fre list click commands ''' + +@list_cli.command() +@click.option('--uppercase', '-u', is_flag=True, help = 'Print statement in uppercase.') +@click.pass_context +def function(context, uppercase): + # pylint: disable=unused-argument + """ - Execute fre list test """ + context.forward(list_test_function) + +if __name__ == "__main__": + list_cli() diff --git a/lib/fre/list/frelistexample.py b/lib/fre/list/frelistexample.py new file mode 100644 index 00000000..7e40e195 --- /dev/null +++ b/lib/fre/list/frelistexample.py @@ -0,0 +1,18 @@ +""" +experimentation file for integrating one file's functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL +""" + +import click + +@click.command() +def list_test_function(uppercase=None): + """Execute fre list testfunction2.""" + statement = "testingtestingtestingtesting" + if uppercase: + statement = statement.upper() + click.echo(statement) + +if __name__ == '__main__': + list_test_function() diff --git a/lib/fre/make/README.md b/lib/fre/make/README.md new file mode 100644 index 00000000..961e564c --- /dev/null +++ b/lib/fre/make/README.md @@ -0,0 +1,180 @@ +# **Fremake Canopy** +Through the fre-cli, `fre make` can be used to create and run a checkout script, makefile, and compile a model. + +* Fremake Canopy Supports: + - multiple targets; use `-t` flag to define each target + - bare-metal build + - container creation + - parallel checkouts for bare-metal build** + +** **Note: Users will not be able to create containers without access to podman** + +The fremake canopy fre-cli subcommands are described below ([Subtools](#subtools)), as well as a Guide on the order in which to use them ([Guide](#guide)). + +Additionally, as mentioned, multiple targets can be used more multiple target-platform combinations. Below is an example of this usage for both the bare-metal build and container build, using the AM5 model + +- [Bare-metal Example](#bare-metal-build) +- [Bare-metal Multi-target Example](#bare-metal-build-multi-target) +- [Container Example](#container-build) + +## **Usage (Users)** +* Refer to fre-cli [README.md](https://github.com/NOAA-GFDL/fre-cli/blob/main/README.md) for foundational fre-cli usage guide and tips. + +## **Quickstart** +### **Bare-metal Build:** +```bash +# Create checkout script +fre make create-checkout -y am5.yaml -p ncrc5.intel23 -t prod + +# Create and run checkout script +fre make create-checkout -y am5.yaml -p ncrc5.intel23 -t prod --execute + +# Create Makefile +fre make create-makefile -y am5.yaml -p ncrc5.intel23 -t prod + +# Create the compile script +fre make create-compile -y am5.yaml -p ncrc5.intel23 -t prod + +# Create and run the compile script +fre make create-compile -y am5.yaml -p ncrc5.intel23 -t prod --execute +``` +### **Bare-metal Build Multi-target:** +```bash +# Create checkout script +fre make create-checkout -y am5.yaml -p ncrc5.intel23 -t prod -t debug + +# Create and run checkout script +fre make create-checkout -y am5.yaml -p ncrc5.intel23 -t prod -t debug --execute + +# Create Makefile +fre make create-makefile -y am5.yaml -p ncrc5.intel23 -t prod -t debug + +# Create the compile script +fre make create-compile -y am5.yaml -p ncrc5.intel23 -t prod -t debug + +# Create and run the compile script +fre make create-compile -y am5.yaml -p ncrc5.intel23 -t prod -t debug --execute +``` + +### **Container Build:** +In order for the container to build successfully, a `-npc`, or `--no-parallel-checkout` is needed. +```bash +# Create checkout script +fre make create-checkout -y am5.yaml -p hpcme.2023 -t prod -npc + +# Create and run checkout script +fre make create-checkout -y am5.yaml -p hpcme.2023 -t prod -npc --execute + +# Create Makefile +fre make create-makefile -y am5.yaml -p hpcme.2023 -t prod + +# Create Dockerfile +fre make create-dockerfile -y am5.yaml -p hpcme.2023 -t prod + +# Create and run the Dockerfile +fre make create-dockerfile -y am5.yaml -p hpcme.2023 -t prod --execute +``` + +### **Run all of fremake:** +```bash +# Bare-metal +fre make run-fremake -y am5.yaml -p ncrc5.intel23 -t prod + +# Container +fre make run-fremake -y am5.yaml -p hpcme.2023 -t prod -npc +``` + +## Subtools +- `fre make create-checkout [options]` + - Purpose: Creates the checkout script and can check out source code (with execute option) + - Options: + - `-y, --yamlfile [experiment yaml] (required)` + - `-p, --platform [platform] (required)` + - `-t, --target [target] (required)` + - `-j, --jobs [number of jobs to run simultneously]` + - `-npc, --no-parallel-checkout (for container build)` + - `-e, --execute` + +- `fre make create-makefile [options]` + - Purpose: Creates the makefile + - Options: + - `-y, --yamlfile [experiment yaml] (required)` + - `-p, --platform [platform] (required)` + - `-t, --target [target] (required)` + +- `fre make create-compile [options]` + - Purpose: Creates the compile script and compiles the model (with execute option) + - Options: + - `-y, --yamlfile [experiment yaml] (required)` + - `-p, --platform [platform] (required)` + - `-t, --target [target] (required)` + - `-j, --jobs [number of jobs to run simultneously]` + - `-n, --parallel [number of concurrent modile compiles]` + - `-e, --execute` + +- `fre make create-dockerfile [options]` + - Purpose: Creates the dockerfile and creates the container (with execute option) + - With the creation of the dockerfile, the Makefile, checkout script, and any other necessary script is copied into the container from a temporary location + - Options: + - `-y, --yamlfile [experiment yaml] (required)` + - `-p, --platform [platform] (required)` + - `-t, --target [target] (required)` + - `-e, --execute` + +- `fre make run-fremake [options]` + - Purpose: Create the checkout script, Makefile, compile script, and dockerfile (platform dependent) for the compilation of the model + - Options: + - `-y, --yamlfile [experiment yaml] (required)` + - `-p, --platform [platform] (required)` + - `-t, --target [target] (required)` + - `-npc, --no-parallel-checkout (for container build)` + - `-j, --jobs [number of jobs to run simultneously]` + - `-n, --parallel [number of concurrent modile compiles]` + +## Guide +In order to use the `fre make` tools, remember to create a combined yaml first. This can be done with the `fre yamltools combine-yamls` tool. This combines the model, compile, platform, experiment, and any analysis yamls into ONE yaml file for parsing and validation. + +To combine: +`fre yamltools combine-yamls -y [model yaml file] -e [experiment name] -p [platform] -t [target]` + +### **Bare-metal Build:** +```bash +# Create checkout script +fre make create-checkout -y [model yaml file] -p [platform] -t [target] + +# Create and run checkout script +fre make create-checkout -y [model yaml file] -p [platform] -t [target] --execute + +# Create Makefile +fre make create-makefile -y [model yaml file] -p [platform] -t [target] + +# Creat the compile script +fre make create-compile -y [model yaml file] -p [platform] -t [target] + +# Create and run the compile script +fre make create-compile -y [model yaml file] -p [platform] -t [target] --execute + +# Run all of fremake +fre make run-fremake -y [model yaml file] -p [platform] -t [target] [other options...] +``` + +### **Container Build:** +For the container build, parallel checkouts are not supported, so the `-npc` options must be used for the checkout script. In addition the platform must be a container platform. + +***To reiterate, users will not be able to create containers unless they have podman access on gaea.*** +```bash +# Create checkout script +fre make create-checkout -y [model yaml file] -p [CONTAINER PLATFORM] -t [target] -npc + +# Create and run checkout script +fre make create-checkout -y [model yaml file] -p [CONTAINER PLATFORM] -t [target] --execute + +# Create Makefile +fre make create-makefile -y [model yaml file] -p [CONTAINER PLATFORM] -t [target] + +#Create a Dockerfile +fre make create-dockerfile -y [model yaml file] -p [CONTAINER PLATFORM] -t [target] + +# Create and run the Dockerfile +fre make create-dockerfile -y [model yaml file] -p [CONTAINER PLATFORM] -t [target] --execute +``` diff --git a/lib/fre/make/__init__.py b/lib/fre/make/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/make/createCheckout.py b/lib/fre/make/createCheckout.py new file mode 100644 index 00000000..fc4d2df4 --- /dev/null +++ b/lib/fre/make/createCheckout.py @@ -0,0 +1,116 @@ +#!/usr/bin/python3 + +import os +import subprocess +import logging +import sys +import click +import fre.yamltools.combine_yamls as cy +from .gfdlfremake import varsfre, yamlfre, checkout, targetfre + +def checkout_create(yamlfile,platform,target,no_parallel_checkout,jobs,execute,verbose): + # Define variables + yml = yamlfile + name = yamlfile.split(".")[0] + run = execute + jobs = str(jobs) + pcheck = no_parallel_checkout + + if pcheck: + pc = "" + else: + pc = " &" + + if verbose: + logging.basicConfig(level=logging.INFO) + else: + logging.basicConfig(level=logging.ERROR) + + srcDir="src" + checkoutScriptName = "checkout.sh" + baremetalRun = False # This is needed if there are no bare metal runs + + ## Split and store the platforms and targets in a list + plist = platform + tlist = target + + # Combine model, compile, and platform yamls + # Default behavior - combine yamls / rewrite combined yaml + comb = cy.init_compile_yaml(yml,platform,target) + full_combined = cy.get_combined_compileyaml(comb) + + ## Get the variables in the model yaml + freVars = varsfre.frevars(full_combined) + + ## Open the yaml file, validate the yaml, and parse as fremakeYaml + modelYaml = yamlfre.freyaml(full_combined,freVars) + fremakeYaml = modelYaml.getCompileYaml() + + ## Error checking the targets + for targetName in tlist: + target = targetfre.fretarget(targetName) + + ## Loop through the platforms specified on the command line + ## If the platform is a baremetal platform, write the checkout script and run it once + ## This should be done separately and serially because bare metal platforms should all be using + ## the same source code. + for platformName in plist: + if modelYaml.platforms.hasPlatform(platformName): + pass + else: + raise ValueError (platformName + " does not exist in platforms.yaml") + ( compiler, modules, modulesInit, fc, cc, modelRoot, + iscontainer, mkTemplate, containerBuild, ContainerRun, + RUNenv ) = modelYaml.platforms.getPlatformFromName(platformName) + + ## Create the source directory for the platform + if iscontainer is False: + srcDir = modelRoot + "/" + fremakeYaml["experiment"] + "/src" + # if the source directory does not exist, it is created + if not os.path.exists(srcDir): + os.system("mkdir -p " + srcDir) + # if the checkout script does not exist, it is created + if not os.path.exists(srcDir+"/checkout.sh"): + freCheckout = checkout.checkout("checkout.sh",srcDir) + freCheckout.writeCheckout(modelYaml.compile.getCompileYaml(),jobs,pc) + freCheckout.finish(pc) + # Make checkout script executable + os.chmod(srcDir+"/checkout.sh", 0o744) + print("\nCheckout script created in "+ srcDir + "/checkout.sh \n") + + # Run the checkout script + if run is True: + freCheckout.run() + else: + sys.exit() + else: + print("\nCheckout script PREVIOUSLY created in "+ srcDir + "/checkout.sh \n") + if run == True: + try: + subprocess.run(args=[srcDir+"/checkout.sh"], check=True) + except: + print("\nThere was an error with the checkout script "+srcDir+"/checkout.sh.", + "\nTry removing test folder: " + modelRoot +"\n") + raise + else: + sys.exit() + + else: + image="ecpe4s/noaa-intel-prototype:2023.09.25" + bldDir = modelRoot + "/" + fremakeYaml["experiment"] + "/exec" + tmpDir = "tmp/"+platformName + freCheckout = checkout.checkoutForContainer("checkout.sh", srcDir, tmpDir) + freCheckout.writeCheckout(modelYaml.compile.getCompileYaml(),jobs,pc) + freCheckout.finish(pc) + print("\nCheckout script created at " + tmpDir + "/checkout.sh" + "\n") + +@click.command() +def _checkout_create(yamlfile,platform,target,no_parallel_checkout,jobs,execute,verbose): + ''' + Decorator for calling checkout_create - allows the decorated version + of the function to be separate from the undecorated version + ''' + return checkout_create(yamlfile,platform,target,no_parallel_checkout,jobs,execute,verbose) + +if __name__ == "__main__": + checkout_create() diff --git a/lib/fre/make/createCompile.py b/lib/fre/make/createCompile.py new file mode 100644 index 00000000..36068329 --- /dev/null +++ b/lib/fre/make/createCompile.py @@ -0,0 +1,99 @@ +#!/usr/bin/python3 + +import os +import sys +import logging +from pathlib import Path +from multiprocessing.dummy import Pool +import click +from .gfdlfremake import varsfre, yamlfre, targetfre, buildBaremetal +import fre.yamltools.combine_yamls as cy + +def compile_create(yamlfile,platform,target,jobs,parallel,execute,verbose): + # Define variables + yml = yamlfile + name = yamlfile.split(".")[0] + nparallel = parallel + jobs = str(jobs) + run = execute + + if verbose: + logging.basicCOnfig(level=logging.INFO) + else: + logging.basicConfig(level=logging.ERROR) + + srcDir="src" + checkoutScriptName = "checkout.sh" + baremetalRun = False # This is needed if there are no bare metal runs + + ## Split and store the platforms and targets in a list + plist = platform + tlist = target + + # Combined compile yaml file + combined = Path(f"combined-{name}.yaml") + + ## If combined yaml exists, note message of its existence + ## If combined yaml does not exist, combine model, compile, and platform yamls + full_combined = cy.combined_compile_existcheck(combined,yml,platform,target) + + ## Get the variables in the model yaml + freVars = varsfre.frevars(full_combined) + + ## Open the yaml file and parse as fremakeYaml + modelYaml = yamlfre.freyaml(full_combined,freVars) + fremakeYaml = modelYaml.getCompileYaml() + + ## Error checking the targets + for targetName in tlist: + target = targetfre.fretarget(targetName) + + fremakeBuildList = [] + ## Loop through platforms and targets + for platformName in plist: + for targetName in tlist: + target = targetfre.fretarget(targetName) + if modelYaml.platforms.hasPlatform(platformName): + pass + else: + raise ValueError (platformName + " does not exist in " + modelYaml.combined.get("compile").get("platformYaml")) + + (compiler,modules,modulesInit,fc,cc,modelRoot,iscontainer,mkTemplate,containerBuild,ContainerRun,RUNenv)=modelYaml.platforms.getPlatformFromName(platformName) + ## Make the bldDir based on the modelRoot, the platform, and the target + srcDir = modelRoot + "/" + fremakeYaml["experiment"] + "/src" + ## Check for type of build + if iscontainer is False: + baremetalRun = True + bldDir = modelRoot + "/" + fremakeYaml["experiment"] + "/" + platformName + "-" + target.gettargetName() + "/exec" + os.system("mkdir -p " + bldDir) + ## Create a list of compile scripts to run in parallel + fremakeBuild = buildBaremetal.buildBaremetal(exp = fremakeYaml["experiment"], + mkTemplatePath = mkTemplate, + srcDir = srcDir, + bldDir = bldDir, + target = target, + modules = modules, + modulesInit = modulesInit, + jobs = jobs) + for c in fremakeYaml['src']: + fremakeBuild.writeBuildComponents(c) + fremakeBuild.writeScript() + fremakeBuildList.append(fremakeBuild) + click.echo("\nCompile script created at " + bldDir + "/compile.sh" + "\n") + if run: + if baremetalRun: + pool = Pool(processes=nparallel) # Create a multiprocessing Pool + pool.map(buildBaremetal.fremake_parallel,fremakeBuildList) # process data_inputs iterable with pool + else: + sys.exit() + +@click.command() +def _compile_create(yamlfile,platform,target,jobs,parallel,execute,verbose): + ''' + Decorator for calling compile_create - allows the decorated version + of the function to be separate from the undecorated version + ''' + return compile_create(yamlfile,platform,target,jobs,parallel,execute,verbose) + +if __name__ == "__main__": + compile_create() diff --git a/lib/fre/make/createDocker.py b/lib/fre/make/createDocker.py new file mode 100644 index 00000000..59b73ee9 --- /dev/null +++ b/lib/fre/make/createDocker.py @@ -0,0 +1,89 @@ +#!/usr/bin/python3 + +import os +import sys +from pathlib import Path +import click +#from .gfdlfremake import varsfre, targetfre, makefilefre, platformfre, yamlfre, buildDocker +from .gfdlfremake import varsfre, targetfre, yamlfre, buildDocker +import fre.yamltools.combine_yamls as cy + +def dockerfile_create(yamlfile,platform,target,execute): + srcDir="src" + checkoutScriptName = "checkout.sh" + baremetalRun = False # This is needed if there are no bare metal runs + ## Split and store the platforms and targets in a list + plist = platform + tlist = target + yml = yamlfile + name = yamlfile.split(".")[0] + run = execute + + # Combined compile yaml file + combined = Path(f"combined-{name}.yaml") + + ## If combined yaml exists, note message of its existence + ## If combined yaml does not exist, combine model, compile, and platform yamls + full_combined = cy.combined_compile_existcheck(combined,yml,platform,target) + + ## Get the variables in the model yaml + freVars = varsfre.frevars(full_combined) + + ## Open the yaml file and parse as fremakeYaml + modelYaml = yamlfre.freyaml(full_combined,freVars) + fremakeYaml = modelYaml.getCompileYaml() + + fremakeBuildList = [] + ## Loop through platforms and targets + for platformName in plist: + for targetName in tlist: + targetObject = targetfre.fretarget(targetName) + if modelYaml.platforms.hasPlatform(platformName): + pass + else: + raise ValueError (platformName + " does not exist in " + \ + modelYaml.combined.get("compile").get("platformYaml")) + + ( compiler, modules, modulesInit, fc, cc, modelRoot, + iscontainer, mkTemplate, containerBuild, containerRun, + RUNenv ) = modelYaml.platforms.getPlatformFromName(platformName) + + ## Make the bldDir based on the modelRoot, the platform, and the target + srcDir = modelRoot + "/" + fremakeYaml["experiment"] + "/src" + ## Check for type of build + if iscontainer is True: + image="ecpe4s/noaa-intel-prototype:2023.09.25" + bldDir = modelRoot + "/" + fremakeYaml["experiment"] + "/exec" + tmpDir = "tmp/"+platformName + + dockerBuild = buildDocker.container(base = image, + exp = fremakeYaml["experiment"], + libs = fremakeYaml["container_addlibs"], + RUNenv = RUNenv, + target = targetObject) + dockerBuild.writeDockerfileCheckout("checkout.sh", tmpDir+"/checkout.sh") + dockerBuild.writeDockerfileMakefile(tmpDir+"/Makefile", tmpDir+"/linkline.sh") + + for c in fremakeYaml['src']: + dockerBuild.writeDockerfileMkmf(c) + + dockerBuild.writeRunscript(RUNenv,containerRun,tmpDir+"/execrunscript.sh") + currDir = os.getcwd() + click.echo("\ntmpDir created in " + currDir + "/tmp") + click.echo("Dockerfile created in " + currDir +"\n") + + if run: + dockerBuild.build(containerBuild, containerRun) + else: + sys.exit() + +@click.command() +def _dockerfile_create(yamlfile,platform,target,execute): + ''' + Decorator for calling dockerfile_create - allows the decorated version + of the function to be separate from the undecorated version + ''' + return dockerfile_create(yamlfile,platform,target,execute) + +if __name__ == "__main__": + dockerfile_create() diff --git a/lib/fre/make/createMakefile.py b/lib/fre/make/createMakefile.py new file mode 100644 index 00000000..eaf340dd --- /dev/null +++ b/lib/fre/make/createMakefile.py @@ -0,0 +1,89 @@ +#!/usr/bin/python3 + +import os +from pathlib import Path + +import click + +from .gfdlfremake import makefilefre, varsfre, targetfre, yamlfre +import fre.yamltools.combine_yamls as cy + +def makefile_create(yamlfile,platform,target): + srcDir="src" + checkoutScriptName = "checkout.sh" + baremetalRun = False # This is needed if there are no bare metal runs + ## Split and store the platforms and targets in a list + plist = platform + tlist = target + yml = yamlfile + name = yamlfile.split(".")[0] + + combined = Path(f"combined-{name}.yaml") + + ## If combined yaml exists, note message of its existence + ## If combined yaml does not exist, combine model, compile, and platform yamls + full_combined = cy.combined_compile_existcheck(combined,yml,platform,target) + + ## Get the variables in the model yaml + freVars = varsfre.frevars(full_combined) + + ## Open the yaml file and parse as fremakeYaml + modelYaml = yamlfre.freyaml(full_combined,freVars) + fremakeYaml = modelYaml.getCompileYaml() + + fremakeBuildList = [] + ## Loop through platforms and targets + for platformName in plist: + for targetName in tlist: + targetObject = targetfre.fretarget(targetName) + if modelYaml.platforms.hasPlatform(platformName): + pass + else: + raise ValueError (platformName + " does not exist in " + modelYaml.combined.get("compile").get("platformYaml")) + + (compiler,modules,modulesInit,fc,cc,modelRoot,iscontainer,mkTemplate,containerBuild,ContainerRun,RUNenv)=modelYaml.platforms.getPlatformFromName(platformName) + ## Make the bldDir based on the modelRoot, the platform, and the target + srcDir = modelRoot + "/" + fremakeYaml["experiment"] + "/src" + ## Check for type of build + if iscontainer is False: + baremetalRun = True + bldDir = modelRoot + "/" + fremakeYaml["experiment"] + "/" + platformName + "-" + targetObject.gettargetName() + "/exec" + os.system("mkdir -p " + bldDir) + ## Create the Makefile + freMakefile = makefilefre.makefile(exp = fremakeYaml["experiment"], + libs = fremakeYaml["baremetal_linkerflags"], + srcDir = srcDir, + bldDir = bldDir, + mkTemplatePath = mkTemplate) + # Loop through components and send the component name, requires, and overrides for the Makefile + for c in fremakeYaml['src']: + freMakefile.addComponent(c['component'],c['requires'],c['makeOverrides']) + freMakefile.writeMakefile() + click.echo("\nMakefile created at " + bldDir + "/Makefile" + "\n") + else: + image="ecpe4s/noaa-intel-prototype:2023.09.25" + bldDir = modelRoot + "/" + fremakeYaml["experiment"] + "/exec" + tmpDir = "tmp/"+platformName + freMakefile = makefilefre.makefileContainer(exp = fremakeYaml["experiment"], + libs = fremakeYaml["container_addlibs"], + srcDir = srcDir, + bldDir = bldDir, + mkTemplatePath = mkTemplate, + tmpDir = tmpDir) + + # Loop through compenents and send the component name and requires for the Makefile + for c in fremakeYaml['src']: + freMakefile.addComponent(c['component'],c['requires'],c['makeOverrides']) + freMakefile.writeMakefile() + click.echo("\nMakefile created at " + bldDir + "/Makefile" + "\n") + +@click.command() +def _makefile_create(yamlfile,platform,target): + ''' + Decorator for calling makefile_create - allows the decorated version + of the function to be separate from the undecorated version + ''' + return makefile_create(yamlfile,platform,target) + +if __name__ == "__main__": + makefile_create() diff --git a/lib/fre/make/fremake.py b/lib/fre/make/fremake.py new file mode 100644 index 00000000..f39a6be0 --- /dev/null +++ b/lib/fre/make/fremake.py @@ -0,0 +1,208 @@ +import click +from fre.make import createCheckout +from fre.make import createMakefile +from fre.make import createCompile +from fre.make import createDocker +from fre.make import runFremake + +yamlfile_opt_help = """Experiment yaml compile FILE +""" +experiment_opt_help = """Name of experiment""" +platform_opt_help = """Hardware and software FRE platform space separated list of STRING(s). +This sets platform-specific data and instructions +""" +target_opt_help = """a space separated list of STRING(s) that defines compilation settings and +linkage directives for experiments. Predefined targets refer to groups of directives that exist in +the mkmf template file (referenced in buildDocker.py). Possible predefined targets include 'prod', +'openmp', 'repro', 'debug, 'hdf5'; however 'prod', 'repro', and 'debug' are mutually exclusive +(cannot not use more than one of these in the target list). Any number of targets can be used. +""" +parallel_opt_help = """Number of concurrent model compiles (default 1) +""" +jobs_opt_help = """Number of jobs to run simultaneously. Used for make -jJOBS and git clone +recursive --jobs=JOBS +""" +no_parallel_checkout_opt_help = """Use this option if you do not want a parallel checkout. +The default is to have parallel checkouts. +""" +verbose_opt_help = """Get verbose messages (repeat the option to increase verbosity level) +""" + + + +@click.group(help=click.style(" - access fre make subcommands", fg=(210,73,57))) +def make_cli(): + pass + +@make_cli.command() +@click.option("-y", + "--yamlfile", + type = str, + help = yamlfile_opt_help, + required = True) # use click.option() over click.argument(), we want help statements +@click.option("-p", + "--platform", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = platform_opt_help, required = True) +@click.option("-t", "--target", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = target_opt_help, + required = True) +@click.option("-n", + "--parallel", + type = int, + metavar = '', + default = 1, + help = parallel_opt_help) +@click.option("-j", + "--jobs", + type = int, + metavar = '', + default = 4, + help = jobs_opt_help) +@click.option("-npc", + "--no-parallel-checkout", + is_flag = True, + help = no_parallel_checkout_opt_help) +@click.option("-v", + "--verbose", + is_flag = True, + help = verbose_opt_help) +@click.pass_context +def run_fremake(context, yamlfile, platform, target, parallel, jobs, no_parallel_checkout, verbose): + """ - Perform all fremake functions to run checkout and compile model""" + context.forward(runFremake._fremake_run) + +#### +@make_cli.command() +@click.option("-y", + "--yamlfile", + type = str, + help = yamlfile_opt_help, + required = True) # use click.option() over click.argument(), we want help statements +@click.option("-p", + "--platform", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = platform_opt_help, + required = True) +@click.option("-t", "--target", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = target_opt_help, + required = True) +@click.option("-j", + "--jobs", + type = int, + metavar = '', + default = 4, + help = jobs_opt_help) +@click.option("-npc", + "--no-parallel-checkout", + is_flag = True, + help = no_parallel_checkout_opt_help) +@click.option("--execute", + is_flag = True, + default = False, + help = "Use this to run the created checkout script.") +@click.option("-v", + "--verbose", + is_flag = True, + help = verbose_opt_help) +@click.pass_context +def create_checkout(context,yamlfile,platform,target,no_parallel_checkout,jobs,execute,verbose): + """ - Write the checkout script """ + context.forward(createCheckout._checkout_create) + +##### +@make_cli.command +@click.option("-y", + "--yamlfile", + type = str, + help = yamlfile_opt_help, + required = True) # use click.option() over click.argument(), we want help statements +@click.option("-p", + "--platform", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = platform_opt_help, required = True) +@click.option("-t", "--target", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = target_opt_help, + required = True) +@click.pass_context +def create_makefile(context,yamlfile,platform,target): + """ - Write the makefile """ + context.forward(createMakefile._makefile_create) + +##### + +@make_cli.command +@click.option("-y", + "--yamlfile", + type = str, + help = yamlfile_opt_help, + required = True) # use click.option() over click.argument(), we want help statements +@click.option("-p", + "--platform", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = platform_opt_help, required = True) +@click.option("-t", "--target", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = target_opt_help, + required = True) +@click.option("-j", + "--jobs", + type = int, + metavar = '', + default = 4, + help = jobs_opt_help) +@click.option("-n", + "--parallel", + type = int, + metavar = '', default = 1, + help = parallel_opt_help) +@click.option("--execute", + is_flag = True, + default = False, + help = "Use this to run the created checkout script.") +@click.option("-v", + "--verbose", + is_flag = True, + help = verbose_opt_help) +@click.pass_context +def create_compile(context,yamlfile,platform,target,jobs,parallel,execute,verbose): + """ - Write the compile script """ + context.forward(createCompile._compile_create) + +@make_cli.command +@click.option("-y", + "--yamlfile", + type = str, + help = yamlfile_opt_help, + required = True) # use click.option() over click.argument(), we want help statements +@click.option("-p", + "--platform", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = platform_opt_help, required = True) +@click.option("-t", "--target", + multiple = True, # replaces nargs = -1, since click.option() + type = str, + help = target_opt_help, + required = True) +@click.option("--execute", + is_flag = True, + help = "Build Dockerfile that has been generated by create-docker.") +@click.pass_context +def create_dockerfile(context,yamlfile,platform,target,execute): + """ - Write the dockerfile """ + context.forward(createDocker._dockerfile_create) + +if __name__ == "__main__": + make_cli() diff --git a/lib/fre/make/gfdlfremake/.gitignore b/lib/fre/make/gfdlfremake/.gitignore new file mode 100644 index 00000000..a623caf0 --- /dev/null +++ b/lib/fre/make/gfdlfremake/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +Dockerfile +checkout.sh +compile.sh +Makefile diff --git a/lib/fre/make/gfdlfremake/.gitlab-ci.yml b/lib/fre/make/gfdlfremake/.gitlab-ci.yml new file mode 100644 index 00000000..1e1b666c --- /dev/null +++ b/lib/fre/make/gfdlfremake/.gitlab-ci.yml @@ -0,0 +1,12 @@ +stages: + - test + +test_build_am5: + stage: test + script: +# conda env +# - /ncrc/sw/gaea-c5/python/3.9/anaconda-base/envs/noaa_py3.9 + - cd yamls/ + - ../fremake -y am5.yaml -p ncrc5.intel -t prod + tags: + - ncrc5 diff --git a/lib/fre/make/gfdlfremake/README.md b/lib/fre/make/gfdlfremake/README.md new file mode 100644 index 00000000..18aa8d44 --- /dev/null +++ b/lib/fre/make/gfdlfremake/README.md @@ -0,0 +1,147 @@ +# FREMAKE CANOPY +The fremake canopy prototype embodies the checking out of source code and compilation of the model. This rewrite was developed with the idea of portability and flexibilty in mind. + +Fremake canopy is written in python and can support: +- Bare-metal and container builds (with container platform) + - bare-metal build: + - supports parallel checkouts (default behavior) + - supports parallel model builds + - container: + - e4s container with spack stack package list +- Multiple platforms and targets can be given (for multiple platform-target combinations) +- One yaml format +- Additional library support if needed by model + +Fremake has also been integrated as a fre-cli tool, creating a more modular and interactive experience. + +## Get Started +Steps for how to build an example with the AM5 model, for both the bare-metal compilation and container, are outlined below. + +- ### [Set-up environment](#how-to-get-started) + - Fremake tools are provided through the fre-cli + - For more information on the fre-cli, see its associated github page: https://github.com/NOAA-GFDL/fre-cli +- ### [Bare-metal build](#how-to-build-the-bare-metal-example) + - Note: make sure to have the correct experiment yaml, compile yaml, and platform yaml available for the model compilation +- ### [Container build](#how-to-build-the-container-example) + - Note: make sure to have the correct experiment yaml, compile yaml and platform yaml available for the model compilation +- ### [Full fremake: bare-metal and container](#how-to-run-complete-fremake) + - this can be done for either bare-metal or container builds + - this tool is available to give the option to run fremake fully, not in steps + + +## YAMLS: +experiment yaml: This is the main yaml and gives the path to the other yamls +```yaml +platformYaml: path to yaml containing the platforms +layoutYaml: path to the yaml containing the layouts (not used currently) +compileYaml: path to the yaml with the model source code and build information +experiments: path to the yaml that has information about expeiment configurations (not currently used) + +``` + +compile yaml: Containers information about source code and how to build +```yaml +experiment: name of the model +container_addlibs: ["comma separated list of additional packages/libraries"] +baremetal_linkerflags: ["comma separated list of linker flags needed for each additional package/library"] +src: + - component: The name of the component model + requires: [Array of required components] + repo: scalar URL of the repo --OR-- [array of URLs to the code repos if more than one is required] + branch: scalar (or array of same size of repo) of the version of the code to clone + doF90Cpp: True if the F90cpp needs to be done (land) + cppdefs: a single string containing all CPPDEFs to add during compilations + paths: [array of paths to build] + otherFlags: Additional flags defined as environment variables in the experiment.yaml +``` + +platform yaml: User defined platform specifications. This will require more user input that bronx +```yaml +platforms: + - name: the platform name + compiler: the compiler you are using + modulesInit: ["array of commands that are needed to load modules." , "each command must end with a newline character"] + modules: [array of modules to load including compiler] + fc: the name of the fortran compiler + cc: the name of the C compiler + mkTemplate: The location of the mkmf make template + modelRoot: The root directory of the model (where src, exec, experiments will go) + container: True if this is a container platform + containerBuild: "podman" - the container build program + containerRun: "apptainer" - the container run program +``` + +## How to Get started +Set up the environment: +```bash +# Load the fre-cli (if on gaea or somewhere where the fre-cli is available) +module load fre/canopy + +# If the fre-cli is not available, follow steps in the fre-cli repository to either create your own conda environment with the fre-cli or activate the fre-cli environment available +# If no fre-cli env available: +conda create -n fremake-env # create environment +conda config --append channels noaa-gfdl # append necessary channels +conda config --append channels conda-forge # append necessary channels +conda activate fremake-env # activate environment +conda install noaa-gfdl::fre-cli # install the fre-cli +``` + +### How to build the Bare-metal example +```bash +# Have access to yamls +# older versino: git clone -b 2023.00 https://gitlab.gfdl.noaa.gov/portable_climate/fremake_canopy.git + +git clone https://gitlab.gfdl.noaa.gov/portable_climate/fremake_canopy.git +cd fremake_canopy/yamls + +# Create and run checkout +# `-e` will run the checkout script after creating it +# without `-e`, the checkout script will just be created +fre make create-checkout -y am5.yaml -p ncrc5.intel -t prod -e + +# Create the Makefile +fre make create-makefile -y am5.yaml -p ncrc5.intel -t prod + +# Create and run the compile script +# `-e` will run the compile script after creating it +# without `-e`, the compile script will just be created +fre make create-compile -y am5.yaml -p ncrc5.intel -t prod -e +``` +*Corresponding files, such as the checkout script, makefile, compile script, and experiment executable will be in the `/exec` folder created in `$HOME/$USER/fremake-canopy/am5/ncrc5.intel-prod/test`* + +### How to build the Container example +```bash +# Have access to yamls +# older version: git clone -b 2023.00 https://gitlab.gfdl.noaa.gov/portable_climate/fremake_canopy.git + +git clone https://gitlab.gfdl.noaa.gov/portable_climate/fremake_canopy.git +cd fremake_canopy/yamls + +# Create and run checkout +# `-e` will run the checkout script after creating it +# without `-e`, the checkout script will just be created +# Be sure to specify `-npc` for non-parallel checkout +fre make create-checkout -y am5.yaml -p hpcme.2023 -t prod -npc -e + +# Create the Makefile +fre make create-makefile -y am5.yaml -p hpcme.2023 -t prod + +# Create and run the dockerfile +# `-e` will run the dockerfile after creating it, to build a container +# without `-e`, the dockerfile will just be created +fre make create-dockerfile -y am5.yaml -p hpcme.2023 -t prod -e +``` +*Corresponding files, such as the checkout script and makefile, will be in a tmp location and copied into the container via the dockerfile. The dockerfile,, as well as the container sif file, will be created in the users current location (yaml folder).* + +### How to run complete fremake +```bash +# Have access to yamls +# older version: git clone -b 2023.00 https://gitlab.gfdl.noaa.gov/portable_climate/fremake_canopy.git + +git clone https://gitlab.gfdl.noaa.gov/portable_climate/fremake_canopy.git +cd fremake_canopy/yamls + +# Run fremake +# Can run fremake for bare-metal or container; be sure to give the correct platform for your build +fre make run-fremake -y am5.yaml -p [ncrc5.intel OR hpcme.2023] -t prod +``` diff --git a/lib/fre/make/gfdlfremake/buildBaremetal.py b/lib/fre/make/gfdlfremake/buildBaremetal.py new file mode 100644 index 00000000..fdb4e2d8 --- /dev/null +++ b/lib/fre/make/gfdlfremake/buildBaremetal.py @@ -0,0 +1,129 @@ +#!/usr/bin/python3 +## \date 2023 +## \author Tom Robinson +## \email thomas.robinson@noaa.gov +## \description + +import subprocess +import os + +def fremake_parallel(fremakeBuildList): + """ + Brief: Called for parallel execution purposes. Runs the builds. + Param: + - fremakeBuildList : fremakeBuild object list passes by pool.map + """ + fremakeBuildList.run() + +class buildBaremetal(): + """ + Brief: Creates the build script to compile the model + Param: + - self : The buildScript object + - exp : The experiment name + - mkTemplatePath : The template used by mkmf to compile the model + - srcDir : The source directory + - bldDir : The build directory + - modules : The list of modules to load before compilation + - modulesInit : A list of commands with new line characters to initialize modules + """ + def __init__(self,exp,mkTemplatePath,srcDir,bldDir,target,modules,modulesInit,jobs): + """ + Initialize variables and set-up the compile script. + """ + self.e = exp + self.t = target.gettargetName() + self.src = srcDir + self.bld = bldDir + self.make = "make --jobs="+str(jobs)+" "+target.getmakeline_add() #make line + self.mkmf = True + self.template = mkTemplatePath + self.modules = "" + for m in modules: + self.modules = self.modules +" "+ m + + ## Set up the top portion of the compile script + self.setup=[ "#!/bin/sh -fx \n", + "bld_dir="+self.bld+"/ \n", + "src_dir="+self.src+"/ \n", + "mkmf_template="+self.template+" \n"] + if self.modules != "": + self.setup.extend(modulesInit) #extend - this is a list + self.setup.append("module load "+self.modules+" \n") # Append -this is a single string + + ## Create the build directory + os.system("mkdir -p "+self.bld) + + ## Create the compile script + self.f=open(self.bld+"/compile.sh","w") + self.f.writelines(self.setup) + + def writeBuildComponents(self, c): + """ + Brief: Adds components to the build script + Param: + - self : The build script object + - c : Component from the compile yaml + """ + # Shorthand for component + comp = c["component"] + + # Make the component directory + self.f.write("\n mkdir -p $bld_dir/"+comp+"\n") + + # Get the paths needed for compiling + pstring = "" + for paths in c["paths"]: + pstring = pstring+"$src_dir/"+paths+" " + + # Run list_paths + self.f.write(" list_paths -l -o $bld_dir/"+comp+"/pathnames_"+comp+" "+pstring+"\n") + self.f.write(" cd $bld_dir/"+comp+"\n") + + # Create the mkmf line + # If this lib doesnt have any code dependencies and + # it requires the preprocessor (no -o and yes --use-cpp) + if c["requires"] == [] and c["doF90Cpp"]: + self.f.write(" mkmf -m Makefile -a $src_dir -b $bld_dir -p lib"+comp+".a -t $mkmf_template --use-cpp -c \""+c["cppdefs"]+"\" "+c["otherFlags"]+" $bld_dir/"+comp+"/pathnames_"+comp+" \n") + elif c["requires"] == []: # If this lib doesnt have any code dependencies (no -o) + self.f.write(" mkmf -m Makefile -a $src_dir -b $bld_dir -p lib"+comp+".a -t $mkmf_template -c \""+c["cppdefs"]+"\" "+c["otherFlags"]+" $bld_dir/"+comp+"/pathnames_"+comp+" \n") + else: #Has requirements + #Set up the requirements as a string to inclue after the -o + reqstring = "" + for r in c["requires"]: + reqstring = reqstring+"-I$bld_dir/"+r+" " + + #Figure out if we need the preprocessor + if c["doF90Cpp"]: + self.f.write(" mkmf -m Makefile -a $src_dir -b $bld_dir -p lib"+comp+".a -t $mkmf_template --use-cpp -c \""+c["cppdefs"]+"\" -o \""+reqstring+"\" "+c["otherFlags"]+" $bld_dir/"+comp+"/pathnames_"+comp+" \n") + else: + self.f.write(" mkmf -m Makefile -a $src_dir -b $bld_dir -p lib"+comp+".a -t $mkmf_template -c \""+c["cppdefs"]+"\" -o \""+reqstring+"\" "+c["otherFlags"]+" $bld_dir/"+comp+"/pathnames_"+comp+" \n") + +##TODO: add targets input + def writeScript(self): + """ + Brief: Finishes and writes the build script + Param: + - self : The buildScript object + """ + self.f.write("cd "+self.bld+"\n") + self.f.write(self.make+"\n") + self.f.close() + + # Make compile script executable + os.chmod(self.bld+"/compile.sh", 0o744) + +## TODO run as a batch job on the login cluster + def run(self): + """ + Brief: Run the build script + Param: + - self : The dockerfile object + """ +###### TODO make the Makefile + command = [self.bld+"/compile.sh","|","tee",self.bld+"/log.compile"] + try: + subprocess.run(args=command, check=True) + except: + print("There was an error running "+self.bld+"/compile.sh") + raise diff --git a/lib/fre/make/gfdlfremake/buildDocker.py b/lib/fre/make/gfdlfremake/buildDocker.py new file mode 100644 index 00000000..6d33d0d2 --- /dev/null +++ b/lib/fre/make/gfdlfremake/buildDocker.py @@ -0,0 +1,200 @@ +#!/usr/bin/python3 +## \date 2023 +## \author Tom Robinson +## \email thomas.robinson@noaa.gov +## \description + +import os + +class container(): + """ + Brief: Opens the Dockerfile for writing + Param: + - self : The dockerfile object + - base : The docker base image to start from + - libs : Additional libraries defined by user + - exp : The experiment name + - RUNenv : The commands that have to be run at + the beginning of a RUN in the dockerfile + to set up the environment + """ + def __init__(self,base,exp,libs,RUNenv,target): + """ + Initialize variables and write to the dockerfile + """ + self.base = base + self.e = exp + self.l = libs + self.src = "/apps/"+self.e+"/src" + self.bld = "/apps/"+self.e+"/exec" + self.mkmf = True + self.target = target + self.template = "/apps/mkmf/templates/hpcme-intel21.mk" + + # Set up spack loads in RUN commands in dockerfile + if RUNenv == "": + self.setup = ["RUN \\ \n"] + else: + self.setup = ["RUN "+RUNenv[0]+" \\ \n"] + self.setup + for env in RUNenv[1:]: + self.setup.append(" && "+env+" \\ \n") + if self.l: + for l in self.l: + self.setup.append(" && spack load "+l+" \\ \n") + + # Clone and copy mkmf through Dockerfile + self.mkmfclone=["RUN cd /apps \\ \n", + " && git clone --recursive https://github.com/NOAA-GFDL/mkmf \\ \n", + " && cp mkmf/bin/* /usr/local/bin \n"] + + # Set bld_dir, src_dir, mkmf_template + self.bldsetup=["RUN bld_dir="+self.bld+" \\ \n", + " && src_dir="+self.src+" \\ \n", + " && mkmf_template="+self.template+ " \\ \n"] + self.d=open("Dockerfile","w") + self.d.writelines("FROM "+self.base+" \n") + + def writeDockerfileCheckout(self, cScriptName, cOnDisk): + """ + Brief: writes to the checkout part of the Dockerfile and sets up the compile + Param: + - self : The dockerfile object + - cScriptName : The name of the checkout script in the container + - cOnDisk : The relative path to the checkout script on disk + """ + self.checkoutPath = self.src+"/"+ cScriptName + self.d.write("COPY " + cOnDisk +" "+ self.checkoutPath +" \n") + self.d.write("RUN chmod 744 "+self.src+"/checkout.sh \n") + self.d.writelines(self.setup) + self.d.write(" && "+self.src+"/checkout.sh \n") + # Clone mkmf + self.d.writelines(self.mkmfclone) + + def writeDockerfileMakefile(self, makefileOnDiskPath, linklineonDiskPath): + """ + Brief: Copies the Makefile into the bldDir in the dockerfile + Param: + - self : The dockerfile object + - makefileOnDiskPath : The path to Makefile on the local disk + - linklineonDiskPath : The path to the link line script on the local disk + """ + # Set up the bldDir + # If no additional libraries defined + if self.l == None: + self.bldCreate=["RUN mkdir -p "+self.bld+" \n", + "COPY "+ makefileOnDiskPath +" "+self.bld+"/Makefile \n"] + self.d.writelines(self.bldCreate) + # If additional libraries defined + if self.l != None: + self.bldCreate=["RUN mkdir -p "+self.bld+" \n", + "COPY "+ makefileOnDiskPath +" "+self.bld+"/Makefile \n", + "RUN chmod +rw "+self.bld+"/Makefile \n", + "COPY "+ linklineonDiskPath +" "+self.bld+"/linkline.sh \n", + "RUN chmod 744 "+self.bld+"/linkline.sh \n"] + self.d.writelines(self.bldCreate) + self.d.writelines(self.setup) + self.d.write(" && "+self.bld+"/linkline.sh \n") + + def writeDockerfileMkmf(self, c): + """ + Brief: Adds components to the build part of the Dockerfile + Param: + - self : The dockerfile object + - c : Component from the compile yaml + """ + # Set up the compile variables + self.d.writelines(self.bldsetup) + + # Shorthand for component + comp = c["component"] + + # Make the component directory + self.d.write(" && mkdir -p $bld_dir/"+comp+" \\ \n") + + # Get the paths needed for compiling + pstring = "" + for paths in c["paths"]: + pstring = pstring+"$src_dir/"+paths+" " + + # Run list_paths + self.d.write(" && list_paths -l -o $bld_dir/"+comp+"/pathnames_"+comp+" "+pstring+" \\ \n") + self.d.write(" && cd $bld_dir/"+comp+" \\ \n") + + # Create the mkmf line + if c["requires"] == [] and c["doF90Cpp"]: # If this lib doesnt have any code dependencies and it requires the preprocessor (no -o and yes --use-cpp) + self.d.write(" && mkmf -m Makefile -a $src_dir -b $bld_dir -p lib"+comp+".a -t $mkmf_template --use-cpp -c \""+c["cppdefs"]+"\" "+c["otherFlags"]+" $bld_dir/"+comp+"/pathnames_"+comp+" \n") + elif c["requires"] == []: # If this lib doesnt have any code dependencies (no -o) + self.d.write(" && mkmf -m Makefile -a $src_dir -b $bld_dir -p lib"+comp+".a -t $mkmf_template -c \""+c["cppdefs"]+"\" "+c["otherFlags"]+" $bld_dir/"+comp+"/pathnames_"+comp+" \n") + else: #Has requirements + #Set up the requirements as a string to inclue after the -o + reqstring = "" + for r in c["requires"]: + reqstring = reqstring+"-I$bld_dir/"+r+" " + + #Figure out if we need the preprocessor + if c["doF90Cpp"]: + self.d.write(" && mkmf -m Makefile -a $src_dir -b $bld_dir -p lib"+comp+".a -t $mkmf_template --use-cpp -c \""+c["cppdefs"]+"\" -o \""+reqstring+"\" "+c["otherFlags"]+" $bld_dir/"+comp+"/pathnames_"+comp+" \n") + else: + self.d.write(" && mkmf -m Makefile -a $src_dir -b $bld_dir -p lib"+comp+".a -t $mkmf_template -c \""+c["cppdefs"]+"\" -o \""+reqstring+"\" "+c["otherFlags"]+" $bld_dir/"+comp+"/pathnames_"+comp+" \n") + + def writeRunscript(self,RUNenv,containerRun,runOnDisk): + """ + Brief: Writes a runscript to set up spack loads/environment + in order to run the executable in the container; + runscript copied into container + Param: + - self : The dockerfile object + - RUNEnv : The commands that have to be run at + the beginning of a RUN in the dockerfile + - containerRun : The container platform used with `exec` + to run the container; apptainer + or singularity used + - runOnDisk : The path to the run script on the local disk + """ + #create runscript in tmp - create spack environment, install necessary packages, + self.createscript = ["#!/bin/bash \n", + "export BACKUP_LD_LIBRARY_PATH=$LD_LIBRARY_PATH\n", + "# Set up spack loads\n", + RUNenv[0]+"\n"] + with open(runOnDisk,"w") as f: + f.writelines(self.createscript) + f.write("# Load spack packages\n") + for env in RUNenv[1:]: + f.write(env+"\n") + + if self.l: + for l in self.l: + self.spackloads = "spack load "+l+"\n" + f.write(self.spackloads) + + f.write("export LD_LIBRARY_PATH=$BACKUP_LD_LIBRARY_PATH:$LD_LIBRARY_PATH\n") + f.write("# Run executable\n") + f.write(self.bld+"/"+self.e+".x\n") + #copy runscript into container in dockerfile + self.d.write("COPY "+runOnDisk+" "+self.bld+"/execrunscript.sh\n") + #make runscript executable + self.d.write("RUN chmod 744 "+self.bld+"/execrunscript.sh\n") + #link runscript to more general location (for frerun container usage) + self.d.write("RUN mkdir -p /apps/bin \ \n") + self.d.write(" && ln -sf "+self.bld+"/execrunscript.sh "+"/apps/bin/execrunscript.sh \n") + #finish the dockerfile + self.d.writelines(self.setup) + self.d.write(" && cd "+self.bld+" && make -j 4 "+self.target.getmakeline_add()+"\n") + self.d.write('ENTRYPOINT ["/bin/bash"]') + self.d.close() + + def build(self,containerBuild,containerRun): + """ + Brief: Builds the container image for the model + Param: + - self : The dockerfile object + - containerBuild : The tool used to build the container; + docker or podman used + - containerRun : The container platform used with `exec` to + run the container; apptainer or singularity used + """ + os.system(containerBuild+" build -f Dockerfile -t "+self.e+":"+self.target.gettargetName()) + os.system("rm -f "+self.e+".tar "+self.e+".sif") + os.system(containerBuild+" save -o "+self.e+"-"+self.target.gettargetName()+".tar localhost/"+self.e+":"+self.target.gettargetName()) + os.system(containerRun+" build --disable-cache "+self.e+"-"+self.target.gettargetName()+".sif docker-archive://"+self.e+"-"+self.target.gettargetName()+".tar") diff --git a/lib/fre/make/gfdlfremake/checkout.py b/lib/fre/make/gfdlfremake/checkout.py new file mode 100644 index 00000000..d3fa5d4e --- /dev/null +++ b/lib/fre/make/gfdlfremake/checkout.py @@ -0,0 +1,154 @@ +import os +import subprocess + +## TODO: Add parallelizations using () and simplify +def writeRepo(file,repo,component,srcDir,branch,add,multi,jobs,pc): + """ + Brief: Creates the clone lines for the checkout script + Param: + - file Checkout script file + - repo the repo(s) to clone + - component Model component name + - srcDir The source directory + - branch The version to clone/checkout + - add Additional instrcutions after the clone + - multi True if a component has more than one repo to clone + """ + ## Write message about cloning repo and branch in component + file.write("echo cloning "+repo+" -b "+branch+" into "+srcDir+"/"+component+"\n") + + ## If this component has multiple repos, clone everything in the component folder + ## If it's not multi, then use the component name (comp) as the folder name to clone into + if multi: + file.write("mkdir -p "+component+"\n") + file.write("cd "+component+"\n") + comp="" + else: + comp=component + + ## Check if there is a branch/version and then write the clone line; + ## record the pid of that clone in dictionary `pids` if parallel + ## checkout option is defined + if pc: + if branch=="": + file.write("(git clone --recursive --jobs="+jobs+" "+repo+" "+comp+")"+pc+"\n") + if multi: + r=repo.split("/")[4].strip(".git") + file.write("pids+=("+r+"pid:$!)\n") + else: + file.write("pids+=("+comp+"pid:$!)\n") + else: + file.write("(git clone --recursive --jobs="+jobs+" "+repo+" -b "+branch+" "+comp+")"+pc+"\n") + if multi: + r=repo.split("/")[4].strip(".git") + file.write("pids+=("+r+"pid:$!)\n") + else: + file.write("pids+=("+comp+"pid:$!)\n") + else: + if branch=="": + file.write("git clone --recursive --jobs="+jobs+" "+repo+" "+comp+"\n") + else: + file.write("git clone --recursive --jobs="+jobs+" "+repo+" -b "+branch+" "+comp+"\n") + + ## Make sure to go back up in the folder structure + if multi: + file.write("cd .. \n") + if add!="": + file.write(add) + +class checkout(): + """ + Brief: Class to create the checkout script + """ + def __init__(self,fname,srcDir): + """ + Brief: Opens the checkout script with the specified name + Param: + - self The checkout script object + - fname The file name of the checkout script + - srcDir The source directory where fname will be run and source will exist + """ + self.fname = fname + self.src = srcDir + os.system("mkdir -p "+self.src) + ##TODO: Force checkout + os.system("rm -rf "+self.src+"/*") + self.checkoutScript = open(self.src+"/"+fname, 'w') + self.checkoutScript.write("#!/bin/sh -f \n") + self.checkoutScript.write("export GIT_TERMINAL_PROMPT=0 \n") + def writeCheckout(self,y,jobs,pc): + """ + Brief: Writes the contents of the checkout script by looping through the input yaml + Param: + - self The checkout script object + - y The fremake compile yaml + """ + self.checkoutScript.write("cd "+self.src +"\n") + for c in y['src']: + if type(c['repo']) is list and type(c['branch']) is list: + for (repo,branch) in zip(c['repo'],c['branch']): + writeRepo(self.checkoutScript,repo,c['component'],self.src,branch,c['additionalInstructions'],True,jobs,pc) + else: + writeRepo(self.checkoutScript,c['repo'],c['component'],self.src,c['branch'],c['additionalInstructions'],False,jobs,pc) + + def finish (self,pc): + """ + Brief: If pc is defined: Loops through dictionary of pids, + waits for each pid individually, writes exit code in + `check` list; + allows checkoutscript to exit if exit code is not 0; + closes the checkout script when writing is done + Param: + - self The checkout script object + """ + if pc: + self.checkoutScript.write('for id in ${pids[@]}; do\n wait ${id##*:}\n check+=("clone of ${id%%:*} exited with status $?")\ndone\n') + self.checkoutScript.write('for stat in "${check[@]}"; do\n echo $stat \n if [ ${stat##* } -ne 0 ]; then\n exit ${stat##* }\n fi\ndone') + self.checkoutScript.close() + else: + self.checkoutScript.close() + +## TODO: batch script building + def run (self): + """ + Brief: Runs the checkout script + Param: + - self The checkout script object + """ + try: + subprocess.run(args=[self.src+"/"+self.fname], check=True) + except: + print("There was an error with the checkout script "+self.src+"/"+self.fname) + raise +################################################################################################### +## Subclass for container checkout +class checkoutForContainer(checkout): + """ + Brief: Subclass for container checkout + """ + def __init__(self,fname,srcDir,tmpdir): + """ + Brief: Opens the checkout script with the specified name + Param: + - self : The checkout script object + - fname : The file name of the checkout script + - srcDir : The source directory where fname will be run and source will exist + - tmpdir : The relative path on disk that fname will be created (and copied from into the + container) + """ + self.fname = fname + self.src = srcDir + self.tmpdir = tmpdir + os.system("mkdir -p "+self.tmpdir) + os.system("rm -rf "+self.tmpdir+"/*") + self.checkoutScript = open(self.tmpdir+"/"+fname, 'w') + self.checkoutScript.write("#!/bin/sh -fx \n") + self.checkoutScript.write("export GIT_TERMINAL_PROMPT=0 \n") + + def cleanup (self): + """ + Brief: Removes the self.tmpdir and contents + Param: + - self The checkout script object + """ + os.system("rm -rf "+self.tmpdir) diff --git a/lib/fre/make/gfdlfremake/fremake b/lib/fre/make/gfdlfremake/fremake new file mode 100755 index 00000000..6c245424 --- /dev/null +++ b/lib/fre/make/gfdlfremake/fremake @@ -0,0 +1,249 @@ +#!/usr/bin/python3 +## \date 2023 +## \author Tom Robinson +## \author Dana Singh +## \description fremake is used to create and run a code checkout script and compile a model. + +import subprocess +import os +import yaml +import argparse +import logging +from . import targetfre, varsfre, yamlfre, checkout, makefilefre, buildDocker, buildBaremetal +from multiprocessing.dummy import Pool + +## Add in cli options +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Fremake is used to create a code checkout script to compile models for FRE experiments.') + parser.add_argument("-y", + "--yamlfile", + type=str, help="Experiment yaml compile FILE",required=True) + parser.add_argument("-p", + "--platform", + nargs='*', + type=str, help="Hardware and software FRE platform space separated list of STRING(s). This sets platform-specific data and instructions",required=True) + parser.add_argument("-t", + "--target", + nargs='*', + type=str, help="FRE target space separated list of STRING(s) that defines compilation settings and linkage directives for experiments.\n\nPredefined targets refer to groups of directives that exist in the mkmf template file (referenced in buildDocker.py). Possible predefined targets include 'prod', 'openmp', 'repro', 'debug, 'hdf5'; however 'prod', 'repro', and 'debug' are mutually exclusive (cannot not use more than one of these in the target list). Any number of targets can be used.",required=True) + parser.add_argument("-f", + "--force-checkout", + action="store_true", + help="Force checkout to get a fresh checkout to source directory in case the source directory exists") + parser.add_argument("-F", + "--force-compile", + action="store_true", + help="Force compile to compile a fresh executable in case the executable directory exists") + parser.add_argument("-K", + "--keep-compiled", + action="store_true", + help="Keep compiled files in the executable directory for future use") + parser.add_argument("--no-link", + action="store_true", + help="Do not link the executable") + parser.add_argument("-E", + "--execute", + action="store_true", + help="Execute all the created scripts in the current session") + parser.add_argument("-n", + "--parallel", + type=int, + metavar='', default=1, + help="Number of concurrent model compiles (default 1)") + parser.add_argument("-j", + "--jobs", + type=int, + metavar='', default=4, + help="Number of jobs to run simultaneously. Used for make -jJOBS and git clone recursive --jobs=JOBS") + parser.add_argument("-npc", + "--no-parallel-checkout", + action="store_true", + help="Use this option if you do not want a parallel checkout. The default is to have parallel checkouts.") + parser.add_argument("-s", + "--submit", + action="store_true", + help="Submit all the created scripts as batch jobs") + parser.add_argument("-v", + "--verbose", + action="store_true", + help="Get verbose messages (repeat the option to increase verbosity level)") + parser.add_argument("-w NUM", + "--walltime=NUM", + type=int, metavar='', + help="Maximum wall time NUM (in minutes) to use") + parser.add_argument("--mail-list=STRING", + action="store_true", + help="Email the comma=separated STRING list of emails rather than \$USER\@noaa.gov") + + ## Parse the arguments + args = parser.parse_args() + + ## Define arguments as variables + yml = args.yamlfile + ps = args.platform + ts = args.target + nparallel = args.parallel + jobs = str(args.jobs) + pcheck = args.no_parallel_checkout + + ## Define parallelism addition for checkouts + # If pcheck is defined, no parallel checkouts + # If pcheck is not defined, default is to have parallel checkouts + if pcheck: + pc = "" + else: + pc = " &" + + ## Define operation of option(s) above + if args.verbose: + logging.basicCOnfig(level=logging.INFO) + else: + logging.basicConfig(level=logging.ERROR) + +#### Main +srcDir="src" +checkoutScriptName = "checkout.sh" +baremetalRun = False # This is needed if there are no bare metal runs + +## Split and store the platforms and targets in a list +plist = args.platform +tlist = args.target + +## Get the variables in the model yaml +freVars = varsfre.frevars(yml) + +## Open the yaml file and parse as fremakeYaml +modelYaml = yamlfre.freyaml(yml,freVars) +fremakeYaml = modelYaml.getCompileYaml() + +## Error checking the targets +for targetName in tlist: + target = targetfre.fretarget(targetName) + +## Loop through the platforms specified on the command line +## If the platform is a baremetal platform, write the checkout script and run it once +## This should be done separately and serially because bare metal platforms should all be using +## the same source code. +for platformName in plist: + if modelYaml.platforms.hasPlatform(platformName): + pass + else: + raise SystemExit (platformName + " does not exist in " + modelYaml.platformsfile) + (compiler,modules,modulesInit,fc,cc,modelRoot,iscontainer,mkTemplate,containerBuild,containerRun,RUNenv)=modelYaml.platforms.getPlatformFromName(platformName) + + ## Create the checkout script + if iscontainer == False: + ## Create the source directory for the platform + srcDir = modelRoot + "/" + fremakeYaml["experiment"] + "/src" + if not os.path.exists(srcDir): + os.system("mkdir -p " + srcDir) + if not os.path.exists(srcDir+"/checkout.sh"): + freCheckout = checkout.checkout("checkout.sh",srcDir) + freCheckout.writeCheckout(modelYaml.compile.getCompileYaml(),jobs,pc) + freCheckout.finish(pc) + +## TODO: Options for running on login cluster? + freCheckout.run() + +fremakeBuildList = [] +## Loop through platforms and targets +for platformName in plist: + for targetName in tlist: + target = targetfre.fretarget(targetName) + if modelYaml.platforms.hasPlatform(platformName): + pass + else: + raise SystemExit (platformName + " does not exist in " + modelYaml.platformsfile) + (compiler,modules,modulesInit,fc,cc,modelRoot,iscontainer,mkTemplate,containerBuild,containerRun,RUNenv)=modelYaml.platforms.getPlatformFromName(platformName) + + ## Make the source directory based on the modelRoot and platform + srcDir = modelRoot + "/" + fremakeYaml["experiment"] + "/src" + + ## Check for type of build + if iscontainer == False: + baremetalRun = True + ## Make the build directory based on the modelRoot, the platform, and the target + bldDir = modelRoot + "/" + fremakeYaml["experiment"] + "/" + platformName + "-" + target.gettargetName() + "/exec" + os.system("mkdir -p " + bldDir) + + ## Create the Makefile + freMakefile = makefilefre.makefile(exp = fremakeYaml["experiment"], + libs = fremakeYaml["baremetal_linkerflags"], + srcDir = srcDir, + bldDir = bldDir, + mkTemplatePath = mkTemplate) + + + # Loop through components and send the component name, requires, and overrides for the Makefile + for c in fremakeYaml['src']: + freMakefile.addComponent(c['component'],c['requires'],c['makeOverrides']) + freMakefile.writeMakefile() + +## Create a list of compile scripts to run in parallel + fremakeBuild = buildBaremetal.buildBaremetal(exp = fremakeYaml["experiment"], + mkTemplatePath = mkTemplate, + srcDir = srcDir, + bldDir = bldDir, + target = target, + modules = modules, + modulesInit = modulesInit, + jobs = jobs) + + for c in fremakeYaml['src']: + fremakeBuild.writeBuildComponents(c) + fremakeBuild.writeScript() + fremakeBuildList.append(fremakeBuild) + ## Run the build + fremakeBuild.run() + else: +#################################### container stuff below ########################################################### + ## Run the checkout script +# image="hpc-me-intel:2021.1.1" + image="ecpe4s/noaa-intel-prototype:2023.09.25" + bldDir = modelRoot + "/" + fremakeYaml["experiment"] + "/exec" + tmpDir = "tmp/"+platformName + + ## Create the checkout script + freCheckout = checkout.checkoutForContainer("checkout.sh", srcDir, tmpDir) + freCheckout.writeCheckout(modelYaml.compile.getCompileYaml(),jobs,pc) + freCheckout.finish(pc) + + ## Create the makefile +### Should this even be a separate class from "makefile" in makefilefre? ~ ejs + freMakefile = makefilefre.makefileContainer(exp = fremakeYaml["experiment"], + libs = fremakeYaml["container_addlibs"], + srcDir = srcDir, + bldDir = bldDir, + mkTemplatePath = mkTemplate, + tmpDir = tmpDir) + + # Loop through components and send the component name and requires for the Makefile + for c in fremakeYaml['src']: + freMakefile.addComponent(c['component'],c['requires'],c['makeOverrides']) + freMakefile.writeMakefile() + + ## Build the dockerfile + dockerBuild = buildDocker.container(base = image, + exp = fremakeYaml["experiment"], + libs = fremakeYaml["container_addlibs"], + RUNenv = RUNenv, + target = target) + + dockerBuild.writeDockerfileCheckout("checkout.sh", tmpDir+"/checkout.sh") + dockerBuild.writeDockerfileMakefile(freMakefile.getTmpDir() + "/Makefile", freMakefile.getTmpDir()+"/linkline.sh") + + for c in fremakeYaml['src']: + dockerBuild.writeDockerfileMkmf(c) + + dockerBuild.writeRunscript(RUNenv,containerRun,tmpDir+"/execrunscript.sh") + + ## Run the dockerfile; build the container + dockerBuild.build(containerBuild,containerRun) + + #freCheckout.cleanup() + #buildDockerfile(fremakeYaml,image) + +if baremetalRun: + if __name__ == '__main__': + pool = Pool(processes=nparallel) # Create a multiprocessing Pool + pool.map(buildBaremetal.fremake_parallel,fremakeBuildList) # process data_inputs iterable with pool diff --git a/lib/fre/make/gfdlfremake/makefilefre.py b/lib/fre/make/gfdlfremake/makefilefre.py new file mode 100644 index 00000000..dfe15fff --- /dev/null +++ b/lib/fre/make/gfdlfremake/makefilefre.py @@ -0,0 +1,204 @@ +import os +import textwrap + +def linklineBuild(self): + """ + Brief: Writes the link line for bare metal and container builds + Param: + - self The Makefile object + """ + linkline="" + +#if additional libraries are defined, populate the link line with the correct information for libraries +## CONTAINER; write a script that will execute in the container, to fill in link line with additional libraries in Makefile + if "tmp" in self.filePath: + with open(self.filePath+"/linkline.sh","a") as fh: + fh.write("set -- ") + for l in self.l: + fh.write(l+" ") + fh.write("\n") + + self.linklinecreate = ''' + line='' + for l in $@; do + loc=$(spack location -i $l) + libraries=$(ls $loc/lib) + if echo "$libraries" | grep -q "_d"; then + for i in $libraries; do + if [ "$i" != "cmake" ] && echo "$i" | grep -q "_d"; then + ln1=${i%.*} + ln2=${ln1#???} + line=$line" -L$loc/lib -l$ln2" + fi + done + else + for i in $libraries; do + if [ "$i" != "cmake" ]; then + ln1=${i%.*} + ln2=${ln1#???} + line=$line" -L$loc/lib -l$ln2" + fi + done + fi + done + ''' + + with open(self.filePath+"/linkline.sh","a") as fh: + fh.writelines(textwrap.dedent(self.linklinecreate)) + fh.write("MF_PATH='/apps/"+self.e+"/exec/Makefile'\n") + fh.write('sed -i "/MK_TEMPLATE = /a LL = $line" $MF_PATH\n') + fh.write("sed -i 's|\($^\) \($(LDFLAGS)\)|\\1 $(LL) \\2|' $MF_PATH\n") + +## BARE METAL; if addlibs defined on bare metal, include those additional libraries in link line + elif "tmp" not in self.filePath: + for l in self.l: # baremetal_linkerflags + linkline = linkline + " " + l + os.system(f"sed -i '/MK_TEMPLATE = /a LL = {linkline}' {self.filePath}/Makefile") + os.system(f"sed -i 's|\($(LDFLAGS)\)|$(LL) \\1|' {self.filePath}/Makefile") + +class makefile(): + def __init__(self,exp,libs,srcDir,bldDir,mkTemplatePath): + """ + Brief: Opens Makefile and sets the experiment and other common variables + Param: + - self The Makefile object + - exp Experiment name + - libs Additional libraries/linker flags defined by user + - srcDir The path to the source directory + - bldDir The path to the build directory + - mkTemplatePath The path of the template .mk file for compiling + """ + self.e = exp + self.l = libs + self.src = srcDir + self.bld = bldDir + self.template = mkTemplatePath + self.c =[] #components + self.r=[] #requires + self.o=[] #overrides + os.system("mkdir -p "+self.bld) + self.filePath = self.bld # Needed so that the container and bare metal builds can + # use the same function to create the Makefile + + def addComponent (self,c,r,o): + """ + Brief: Adds a component and corresponding requires to the list + Param: + - self The Makefile object + - c The component + - r The requires for that componenet + - o The overrides for that component + """ + self.c.append(c) + self.r.append(r) + self.o.append(o) + + def createLibstring (self,c,r,o): + """ + Brief: Sorts the component by how many requires there are for that component + Param: + - self The Makefile object + - c The component + - r The requires for that component + - o The overrides for that component + """ + # org_comp : returns a zip object + org_comp = zip(self.c,self.r,self.o) + # Sort zip object so that the component with the most requires (self.r) is listed first, and so on + sort = sorted(org_comp,key=lambda values:len(values[1]),reverse=True) + + return sort + + def writeMakefile (self): + """ + Brief: Writes the Makefile. Should be called after all components are added + Param: + - self The Makefile object + """ + # Get the list of all of the libraries + sd=self.createLibstring(self.c,self.r,self.o) + libstring=" " + for i in sd: + lib=i[0] + libstring = libstring+lib+"/lib"+lib+".a " + + # Open the Makefile for Writing + with open(self.filePath+"/Makefile","w") as fh: + # Write the header information for the Makefile + fh.write("# Makefile for "+self.e+"\n") + fh.write("SRCROOT = "+self.src+"/\n") + fh.write("BUILDROOT = "+self.bld+"/\n") + fh.write("MK_TEMPLATE = "+self.template+"\n") + fh.write("include $(MK_TEMPLATE)"+"\n") + + # Write the main experiment compile + fh.write(self.e+".x: "+libstring+"\n") + fh.write("\t$(LD) $^ $(LDFLAGS) -o $@ $(STATIC_LIBS)"+"\n") + + # Write the link line script with user-provided libraries + if self.l: + linklineBuild(self) + + # Write the individual component library compiles + with open(self.filePath+"/Makefile","a") as fh: + for (c,r,o) in sd: + libstring = " " + for lib in r: + libstring = libstring+lib+"/lib"+lib+".a " + cstring = c+"/lib"+c+".a: " + fh.write(cstring+libstring+" FORCE"+"\n") + if o == "": + fh.write("\t$(MAKE) SRCROOT=$(SRCROOT) BUILDROOT=$(BUILDROOT) MK_TEMPLATE=$(MK_TEMPLATE) --directory="+c+" $(@F)\n") + else: + fh.write("\t$(MAKE) SRCROOT=$(SRCROOT) BUILDROOT=$(BUILDROOT) MK_TEMPLATE=$(MK_TEMPLATE) "+o+" --directory="+c+" $(@F)\n") + fh.write("FORCE:\n") + fh.write("\n") + + # Set up the clean + fh.write("clean:\n") + for c in self.c: + fh.write("\t$(MAKE) --directory="+c+" clean\n") + + # Set up localize + fh.write("localize:\n") + for c in self.c: + fh.write("\t$(MAKE) -f $(BUILDROOT)"+c+" localize\n") + + # Set up distclean + fh.write("distclean:\n") + for c in self.c: + fh.write("\t$(RM) -r "+c+"\n") + fh.write("\t$(RM) -r "+self.e+"\n") + fh.write("\t$(RM) -r Makefile \n") + +### This seems incomplete? ~ ejs +## The makefile class for a container. It gets built into a temporary directory so it can be copied +## into the container. +## \param exp Experiment name +## \param libs Additional libraries/linker flags defined by user +## \param srcDir The path to the source directory +## \param bldDir The path to the build directory +## \param mkTemplatePath The path of the template .mk file for compiling +## \param tmpDir A local path to temporarily store files build to be copied to the container +class makefileContainer(makefile): + def __init__(self,exp,libs,srcDir,bldDir,mkTemplatePath,tmpDir): + self.e = exp + self.l = libs + self.src = srcDir + self.bld = bldDir + self.template = mkTemplatePath + self.tmpDir = tmpDir + self.c =[] #components + self.r=[] #requires + self.o=[] #overrides + os.system("mkdir -p "+self.tmpDir) + self.filePath = self.tmpDir # Needed so that the container and bare metal builds can + # use the same function to create the Makefile + + def getTmpDir(self): + """ + Brief: Return the tmpDir + Param: + - self The makefile object + """ + return self.tmpDir diff --git a/lib/fre/make/gfdlfremake/platformfre.py b/lib/fre/make/gfdlfremake/platformfre.py new file mode 100644 index 00000000..4f8d0eed --- /dev/null +++ b/lib/fre/make/gfdlfremake/platformfre.py @@ -0,0 +1,108 @@ +import yaml + +class platforms (): + def __init__(self,platforminfo): + """ + Param: + - self The platform yaml object + - platforminfo dictionary with platform information + from the combined yaml + """ + self.yaml = platforminfo + + ## Check the yaml for errors/omissions + ## Loop through the platforms + for p in self.yaml: + ## Check the platform name + try: + p["name"] + except: + raise Exception("At least one of the platforms is missing a name in "+fname+"\n") + ## Check the compiler + try: + p["compiler"] + except: + raise Exception("You must specify a compiler in your "+p["name"]+" platform in the file "+fname+"\n") + ## Check for the Fortran (fc) and C (cc) compilers + try: + p["fc"] + except: + raise Exception("You must specify the name of the Fortran compiler as fc on the "+p["name"]+" platform in the file "+fname+"\n") + try: + p["cc"] + except: + raise Exception("You must specify the name of the Fortran compiler as cc on the "+p["name"]+" platform in the file "+fname+"\n") + ## Check for modules to load + try: + p["modules"] + except: + p["modules"]=[""] + ## Check for modulesInit to set up the modules environment + try: + p["modulesInit"] + except: + p["modulesInit"]=[""] + ## Get the root for the build + try: + p["modelRoot"] + except: + p["modelRoot"] = "/apps" + ## Check if we are working with a container and get the info for that + try: + p["container"] + except: + p["container"] = False + p["RUNenv"] = [""] + p["containerBuild"] = "" + p["containerRun"] = "" + if p["container"]: + ## Check the container builder + try: + p["containerBuild"] + except: + raise Exception("You must specify the program used to build the container (containerBuild) on the "+p["name"]+" platform in the file "+fname+"\n") + if p["containerBuild"] != "podman" and p["containerBuild"] != "docker": + raise ValueError("Container builds only supported with docker or podman, but you listed "+p["containerBuild"]+"\n") + ## Check for container environment set up for RUN commands + try: + p["RUNenv"] + except: + p["RUNenv"] = "" + ## Check the container runner + try: + p["containerRun"] + except: + raise Exception("You must specify the program used to run the container (containerRun) on the "+p["name"]+" platform in the file "+fname+"\n") + if p["containerRun"] != "apptainer" and p["containerRun"] != "singularity": + raise ValueError("Container builds only supported with apptainer, but you listed "+p["containerRun"]+"\n") + ## set the location of the mkTemplate. + ## In a container, it uses the hpc-me template cloned from mkmf + p["mkTemplate"] = "/apps/mkmf/templates/hpcme-intel21.mk" + else: + try: + p["mkTemplate"] + except: + raise ValueError("The non-container platform "+p["name"]+" must specify a mkTemplate \n") + + def hasPlatform(self,name): + """ + Brief: Checks if the platform yaml has the named platform + """ + for p in self.yaml: + if p["name"] == name: + return True + return False + + def getPlatformsYaml(self): + """ + Brief: Get the platform yaml + """ + return self.yaml + + def getPlatformFromName(self,name): + """ + Brief: Get the platform information from the name of the platform + """ + for p in self.yaml: + if p["name"] == name: + return (p["compiler"], p["modules"], p["modulesInit"], p["fc"], p["cc"], p["modelRoot"],p["container"], p["mkTemplate"],p["containerBuild"], p["containerRun"], p["RUNenv"]) diff --git a/lib/fre/make/gfdlfremake/targetfre.py b/lib/fre/make/gfdlfremake/targetfre.py new file mode 100644 index 00000000..0b2b5964 --- /dev/null +++ b/lib/fre/make/gfdlfremake/targetfre.py @@ -0,0 +1,81 @@ +class fretarget: + """ + Class: Stores information about the target + """ + def __init__(self,t): + """ + Brief: Sets up information about the target and handles errors + Note: The default target is prod + Param: + - self the fretarget object + - t The target string + """ + self.target = t # The target string + + ## Split the target string + targ = self.target.split('-') + self.makeline_add = "" + self.debug = False + self.repro = False + self.prod = False + + ## Parse the target string for prod, repro, and debug. Set up what to add to the + ## make line during compile when using mkmf builds + for target in targ: + if target == "debug": + targ = target.upper() + self.makeline_add = self.makeline_add + targ + "=on " + self.debug = True + elif target == "prod": + targ = target.upper() + self.makeline_add = self.makeline_add + targ + "=on " + self.prod = True + elif target == "repro": + targ = target.upper() + self.makeline_add = self.makeline_add + targ + "=on " + self.repro = True + + ## Check to see if openmp is included in the target and add that to the makeline add string + if target == "openmp": + targ = target.upper() + self.makeline_add = self.makeline_add + targ + "=on " + self.openmp = True + else: + self.openmp = False + + ## Check to make sure only one of the prod, debug, repro are used + errormsg = "You can only list one mutually exclusive target, but your target '"+self.target+"' lists more than one of the following targets: \n debug \n prod \n repro" + if self.debug: + try: + if self.repro or self.prod == True: + raise ValueError(errormsg) + except ValueError: + raise + elif self.repro: + try: + if self.prod == True: + raise ValueError(errormsg) + except ValueError: + raise + else: + try: + if self.prod == False: + raise ValueError("Your target '"+self.target+"' needs to include one of the following: prod, repro, debug") + except ValueError: + raise + + def gettargetName(self): + """ + Brief: Returns the name of the target + Param: + - self The fretarget object + """ + return self.target + + def getmakeline_add(self): + """ + Brief: Returns the makeline_add + Param: + - self The fretarget object + """ + return self.makeline_add diff --git a/lib/fre/make/gfdlfremake/varsfre.py b/lib/fre/make/gfdlfremake/varsfre.py new file mode 100644 index 00000000..c153e845 --- /dev/null +++ b/lib/fre/make/gfdlfremake/varsfre.py @@ -0,0 +1,56 @@ +import yaml +import os +import re + +## Removes enclosing braces or parentheses and optionally leading dollar sign from a string +## \param inString A string from which characters will be removed +## \param bound A set of characters to be removed from the beginning and end of the string +## \param leadingDollar (optional) If True, removed leading dollar sign +def removeEnclosing(inString, bound='()', leadingDollar=True): + if leadingDollar: + inString = inString.lstrip('$') + return inString.lstrip(bound[0]).rstrip(bound[1]) + +## Retrieves an environmental variable based on a string of the form $(VAR) +## \param inString A string that specifies the variable to be retrieved +def getEnvSub(inString): + return os.getenv(removeEnclosing(inString.group(), bound='{}')) + +## Replaces all instances of Linux environment variables (${VAR}) in a string using getEnvSub +## \param string A string to have the variables replaced +def envReplace(string): + return re.sub("\$\{\w+\}", getEnvSub, string) + +## Reads stores and replaces the fre variables set in the model YAML +class frevars(): +## Grabs the FRE variables from the model yaml +## \param self The frevars object +## \param y The model yaml file name + def __init__(self,y): + with open(y, 'r') as file: + self.modelyaml = yaml.safe_load(file) +## Substitutes fre variables with the format $(variable) into the input string +## \string The string that contains the variables +## \returns String with the fre variables filled in + def freVarReplace(self,string): + ## Retrieves a value from the modelyaml based on a key of the form $(VAR) + ## \param inString A string that specifies the value to be retrieved + def getVarYamlSub(inString): + return self.modelyaml[removeEnclosing(inString.group())] + return re.sub("\$\(\w+\)", getVarYamlSub, string) + +## Wrapper that relaces environment ${} and FRE $() variables +## \param self the FRE yaml varaibles (FRE properties) +## \param string The YAML string that is having its variables replaced +## \returns string with the environment and FRE variables replaced + def freVarSub(self, string): + tmpString = envReplace(string) + returnString = self.freVarReplace(tmpString) + return returnString + +## Wrapper that takes in a string (yaml) and fills in the FRE and Environment variables +## \param y Path to yaml file whose variables need to be filled in + def fillInYamlWithVars(self,y): + with open(y, 'r') as file: + yamlString=read(file) + return self.freVarSub(yamlString) diff --git a/lib/fre/make/gfdlfremake/yamlfre.py b/lib/fre/make/gfdlfremake/yamlfre.py new file mode 100644 index 00000000..72458a8c --- /dev/null +++ b/lib/fre/make/gfdlfremake/yamlfre.py @@ -0,0 +1,199 @@ +import os +import json +from pathlib import Path +import yaml +from jsonschema import validate, ValidationError, SchemaError +from . import platformfre + +def parseCompile(fname,v): + """ + Brief: Open the yaml file and parse as fremakeYaml + Param: + - fname the name of the yaml file to parse + - v the FRE yaml variables + """ + # Open the yaml file and parse as fremakeYaml + with open(fname, 'r') as yamlfile: + y = yaml.safe_load(v.freVarSub(yamlfile.read())) + + return y + +##### THIS SEEMS UNUSED +## \brief Checks the yaml for variables. Required variables will dump and error. Non-required variables will +## set a default value for the variable +#def yamlVarCheck(var,val="",req=False,err="error"): +# """ +# Brief: Checks the yaml for variables. Required variables will dump and error. +# Non-required variables will set a default value for the variable +# Param: +# - var A variable in the yaml +# - val a default value for var +# - req if true, the variable is required in the yaml and an exception will be raised +# - err An error message to print if the variable is required and doesn't exist +# """ +# try: +# var +# except: +# if req: +# print (err) +# raise +# else: +# var = val + +class compileYaml(): + """ + Brief: This will read the compile yaml for FRE and then fill in any of the missing non-required variables + """ + def __init__(self,compileinfo): + """ + Brief: Read get the compile yaml and fill in the missing pieces + Param: + - self the compile Yaml object + - compileinfo dictionary with compile information from the combined yaml + """ + # compile information from the combined yaml + self.yaml = compileinfo + + ## Check the yaml for required things + ## Check for required experiment name + try: + self.yaml["experiment"] + except: + print("You must set an experiment name to compile \n") + raise + ## Check for optional libraries and packages for linking in container + try: + self.yaml["container_addlibs"] + except: + self.yaml["container_addlibs"]="" + ## Check for optional libraries and packages for linking on bare-metal system + try: + self.yaml["baremetal_linkerflags"] + except: + self.yaml["baremetal_linkerflags"]="" + ## Check for required src + try: + self.yaml["src"] + except: + print("You must set a src to specify the sources in modelRoot/"+self.yaml["experiment"]+"\n") + raise + ## Loop through the src array + for c in self.yaml['src']: + ## Check for required componenet name + try: + c['component'] + except: + print("You must set the 'componet' name for each src component") + raise + ## Check for required repo url + try: + c['repo'] + except: + print("'repo' is missing from the component "+c['component']+" in "+self.yaml["experiment"]+"\n") + raise + # Check for optional branch. Otherwise set it to blank + try: + c['branch'] + except: + c['branch']="" + # Check for optional cppdefs. Otherwise set it to blank + try: + c['cppdefs'] + except: + c['cppdefs']="" + # Check for optional doF90Cpp. Otherwise set it to False + try: + c['doF90Cpp'] + except: + c['doF90Cpp']=False + # Check for optional additional instructions. Otherwise set it to blank + try: + c['additionalInstructions'] + except: + c['additionalInstructions']="" + # Check for optional paths. Otherwise set it to blank + try: + c['paths'] + except: + c['paths']=[c['component']] + # Check for optional requires. Otherwise set it to blank + try: + c['requires'] + except: + c['requires']=[] + # Check for optional overrides. Otherwise set it to blank + try: + c['makeOverrides'] + except: + c['makeOverrides']="" + # Check for optional flags. Otherwise set it to blank. + try: + c["otherFlags"] + except: + c["otherFlags"]="" + + def getCompileYaml(self): + """ + Brief: Returns the compile yaml + """ + try: + self.yaml + except: + print ("You must initialize the compile YAML object before you try to get the yaml \n") + raise + return self.yaml + +class freyaml(): + """ + Brief: This will take the combined yaml file, parse information, and fill in missing variables + to make the full freyaml that can be used and checked + Note: + - platformYaml: platforms.yaml + - compileYaml: compile.yaml + """ + def __init__(self,combinedyaml,v): + """ + Param: + - self The freyaml object + - combinedyaml The name of the combined yaml file + - v FRE yaml variables + """ + self.combinedfile = combinedyaml + + self.freyaml = parseCompile(self.combinedfile, v) + + #get compile info + self.compiledict = self.freyaml.get("compile") + self.compile = compileYaml(self.compiledict) + self.compileyaml = self.compile.getCompileYaml() + + #self.freyaml.update(self.compileyaml) + + #get platform info + self.platformsdict = self.freyaml.get("platforms") + self.platforms = platformfre.platforms(self.platformsdict) + self.platformsyaml = self.platforms.getPlatformsYaml() + + #self.freyaml.update(self.platformsyaml) + + ## VALIDATION OF COMBINED YAML FOR COMPILATION + fremake_package_dir = Path(__file__).resolve().parents[2] + schema_path = os.path.join(fremake_package_dir, 'gfdl_msd_schemas', 'FRE', 'fre_make.json') + with open(schema_path, 'r') as f: + s = f.read() + schema = json.loads(s) + + validate(instance=self.freyaml,schema=schema) + print("\nCOMBINED YAML VALID") + + def getCompileYaml(self): + """ + Brief: Returns the compile yaml + """ + return self.compileyaml + + def getPlatformsYaml(self): + """ + Brief: Returns the compile yaml + """ + return self.platformsyaml diff --git a/lib/fre/make/gfdlfremake/yamls/SHiELD_example/SHiELD.yaml b/lib/fre/make/gfdlfremake/yamls/SHiELD_example/SHiELD.yaml new file mode 100644 index 00000000..9f063d0a --- /dev/null +++ b/lib/fre/make/gfdlfremake/yamls/SHiELD_example/SHiELD.yaml @@ -0,0 +1,10 @@ +platformYaml: platforms.yaml +compileYaml: compile.yaml +fv3_release: main +phy_release: main +fms_release: "2023.02" +drivers_release: main +coupler_release: "2023.02" +FMSincludes: "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" +momIncludes: "-Imom6/MOM6-examples/src/MOM6/pkg/CVMix-src/include" +INTEL: intel-classic diff --git a/lib/fre/make/gfdlfremake/yamls/SHiELD_example/compile.yaml b/lib/fre/make/gfdlfremake/yamls/SHiELD_example/compile.yaml new file mode 100644 index 00000000..a83bb1ce --- /dev/null +++ b/lib/fre/make/gfdlfremake/yamls/SHiELD_example/compile.yaml @@ -0,0 +1,38 @@ +experiment: shield_nh +compileInclude: "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" +container_addlibs: ["bacio","sp","w3emc","w3nco"] +baremetal_addlibs: ["-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/bacio-2.4.1-wrykbu2/lib -lbacio_4", "-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/bacio-2.4.1-wrykbu2/lib -lbacio_8", "-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/sp-2.5.0-7bumbmx/lib64 -lsp_d", "-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/w3emc-2.10.0-zmuykep/lib64 -lw3emc_d", "-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/w3nco-2.4.1-76qm6h2/lib -lw3nco_d"] +src: + - component: "FMS" + repo: "https://github.com/NOAA-GFDL/FMS.git" + cppdefs: "-Duse_libMPI -Duse_netCDF -Duse_LARGEFILE -DHAVE_SCHED_GETAFFINITY -DINTERNAL_FILE_NML -DGFS_PHYS -DGFS_CONSTANTS -DHAVE_GETTID" + branch: "$(fms_release)" + - component: "SHiELD_physics" + requires: ["FMS"] + repo: "https://github.com/NOAA-GFDL/SHiELD_physics.git" + branch: "$(phy_release)" + paths: [SHiELD_physics/gsmphys, + SHiELD_physics/GFS_layer, + SHiELD_physics/IPD_layer] + cppdefs: "-Duse_libMPI -Duse_netCDF -DHAVE_SCHED_GETAFFINITY -DSPMD -Duse_LARGEFILE -DGFS_PHYS -DUSE_GFSL63 -DNEW_TAUCTMAX -DNEMS_GSM -DINTERNAL_FILE_NML -DMOIST_CAPPA -DUSE_COND" + otherFlags: "$(FMSincludes)" + - component: "fv3" + requires: ["FMS", "SHiELD_physics"] + repo: ["https://github.com/NOAA-GFDL/GFDL_atmos_cubed_sphere.git", + "https://github.com/NOAA-GFDL/atmos_drivers.git"] + cppdefs: "-Duse_libMPI -Duse_netCDF -DHAVE_SCHED_GETAFFINITY -DSPMD -Duse_LARGEFILE -DGFS_PHYS -DUSE_GFSL63 -DNEW_TAUCTMAX -DNEMS_GSM -DINTERNAL_FILE_NML -DMOIST_CAPPA -DUSE_COND" + branch: ["$(fv3_release)","$(drivers_release)"] + paths: [SHiELD_physics/FV3GFS/, + fv3/atmos_drivers/SHiELD/atmos_model.F90, + fv3/GFDL_atmos_cubed_sphere/driver/SHiELD/atmosphere.F90, + fv3/GFDL_atmos_cubed_sphere/tools/, + fv3/GFDL_atmos_cubed_sphere/model/, + fv3/GFDL_atmos_cubed_sphere/GFDL_tools/fv_diag_column.F90] + otherFlags: "$(FMSincludes)" + - component: "FMScoupler" + requires: ["FMS", "SHiELD_physics", "fv3"] + repo: "https://github.com/NOAA-GFDL/FMScoupler.git" + cppdefs: "-Duse_libMPI -Duse_netCDF -DHAVE_SCHED_GETAFFINITY -DSPMD -Duse_LARGEFILE -DGFS_PHYS -DUSE_GFSL63 -DNEW_TAUCTMAX -DNEMS_GSM -DINTERNAL_FILE_NML -DMOIST_CAPPA -DUSE_COND" + branch: "$(coupler_release)" + paths: ["FMScoupler/SHiELD/coupler_main.F90"] + otherFlags: "$(FMSincludes)" diff --git a/lib/fre/make/gfdlfremake/yamls/SHiELD_example/platforms.yaml b/lib/fre/make/gfdlfremake/yamls/SHiELD_example/platforms.yaml new file mode 100644 index 00000000..9f72043b --- /dev/null +++ b/lib/fre/make/gfdlfremake/yamls/SHiELD_example/platforms.yaml @@ -0,0 +1,26 @@ +platforms: + - name: ncrc5.intel + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: ["$(INTEL)/2022.2.1","fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/SHiELDtest + - name: ncrc5.intel23 + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: ["$(INTEL)/2023.1.0","fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/SHiELDtest + - name: hpcme.2023 + compiler: intel + RUNenv: [". /spack/share/spack/setup-env.sh", "spack load libyaml", "spack load netcdf-fortran@4.5.4", "spack load hdf5@1.12.1"] + modelRoot: /apps + fc: mpiifort + cc: mpiicc + container: True + containerBuild: "podman" + containerRun: "apptainer" diff --git a/lib/fre/make/gfdlfremake/yamls/am5.yaml b/lib/fre/make/gfdlfremake/yamls/am5.yaml new file mode 100644 index 00000000..4b7bf8d4 --- /dev/null +++ b/lib/fre/make/gfdlfremake/yamls/am5.yaml @@ -0,0 +1,6 @@ +platformYaml: platforms.yaml +compileYaml: compile.yaml +release: f1a1r1 +INTEL: "intel-classic" +FMSincludes: "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" +momIncludes: "-Imom6/MOM6-examples/src/MOM6/pkg/CVMix-src/include" diff --git a/lib/fre/make/gfdlfremake/yamls/compile.yaml b/lib/fre/make/gfdlfremake/yamls/compile.yaml new file mode 100644 index 00000000..5200599c --- /dev/null +++ b/lib/fre/make/gfdlfremake/yamls/compile.yaml @@ -0,0 +1,66 @@ +experiment: "am5" +container_addlibs: +baremetal_linkerflags: +src: + - component: "FMS" + repo: "https://github.com/NOAA-GFDL/FMS.git" + cppdefs: "-DINTERNAL_FILE_NML -Duse_libMPI -Duse_netCDF" + branch: "2022.01" + cppdefs: "-DHAVE_GETTID -Duse_libMPI -Duse_netCDF" + otherFlags: "$(FMSincludes)" + - component: "am5_phys" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/am5_phys.git" + branch: "2022.01" + otherFlags: "$(FMSincludes)" + - component: "GFDL_atmos_cubed_sphere" + requires: ["FMS", "am5_phys"] + repo: "https://github.com/NOAA-GFDL/GFDL_atmos_cubed_sphere.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE -DINTERNAL_FILE_NML" + branch: "2022.01" + paths: ["GFDL_atmos_cubed_sphere/driver/GFDL", + "GFDL_atmos_cubed_sphere/model", + "GFDL_atmos_cubed_sphere/driver/SHiELD/cloud_diagnosis.F90", + "GFDL_atmos_cubed_sphere/driver/SHiELD/gfdl_cloud_microphys.F90", + "GFDL_atmos_cubed_sphere/tools", + "GFDL_atmos_cubed_sphere/GFDL_tools"] + otherFlags: "$(FMSincludes)" + - component: "atmos_drivers" + requires: ["FMS", "am5_phys", "GFDL_atmos_cubed_sphere"] + repo: "https://github.com/NOAA-GFDL/atmos_drivers.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE" + branch: "2022.01" + paths: ["atmos_drivers/coupled"] + otherFlags: "$(FMSincludes)" + - component: "ice_sis" + requires: ["FMS", "ice_param", "mom6"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/ice_sis.git" + branch: "2021.02" + otherFlags: "$(FMSincludes) $(momIncludes)" + - component: "ice_param" + repo: "https://github.com/NOAA-GFDL/ice_param.git" + cppdefs: "-Duse_yaml -Duse_libMPI -Duse_netCDF" + branch: "2021.02" + requires: ["FMS", "mom6"] + otherFlags: "$(FMSincludes) $(momIncludes)" + - component: "land_lad2" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/land_lad2.git" + branch: "2022.01" + branch: "land_lad2_2021.02" + doF90Cpp: True + cppdefs: "-DINTERNAL_FILE_NML" + otherFlags: "$(FMSincludes)" + - component: "mom6" + requires: ["FMS"] + paths: ["mom6/MOM6-examples/src/MOM6/config_src/dynamic", "mom6/MOM6-examples/src/MOM6/config_src/coupled_driver", "mom6/MOM6-examples/src/MOM6/src/*/", "mom6/MOM6-examples/src/MOM6/src/*/*/", "mom6/ocean_BGC/generic_tracers", "mom6/ocean_BGC/mocsy/src"] + branch: ["2021.02","dev/gfdl/2018.04.06"] + repo: ["https://github.com/NOAA-GFDL/ocean_BGC.git","https://github.com/NOAA-GFDL/MOM6-examples.git"] + makeOverrides: 'OPENMP=""' + otherFlags: "$(FMSincludes) $(momIncludes)" + - component: "FMScoupler" + paths: ["FMScoupler/full", "FMScoupler/shared"] + repo: "https://github.com/NOAA-GFDL/FMScoupler.git" + branch: "2022.01" + requires: ["FMS", "atmos_drivers", "am5_phys", "land_lad2", "ice_sis", "ice_param", "mom6"] + otherFlags: "$(FMSincludes) $(momIncludes)" diff --git a/lib/fre/make/gfdlfremake/yamls/platforms.yaml b/lib/fre/make/gfdlfremake/yamls/platforms.yaml new file mode 100644 index 00000000..02b7d222 --- /dev/null +++ b/lib/fre/make/gfdlfremake/yamls/platforms.yaml @@ -0,0 +1,26 @@ +platforms: + - name: ncrc5.intel + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: ["$(INTEL)/2022.2.1","fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/test + - name: ncrc5.intel23 + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: ["$(INTEL)/2023.1.0","fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/test + - name: hpcme.2023 + compiler: intel + RUNenv: [". /spack/share/spack/setup-env.sh", "spack load libyaml", "spack load netcdf-fortran@4.5.4", "spack load hdf5@1.14.0"] + modelRoot: /apps + fc: mpiifort + cc: mpiicc + container: True + containerBuild: "podman" + containerRun: "apptainer" diff --git a/lib/fre/make/runFremake.py b/lib/fre/make/runFremake.py new file mode 100644 index 00000000..ffe2ec96 --- /dev/null +++ b/lib/fre/make/runFremake.py @@ -0,0 +1,212 @@ +#!/usr/bin/python3 +''' +date 2023 +author(s): Tom Robinson, Dana Singh, Bennett Chang +fre make is used to create, run and checkout code, and compile a model. +''' + +import os +import logging +from multiprocessing.dummy import Pool +from pathlib import Path +import click +import fre.yamltools.combine_yamls as cy +from .gfdlfremake import ( + targetfre, varsfre, yamlfre, checkout, + makefilefre, buildDocker, buildBaremetal ) + +def fremake_run(yamlfile,platform,target,parallel,jobs,no_parallel_checkout,verbose): + ''' run fremake via click''' + yml = yamlfile + name = yamlfile.split(".")[0] + nparallel = parallel + jobs = str(jobs) + pcheck = no_parallel_checkout + + if pcheck: + pc = "" + else: + pc = " &" + + if verbose: + logging.basicConfig(level=logging.INFO) + else: + logging.basicConfig(level=logging.ERROR) + + #### Main + srcDir="src" + checkoutScriptName = "checkout.sh" + baremetalRun = False # This is needed if there are no bare metal runs + + ## Split and store the platforms and targets in a list + plist = platform + tlist = target + + # Combined compile yaml file + combined = Path(f"combined-{name}.yaml") + + ## If combined yaml exists, note message of its existence + ## If combined yaml does not exist, combine model, compile, and platform yamls + full_combined = cy.combined_compile_existcheck(combined,yml,platform,target) + + ## Get the variables in the model yaml + freVars = varsfre.frevars(full_combined) + + ## Open the yaml file and parse as fremakeYaml + modelYaml = yamlfre.freyaml(full_combined,freVars) + fremakeYaml = modelYaml.getCompileYaml() + + ## Error checking the targets + for targetName in tlist: + target = targetfre.fretarget(targetName) + + ## Loop through the platforms specified on the command line + ## If the platform is a baremetal platform, write the checkout script and run it once + ## This should be done separately and serially because bare metal platforms should all be using + ## the same source code. + for platformName in plist: + if modelYaml.platforms.hasPlatform(platformName): + pass + else: + raise ValueError(f'{platformName} does not exist in ' + f'{modelYaml.combined.get("compile").get("platformYaml")}') + + ( compiler, modules, modulesInit, + fc, cc, modelRoot, iscontainer, + mkTemplate, containerBuild, ContainerRun, + RUNenv ) = modelYaml.platforms.getPlatformFromName(platformName) + + ## Create the checkout script + if not iscontainer: + ## Create the source directory for the platform + srcDir = modelRoot + "/" + fremakeYaml["experiment"] + "/src" + if not os.path.exists(srcDir): + os.system("mkdir -p " + srcDir) + if not os.path.exists(srcDir+"/checkout.sh"): + freCheckout = checkout.checkout("checkout.sh",srcDir) + freCheckout.writeCheckout(modelYaml.compile.getCompileYaml(),jobs,pc) + freCheckout.finish(pc) + os.chmod(srcDir+"/checkout.sh", 0o744) + ## TODO: Options for running on login cluster? + freCheckout.run() + + fremakeBuildList = [] + ## Loop through platforms and targets + for platformName in plist: + for targetName in tlist: + target = targetfre.fretarget(targetName) + if modelYaml.platforms.hasPlatform(platformName): + pass + else: + raise ValueError (platformName + " does not exist in " + modelYaml.platformsfile) + ( compiler, modules, modulesInit, + fc, cc, modelRoot, iscontainer, + mkTemplate, containerBuild, containerRun, + RUNenv ) = modelYaml.platforms.getPlatformFromName(platformName) + + ## Make the source directory based on the modelRoot and platform + srcDir = modelRoot + "/" + fremakeYaml["experiment"] + "/src" + + ## Check for type of build + if not iscontainer: + baremetalRun = True + ## Make the build directory based on the modelRoot, the platform, and the target + bldDir = f'{modelRoot}/{fremakeYaml["experiment"]}/' + \ + f'{platformName}-{target.gettargetName()}/exec' + os.system("mkdir -p " + bldDir) + + ## Create the Makefile + freMakefile = makefilefre.makefile(exp = fremakeYaml["experiment"], + libs = fremakeYaml["baremetal_linkerflags"], + srcDir = srcDir, + bldDir = bldDir, + mkTemplatePath = mkTemplate) + + + # Loop through components, send component name/requires/overrides for Makefile + for c in fremakeYaml['src']: + freMakefile.addComponent(c['component'],c['requires'],c['makeOverrides']) + freMakefile.writeMakefile() + + ## Create a list of compile scripts to run in parallel + fremakeBuild = buildBaremetal.buildBaremetal(exp = fremakeYaml["experiment"], + mkTemplatePath = mkTemplate, + srcDir = srcDir, + bldDir = bldDir, + target = target, + modules = modules, + modulesInit = modulesInit, + jobs = jobs) + + for c in fremakeYaml['src']: + fremakeBuild.writeBuildComponents(c) + fremakeBuild.writeScript() + fremakeBuildList.append(fremakeBuild) + ## Run the build + fremakeBuild.run() + else: + ###################### container stuff below ####################################### + ## Run the checkout script + # image="hpc-me-intel:2021.1.1" + image="ecpe4s/noaa-intel-prototype:2023.09.25" + bldDir = modelRoot + "/" + fremakeYaml["experiment"] + "/exec" + tmpDir = "tmp/"+platformName + + ## Create the checkout script + freCheckout = checkout.checkoutForContainer("checkout.sh", srcDir, tmpDir) + freCheckout.writeCheckout(modelYaml.compile.getCompileYaml(),jobs,pc) + freCheckout.finish(pc) + + ## Create the makefile + ### Should this even be a separate class from "makefile" in makefilefre? ~ ejs + freMakefile = makefilefre.makefileContainer(exp = fremakeYaml["experiment"], + libs = fremakeYaml["container_addlibs"], + srcDir = srcDir, + bldDir = bldDir, + mkTemplatePath = mkTemplate, + tmpDir = tmpDir) + + # Loop through components and send the component name and requires for the Makefile + for c in fremakeYaml['src']: + freMakefile.addComponent(c['component'],c['requires'],c['makeOverrides']) + freMakefile.writeMakefile() + + ## Build the dockerfile + dockerBuild = buildDocker.container(base = image, + exp = fremakeYaml["experiment"], + libs = fremakeYaml["container_addlibs"], + RUNenv = RUNenv, + target = target) + + dockerBuild.writeDockerfileCheckout("checkout.sh", tmpDir+"/checkout.sh") + dockerBuild.writeDockerfileMakefile(freMakefile.getTmpDir() + "/Makefile", + freMakefile.getTmpDir() + "/linkline.sh") + + for c in fremakeYaml['src']: + dockerBuild.writeDockerfileMkmf(c) + + dockerBuild.writeRunscript(RUNenv,containerRun,tmpDir+"/execrunscript.sh") + + ## Run the dockerfile; build the container + dockerBuild.build(containerBuild,containerRun) + + #freCheckout.cleanup() + #buildDockerfile(fremakeYaml,image) + + if baremetalRun: + if __name__ == '__main__': + # Create a multiprocessing Pool + pool = Pool(processes=nparallel) + # process data_inputs iterable with pool + pool.map(buildBaremetal.fremake_parallel,fremakeBuildList) + +@click.command() +def _fremake_run(yamlfile,platform,target,parallel,jobs,no_parallel_checkout,verbose): + ''' + Decorator for calling _fremake_run - allows the decorated version + of the function to be separate from the undecorated version + ''' + return fremake_run(yamlfile,platform,target,parallel,jobs,no_parallel_checkout,verbose) + +if __name__ == "__main__": + fremake_run() diff --git a/lib/fre/make/tests/AM5_example/am5.yaml b/lib/fre/make/tests/AM5_example/am5.yaml new file mode 100644 index 00000000..359755ec --- /dev/null +++ b/lib/fre/make/tests/AM5_example/am5.yaml @@ -0,0 +1,104 @@ +# reusable variables +fre_properties: + - &AM5_VERSION "am5f7b12r1" + - &FRE_STEM !join [am5/, *AM5_VERSION] + + # amip + - &EXP_AMIP_START "1979" + - &EXP_AMIP_END "2020" + - &ANA_AMIP_START "1980" + - &ANA_AMIP_END "2020" + + - &PP_AMIP_CHUNK96 "1yr" + - &PP_AMIP_CHUNK384 "1yr" + - &PP_XYINTERP96 "180,288" + - &PP_XYINTERP384 "720,1152" + + # climo + - &EXP_CLIMO_START96 "0001" + - &EXP_CLIMO_END96 "0011" + - &ANA_CLIMO_START96 "0002" + - &ANA_CLIMO_END96 "0011" + + - &EXP_CLIMO_START384 "0001" + - &EXP_CLIMO_END384 "0006" + - &ANA_CLIMO_START384 "0002" + - &ANA_CLIMO_END384 "0006" + + # coupled + - &PP_CPLD_CHUNK_A "5yr" + - &PP_CPLD_CHUNK_B "20yr" + + # grids + - &GRID_SPEC96 "/archive/oar.gfdl.am5/model_gen5/inputs/c96_grid/c96_OM4_025_grid_No_mg_drag_v20160808.tar" + + # compile information + - &release "f1a1r1" + - &INTEL "intel-classic" + - &FMSincludes "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" + - &momIncludes "-Imom6/MOM6-examples/src/MOM6/pkg/CVMix-src/include" + +build: + # compile information + compileYaml: "compile.yaml" + platformYaml: "yaml_include/platforms.yaml" + +shared: + # directories shared across tools + directories: &shared_directories + history_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, history] + pp_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, pp] + analysis_dir: !join [/nbhome/$USER/, *FRE_STEM, /, *name] + ptmp_dir: "/xtmp/$USER/ptmp" + fre_analysis_home: "/home/fms/local/opt/fre-analysis/test" + + # shared pp settings + postprocess: + settings: &shared_settings + history_segment: "P1Y" + site: "ppan" + switches: &shared_switches + do_statics: True + do_timeavgs: True + clean_work: True + do_refinediag: False + do_atmos_plevel_masking: True + do_preanalysis: False + do_analysis: True + +experiments: + - name: "c96L65_am5f7b12r1_amip" + pp: + - "yaml_include/pp.c96_amip.yaml" + - name: "c96L65_am5f7b12r1_pdclim1850F" + pp: + - "yaml_include/pp.c96_clim.yaml" + - name: "c96L65_am5f7b12r1_pdclim2010F" + pp: + - "yaml_include/pp.c96_clim.yaml" + - name: "c96L65_am5f7b12r1_pdclim2010AERF" + pp: + - "yaml_include/pp.c96_clim.yaml" + - name: "c384L65_am5f7b12r1_amip" + pp: + - "yaml_include/pp.c384_amip.yaml" + - name: "c384L65_am5f7b12r1_pdclim2010F" + pp: + - "yaml_include/pp.c384_clim.yaml" + - name: "c384L65_am5f7b12r1_pdclim1850F" + pp: + - "yaml_include/pp.c384_clim.yaml" + - name: "c384L65_am5f7b12r1_pdclim2010AERF" + pp: + - "yaml_include/pp.c384_clim.yaml" + - name: "c384L65_am5f7b12r1_OM4_p25_piControl_noBLING_DynVeg" + pp: + - "yaml_include/pp.c384_amip.yaml" + - "yaml_include/pp.om4.yaml" + - name: "c96L65_am5f7b12r1_OM4_p25_piControl_noBLING_DynVeg" + pp: + - "yaml_include/pp.c96_amip.yaml" + - "yaml_include/pp.om4.yaml" + - name: "c96L65_am5f7b12r1_amip_cosp" + pp: + - "yaml_include/pp.c96_amip.yaml" diff --git a/lib/fre/make/tests/AM5_example/compile.yaml b/lib/fre/make/tests/AM5_example/compile.yaml new file mode 100644 index 00000000..5f9a361b --- /dev/null +++ b/lib/fre/make/tests/AM5_example/compile.yaml @@ -0,0 +1,67 @@ +compile: + experiment: "am5" + container_addlibs: + baremetal_linkerflags: + src: + - component: "FMS" + repo: "https://github.com/NOAA-GFDL/FMS.git" + cppdefs: "-DINTERNAL_FILE_NML -Duse_libMPI -Duse_netCDF" + branch: "2022.01" + cppdefs: "-DHAVE_GETTID -Duse_libMPI -Duse_netCDF" + otherFlags: *FMSincludes + - component: "am5_phys" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/am5_phys.git" + branch: "2022.01" + otherFlags: *FMSincludes + - component: "GFDL_atmos_cubed_sphere" + requires: ["FMS", "am5_phys"] + repo: "https://github.com/NOAA-GFDL/GFDL_atmos_cubed_sphere.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE -DINTERNAL_FILE_NML" + branch: "2022.01" + paths: ["GFDL_atmos_cubed_sphere/driver/GFDL", + "GFDL_atmos_cubed_sphere/model", + "GFDL_atmos_cubed_sphere/driver/SHiELD/cloud_diagnosis.F90", + "GFDL_atmos_cubed_sphere/driver/SHiELD/gfdl_cloud_microphys.F90", + "GFDL_atmos_cubed_sphere/tools", + "GFDL_atmos_cubed_sphere/GFDL_tools"] + otherFlags: *FMSincludes + - component: "atmos_drivers" + requires: ["FMS", "am5_phys", "GFDL_atmos_cubed_sphere"] + repo: "https://github.com/NOAA-GFDL/atmos_drivers.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE" + branch: "2022.01" + paths: ["atmos_drivers/coupled"] + otherFlags: *FMSincludes + - component: "ice_sis" + requires: ["FMS", "ice_param", "mom6"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/ice_sis.git" + branch: "2021.02" + otherFlags: !join [*FMSincludes, " ", *momIncludes] + - component: "ice_param" + repo: "https://github.com/NOAA-GFDL/ice_param.git" + cppdefs: "-Duse_yaml -Duse_libMPI -Duse_netCDF" + branch: "2021.02" + requires: ["FMS", "mom6"] + otherFlags: !join [*FMSincludes," ", *momIncludes] + - component: "land_lad2" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/land_lad2.git" + branch: "2022.01" + branch: "land_lad2_2021.02" + doF90Cpp: True + cppdefs: "-DINTERNAL_FILE_NML" + otherFlags: *FMSincludes + - component: "mom6" + requires: ["FMS"] + paths: ["mom6/MOM6-examples/src/MOM6/config_src/dynamic", "mom6/MOM6-examples/src/MOM6/config_src/coupled_driver", "mom6/MOM6-examples/src/MOM6/src/*/", "mom6/MOM6-examples/src/MOM6/src/*/*/", "mom6/ocean_BGC/generic_tracers", "mom6/ocean_BGC/mocsy/src"] + branch: ["2021.02","dev/gfdl/2018.04.06"] + repo: ["https://github.com/NOAA-GFDL/ocean_BGC.git","https://github.com/NOAA-GFDL/MOM6-examples.git"] + makeOverrides: 'OPENMP=""' + otherFlags: !join [*FMSincludes, " ", *momIncludes] + - component: "FMScoupler" + paths: ["FMScoupler/full", "FMScoupler/shared"] + repo: "https://github.com/NOAA-GFDL/FMScoupler.git" + branch: "2022.01" + requires: ["FMS", "atmos_drivers", "am5_phys", "land_lad2", "ice_sis", "ice_param", "mom6"] + otherFlags: !join [*FMSincludes, " ", *momIncludes] diff --git a/lib/fre/make/tests/AM5_example/yaml_include/platforms.yaml b/lib/fre/make/tests/AM5_example/yaml_include/platforms.yaml new file mode 100644 index 00000000..60d1aad2 --- /dev/null +++ b/lib/fre/make/tests/AM5_example/yaml_include/platforms.yaml @@ -0,0 +1,26 @@ +platforms: + - name: ncrc5.intel + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [ !join [*INTEL, "/2022.2.1"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: !join ["/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/", *INTEL, ".mk"] + modelRoot: ${HOME}/fremake_canopy/test + - name: ncrc5.intel23 + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [!join [*INTEL, "/2023.1.0"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: !join ["/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/", *INTEL, ".mk"] + modelRoot: ${HOME}/fremake_canopy/test + - name: hpcme.2023 + compiler: intel + RUNenv: [". /spack/share/spack/setup-env.sh", "spack load libyaml", "spack load netcdf-fortran@4.5.4", "spack load hdf5@1.14.0"] + modelRoot: /apps + fc: mpiifort + cc: mpiicc + container: True + containerBuild: "podman" + containerRun: "apptainer" diff --git a/lib/fre/make/tests/AM5_example/yaml_include/pp.c96_amip.yaml b/lib/fre/make/tests/AM5_example/yaml_include/pp.c96_amip.yaml new file mode 100644 index 00000000..117c66c6 --- /dev/null +++ b/lib/fre/make/tests/AM5_example/yaml_include/pp.c96_amip.yaml @@ -0,0 +1,88 @@ +# local reusable variable overrides +fre_properties: + - &custom_interp "180,360" + +# directory overrides +#c96_amip_directories: +directories: + <<: *shared_directories + ptmp_dir: "/ptmp/$USER" + pp_grid_spec: *GRID_SPEC96 + +#c96_amip_postprocess: +postprocess: + # pp setting overrides + settings: + <<: *shared_settings + pp_start: *ANA_AMIP_START + pp_stop: *ANA_AMIP_END + pp_chunk_a: *PP_AMIP_CHUNK96 + pp_components: "atmos atmos_scalar" + switches: + <<: *shared_switches + do_statics: False + + # main pp instructions + components: + - type: "atmos_cmip" + sources: "atmos_month_cmip atmos_8xdaily_cmip atmos_daily_cmip" + sourceGrid: "cubedsphere" + xyInterp: *custom_interp + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos" + sources: "atmos_month" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_level_cmip" + sources: "atmos_level_cmip" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_level" + sources: "atmos_month" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_month_aer" + sources: "atmos_month_aer" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'atmos' + - type: "atmos_diurnal" + sources: "atmos_diurnal" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_scalar" + sources: "atmos_scalar" + - type: "aerosol_cmip" + xyInterp: *PP_XYINTERP96 + sources: "aerosol_month_cmip" + sourceGrid: "cubedsphere" + interpMethod: "conserve_order1" + inputRealm: 'atmos' + - type: "land" + sources: "land_month" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'land' + - type: "land_cmip" + sources: "land_month_cmip" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'land' + - type: "tracer_level" + sources: "atmos_tracer" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'atmos' diff --git a/lib/fre/make/tests/ESM4_example/compile.yaml b/lib/fre/make/tests/ESM4_example/compile.yaml new file mode 100644 index 00000000..ee51658d --- /dev/null +++ b/lib/fre/make/tests/ESM4_example/compile.yaml @@ -0,0 +1,124 @@ +compile: + experiment: "esm4" + container_addlibs: + baremetal_linkerflags: + src: + - component: "FMS" + repo: "https://github.com/NOAA-GFDL/FMS.git" + cppdefs: "-Duse_libMPI -Duse_netCDF -DMAXFIELDMETHODS_=500" + branch: *FMS_GIT_TAG + - component: "atmos_phys" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/atmos_phys.git" + branch: *ATM_PHYS_GIT_TAG + otherFlags: *FMSincludes + - component: "atmos_dyn" + requires: ["FMS", "atmos_phys"] + repo: "https://github.com/NOAA-GFDL/GFDL_atmos_cubed_sphere.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE -DINTERNAL_FILE_NML" + otherFlags: *FMSincludes + branch: *ATM_FV3_GIT_TAG + paths: [ "atmos_dyn/driver/GFDL", + "atmos_dyn/model", + "atmos_dyn/model_nh_null", + "atmos_dyn/GFDL_tools", + "atmos_dyn/driver/SHiELD/cloud_diagnosis.F90", + "atmos_dyn/driver/SHiELD/gfdl_cloud_microphys.F90", + "atmos_dyn/tools" ] + - component: "atmos_drivers" + requires: ["FMS", "atmos_phys", "atmos_dyn"] + repo: "https://github.com/NOAA-GFDL/atmos_drivers.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE" + branch: *ATM_DRV_GIT_TAG + otherFlags: *FMSincludes + paths: ["atmos_drivers/coupled"] + - component: "lm4p" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/lm4p.git" + branch: *LAND_GIT_TAG + cppdefs: "-DINTERNAL_FILE_NML" + otherFlags: *FMSincludes + - component: "mom6" + requires: ["FMS"] + repo: ["https://github.com/NOAA-GFDL/MOM6-examples.git", + "https://github.com/NOAA-GFDL/ocean_BGC.git" ] + branch: [ "dev/gfdl", *OCEAN_BGC_GIT_TAG ] # cant use property for mom6 since its a commit hash instead of a branch + otherFlags: !join [ *FMSincludes, " ", *MOMincludes ] + additionalInstructions: | + pushd mom6/MOM6-examples + git checkout 40e3937 # this is just the value of MOM6_GIT_TAG property, can't seem to use variable + popd + # this step might be covered by initial checkout since the default is recursive + git submodule update --recursive --init mom6/MOM6-examples/src/MOM6 mom6/MOM6-examples/src/SIS2 mom6/MOM6-examples/src/icebergs + # checkout dev/gfdl on the icebergs submodule + pushd mom6/MOM6-examples/src/icebergs + git checkout dev/gfdl + popd + # this is different than the MOM6_GIT_TAG above since its pulling a submodule not the whole repo + if [[ $MOM6_GIT_FIX ]]; then + echo WARNING: Checking out MOM6_GIT_FIX, set to: $MOM6_GIT_FIX + pushd mom6/MOM6-examples/src/MOM6/ + git checkout $MOM6_GIT_FIX + popd + fi + if [[ $SIS2_GIT_FIX ]]; then + echo WARNING: Checking out SIS2_GIT_FIX, set to: $SIS2_GIT_FIX + pushd mom6/MOM6-examples/src/SIS2/ + git checkout $SIS2_GIT_FIX + popd + fi + # link in dataset + pushd mom6/MOM6-examples + # skip the check for orion + #set platform_domain = `perl -T -e "use Net::Domain(hostdomain) ; print hostdomain"` + #if ("${platform_domain}" =~ *"MsState"* ) then + # ln -s /work/noaa/gfdlscr/pdata/gfdl/gfdl_O/datasets/ .datasets + #else + ln -s /gpfs/f5/gfdl_o/world-shared/datasets .datasets + #endif + popd + test -e mom6/.datasets + if [[ $status != 0 ]]; then + echo ""; echo "" ; echo " WARNING: .datasets link in MOM6 examples directory is invalid"; echo ""; echo "" + fi + cppdefs: "-DMAX_FIELDS_=100 -DNOT_SET_AFFINITY -D_USE_MOM6_DIAG -D_USE_GENERIC_TRACER -DUSE_PRECISION=2" + paths: [ "mom6/MOM6-examples/src/MOM6/config_src/infra/FMS2", + "mom6/MOM6-examples/src/MOM6/config_src/memory/dynamic_symmetric", + "mom6/MOM6-examples/src/MOM6/config_src/drivers/FMS_cap", + "mom6/MOM6-examples/src/MOM6/src/ALE", + "mom6/MOM6-examples/src/MOM6/src/core", + "mom6/MOM6-examples/src/MOM6/src/diagnostics", + "mom6/MOM6-examples/src/MOM6/src/equation_of_state", + "mom6/MOM6-examples/src/MOM6/src/framework", + "mom6/MOM6-examples/src/MOM6/src/ice_shelf", + "mom6/MOM6-examples/src/MOM6/src/initialization", + "mom6/MOM6-examples/src/MOM6/src/ocean_data_assim", + "mom6/MOM6-examples/src/MOM6/src/parameterizations", + "mom6/MOM6-examples/src/MOM6/src/tracer", + "mom6/MOM6-examples/src/MOM6/src/user", + "mom6/MOM6-examples/src/MOM6/config_src/external/ODA_hooks", + "mom6/MOM6-examples/src/MOM6/config_src/external/database_comms", + "mom6/MOM6-examples/src/MOM6/config_src/external/drifters", + "mom6/MOM6-examples/src/MOM6/config_src/external/stochastic_physics", + "mom6/MOM6-examples/src/MOM6/config_src/external/stochastic_physics", + "mom6/ocean_BGC/generic_tracers", + "mom6/ocean_BGC/mocsy/src" ] + - component: "sis2" + requires: ["FMS", "mom6"] + repo: "https://github.com/NOAA-GFDL/ice_param.git" + branch: "2024.01" + cppdefs: "-DUSE_FMS2_IO" + otherFlags: !join [ *FMSincludes, " ", *MOMincludes ] + paths: [ "mom6/MOM6-examples/src/SIS2/config_src/dynamic_symmetric", + "mom6/MOM6-examples/src/SIS2/config_src/external/Icepack_interfaces", + "mom6/MOM6-examples/src/SIS2/src", + "mom6/MOM6-examples/src/icebergs/src", + "sis2" ] + - component: "coupler" + requires: ["FMS", "atmos_dyn", "atmos_drivers", "atmos_phys", "lm4p", "sis2", "mom6"] + repo: "https://github.com/NOAA-GFDL/FMScoupler" + branch: "2024.01" + otherFlags: !join [ *FMSincludes, " ", *MOMincludes ] + paths: [ "coupler/shared", + "coupler/full" ] + diff --git a/lib/fre/make/tests/ESM4_example/esm4.yaml b/lib/fre/make/tests/ESM4_example/esm4.yaml new file mode 100644 index 00000000..436672ab --- /dev/null +++ b/lib/fre/make/tests/ESM4_example/esm4.yaml @@ -0,0 +1,86 @@ +# esm4.2, based off ESM4p2_piControl_spinup_J_rts.xml +# this needs -npc (non-parallel checkout) flag during checkout script creation for additional checkouts to work properly +fre_properties: + - &RELEASE "2024.01" + - &FMS_GIT_TAG "2024.01" + - &ATM_PHYS_GIT_TAG "2024.01-alpha6" + - &ATM_FV3_GIT_TAG "2023.03" + - &ATM_DRV_GIT_TAG "2023.04" + - &LAND_GIT_TAG "2024.01" + - &ICE_PARAM_GIT_TAG "2023.04" + - &ESM4_VERSION "2024.01" + - &OCEAN_BGC_GIT_TAG "dev4.2_benthic" + - &MOM6_DATE "20231130" + - &MOM6_GIT_TAG "40e3937" + - &MOM6_GIT_FIX "" + - &SIS2_GIT_FIX "" + # compile + - &FRE_STEM !join [fre/FMS, *RELEASE, _mom6_, *MOM6_DATE] + - &INTEL "intel-classic" + - &FMSincludes "-IFMS/include" + - &MOMincludes "-Imom6/MOM6-examples/src/MOM6/src/framework" + # post processing + - &OM4_ANALYSIS "ice_ocean_SIS2/OM4_05" + - &PROD_SIMTIME "5" # simulation length in years + - &PP_CMIP_CHUNK_A "5yr" # smaller chunk length for pp/analysis + - &PP_CMIP_CHUNK_B "10yr" # bigger chunk length for pp/analysis + - &PP_START_YEAR "0001" # starting year + - &PROD_RUNTIME "16:00:00" # Maximum wall clock per simulation + - &PROD_SEGTIME "03:00:00" # Wall clock per segment (usually 1 year) + # MDBI settings + - &EXP_CPLD_START "0001" # simulation start year + - &EXP_CPLD_END "1000" # simulation end year + # this property wasn't actually used in the xml, but seems to be intended for specifying reference files to verify regression tests + - &reference_tag "FMS2022.03_mom6_20220703" + # these properties modify the build + - &BUILD_DATE "" # included as part of the src directory path + - &MODIFIER "" # appended to compile experiment name, can likely remove since compile experiments are no longer a thing + - &PLATINFO "" # used for awg_input + - &LIBS_ROOT "esm4.2_compile$(MODIFIER)" # used below + - &SRC_DIR !join [ $root/, *BUILD_DATE, /, *LIBS_ROOT, /src] # this ends up being used for input paths + - &MOM6_EXAMPLES !join [ $root/, *BUILD_DATE, /, *LIBS_ROOT, /src/mom6] # also for input paths + # input paths + - &AWG_INPUT_HOME_GAEA "awg_include" + - &AWG_INPUT_HOME_NESCC "awg_include" + - &AWG_INPUT_HOME_GFDL !join [ /nbhome/$USER/, *FRE_STEM, *PLATINFO, /$(name)/mdt_xml/awg_include] + - &USER_FILES_F2toF5 "/gpfs/f5/gfdl_f/world-shared/Niki.Zadeh/archive/input/f2_user_files_in_xmls" + - &append_to_setup_csh "" # The start year of forcing dataset. FRE hack to set fyear + +build: + compileYaml: "compile.yaml" + platformYaml: "platforms.yaml" + +shared: + # directories shared across tools + # shamelessly stolen from am5 example + directories: &shared_directories + history_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, history] + pp_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, pp] + analysis_dir: !join [/nbhome/$USER/, *FRE_STEM, /, *name] + ptmp_dir: "/xtmp/$USER/ptmp" + fre_analysis_home: "/home/fms/local/opt/fre-analysis/test" + + # shared pp settings + # also shamelessly stolen from am5 example + postprocess: + settings: &shared_settings + history_segment: "P1Y" + site: "ppan" + switches: &shared_switches + do_statics: True + do_timeavgs: True + clean_work: True + do_refinediag: False + do_atmos_plevel_masking: True + do_preanalysis: False + do_analysis: True + +experiments: + - name: "ESM4p2_piControl_spinup_J" + pp: + - name: "ESM4p2_piControl_spinup_Jb" + pp: + - name: "ESM4p2_piControl_spinup_J_redoyr450_btmdiags" + pp: + - name: "ESM4p2_piControl_spinup_J_redoyr450" + pp: diff --git a/lib/fre/make/tests/ESM4_example/platforms.yaml b/lib/fre/make/tests/ESM4_example/platforms.yaml new file mode 100644 index 00000000..14d4dfff --- /dev/null +++ b/lib/fre/make/tests/ESM4_example/platforms.yaml @@ -0,0 +1,26 @@ +platforms: + - name: ncrc5.intel + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [ !join [*INTEL, "/2022.2.1"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: !join ["/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/", *INTEL,".mk"] + modelRoot: ${HOME}/fremake_canopy/test + - name: ncrc5.intel23 + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [!join [*INTEL, "/2023.1.0"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: !join ["/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/", *INTEL, ".mk"] + modelRoot: ${HOME}/fremake_canopy/test + - name: hpcme.2023 + compiler: intel + RUNenv: [". /spack/share/spack/setup-env.sh", "spack load libyaml", "spack load netcdf-fortran@4.5.4", "spack load hdf5@1.14.0"] + modelRoot: /apps + fc: mpiifort + cc: mpiicc + container: True + containerBuild: "podman" + containerRun: "apptainer" diff --git a/lib/fre/make/tests/SHiELD_example/SHiELD.yaml b/lib/fre/make/tests/SHiELD_example/SHiELD.yaml new file mode 100644 index 00000000..9f063d0a --- /dev/null +++ b/lib/fre/make/tests/SHiELD_example/SHiELD.yaml @@ -0,0 +1,10 @@ +platformYaml: platforms.yaml +compileYaml: compile.yaml +fv3_release: main +phy_release: main +fms_release: "2023.02" +drivers_release: main +coupler_release: "2023.02" +FMSincludes: "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" +momIncludes: "-Imom6/MOM6-examples/src/MOM6/pkg/CVMix-src/include" +INTEL: intel-classic diff --git a/lib/fre/make/tests/SHiELD_example/compile.yaml b/lib/fre/make/tests/SHiELD_example/compile.yaml new file mode 100644 index 00000000..a83bb1ce --- /dev/null +++ b/lib/fre/make/tests/SHiELD_example/compile.yaml @@ -0,0 +1,38 @@ +experiment: shield_nh +compileInclude: "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" +container_addlibs: ["bacio","sp","w3emc","w3nco"] +baremetal_addlibs: ["-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/bacio-2.4.1-wrykbu2/lib -lbacio_4", "-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/bacio-2.4.1-wrykbu2/lib -lbacio_8", "-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/sp-2.5.0-7bumbmx/lib64 -lsp_d", "-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/w3emc-2.10.0-zmuykep/lib64 -lw3emc_d", "-L/autofs/ncrc-svm1_proj/epic/spack-stack/spack-stack-1.6.0/envs/unified-env/install/intel/2023.1.0/w3nco-2.4.1-76qm6h2/lib -lw3nco_d"] +src: + - component: "FMS" + repo: "https://github.com/NOAA-GFDL/FMS.git" + cppdefs: "-Duse_libMPI -Duse_netCDF -Duse_LARGEFILE -DHAVE_SCHED_GETAFFINITY -DINTERNAL_FILE_NML -DGFS_PHYS -DGFS_CONSTANTS -DHAVE_GETTID" + branch: "$(fms_release)" + - component: "SHiELD_physics" + requires: ["FMS"] + repo: "https://github.com/NOAA-GFDL/SHiELD_physics.git" + branch: "$(phy_release)" + paths: [SHiELD_physics/gsmphys, + SHiELD_physics/GFS_layer, + SHiELD_physics/IPD_layer] + cppdefs: "-Duse_libMPI -Duse_netCDF -DHAVE_SCHED_GETAFFINITY -DSPMD -Duse_LARGEFILE -DGFS_PHYS -DUSE_GFSL63 -DNEW_TAUCTMAX -DNEMS_GSM -DINTERNAL_FILE_NML -DMOIST_CAPPA -DUSE_COND" + otherFlags: "$(FMSincludes)" + - component: "fv3" + requires: ["FMS", "SHiELD_physics"] + repo: ["https://github.com/NOAA-GFDL/GFDL_atmos_cubed_sphere.git", + "https://github.com/NOAA-GFDL/atmos_drivers.git"] + cppdefs: "-Duse_libMPI -Duse_netCDF -DHAVE_SCHED_GETAFFINITY -DSPMD -Duse_LARGEFILE -DGFS_PHYS -DUSE_GFSL63 -DNEW_TAUCTMAX -DNEMS_GSM -DINTERNAL_FILE_NML -DMOIST_CAPPA -DUSE_COND" + branch: ["$(fv3_release)","$(drivers_release)"] + paths: [SHiELD_physics/FV3GFS/, + fv3/atmos_drivers/SHiELD/atmos_model.F90, + fv3/GFDL_atmos_cubed_sphere/driver/SHiELD/atmosphere.F90, + fv3/GFDL_atmos_cubed_sphere/tools/, + fv3/GFDL_atmos_cubed_sphere/model/, + fv3/GFDL_atmos_cubed_sphere/GFDL_tools/fv_diag_column.F90] + otherFlags: "$(FMSincludes)" + - component: "FMScoupler" + requires: ["FMS", "SHiELD_physics", "fv3"] + repo: "https://github.com/NOAA-GFDL/FMScoupler.git" + cppdefs: "-Duse_libMPI -Duse_netCDF -DHAVE_SCHED_GETAFFINITY -DSPMD -Duse_LARGEFILE -DGFS_PHYS -DUSE_GFSL63 -DNEW_TAUCTMAX -DNEMS_GSM -DINTERNAL_FILE_NML -DMOIST_CAPPA -DUSE_COND" + branch: "$(coupler_release)" + paths: ["FMScoupler/SHiELD/coupler_main.F90"] + otherFlags: "$(FMSincludes)" diff --git a/lib/fre/make/tests/SHiELD_example/platforms.yaml b/lib/fre/make/tests/SHiELD_example/platforms.yaml new file mode 100644 index 00000000..9f72043b --- /dev/null +++ b/lib/fre/make/tests/SHiELD_example/platforms.yaml @@ -0,0 +1,26 @@ +platforms: + - name: ncrc5.intel + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: ["$(INTEL)/2022.2.1","fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/SHiELDtest + - name: ncrc5.intel23 + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: ["$(INTEL)/2023.1.0","fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/SHiELDtest + - name: hpcme.2023 + compiler: intel + RUNenv: [". /spack/share/spack/setup-env.sh", "spack load libyaml", "spack load netcdf-fortran@4.5.4", "spack load hdf5@1.12.1"] + modelRoot: /apps + fc: mpiifort + cc: mpiicc + container: True + containerBuild: "podman" + containerRun: "apptainer" diff --git a/lib/fre/make/tests/__init__.py b/lib/fre/make/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/make/tests/null_example/compile.yaml b/lib/fre/make/tests/null_example/compile.yaml new file mode 100644 index 00000000..ab5052a0 --- /dev/null +++ b/lib/fre/make/tests/null_example/compile.yaml @@ -0,0 +1,48 @@ +compile: + experiment: "null_model_full" + container_addlibs: + baremetal_linkerflags: + src: + - component: "FMS" + repo: "https://github.com/NOAA-GFDL/FMS.git" + cppdefs: "-Duse_netCDF -Duse_libMPI -DMAXFIELDS_=200 -DMAXFIELDMETHODS_=200 -DINTERNAL_FILE_NML -DHAVE_GETTID" # gettid flag is platform specific + otherFlags: "-fallow-argument-mismatch" # only needed for gcc + branch: *branch + - component: "atmos_null" + requires: ["FMS"] + repo: "https://github.com/NOAA-GFDL/atmos_null.git" + branch: *branch + cppdefs: "-DINTERNAL_FILE_NML" + otherFlags: *FMSincludes + - component: "land_null" + requires: ["FMS"] + repo: "https://github.com/NOAA-GFDL/land_null.git" + branch: *branch + cppdefs: "-DINTERNAL_FILE_NML" + otherFlags: *FMSincludes + - component: "ice_param" + requires: ["FMS"] + repo: "https://github.com/NOAA-GFDL/ice_param.git" + branch: *branch + cppdefs: "-DINTERNAL_FILE_NML" + otherFlags: *FMSincludes + - component: "ocean_null" + requires: ["FMS"] + repo: "https://github.com/NOAA-GFDL/ocean_null.git" + branch: *branch + otherFlags: *FMSincludes + - component: "ice_null" + requires: ["FMS", "ice_param", "ocean_null"] + repo: "https://github.com/NOAA-GFDL/ice_null.git" + branch: *branch + cppdefs: "-DINTERNAL_FILE_NML" + otherFlags: *FMSincludes + - component: "coupler" + requires: ["FMS", "ocean_null", "atmos_null", "land_null", "ice_param", "ocean_null", "ice_null"] + repo: "https://github.com/NOAA-GFDL/FMScoupler.git" + branch: *branch + otherFlags: *FMSincludes + cppdefs: "-D_USE_LEGACY_LAND_ -Duse_AM3_physics -DINTERNAL_FILE_NML" + paths: [ "coupler/full", + "coupler/shared" ] + diff --git a/lib/fre/make/tests/null_example/null_model.yaml b/lib/fre/make/tests/null_example/null_model.yaml new file mode 100644 index 00000000..2dcbcad4 --- /dev/null +++ b/lib/fre/make/tests/null_example/null_model.yaml @@ -0,0 +1,15 @@ +# reusable variables +fre_properties: + - &VERSION "full" + - &FRE_STEM !join [null_model/, *VERSION] + # compile information + - &branch "main" + - &INTEL "intel-classic" + - &FMSincludes "-IFMS/include" + +build: + compileYaml: "compile.yaml" + platformYaml: "platforms.yaml" + +experiments: + - name: "null_model_full" diff --git a/lib/fre/make/tests/null_example/platforms.yaml b/lib/fre/make/tests/null_example/platforms.yaml new file mode 100644 index 00000000..60d1aad2 --- /dev/null +++ b/lib/fre/make/tests/null_example/platforms.yaml @@ -0,0 +1,26 @@ +platforms: + - name: ncrc5.intel + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [ !join [*INTEL, "/2022.2.1"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: !join ["/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/", *INTEL, ".mk"] + modelRoot: ${HOME}/fremake_canopy/test + - name: ncrc5.intel23 + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [!join [*INTEL, "/2023.1.0"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: !join ["/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/", *INTEL, ".mk"] + modelRoot: ${HOME}/fremake_canopy/test + - name: hpcme.2023 + compiler: intel + RUNenv: [". /spack/share/spack/setup-env.sh", "spack load libyaml", "spack load netcdf-fortran@4.5.4", "spack load hdf5@1.14.0"] + modelRoot: /apps + fc: mpiifort + cc: mpiicc + container: True + containerBuild: "podman" + containerRun: "apptainer" diff --git a/lib/fre/make/tests/test_create_makefile.py b/lib/fre/make/tests/test_create_makefile.py new file mode 100644 index 00000000..36188b33 --- /dev/null +++ b/lib/fre/make/tests/test_create_makefile.py @@ -0,0 +1,72 @@ +""" +Test fre make create-makefile +""" +import os +import shutil +from pathlib import Path +from fre.make import createMakefile + +# SET-UP +test_dir = Path("fre/make/tests") +NM_EXAMPLE = Path("null_example") +YAMLFILE = "null_model.yaml" +BM_PLATFORM = ["ncrc5.intel23"] +CONTAINER_PLATFORM = ["hpcme.2023"] +TARGET = ["debug"] +EXPERIMENT = "null_model_full" + +# Create output location +out = f"{test_dir}/makefile_out" +if Path(out).exists(): + # remove + shutil.rmtree(out) + # create output directory + Path(out).mkdir(parents=True,exist_ok=True) +else: + Path(out).mkdir(parents=True,exist_ok=True) + +# Set output directory as home for fre make output +#os.environ["HOME"]=str(Path(out)) + +def test_modelyaml_exists(): + """ + Check the model yaml exists + """ + assert Path(f"{test_dir}/{NM_EXAMPLE}/{YAMLFILE}").exists() + +def test_compileyaml_exists(): + """ + Check the compile yaml exists + """ + assert Path(f"{test_dir}/{NM_EXAMPLE}/compile.yaml").exists() + +def test_platformyaml_exists(): + """ + Check the platform yaml exists + """ + assert Path(f"{test_dir}/{NM_EXAMPLE}/platforms.yaml").exists() + +def test_bm_makefile_creation(): + """ + Check the makefile is created when a bare-metal platform is used + """ + # Set output directory as home for fre make output + os.environ["HOME"]=str(Path(out)) + + bm_plat = BM_PLATFORM[0] + targ = TARGET[0] + yamlfile_path = f"{test_dir}/{NM_EXAMPLE}/{YAMLFILE}" + + createMakefile.makefile_create(yamlfile_path,BM_PLATFORM,TARGET) + + assert Path(f"{out}/fremake_canopy/test/{EXPERIMENT}/{bm_plat}-{targ}/exec/Makefile").exists() + +def test_container_makefile_creation(): + """ + Check the makefile is created when the container platform is used + """ + container_plat = CONTAINER_PLATFORM[0] + yamlfile_path = f"{test_dir}/{NM_EXAMPLE}/{YAMLFILE}" + createMakefile.makefile_create(yamlfile_path,CONTAINER_PLATFORM,TARGET) + + assert Path(f"tmp/{container_plat}/Makefile").exists() diff --git a/lib/fre/pp/README.md b/lib/fre/pp/README.md new file mode 100644 index 00000000..173c1055 --- /dev/null +++ b/lib/fre/pp/README.md @@ -0,0 +1,148 @@ + + + + + + + +## **Quickstart instructions to postprocess FMS history output on PP/AN or gaea** + +1. Checkout postprocessing workflow template +This will clone the postprocessing repository into `/home/$USER/cylc-src/EXPNAME__PLATFORM__TARGET`. +``` +module load fre/canopy +fre pp checkout -e EXPNAME -p PLATFORM -t TARGET +``` + +2. Configure pp template with either XML or pp.yaml + +``` +fre pp configure-xml -e EXPNAME -p PLATFORM -t TARGET -x XML +``` +or +``` +fre pp configure-yaml -e EXPNAME -p PLATFORM -t TARGET -y YAML + +``` + +3. (OPTIONAL BUT RECCOMENDED) Create `history-manifest` for config validation + +Create a `history-manifest` of a single tar file archive first for use in the validation. +This list represents the available source files within the history tar archives, and enables the +validation procedure to catch a wider variety of potential errors. This can be done like so- +``` +tar -tf /archive/$USER/path/to/history/files/YYYYMMDD.nc.tar | grep -v tile[2-6] | sort > /home/$USER/cylc-src/EXPNAME__PLATFORM__TARGET/history-manifest +``` + +4. Validate the configuration +``` +fre pp validate -e EXPNAME -p PLATFORM -t TARGET +``` + +Warnings related to directories are probably valid and should be fixed in `rose-suite.conf`, or created as necessary via `mkdir`. + +If you are running postprocessing gaea, you'll need to change the `SITE` variable in `rose-suite.conf` from `ppan` to `gaea`. + +5. Install the workflow + +``` +fre pp install -e EXPNAME -p PLATFORM -t TARGET +``` + +If you are attempting this on gaea, you'll need to make two one-time changes before installing. +- Currently, `cylc`, `rose`, and `isodatetime` must be in your PATH for new shells. One approach to do this is +to symlink the fms-user-installed fre-cli cylc/rose/isodatetime scripts into your local `~/bin` directory, +and then add that `~/bin` directory to your PATH in your `.bashrc` or `.cshrc`. (If you don't do this, Cylc tasks +will fail complaining those 3 tools are not available.) + +``` +cd ~/bin +ln -s /ncrc/home2/Flexible.Modeling.System/conda/envs/fre-cli/bin/{cylc,rose,isodatetime} . +echo 'setenv PATH ${PATH}:~/bin' >> ~/.cshrc +``` +- Currently, the cylc available on gaea (through `module load cylc` or the `PATH` trick above) does not +include any global configuration, so you'll need to create a file `~/.cylc/flow/global.cylc` that contains the following. +If you don't do this, Cylc will use your home directory for the scratch space and rapidly fill your quota.) + +``` +[install] + [[symlink dirs]] + [[[localhost]]] + run = /gpfs/f5/scratch/gfdl_f/$USER +``` + +6. Run the workflow + +``` +fre pp run -e EXPNAME -p PLATFORM -t TARGET +``` + +7. Report status of workflow progress + +``` +fre pp status -e EXPNAME -p PLATFORM -t TARGET +``` + +8. Launch GUI + +``` +TODO: fre pp gui? + +The full GUI can be launched on jhan or jhanbigmem (an107 or an201). + +cylc gui --ip=`hostname -f` --port=`jhp 1` --no-browser +``` + +## **PP Wrapper Usage** + +Here + +### **PP Wrapper Decision Tree** +![pp_wrapper_decsiontree](https://github.com/NOAA-GFDL/fre-cli/assets/98476720/d3eaa237-1e29-4922-9d83-8d9d11925c54) + +### **Tests** + +To run `fre pp` tests, return to root directory of the fre-cli repo and call just those tests with + + python -m pytest fre/pp/tests/[test script.py] + +Or run all tests with + + python -m pytests fre/pp/tests diff --git a/lib/fre/pp/__init__.py b/lib/fre/pp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/pp/checkoutScript.py b/lib/fre/pp/checkoutScript.py new file mode 100644 index 00000000..57036634 --- /dev/null +++ b/lib/fre/pp/checkoutScript.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +# Author: Bennett Chang +# Description: + +import os +import subprocess +from subprocess import PIPE +from subprocess import STDOUT +import re +import click + +############################################# + +package_dir = os.path.dirname(os.path.abspath(__file__)) + +############################################# + +def _checkoutTemplate(experiment, platform, target, branch='main'): + """ + Checkout the workflow template files from the repo + """ + # Create the directory if it doesn't exist + directory = os.path.expanduser("~/cylc-src") + os.makedirs(directory, exist_ok=True) + + # Change the current working directory + os.chdir(directory) + + # Set the name of the directory + name = f"{experiment}__{platform}__{target}" + + # Clone the repository with depth=1; check for errors + click.echo("cloning experiment into directory " + directory + "/" + name) + clonecmd = ( + f"git clone -b {branch} --single-branch --depth=1 --recursive " + f"https://github.com/NOAA-GFDL/fre-workflows.git {name}" ) + preexist_error = f"fatal: destination path '{name}' exists and is not an empty directory." + click.echo(clonecmd) + cloneproc = subprocess.run(clonecmd, shell=True, check=False, stdout=PIPE, stderr=STDOUT) + if not cloneproc.returncode == 0: + if re.search(preexist_error.encode('ASCII'),cloneproc.stdout) is not None: + argstring = f" -e {experiment} -p {platform} -t {target}" + stop_report = ( + "Error in checkoutTemplate: the workflow definition specified by -e/-p/-t already" + f" exists at the location ~/cylc-src/{name}!\n" + f"In the future, we will confirm that ~/cylc-src/{name} is usable and will check " + "whether it is up-to-date.\n" + "But for now, if you wish to proceed, you must delete the workflow definition.\n" + "To start over, try:\n" + f"\t cylc stop {name}\n" + f"\t cylc clean {name}\n" + f"\t rm -r ~/cylc-src/{name}" ) + click.echo(stop_report) + return 1 + else: + #if not identified, just print the error + click.echo(clonecmd) + click.echo(cloneproc.stdout) + return 1 + +############################################# + +@click.command() +def checkoutTemplate(experiment, platform, target, branch="main"): + ''' + Wrapper script for calling checkoutTemplate - allows the decorated version + of the function to be separate from the undecorated version + ''' + return _checkoutTemplate(experiment, platform, target, branch) + + +if __name__ == '__main__': + checkoutTemplate() diff --git a/lib/fre/pp/configure_script_xml.py b/lib/fre/pp/configure_script_xml.py new file mode 100644 index 00000000..899b4830 --- /dev/null +++ b/lib/fre/pp/configure_script_xml.py @@ -0,0 +1,696 @@ +#!/usr/bin/env python3 +''' + Primary Usage: fre-bronx-to-canopy -x XML -e EXP -p PLATFORM -t TARGET + + The Bronx-to-Canopy XML converter overwrites 3 files: + - rose-suite.conf + - app/remap-pp-components/rose-app.conf + - app/regrid-xy/rose-app.conf +''' + +# std lib +import re +import os +import subprocess +import logging + +# third party +import click +import metomi.rose.config +import metomi.isodatetime.parsers + +############################################# + +LOGGING_FORMAT = '%(asctime)s %(levelname)s: %(message)s' +CYLC_PATH = '/home/fms/fre-canopy/system-settings/bin' +CYLC_REFINED_SCRIPTS = ["check4ptop.pl", + "module_init_3_1_6.pl", + "plevel_mask.ncl", + "refineDiag_atmos.csh", + "refine_fields.pl", + "surface_albedo.ncl", + "tasminmax.ncl", + "tracer_refine.ncl", + "refineDiag_atmos_cmip6.csh" + ] +CYLC_REFINED_DIR = "\\$CYLC_WORKFLOW_RUN_DIR/etc/refineDiag" +PREANALYSIS_SCRIPT = "refineDiag_data_stager_globalAve.csh" + + +def freq_from_legacy(legacy_freq): + """Return ISO8601 duration given Bronx-style frequencies + + Arguments: + 1. legacy_freq[str]: The Bronx frequency + """ + lookup = { + 'annual': 'P1Y', + 'monthly': 'P1M', + 'seasonal': 'P3M', + 'daily': 'P1D', + '120hr': 'P120D', + '12hr': 'PT12H', + '8hr': 'PT8H', + '6hr': 'PT6H', + '4hr': 'PT4H', + '3hr': 'PT3H', + '2hr': 'PT2H', + '1hr': 'PT1H', + 'hourly': 'PT1H', + '30min': 'PT30M' + } + return lookup[legacy_freq] + +def chunk_from_legacy(legacy_chunk): + """Return ISO8601 duration given Bronx-style chunk + + Arguments: + 1. legacy_chunk[str]: The Bronx chunk + """ + regex = re.compile( r'(\d+)(\w+)' ) + match = regex.match( legacy_chunk ) + if not match: + logging.error( "Could not convert Bronx chunk to ISO8601 duration: %s", + legacy_chunk ) + raise ValueError + + time_unit = match.group(2) + if time_unit not in ['yr','mo']: + logging.error("Unknown time units %s", match.group(2) ) + raise ValueError + + time_quant = match.group(1) + ret_val=f'P{time_quant}' + if time_unit == "yr": + ret_val+='Y'#return f'P{time_quant}Y' + elif time_unit == 'mo': + ret_val+='M'#return f'P{time_quant}M' + return ret_val + +def frelist_xpath(xml, platform, target, experiment, +# do_analysis, historydir, refinedir, ppdir, +# do_refinediag, pp_start, pp_stop, validate, verbose, quiet, dual, + xpath): + """Returns filepaths of FRE XML elements that use X-path notation + using Bronx's 'frelist' command via subprocess module + + Arguments: + 1. args[str]: Argparse user-input arguments + 2. xpath[str]: X-path (XML) notation required by 'frelist' + """ + + cmd = f"frelist -x {xml} -p {platform} -t {target} {experiment} --evaluate '{xpath}'" + logging.info("running cmd:\n %s",cmd) #logging.info(f"running cmd:\n {cmd}") + logging.info(">> %s",xpath) + process = subprocess.run(cmd, + shell=True, + check=False, #if True, retrieving std err difficult... + capture_output=True, + universal_newlines=True) + + logging.info("stdout: \n%s",process.stdout.strip()) + logging.info("stderr: \n%s",process.stderr.strip()) + logging.info("returncode: \n%s",process.returncode) + + + result = process.stdout.strip() + if process.returncode > 0: + raise subprocess.CalledProcessError( + returncode=process.returncode, + cmd=cmd, + output=process.stdout.strip(), + stderr=process.stderr.strip() ) + return result + +def duration_to_seconds(duration): + """Returns the conversion of a chunk duration to seconds + + Arguments: + 1. duration[str]: The original chunk duration + """ + dur = metomi.isodatetime.parsers.DurationParser().parse(duration) + return dur.get_seconds() + + +def main(xml, platform, target, experiment, do_analysis, historydir, refinedir, ppdir, + do_refinediag, pp_start, pp_stop, validate, verbose, quiet, dual): + """The meat of the converter + + Arguments: + 1. args[argparse Namespace]: Arguments given at the command line + + Tasks: + 1. Generate key-value pairs for the rose-suite.conf. + 2. Generate content for the regrid-xy app + 3. Generate content for the remap-pp-components app + """ + + ########################################################################## + # Set up default configurations for regrid-xy and remap-pp-components + ########################################################################## + rose_remap = metomi.rose.config.ConfigNode() + rose_remap.set(keys=['command', 'default'], value='remap-pp-components') + + rose_regrid_xy = metomi.rose.config.ConfigNode() + rose_regrid_xy.set(keys=['command', 'default'], value='regrid-xy') + + ########################################################################## + # Create the rose-suite config and begin setting up key-value pairs + # Note: All strings inside the rose-suite configuration MUST be quoted. + # Note: The exception to the 'quote' rule is boolean values. + # Note: Addition of quotes will appear as "'" in the code. + ########################################################################## + rose_suite = metomi.rose.config.ConfigNode() + rose_suite.set(keys=['template variables', 'SITE'], value='"ppan"') + rose_suite.set(keys=['template variables', 'CLEAN_WORK'], value='True') + rose_suite.set(keys=['template variables', 'PTMP_DIR'], value='"/xtmp/$USER/ptmp"') + rose_suite.set(keys=['template variables', 'DO_MDTF'], value='False') + rose_suite.set(keys=['template variables', 'DO_STATICS'], value='True') + rose_suite.set(keys=['template variables', 'DO_TIMEAVGS'], value='True') + rose_suite.set(keys=['template variables', 'DO_ANALYSIS_ONLY'], value='False') + rose_suite.set(keys=['template variables', 'DO_ATMOS_PLEVEL_MASKING'], value='True') + rose_suite.set(keys=['template variables', 'FRE_ANALYSIS_HOME'], + value='"/home/fms/local/opt/fre-analysis/test"') + + # not sure about these + rose_suite.set(keys=['template variables', 'PP_DEFAULT_XYINTERP'], value='"360,180"') + rose_suite.set(keys=['template variables', 'DO_ANALYSIS'], value='True') + + rose_suite.set(keys=['template variables', 'EXPERIMENT'], + value=f"'{experiment}'") #value=f"'{}'".format(experiment)) + rose_suite.set(keys=['template variables', 'PLATFORM'], + value=f"'{platform}'")#value="'{}'".format(platform)) + rose_suite.set(keys=['template variables', 'TARGET'], + value=f"'{target}'")#value="'{}'".format(target)) + + #regex_fre_property = re.compile(r'\$\((\w+)') #notyetimplemented + #all_components = set() #notyetimplemented + + ########################################################################## + # Run 'frelist' in the background to fetch the default history directory, + # the default history_refineDiag directory, and the default PP directory, + # all of which have been set in the XML. If the custom arguments for these + # directory variables have not been set by the user at the command line, + # the XML's default paths will be inserted into rose-suite.conf. + ########################################################################## + if historydir is None: + logging.info("Running frelist for historydir assignment, this may fail") + logging.info("If so, try the frelist call manually and use the historydir argument") + + fetch_history_cmd = f"frelist -x {xml} -p {platform} -t {target} {experiment} -d archive" + logging.info(">> %s", fetch_history_cmd) + + fetch_history_process = subprocess.run(fetch_history_cmd, + shell=True, + check=True, + capture_output=True, + universal_newlines=True) + historydir = fetch_history_process.stdout.strip() + '/history' + logging.info(historydir) + + + + # Q: should respond to do_refineDiag and refinedir args of this function? + historydir_refined = historydir + '_refineDiag' + + if ppdir is None: + logging.info("Running frelist for ppdir assignment...") + logging.info("If this fails, try the frelist call manually and use the ppdir argument") + fetch_pp_cmd = f"frelist -x {xml} -p {platform} -t {target} {experiment} -d postProcess" + logging.info(">> %s", fetch_pp_cmd) + + fetch_pp_process = subprocess.run(fetch_pp_cmd, + shell=True, + check=True, + capture_output=True, + universal_newlines=True) + ppdir = fetch_pp_process.stdout.strip() + logging.info(ppdir) + + + # Q: shouldn't there be a CLI analysis dir arg while we're here? + # basically, this is borderline on the same level as the ppDir and historydir fields. + #if do_analysis: + logging.info("Running frelist for analysis_dir assignment...") + fetch_analysis_dir_cmd = f"frelist -x {xml} -p {platform} -t {target} {experiment} -d analysis" + logging.info(">> %s", fetch_analysis_dir_cmd) + + fetch_analysis_dir_process = subprocess.run(fetch_analysis_dir_cmd, + shell=True, + check=True, + capture_output=True, + universal_newlines=True) + analysis_dir = fetch_analysis_dir_process.stdout.strip() + logging.info(analysis_dir) + #else: + # logging.info('not doing analysis.') + + + grid_spec = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, do_refinediag, + #pp_start, pp_stop, validate, + #verbose, quiet, dual, + 'input/dataFile[@label="gridSpec"]') + sim_time = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, do_refinediag, + #pp_start, pp_stop, validate, + #verbose, quiet, dual, + 'runtime/production/@simTime') + sim_units = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, do_refinediag, + #pp_start, pp_stop, validate, + #verbose, quiet, dual, + 'runtime/production/@units') + + rose_suite.set(keys=['template variables', 'HISTORY_DIR'], + value=f"'{historydir}'") #value="'{}'".format(historydir)) + # set some dirs to something else to allow bronx dual-pps easily + if dual: + rose_suite.set(keys=['template variables', 'PP_DIR'], + value=f"'{ppdir}_canopy'") + rose_suite.set(keys=['template variables', 'ANALYSIS_DIR'], + value=f"'{analysis_dir}_canopy'") + else: + rose_suite.set(keys=['template variables', 'PP_DIR'], + value=f"'{ppdir}'") + rose_suite.set(keys=['template variables', 'ANALYSIS_DIR'], + value=f"'{analysis_dir}'") + rose_suite.set(keys=['template variables', 'PP_GRID_SPEC'], + value=f"'{grid_spec}'") + + ########################################################################## + # Process the refineDiag scripts into the rose-suite configuration from + # the tags in the XML. There is one special + # script that contains its own key-value pair: PREANALYSIS. Everything + # else gets processed into a list of strings under the REFINEDIAG_SCRIPT + # rose-suite setting. Also, if a script referenced can also be found in + # Canopy's centralized refineDiag repository, located in the + # $CYLC_WORKFLOW_DIR/etc/refineDiag, then THOSE references will be used. + # Otherwise, the reference will be whatever path the XML finds. + ########################################################################## + #preanalysis_path_xml = None #notyetimplemented + #preanalysis_path_cylc = "'{}/{}'".format(CYLC_REFINED_DIR, #notyetimplemented + # PREANALYSIS_SCRIPT) + + # get the refinediag scripts + refine_diag_cmd = f"frelist -x {xml} -p {platform} -t {target} {experiment} " \ + "--evaluate postProcess/refineDiag/@script" + refine_diag_process = subprocess.run(refine_diag_cmd, + shell=True, + check=True, + capture_output=True, + universal_newlines=True) + refine_diag_scripts = refine_diag_process.stdout.strip('\n') + + # If one of the refinediag scripts contains "vitals", assume it + # won't generate output, so relabel it as a preAnalysis script. + # There is only one preAnalysis slot currently, so if there are multiple + # refineDiag scripts that match "vitals" throw away the rest. + list_refinediags = [] + str_preanalysis = None + for x in refine_diag_scripts.split(): + if "vitals" in x: + if str_preanalysis is None: + str_preanalysis = x + else: + list_refinediags.append(x) + + if list_refinediags: + # turn refinediag off by default in favor of the built-in plevel masking + rose_suite.set(keys=['template variables', 'DO_REFINEDIAG'], value='False') + if dual: + rose_suite.set(keys=['template variables', '#HISTORY_DIR_REFINED'], + value=f"'{historydir_refined}_canopy'") + else: + rose_suite.set(keys=['template variables', '#HISTORY_DIR_REFINED'], + value=f"'{historydir_refined}'") + + rose_suite.set(keys=['template variables', '#REFINEDIAG_SCRIPTS'], + value=f"'{' '.join(list_refinediags)}'") + + logging.info( "refineDiag scripts: %s", ' '.join(list_refinediags) ) + logging.info( "NOTE: Now turned off by default; please re-enable in config file if needed" ) + else: + rose_suite.set(keys=['template variables', 'DO_REFINEDIAG'], value='False') + logging.info("No refineDiag scripts written. ") + + if str_preanalysis is not None: + rose_suite.set(keys=['template variables', 'DO_PREANALYSIS'], value='True') + rose_suite.set(keys=['template variables', 'PREANALYSIS_SCRIPT'], + value=f"'{str_preanalysis}'" ) + logging.info( "Preanalysis script: %s", str_preanalysis ) + else: + rose_suite.set(keys=['template variables', 'DO_PREANALYSIS'], value='False') + logging.info( "No preAnalysis scripts written." ) + + # Grab all of the necessary PP component items/elements from the XML + comps = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + 'postProcess/component/@type').split() + rose_suite.set(keys=['template variables', 'PP_COMPONENTS'], + value=f"'{' '.join(sorted(comps))}'" ) + + segment_time = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + 'runtime/production/segment/@simTime') + segment_units = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + 'runtime/production/segment/@units') + + if segment_units == 'years': + segment = f'P{segment_time}Y' + elif segment_units == 'months': + segment = f'P{segment_time}M' + else: + logging.error("Unknown segment units: %s", segment_units) + raise ValueError + + # P12M is identical to P1Y but the latter looks nicer + if segment == 'P12M': + segment = 'P1Y' + rose_suite.set(keys=['template variables', 'HISTORY_SEGMENT'], value=f"'{segment}'" ) + + # Get the namelist current_date as the likely PP_START (unless "start" is used in the PP tags) + # frelist --namelist may be better, but sometimes may not work + current_date_str = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + 'input/namelist') + match = re.search(r'current_date\s*=\s*(\d+),(\d+),(\d+)', current_date_str) + if match: + try: + current_date = metomi.isodatetime.data.TimePoint( + year=match.group(1), month_of_year=match.group(2), day_of_month=match.group(3) ) + except: + logging.warning("Could not parse date from namelist current_date") + current_date = None + else: + current_date = None + logging.warning("Could not find current_date in namelists") + logging.info("current_date (from namelists): %s", current_date) + + # Take a good guess for the PP_START and PP_STOP + # PP_START could be the coupler_nml/current_date + # PP_STOP could be the PP_START plus the simulation length + if sim_units == "years": + oneless = int(sim_time) - 1 + duration = f"P{oneless}Y" + elif sim_units == "months": + duration = f"P{sim_time}M" + else: + raise ValueError(f"Was hoping sim_units would be years or months; got {sim_units}") + dur = metomi.isodatetime.parsers.DurationParser().parse(duration) + pp_stop = current_date + dur + rose_suite.set(keys=['template variables', 'PP_START'], value=f'"{current_date}"') + rose_suite.set(keys=['template variables', 'PP_STOP'], value=f'"{pp_stop}"') + + # Loop over all of the PP components, fetching the sources, xyInterp, + # and sourceGrid. + chunks = set() + comp_count = 0 + for comp in comps: + comp_count += 1 + pp_comp_xpath_header = f'postProcess/component[@type="{comp}"]' + logging.info( "Component loop: %s out of %s", comp_count, len(comps)) + + # get the comp attributes + comp_source = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + f'{pp_comp_xpath_header}/@source' ) + xy_interp = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + f'{pp_comp_xpath_header}/@xyInterp' ) + source_grid = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + f'{pp_comp_xpath_header}/@sourceGrid' ) + interp_method = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + f'{pp_comp_xpath_header}/@interpMethod') + + # split some of the stuffs + if xy_interp != "": + interp_split = xy_interp.split(',') + output_grid_lon = interp_split[1] + output_grid_lat = interp_split[0] + if source_grid != "": + sourcegrid_split = source_grid.split('-') + input_grid = sourcegrid_split[1] + input_realm = sourcegrid_split[0] + + # determine the interp method + if xy_interp: + if interp_method == "": + if input_grid == "cubedsphere": + interp_method = 'conserve_order2' + elif input_grid == 'tripolar': + interp_method = 'conserve_order1' + else: + raise Exception(f"Expected cubedsphere or tripolar, not {source_grid}") + + # determine the grid label + if xy_interp: + grid = f"regrid-xy/{output_grid_lon}_{output_grid_lat}.{interp_method}" + grid_tail = f"{output_grid_lon}_{output_grid_lat}.{interp_method}" + else: + grid = "native" + + sources = set() + if comp_source.endswith('_refined'): + logging.info( + "NOTE: Skipping history file %s, refineDiag is turned off by default.", comp_source) + else: + sources.add(comp_source) + timeseries_count = 0 + + # Get the number of TS nodes + results = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + f'{pp_comp_xpath_header}/timeSeries/@freq' ).split() + timeseries_count = len(results) + + # Loop over the TS nodes and write out the frequency, chunklength, and + # grid to the remap-pp-components Rose app configuration + for i in range(1, timeseries_count + 1): + #label = "{}.{}".format(comp, str(i)) #notyetimplemented + + source = frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + f'{pp_comp_xpath_header}/timeSeries[{i}]/@source' ) + if source.endswith('_refined'): + logging.info( + "NOTE: Skipping history file %s, refineDiag is turned off by default.", source) + else: + sources.add(source) + + #freq = freq_from_legacy(frelist_xpath(args, + # '{}/timeSeries[{}]/@freq' \ + # .format(pp_comp_xpath_header, i))) + chunk = chunk_from_legacy( + frelist_xpath(xml, platform, target, experiment, + #do_analysis, + #historydir, refinedir, ppdir, + #do_refinediag, pp_start, pp_stop, validate, + #verbose, quiet, dual, + f'{pp_comp_xpath_header}/timeSeries[{i}]/@chunkLength' + ) ) + chunks.add(chunk) + #rose_remap.set(keys=[label, 'freq'], value=freq) + #rose_remap.set(keys=[label, 'chunk'], value=chunk) + + rose_remap.set(keys=[comp, 'sources'], value=' '.join(sources)) + rose_remap.set(keys=[comp, 'grid'], value=grid) + + if grid == "native": + pass + else: + # Write out values to the 'regrid-xy' Rose app + rose_regrid_xy.set(keys=[comp, 'sources'], value=' '.join(sources)) + rose_regrid_xy.set(keys=[comp, 'inputGrid'], value=input_grid) + rose_regrid_xy.set(keys=[comp, 'inputRealm'], value=input_realm) + rose_regrid_xy.set(keys=[comp, 'interpMethod'], value=interp_method) + rose_regrid_xy.set(keys=[comp, 'outputGridType'], value=grid_tail) + rose_regrid_xy.set(keys=[comp, 'outputGridLon'], value=output_grid_lon) + rose_regrid_xy.set(keys=[comp, 'outputGridLat'], value=output_grid_lat) + + # Process all of the found PP chunks into the rose-suite configuration + if verbose: + print("") + logging.info("Setting PP chunks...") + + sorted_chunks = list(chunks) + sorted_chunks.sort(key=duration_to_seconds, reverse=False) + + if len(chunks) == 0: + raise ValueError('no chunks found! exit.') + + logging.info(" Chunks found: %s", ', '.join(sorted_chunks)) + if len(chunks) == 1: + rose_suite.set(['template variables', 'PP_CHUNK_A'], + f"'{sorted_chunks[0]}'") + else: + rose_suite.set(['template variables', 'PP_CHUNK_A'], + f"'{sorted_chunks[0]}'") + rose_suite.set(['template variables', 'PP_CHUNK_B'], + f"'{sorted_chunks[1]}'") + logging.info(" Chunks used: %s", ', '.join(sorted_chunks[0:2]) ) + + # Write out the final configurations. + if verbose: + print("") + logging.info("Writing output files...") + + dumper = metomi.rose.config.ConfigDumper() + rose_outfiles=[ ( rose_suite, 'rose-suite.conf' ), + ( rose_remap, 'app/remap-pp-components/rose-app.conf' ), + ( rose_regrid_xy, 'app/regrid-xy/rose-app.conf' ) ] + for outfile in rose_outfiles: + logging.info(" %s", outfile[1]) + dumper(outfile[0], outfile[1]) + + #outfile = "app/remap-pp-components/rose-app.conf" + #logging.info(" %s", outfile) + #dumper(rose_remap, outfile) + # + #outfile = "app/regrid-xy/rose-app.conf" + #logging.info(" %s", outfile) + #dumper(rose_regrid_xy, outfile) + +def _convert(xml, platform, target, experiment, do_analysis=False, historydir=None, + refinedir=None, ppdir=None, do_refinediag=False, pp_start=None, + pp_stop=None, validate=False, verbose=False, quiet=False, dual=False): + """ + Converts a Bronx XML to a Canopy rose-suite.conf + """ + + # Logging settings. The default is throwing only warning messages + if verbose: + logging.basicConfig(level=logging.INFO, format=LOGGING_FORMAT) + elif quiet: + logging.basicConfig(level=logging.ERROR, format=LOGGING_FORMAT) + else: + logging.basicConfig(level=logging.WARNING, format=LOGGING_FORMAT) + + # Set the name of the directory + name = f"{experiment}__{platform}__{target}" + + # Create the directory if it doesn't exist + cylc_dir = os.path.expanduser("~/cylc-src") + new_dir = os.path.join(cylc_dir, name) + os.makedirs(new_dir, exist_ok=True) + + # Change the current working directory + xml = os.path.abspath(xml) + os.chdir(new_dir) + + ########################################################################## + # NOTE: Ideally we would check that "frelist" is in the PATH here, + # but in practice the frelist will just fail later if it's not loaded. + # If that error scenario is too confusing, let's add a check back here. + cylc_loaded = False + if validate: + if CYLC_PATH in os.getenv('PATH'): + cylc_loaded = True + else: + raise EnvironmentError("Cannot run the validator tool because " \ + "the Cylc module isn't loaded. Please " \ + "run 'module load cylc/test' and try again.") + + # Alert the user if only 1 or zero PP years are given as an option, and + # notify them that a default of '0000' for those years will be set in the + # rose-suite configuration + if any( [ pp_stop is None, pp_start is None ] ): + if pp_start is None: + logging.warning("PP start year was not specified.") + if pp_stop is None: + logging.warning("PP stop year was not specified.") + logging.warning("After the converter has run, please edit the " \ + "default '0000' values within your rose-suite.conf.") + + # These series of conditionals takes into account input from the user + # (for the PP_START and PP_STOP year) that is not 4 digits or other + # nonsensical years. The rose-suite config requires 4 digits for years + # and if the year is under '1000' (but > 0), then leading zeros must be used. + if all( [ pp_start is not None, pp_stop is not None ] ): + def format_req_pp_year(pp_year): + if len(pp_year) < 4 and int(pp_year) > 0: + pp_year = '0' * (4 - len(pp_year)) + pp_year + return pp_year + pp_start = format_req_pp_year(pp_start) + pp_stop = format_req_pp_year(pp_stop) + + if int(pp_start) >= int(pp_stop): + logging.warning("Your PP_START date is equal to or later than " \ + "your PP_STOP date. Please revise these values in " \ + "your configuration after the converter has run.") + if any( [ len(pp_start) > 4, len(pp_stop) > 4, + int(pp_start) <= 0, int(pp_stop) <= 0 ] ): + logging.warning("At least one of your PP_start or PP_stop years " \ + "does not make sense. Please revise this value in " \ + "your configuration after the converter has run.") + + main( xml, platform, target, experiment, do_analysis, historydir, refinedir, ppdir, + do_refinediag, pp_start, pp_stop, validate, verbose, quiet, dual ) + + if verbose: + print("") + logging.info("XML conversion complete!") + + # Run the Cylc validator tool on the current directory if conditions are met. + # Note: the user must be running the converter in the parent Cylc Workflow + # Directory if the validator is run. + if cylc_loaded: + if verbose: + print("") + logging.info("Running the Cylc validator tool...") + try: + subprocess.run("cylc validate .", shell=True, check=True) + except subprocess.CalledProcessError: + logging.error("Errant values in rose-suite.conf or other Cylc errors. " \ + "Please check your configuration and run the validator " \ + "again separately.") + finally: + logging.info("Validation step complete!") + +############################################## + +@click.command() +def convert(): + ''' + Wrapper for convert call - allows users to call without command-line args + ''' + _convert() + +if __name__ == '__main__': + convert() diff --git a/lib/fre/pp/configure_script_yaml.py b/lib/fre/pp/configure_script_yaml.py new file mode 100644 index 00000000..b782e3de --- /dev/null +++ b/lib/fre/pp/configure_script_yaml.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +""" + Script creates rose-apps and rose-suite + files for the workflow from the pp yaml. +""" + +## TO-DO: +# - condition where there are multiple pp yamls +# - validation here or in combined-yamls tools + +import os +import json +import shutil +import click + +from jsonschema import validate +import yaml +import metomi.rose.config + +# Relative import +#f = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +#sys.path.append(f) +import fre.yamltools.combine_yamls as cy + +#################### +def yaml_load(yamlfile): + """ + Load the given yaml and validate. + """ + # Load the main yaml and validate + with open(yamlfile,'r') as f: + y=yaml.safe_load(f) + + return y + +######VALIDATE##### +def validate_yaml(yamlfile): + """ + Using the schema.json file, the yaml format is validated. + """ + # Load the yaml + yml = yaml_load(yamlfile) + + package_dir = os.path.dirname(os.path.abspath(__file__)) + schema_path = os.path.join(package_dir, 'schema.json') + # Load the json schema: .load() (vs .loads()) reads and parses the json in one + with open(schema_path,'r') as s: + schema = json.load(s) + + # Validate yaml + # If the yaml is not valid, the schema validation will raise errors and exit + if validate(instance=yml,schema=schema) is None: + print("COMBINED YAML VALID \n") + +#################### +def rose_init(experiment,platform,target): + """ + Initialize the rose suite and app configurations. + """ + # initialize rose suite config + rose_suite = metomi.rose.config.ConfigNode() + # disagreeable; these should be optional + rose_suite.set(keys=['template variables', 'DO_ANALYSIS_ONLY'], value='False') + rose_suite.set(keys=['template variables', 'DO_MDTF'], value='False') + rose_suite.set(keys=['template variables', 'PP_DEFAULT_XYINTERP'], value='0,0') + + # set some rose suite vars + rose_suite.set(keys=['template variables', 'EXPERIMENT'], value=f'"{experiment}"') + rose_suite.set(keys=['template variables', 'PLATFORM'], value=f'"{platform}"') + rose_suite.set(keys=['template variables', 'TARGET'], value=f'"{target}"') + + # initialize rose regrid config + rose_regrid = metomi.rose.config.ConfigNode() + rose_regrid.set(keys=['command', 'default'], value='regrid-xy') + + # initialize rose remap config + rose_remap = metomi.rose.config.ConfigNode() + rose_remap.set(keys=['command', 'default'], value='remap-pp-components') + + return(rose_suite,rose_regrid,rose_remap) + +#################### +def quote_rose_values(value): + """ + rose-suite.conf template variables must be quoted unless they are + boolean, in which case do not quote them. + """ + if isinstance(value, bool): + return f"{value}" + else: + return "'" + value + "'" + +#################### +def set_rose_suite(yamlfile,rose_suite): + """ + Set items in the rose suite configuration. + """ + pp=yamlfile.get("postprocess") + dirs=yamlfile.get("directories") + + # set rose-suite items + if pp is not None: + for i in pp.values(): + if not isinstance(i,list): + for key,value in i.items(): + # rose-suite.conf is somewhat finicky with quoting + # cylc validate will reveal any complaints + rose_suite.set( keys = ['template variables', key.upper()], + value = quote_rose_values(value) ) + if dirs is not None: + for key,value in dirs.items(): + rose_suite.set(keys=['template variables', key.upper()], value=quote_rose_values(value)) + +#################### +def set_rose_apps(yamlfile,rose_regrid,rose_remap): + """ + Set items in the regrid and remap rose app configurations. + """ + components = yamlfile.get("postprocess").get("components") + for i in components: + comp = i.get('type') + sources = i.get('sources') + interp_method = i.get('interpMethod') + + # set remap items + rose_remap.set(keys=[f'{comp}', 'sources'], value=f'{sources}') + # if xyInterp doesnt exist, grid is native + if i.get("xyInterp") is None: + rose_remap.set(keys=[f'{comp}', 'grid'], value='native') + + # if xyInterp exists, component can be regridded + else: + interp_split = i.get('xyInterp').split(',') + rose_remap.set(keys=[f'{comp}', 'grid'], + value=f'regrid-xy/{interp_split[0]}_{interp_split[1]}.{interp_method}') + + # set regrid items + if i.get("xyInterp") is not None: + rose_regrid.set(keys=[f'{comp}', 'sources'], value=f'{sources}') + rose_regrid.set(keys=[f'{comp}', 'inputRealm'], value=f'{i.get("inputRealm")}') + rose_regrid.set(keys=[f'{comp}', 'inputGrid'], value=f'{i.get("sourceGrid")}') + rose_regrid.set(keys=[f'{comp}', 'interpMethod'], value=f'{interp_method}') + interp_split = i.get('xyInterp').split(',') + rose_regrid.set(keys=[f'{comp}', 'outputGridLon'], value=f'{interp_split[1]}') + rose_regrid.set(keys=[f'{comp}', 'outputGridLat'], value=f'{interp_split[0]}') + rose_regrid.set(keys=[f'{comp}', 'outputGridType'], + value=f'{interp_split[0]}_{interp_split[1]}.{interp_method}') + +#################### +def yamlInfo(yamlfile,experiment,platform,target): + """ + Using a valid pp.yaml, the rose-app and rose-suite + configuration files are created in the cylc-src + directory. The pp.yaml is also copied to the + cylc-src directory. + """ + e = experiment + p = platform + t = target + yml = yamlfile + + # Initialize the rose configurations + rose_suite,rose_regrid,rose_remap = rose_init(e,p,t) + + # Combine model, experiment, and analysis yamls + comb = cy.init_pp_yaml(yml,e,p,t) + full_combined = cy.get_combined_ppyaml(comb) + + # Validate yaml + validate_yaml(full_combined) + + # Load the combined yaml + comb_pp_yaml = yaml_load(full_combined) + + ## PARSE COMBINED YAML TO CREATE CONFIGS + # Set rose-suite items + set_rose_suite(comb_pp_yaml,rose_suite) + + # Set regrid and remap rose app items + set_rose_apps(comb_pp_yaml,rose_regrid,rose_remap) + + # write output files + print("Writing output files...") + cylc_dir = os.path.join(os.path.expanduser("~/cylc-src"), f"{e}__{p}__{t}") + outfile = os.path.join(cylc_dir, f"{e}.yaml") + shutil.copyfile(full_combined, outfile) + print(" " + outfile) + + dumper = metomi.rose.config.ConfigDumper() + outfile = os.path.join(cylc_dir, "rose-suite.conf") + dumper(rose_suite, outfile) + print(" " + outfile) + + outfile = os.path.join(cylc_dir, "app", "regrid-xy", "rose-app.conf") + dumper(rose_regrid, outfile) + print(" " + outfile) + + outfile = os.path.join(cylc_dir, "app", "remap-pp-components", "rose-app.conf") + dumper(rose_remap, outfile) + print(" " + outfile) + +@click.command() +def _yamlInfo(yamlfile,experiment,platform,target): + ''' + Wrapper script for calling yamlInfo - allows the decorated version + of the function to be separate from the undecorated version + ''' + return yamlInfo(yamlfile,experiment,platform,target) + +# Use parseyaml function to parse created edits.yaml +if __name__ == '__main__': + yamlInfo() diff --git a/lib/fre/pp/edits-template.yaml b/lib/fre/pp/edits-template.yaml new file mode 100644 index 00000000..9bedfe71 --- /dev/null +++ b/lib/fre/pp/edits-template.yaml @@ -0,0 +1,271 @@ +## User defined edits + +# Paths +configuration paths: + # Path to where rose-suite experiment configuration should be in pp repo + rose-suite: "opt/" + # Path to remap-pp-components rose-app.conf in pp repo + rose-remap: "app/remap-pp-components/rose-app.conf" + # Path to regrid rose-app.conf in pp repo + rose-regrid: "app/regrid-xy/rose-app.conf" + +# Define widely used variables +# Length of analysis of experiment, string in double quotes +ana_amip_len: &ANA_AMIP_LEN "" +# String in double quotes +pp_amip_chunk: &PP_AMIP_CHUNK96 "" +# Year to start analysis on experiment, string in double quotes +ana_amip_start: &ANA_AMIP_START "" +# Year to stop analysis on experiment, string in double quotes +ana_amip_stop: &ANA_AMIP_STOP "" +# Default xy Interp, string in double quotes +defaultyInterp: &defaultxyInterp "" + +# Information for rose-suite experiment configuration +rose-suite: + settings: + # ISO8601 duration of the length of the history segment + history_segment: + # ISO8601 date to begin postprocessing + pp_start: + # ISO8601 date to stop postprocessing + pp_stop: + # Length of experiment + len: + # Specific site used for workflow; must be 'generic', 'gfdl-ws' or 'ppan' + site: + # Location for shared analysis scripts + fre_analysis_home: + # ISO8601 duration of the desired post-processed output + pp_chunk_a: + # ISO8601 duration of a second desired postprocessed output + pp_chunk_b: + # Components to be post-processed + pp_components: + # Default regridded resolution + pp_default_xyinterp: *defaultxyInterp + # Filepath to an FMS gridSpec netCDF file or a tarfile + pp_grid_spec: + + switches: + # Switch to remove intermediate data files when they are no longer needed + clean_work: + # Switch to run MDTF on generated pp output + do_mdtf: + # Switch to turn on/off statics processing + do_statics: + # Switch to turn on/off time-average file generation + do_timeavgs: + # Switch to run refine-diag script(s) on history file to generate additional diagnostics + do_refinediag: + # Switch to mask atmos pressure-level output above/below surface pressure/atmos top + do_atmos_plevel_masking: + # Switch to run a pre-analysis script on history files + do_preanalysis: + do_analysis: + do_analysis_only: + + directories: + # Directory to the primary location for history files + history_dir: + # Directory to write pp products to + pp_dir: + # Directory to use for history file cache + ptmp_dir: + # Filepath to refineDiag scripts + refinediag_scripts: + # Filepath to the user script + preanalysis_script: + # Directory to the secondary location for history files + history_dir_refined: + # Directory to write analysis output + analysis: + +# Information needed to populate the rose-apps for regrid-xy and remap-pp-components +# These rose apps will be retired eventually +components: + # Name of component + - type: + # Analysis start + start: *ANA_AMIP_START + sources: + sourceGrid: + # Default regridded grid + xyInterp: *defaultxyInterp + # Should be 'conserve_order1', 'conserve_order2' or 'bilinear' + interpMethod: + timeSeries: + - freq: + source: + chunkLength: *PP_AMIP_CHUNK96 + - freq: + source: + chunkLength: *PP_AMIP_CHUNK96 + variables: + - freq: + source: + chunkLength: *PP_AMIP_CHUNK96 + - freq: + chunkLength: *PP_AMIP_CHUNK96 + timeAverage: + - source: + interval: *PP_AMIP_CHUNK96 + - source: + interval: *ANA_AMIP_LEN + - source: + interval: *PP_AMIP_CHUNK96 + calcInterval: *PP_AMIP_CHUNK96 + - type: + zInterp: + start: *ANA_AMIP_START + sources: + sourceGrid: + xyInterp: *defaultxyInterp + interpMethod: + cmip: + timeSeries: + - freq: + source: + chunkLength: *PP_AMIP_CHUNK96 + - freq: + chunkLength: *PP_AMIP_CHUNK96 + timeAverage: + - source: + interval: *PP_AMIP_CHUNK96 + - source: + interval: *ANA_AMIP_LEN + - source: + interval: *PP_AMIP_CHUNK96 + calcInterval: *PP_AMIP_CHUNK96 + - source: + interval: *ANA_AMIP_LEN + - type: + start: *ANA_AMIP_START + sources: + sourceGrid: + xyInterp: *defaultxyInterp + interpMethod: + cmip: + timeSeries: + - freq: + chunkLength: *PP_AMIP_CHUNK96 + - type: + start: *ANA_AMIP_START + sources: + sourceGrid: + xyInterp: *defaultxyInterp + interpMethod: + cmip: + timeSeries: + - freq: + chunkLength: *PP_AMIP_CHUNK96 + - freq: + chunkLength: *PP_AMIP_CHUNK96 + timeAverage: + - source: + interval: *PP_AMIP_CHUNK96 + - source: + interval: *ANA_AMIP_LEN + - type: + start: *ANA_AMIP_START + sources: + sourceGrid: + xyInterp: *defaultxyInterp + interpMethod: + cmip: + timeAverage: + - source: + interval: *PP_AMIP_CHUNK96 + - source: + interval: *ANA_AMIP_LEN + timeSeries: + - freq: + chunkLength: *PP_AMIP_CHUNK96 + - type: + start: *ANA_AMIP_START + sources: + sourceGrid: + xyInterp: *defaultxyInterp + interpMethod: + timeSeries: + - freq: + chunkLength: *PP_AMIP_CHUNK96 + - type: + start: *ANA_AMIP_START + sources: + xyInterp: + interpMethod: + cmip: + timeSeries: + - freq: + chunkLength: *PP_AMIP_CHUNK96 + - freq: + source: + chunkLength: *PP_AMIP_CHUNK96 + - type: + start: *ANA_AMIP_START + sources: + sourceGrid: + xyInterp: *defaultxyInterp + interpMethod: + cmip: + timeSeries: + - freq: + chunkLength: *PP_AMIP_CHUNK96 + - freq: + chunkLength: *PP_AMIP_CHUNK96 + timeAverage: + - source: + interval: *PP_AMIP_CHUNK96 + - source: + interval: *ANA_AMIP_LEN + - type: + start: *ANA_AMIP_START + xyInterp: *defaultxyInterp + sources: + sourceGrid: + interpMethod: + cmip: + timeSeries: + - freq: + chunkLength: *PP_AMIP_CHUNK96 + - freq: + chunkLength: *PP_AMIP_CHUNK96 + source: + - type: + start: *ANA_AMIP_START + sources: + sourceGrid: + xyInterp: *defaultxyInterp + interpMethod: + cmip: + timeSeries: + - freq: + chunkLength: *PP_AMIP_CHUNK96 + timeAverage: + - source: + interval: *PP_AMIP_CHUNK96 + - source: + interval: *ANA_AMIP_LEN + - type: + start: *ANA_AMIP_START + sources: + sourceGrid: + xyInterp: *defaultxyInterp + interpMethod: + cmip: + timeSeries: + - freq: + chunkLength: *PP_AMIP_CHUNK96 + +# TMP DIRECTORY CREATION +# This edit is not needed when working on pp/an. If not working on pp/an, a tmpDir must be defined. +tmpdir: + tmpdirpath: + +# Optional edit: Add --symlink-dirs option to install cylc-run directory to location with more space available; default installation location for cylc-run directory is $HOME; +# Utilize this edit if you do not have enough space in home directory +install-exp-script: + - path: + - install-option: + install: diff --git a/lib/fre/pp/frepp.py b/lib/fre/pp/frepp.py new file mode 100644 index 00000000..b3456e31 --- /dev/null +++ b/lib/fre/pp/frepp.py @@ -0,0 +1,213 @@ +''' fre pp ''' + +import click +from .checkoutScript import checkoutTemplate +from .configure_script_yaml import yamlInfo +from .configure_script_xml import convert +from .validate import validate_subtool +from .install import install_subtool +from .run import pp_run_subtool +from .status import status_subtool +from .wrapper import runFre2pp + +@click.group(help=click.style(" - access fre pp subcommands", fg=(57,139,210))) +def pp_cli(): + ''' entry point to fre pp click commands ''' + + +# fre pp status +@pp_cli.command() +@click.option("-e", "--experiment", type=str, + help="Experiment name", + required=True) +@click.option("-p", "--platform", type=str, + help="Platform name", + required=True) +@click.option("-t", "--target", type=str, + help="Target name", + required=True) +@click.pass_context +def status(context, experiment, platform, target): + # pylint: disable=unused-argument + """ - Report status of PP configuration""" + context.forward(status_subtool) + +# fre pp run +@pp_cli.command() +@click.option("-e", "--experiment", type=str, + help="Experiment name", + required=True) +@click.option("-p", "--platform", type=str, + help="Platform name", + required=True) +@click.option("-t", "--target", type=str, + help="Target name", + required=True) +@click.pass_context +def run(context, experiment, platform, target): + # pylint: disable=unused-argument + """ - Run PP configuration""" + context.forward(pp_run_subtool) + +# fre pp validate +@pp_cli.command() +@click.option("-e", "--experiment", type=str, + help="Experiment name", + required=True) +@click.option("-p", "--platform", type=str, + help="Platform name", + required=True) +@click.option("-t", "--target", type=str, + help="Target name", + required=True) +@click.pass_context +def validate(context, experiment, platform, target): + # pylint: disable=unused-argument + """ - Validate PP configuration""" + context.forward(validate_subtool) + +# fre pp install +@pp_cli.command() +@click.option("-e", "--experiment", type=str, + help="Experiment name", + required=True) +@click.option("-p", "--platform", type=str, + help="Platform name", + required=True) +@click.option("-t", "--target", type=str, + help="Target name", + required=True) +@click.pass_context +def install(context, experiment, platform, target): + # pylint: disable=unused-argument + """ - Install PP configuration""" + context.forward(install_subtool) + +@pp_cli.command() +@click.option("-y", "--yamlfile", type=str, + help="YAML file to be used for parsing", + required=True) +@click.option("-e", "--experiment", type=str, + help="Experiment name", + required=True) +@click.option("-p", "--platform", type=str, + help="Platform name", + required=True) +@click.option("-t", "--target", type=str, + help="Target name", + required=True) +@click.pass_context +def configure_yaml(context,yamlfile,experiment,platform,target): + # pylint: disable=unused-argument + """ - Execute fre pp configure """ + context.forward(yamlInfo) + +@pp_cli.command() +@click.option("-e", "--experiment", type=str, + help="Experiment name", + required=True) +@click.option("-p", "--platform", type=str, + help="Platform name", + required=True) +@click.option("-t", "--target", type=str, + help="Target name", + required=True) +@click.option("-b", "--branch", + show_default=True, + default="main", type=str, + help="Name of fre2/workflows/postproc branch to clone; " \ + "defaults to 'main'. Not intended for production use, " \ + "but needed for branch testing." ) +@click.pass_context +def checkout(context, experiment, platform, target, branch='main'): + # pylint: disable=unused-argument + """ - Execute fre pp checkout """ + context.forward(checkoutTemplate) + +@pp_cli.command() +@click.option('-x', '--xml', + required=True, + help="Required. The Bronx XML") +@click.option('-p', '--platform', + required=True, + help="Required. The Bronx XML Platform") +@click.option('-t', '--target', + required=True, + help="Required. The Bronx XML Target") +@click.option('-e', '--experiment', + required=True, + help="Required. The Bronx XML Experiment") +@click.option('--do_analysis', + is_flag=True, + default=False, + help="Optional. Runs the analysis scripts.") +@click.option('--historydir', + help="Optional. History directory to reference. " \ + "If not specified, the XML's default will be used.") +@click.option('--refinedir', + help="Optional. History refineDiag directory to reference. " \ + "If not specified, the XML's default will be used.") +@click.option('--ppdir', + help="Optional. Postprocessing directory to reference. " \ + "If not specified, the XML's default will be used.") +@click.option('--do_refinediag', + is_flag=True, + default=False, + help="Optional. Process refineDiag scripts") +@click.option('--pp_start', + help="Optional. Starting year of postprocessing. " \ + "If not specified, a default value of '0000' " \ + "will be set and must be changed in rose-suite.conf") +@click.option('--pp_stop', + help="Optional. Ending year of postprocessing. " \ + "If not specified, a default value of '0000' " \ + "will be set and must be changed in rose-suite.conf") +@click.option('--validate', + is_flag=True, + help="Optional. Run the Cylc validator " \ + "immediately after conversion") +@click.option('-v', '--verbose', + is_flag=True, + help="Optional. Display detailed output") +@click.option('-q', '--quiet', + is_flag=True, + help="Optional. Display only serious messages and/or errors") +@click.option('--dual', + is_flag=True, + help="Optional. Append '_canopy' to pp, analysis, and refinediag dirs") +@click.pass_context +def configure_xml(context, xml, platform, target, experiment, do_analysis, historydir, refinedir, + ppdir, do_refinediag, pp_start, pp_stop, validate, verbose, quiet, dual): + # pylint: disable=unused-argument + """ - Converts a Bronx XML to a Canopy rose-suite.conf """ + context.forward(convert) + +#fre pp wrapper +@pp_cli.command() +@click.option("-e", "--experiment", type=str, + help="Experiment name", + required=True) +@click.option("-p", "--platform", type=str, + help="Platform name", + required=True) +@click.option("-t", "--target", type=str, + help="Target name", + required=True) +@click.option("-c", "--config-file", type=str, + help="Path to a configuration file in either XML or YAML", + required=True) +@click.option("-b", "--branch", + show_default=True, + default="main", type=str, + help="Name of fre2/workflows/postproc branch to clone; " \ + "defaults to 'main'. Not intended for production use, " \ + "but needed for branch testing." ) +@click.pass_context +def wrapper(context, experiment, platform, target, config_file, branch='main'): + # pylint: disable=unused-argument + """ - Execute fre pp steps in order """ + context.forward(runFre2pp) + +if __name__ == "__main__": + ''' entry point for click to fre pp commands ''' + pp_cli() diff --git a/lib/fre/pp/install.py b/lib/fre/pp/install.py new file mode 100644 index 00000000..9ffc00ee --- /dev/null +++ b/lib/fre/pp/install.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +''' fre pp install ''' + +import subprocess +import click + +def _install_subtool(experiment, platform, target): + """ + Install the Cylc workflow definition located in + ~/cylc-src/____ + to + ~/cylc-run/____ + """ + + name = experiment + '__' + platform + '__' + target + cmd = f"cylc install --no-run-name {name}" + subprocess.run(cmd, shell=True, check=True) + +@click.command() +def install_subtool(experiment, platform, target): + ''' entry point to install for click ''' + return _install_subtool(experiment, platform, target) diff --git a/lib/fre/pp/run.py b/lib/fre/pp/run.py new file mode 100644 index 00000000..57f2c427 --- /dev/null +++ b/lib/fre/pp/run.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +''' fre pp run ''' + +import subprocess +import click + +def _pp_run_subtool(experiment, platform, target): + """ + Start or restart the Cylc workflow identified by: + ____ + """ + + name = experiment + '__' + platform + '__' + target + cmd = f"cylc play {name}" + subprocess.run(cmd, shell=True, check=True) + +@click.command() +def pp_run_subtool(experiment, platform, target): + ''' entry point to run for click ''' + return _pp_run_subtool(experiment, platform, target) diff --git a/lib/fre/pp/schema.json b/lib/fre/pp/schema.json new file mode 100644 index 00000000..dfb9cff5 --- /dev/null +++ b/lib/fre/pp/schema.json @@ -0,0 +1,75 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Schema for PP Yaml", + "type": "object", + "properties": { + "name": {"type": "string"}, + "platform": {"type": "string"}, + "target": {"type": "string"}, + "directories": { + "description": "FRE shared directories", + "type": "object", + "items":{"$ref": "#/$defs/dirs" } + }, + "postprocess": { + "description": "FRE post-processing information", + "type": "object", + "items":{"$ref": "#/$defs/pp" } + } + }, + "$defs": { + "dirs": { + "history_dir": {"type":"string"}, + "pp_dir": {"type":"string"}, + "ptmp_dir": {"type":"string"}, + "refinediag_scripts":{"type":["string","null"]}, + "preanalysis_script":{"type":["string","null"]}, + "history_refined":{"type":["string","null"]}, + "analysis_dir":{"type":["string","null"]}, + "pp_grid_spec": {"type":"string"}, + "fre_analysis_home": {"type":["string","null"]} + }, + "pp": { + "type": "object", + "properties": { + "settings": { + "type:": "object", + "properties": { + "history_segment": {"type":"string"}, + "site": {"type":"string"}, + "pp_chunk_a": {"type":"string"}, + "pp_chunk_b": {"type":"string"}, + "pp_start": {"type":"string"}, + "pp_stop": {"type":"string"}, + "pp_components": {"type":"string"} + } + }, + "switches": { + "type": "object", + "properties": { + "clean_work": {"type":"boolean"}, + "do_mdtf": {"type":"boolean"}, + "do_statics": {"type":"boolean"}, + "do_timeavgs": {"type":"boolean"}, + "do_refinediag": {"type":"boolean"}, + "do_atmos_plevel_masking": {"type":"boolean"}, + "do_preanalysis": {"type":"boolean"}, + "do_analysis": {"type":"boolean"}, + "do_analysis_only": {"type":"boolean"} + } + }, + "components": { + "type": "array", + "properties": { + "type": {"type":"string"}, + "sources": {"type":"string"}, + "sourceGrid": {"type":"string"}, + "xyInterp": {"type":"string"}, + "interpMethod": {"type":"string"}, + "inputRealm": {"type":"string"} + } + } + } + } + } +} diff --git a/lib/fre/pp/status.py b/lib/fre/pp/status.py new file mode 100644 index 00000000..6e2c07e1 --- /dev/null +++ b/lib/fre/pp/status.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +''' fre pp status ''' + +import subprocess +import click + +def _status_subtool(experiment, platform, target): + """ + Report workflow state for the Cylc workflow + ____ + """ + + name = experiment + '__' + platform + '__' + target + cmd = f"cylc workflow-state {name}" + subprocess.run(cmd, shell=True, check=True, timeout=30) + + +@click.command() +def status_subtool(experiment, platform, target): + ''' entry point to status for click ''' + return _status_subtool(experiment, platform, target) diff --git a/lib/fre/pp/tests/AM5_example/am5.yaml b/lib/fre/pp/tests/AM5_example/am5.yaml new file mode 100644 index 00000000..4f3e5a4d --- /dev/null +++ b/lib/fre/pp/tests/AM5_example/am5.yaml @@ -0,0 +1,115 @@ +# reusable variables +fre_properties: + - &AM5_VERSION "am5f7b12r1" + - &FRE_STEM !join [am5/, *AM5_VERSION] + + # amip + - &EXP_AMIP_START "19790101T0000Z" + - &EXP_AMIP_END "20200101T0000Z" + - &ANA_AMIP_START "19800101T0000Z" + - &ANA_AMIP_END "20200101T0000Z" + + - &PP_AMIP_CHUNK96 "P1Y" + - &PP_AMIP_CHUNK384 "P1Y" + - &PP_XYINTERP96 "180,288" + - &PP_XYINTERP384 "720,1152" + + # climo + - &EXP_CLIMO_START96 "0001" + - &EXP_CLIMO_END96 "0011" + - &ANA_CLIMO_START96 "0002" + - &ANA_CLIMO_END96 "0011" + + - &EXP_CLIMO_START384 "0001" + - &EXP_CLIMO_END384 "0006" + - &ANA_CLIMO_START384 "0002" + - &ANA_CLIMO_END384 "0006" + + # coupled + - &PP_CPLD_CHUNK_A "P5Y" + - &PP_CPLD_CHUNK_B "P20Y" + + # grids + - &GRID_SPEC96 "/archive/oar.gfdl.am5/model_gen5/inputs/c96_grid/c96_OM4_025_grid_No_mg_drag_v20160808.tar" + + # compile information + - &release "f1a1r1" + - &INTEL "intel-classic" + - &FMSincludes "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" + - &momIncludes "-Imom6/MOM6-examples/src/MOM6/pkg/CVMix-src/include" + +shared: + # compile information + compile: + compileYaml: &compile_yaml "compile.yaml" + platformYaml: "yaml_include/platforms.yaml" + + # directories shared across tools + directories: &shared_directories + history_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, history] + pp_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, pp] + analysis_dir: !join [/nbhome/$USER/, *FRE_STEM, /, *name] + ptmp_dir: "/xtmp/$USER/ptmp" + fre_analysis_home: "/home/fms/local/opt/fre-analysis/test" + + # shared pp settings + postprocess: + settings: &shared_settings + history_segment: "P1Y" + site: "ppan" + switches: &shared_switches + do_statics: True + do_timeavgs: True + clean_work: True + do_refinediag: False + do_atmos_plevel_masking: True + do_preanalysis: False + do_analysis: True + +experiments: + - name: "c96L65_am5f7b12r1_amip" + pp: + - "yaml_include/pp.c96_amip.yaml" + compile: *compile_yaml + - name: "c96L65_am5f7b12r1_pdclim1850F" + pp: + - "yaml_include/pp.c96_clim.yaml" + compile: *compile_yaml + - name: "c96L65_am5f7b12r1_pdclim2010F" + pp: + - "yaml_include/pp.c96_clim.yaml" + compile: *compile_yaml + - name: "c96L65_am5f7b12r1_pdclim2010AERF" + pp: + - "yaml_include/pp.c96_clim.yaml" + compile: *compile_yaml + - name: "c384L65_am5f7b12r1_amip" + pp: + - "yaml_include/pp.c384_amip.yaml" + compile: *compile_yaml + - name: "c384L65_am5f7b12r1_pdclim2010F" + pp: + - "yaml_include/pp.c384_clim.yaml" + compile: *compile_yaml + - name: "c384L65_am5f7b12r1_pdclim1850F" + pp: + - "yaml_include/pp.c384_clim.yaml" + compile: *compile_yaml + - name: "c384L65_am5f7b12r1_pdclim2010AERF" + pp: + - "yaml_include/pp.c384_clim.yaml" + compile: *compile_yaml + - name: "c384L65_am5f7b12r1_OM4_p25_piControl_noBLING_DynVeg" + pp: + - "yaml_include/pp.c384_amip.yaml" + - "yaml_include/pp.om4.yaml" + compile: *compile_yaml + - name: "c96L65_am5f7b12r1_OM4_p25_piControl_noBLING_DynVeg" + pp: + - "yaml_include/pp.c96_amip.yaml" + - "yaml_include/pp.om4.yaml" + compile: *compile_yaml + - name: "c96L65_am5f7b12r1_amip_cosp" + pp: + - "yaml_include/pp.c96_amip.yaml" + compile: *compile_yaml diff --git a/lib/fre/pp/tests/AM5_example/compile.yaml b/lib/fre/pp/tests/AM5_example/compile.yaml new file mode 100644 index 00000000..5f9a361b --- /dev/null +++ b/lib/fre/pp/tests/AM5_example/compile.yaml @@ -0,0 +1,67 @@ +compile: + experiment: "am5" + container_addlibs: + baremetal_linkerflags: + src: + - component: "FMS" + repo: "https://github.com/NOAA-GFDL/FMS.git" + cppdefs: "-DINTERNAL_FILE_NML -Duse_libMPI -Duse_netCDF" + branch: "2022.01" + cppdefs: "-DHAVE_GETTID -Duse_libMPI -Duse_netCDF" + otherFlags: *FMSincludes + - component: "am5_phys" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/am5_phys.git" + branch: "2022.01" + otherFlags: *FMSincludes + - component: "GFDL_atmos_cubed_sphere" + requires: ["FMS", "am5_phys"] + repo: "https://github.com/NOAA-GFDL/GFDL_atmos_cubed_sphere.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE -DINTERNAL_FILE_NML" + branch: "2022.01" + paths: ["GFDL_atmos_cubed_sphere/driver/GFDL", + "GFDL_atmos_cubed_sphere/model", + "GFDL_atmos_cubed_sphere/driver/SHiELD/cloud_diagnosis.F90", + "GFDL_atmos_cubed_sphere/driver/SHiELD/gfdl_cloud_microphys.F90", + "GFDL_atmos_cubed_sphere/tools", + "GFDL_atmos_cubed_sphere/GFDL_tools"] + otherFlags: *FMSincludes + - component: "atmos_drivers" + requires: ["FMS", "am5_phys", "GFDL_atmos_cubed_sphere"] + repo: "https://github.com/NOAA-GFDL/atmos_drivers.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE" + branch: "2022.01" + paths: ["atmos_drivers/coupled"] + otherFlags: *FMSincludes + - component: "ice_sis" + requires: ["FMS", "ice_param", "mom6"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/ice_sis.git" + branch: "2021.02" + otherFlags: !join [*FMSincludes, " ", *momIncludes] + - component: "ice_param" + repo: "https://github.com/NOAA-GFDL/ice_param.git" + cppdefs: "-Duse_yaml -Duse_libMPI -Duse_netCDF" + branch: "2021.02" + requires: ["FMS", "mom6"] + otherFlags: !join [*FMSincludes," ", *momIncludes] + - component: "land_lad2" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/land_lad2.git" + branch: "2022.01" + branch: "land_lad2_2021.02" + doF90Cpp: True + cppdefs: "-DINTERNAL_FILE_NML" + otherFlags: *FMSincludes + - component: "mom6" + requires: ["FMS"] + paths: ["mom6/MOM6-examples/src/MOM6/config_src/dynamic", "mom6/MOM6-examples/src/MOM6/config_src/coupled_driver", "mom6/MOM6-examples/src/MOM6/src/*/", "mom6/MOM6-examples/src/MOM6/src/*/*/", "mom6/ocean_BGC/generic_tracers", "mom6/ocean_BGC/mocsy/src"] + branch: ["2021.02","dev/gfdl/2018.04.06"] + repo: ["https://github.com/NOAA-GFDL/ocean_BGC.git","https://github.com/NOAA-GFDL/MOM6-examples.git"] + makeOverrides: 'OPENMP=""' + otherFlags: !join [*FMSincludes, " ", *momIncludes] + - component: "FMScoupler" + paths: ["FMScoupler/full", "FMScoupler/shared"] + repo: "https://github.com/NOAA-GFDL/FMScoupler.git" + branch: "2022.01" + requires: ["FMS", "atmos_drivers", "am5_phys", "land_lad2", "ice_sis", "ice_param", "mom6"] + otherFlags: !join [*FMSincludes, " ", *momIncludes] diff --git a/lib/fre/pp/tests/AM5_example/yaml_include/platforms.yaml b/lib/fre/pp/tests/AM5_example/yaml_include/platforms.yaml new file mode 100644 index 00000000..7e1b9f49 --- /dev/null +++ b/lib/fre/pp/tests/AM5_example/yaml_include/platforms.yaml @@ -0,0 +1,26 @@ +platforms: + - name: ncrc5.intel + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [ !join [*INTEL, "/2022.2.1"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/test + - name: ncrc5.intel23 + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [!join [*INTEL, "/2023.1.0"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/test + - name: hpcme.2023 + compiler: intel + RUNenv: [". /spack/share/spack/setup-env.sh", "spack load libyaml", "spack load netcdf-fortran@4.5.4", "spack load hdf5@1.14.0"] + modelRoot: /apps + fc: mpiifort + cc: mpiicc + container: True + containerBuild: "podman" + containerRun: "apptainer" diff --git a/lib/fre/pp/tests/AM5_example/yaml_include/pp.c96_amip.yaml b/lib/fre/pp/tests/AM5_example/yaml_include/pp.c96_amip.yaml new file mode 100644 index 00000000..117c66c6 --- /dev/null +++ b/lib/fre/pp/tests/AM5_example/yaml_include/pp.c96_amip.yaml @@ -0,0 +1,88 @@ +# local reusable variable overrides +fre_properties: + - &custom_interp "180,360" + +# directory overrides +#c96_amip_directories: +directories: + <<: *shared_directories + ptmp_dir: "/ptmp/$USER" + pp_grid_spec: *GRID_SPEC96 + +#c96_amip_postprocess: +postprocess: + # pp setting overrides + settings: + <<: *shared_settings + pp_start: *ANA_AMIP_START + pp_stop: *ANA_AMIP_END + pp_chunk_a: *PP_AMIP_CHUNK96 + pp_components: "atmos atmos_scalar" + switches: + <<: *shared_switches + do_statics: False + + # main pp instructions + components: + - type: "atmos_cmip" + sources: "atmos_month_cmip atmos_8xdaily_cmip atmos_daily_cmip" + sourceGrid: "cubedsphere" + xyInterp: *custom_interp + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos" + sources: "atmos_month" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_level_cmip" + sources: "atmos_level_cmip" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_level" + sources: "atmos_month" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_month_aer" + sources: "atmos_month_aer" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'atmos' + - type: "atmos_diurnal" + sources: "atmos_diurnal" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_scalar" + sources: "atmos_scalar" + - type: "aerosol_cmip" + xyInterp: *PP_XYINTERP96 + sources: "aerosol_month_cmip" + sourceGrid: "cubedsphere" + interpMethod: "conserve_order1" + inputRealm: 'atmos' + - type: "land" + sources: "land_month" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'land' + - type: "land_cmip" + sources: "land_month_cmip" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'land' + - type: "tracer_level" + sources: "atmos_tracer" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'atmos' diff --git a/lib/fre/pp/tests/__init__.py b/lib/fre/pp/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/pp/tests/test_configure_script_yaml.py b/lib/fre/pp/tests/test_configure_script_yaml.py new file mode 100644 index 00000000..eaf1fc2e --- /dev/null +++ b/lib/fre/pp/tests/test_configure_script_yaml.py @@ -0,0 +1,46 @@ +""" +Test configure_script_yaml +""" +import os +from pathlib import Path +from fre.pp import configure_script_yaml as csy + +# Set what would be click options +EXPERIMENT = "c96L65_am5f7b12r1_amip" +PLATFORM = "gfdl.ncrc5-intel22-classic" +TARGET = "prod-openmp" + +# Set example yaml paths, input directory +test_dir = Path("fre/pp/tests") +test_yaml = Path("AM5_example/am5.yaml") + +def test_combinedyaml_exists(): + """ + Make sure combined yaml file exists + """ + assert Path(f"{test_dir}/{test_yaml}").exists() + +def test_configure_script(): + """ + Tests success of confgure yaml script + Creates rose-suite, regrid rose-app, remap rose-app + TO-DO: will break this up for better tests + """ + # Set home for ~/cylc-src location in script + os.environ["HOME"]=str(Path(f"{test_dir}/configure_yaml_out")) + + # Set output directory + out_dir = Path(f"{os.getenv('HOME')}/cylc-src/{EXPERIMENT}__{PLATFORM}__{TARGET}") + Path(out_dir).mkdir(parents=True,exist_ok=True) + + # Define combined yaml + model_yaml = str(Path(f"{test_dir}/{test_yaml}")) + + # Invoke configure_yaml_script.py + csy.yamlInfo(model_yaml,EXPERIMENT,PLATFORM,TARGET) + + # Check for configuration creation and final combined yaml + assert all([Path(f"{out_dir}/{EXPERIMENT}.yaml").exists(), + Path(f"{out_dir}/rose-suite.conf").exists(), + Path(f"{out_dir}/app/regrid-xy/rose-app.conf").exists(), + Path(f"{out_dir}/app/remap-pp-components/rose-app.conf").exists()]) diff --git a/lib/fre/pp/tests/test_rose_quoting.py b/lib/fre/pp/tests/test_rose_quoting.py new file mode 100644 index 00000000..bd5353b1 --- /dev/null +++ b/lib/fre/pp/tests/test_rose_quoting.py @@ -0,0 +1,7 @@ +from fre.pp.configure_script_yaml import quote_rose_values + +def test_boolean(): + assert quote_rose_values(True) == 'True' + +def test_string(): + assert quote_rose_values('foo') == "'foo'" diff --git a/lib/fre/pp/validate.py b/lib/fre/pp/validate.py new file mode 100644 index 00000000..9c07340f --- /dev/null +++ b/lib/fre/pp/validate.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +''' fre pp validate ''' + +import os +import subprocess +import click + +def _validate_subtool(experiment, platform, target): + """ + Validate the Cylc workflow definition located in + ~/cylc-src/____ + """ + + directory = os.path.expanduser('~/cylc-src/' + experiment + '__' + platform + '__' + target) + + # Change the current working directory + os.chdir(directory) + + # Run the Rose validation macros + cmd = "rose macro --validate" + subprocess.run(cmd, shell=True, check=True) + + # Validate the Cylc configuration + cmd = "cylc validate ." + subprocess.run(cmd, shell=True, check=True) + +@click.command() +def validate_subtool(experiment, platform, target): + ''' entry point to validate for click ''' + return _validate_subtool(experiment, platform, target) diff --git a/lib/fre/pp/wrapper.py b/lib/fre/pp/wrapper.py new file mode 100644 index 00000000..0f0aad39 --- /dev/null +++ b/lib/fre/pp/wrapper.py @@ -0,0 +1,85 @@ +""" +frepp.py, a replacement for the frepp bash script located at: +https://gitlab.gfdl.noaa.gov/fre2/system-settings/-/blob/main/bin/frepp +Author: Carolyn.Whitlock +""" + +#todo: +# add relative path import to rest of pp tools +# add command-line args using same format as fre.py +# include arg for pp start / stop +# test yaml path +# error handling + +import os +import time +import click + +# Import from the local packages +from .checkoutScript import _checkoutTemplate +from .configure_script_xml import _convert +from .configure_script_yaml import _yamlInfo +from .validate import _validate_subtool +from .install import _install_subtool +from .run import _pp_run_subtool +from .status import _status_subtool + +@click.command() +def runFre2pp(experiment, platform, target, config_file, branch): + ''' + Wrapper script for calling a FRE2 pp experiment with the canopy-style + infrastructure and fre-cli + time=0000 + ''' + + config_file = os.path.abspath(config_file) + + #env_setup + #todo: check for experiment existing, call frepp_stop to clean experiment, + try: + print("calling _checkoutTemplate") + _checkoutTemplate(experiment, platform, target, branch) + except Exception as err: + raise + + #dumb xml check;does it need to be smarter? + is_xml = config_file[-3:] == "xml" + if is_xml: + #TODO: should this prompt for pp start/stop years? + try: + _convert(config_file, platform, target, experiment, do_analysis=False) + #note: arg list for this function is a looooot longer, but all those + #args can be deduced from the xml when given default vals + except Exception as err: + raise + try: + _validate_subtool(experiment, platform, target) + #See notes in main() function + except Exception as err: + raise + else: + try: + _yamlInfo(config_file, experiment, platform, target) + except Exception as err: + raise + + try: + _install_subtool(experiment, platform, target) + except: + raise + + try: + _pp_run_subtool(experiment, platform, target) + except Exception as err: + raise + + #send off a watcher script that reports on how it's going + for n in range(1,12): + try: + _status_subtool(experiment, platform, target) + except Exception as err: + raise + time.sleep(300) + +if __name__ == '__main__': + runFre2pp() diff --git a/lib/fre/pp/wrapperscript b/lib/fre/pp/wrapperscript new file mode 100755 index 00000000..f98d9cd6 --- /dev/null +++ b/lib/fre/pp/wrapperscript @@ -0,0 +1,320 @@ +#!/bin/bash +set -euo pipefail +set -x + +# https://stackoverflow.com/questions/402377/using-getopts-to-process-long-and-short-command-line-options +TEMP=$(getopt -o x:p:P:T:t:shvc:D:d: --long xml:,platform:,target:,time:,help,mppnccombine-opts:,mail-list: -n 'frepp' -- "$@") +eval set -- "$TEMP" + +# defaults +xml= +platform= +target= +time= +help= + +# arg parsing +while true; do + case "$1" in + # required + -x | --xml ) xml="$2"; shift 2 ;; + -p | -P | --platform ) platform="$2"; shift 2 ;; + -T | --target ) target="$2"; shift 2 ;; + -t | --time ) time="$2"; shift 2 ;; + + # optional + -h | --help ) help=true; shift ;; + + # ignored + -v ) shift ;; + -c ) shift 2 ;; + -D ) shift 2 ;; + -d ) shift 2 ;; + -s ) shift ;; + --mppnccombine-opts ) shift 2 ;; + --mail-list ) shift 2 ;; + + -- ) shift; break ;; + * ) break ;; + esac +done +if [[ -n ${1-} ]]; then + expname=$1 +else + expname= +fi + +# If $FRE_DUALPP is set, then take two different actions +# 1. Append "_canopy" to pp, analysis, and history_refined directories created through the XML converter +# 2. Submit Bronx frepp as well +set +u +if [[ $FRE_DUALPP ]]; then + dual=true +else + dual=false +fi +set -u + +# Help +usage="Usage: frepp --xml=XML --platform=PLATFORM --target=TARGET --time=YYYY EXP" +if [[ $help ]]; then + echo $usage + cat << EOF +################################################################################ +FRE Canopy frepp wrapper to start Canopy postprocessing workflow with +traditional Bronx frepp usage. + +Cylc implementation current settings used by this wrapper: +1. Workflow name is ____ +e.g. use cylc commands such as: + +cylc workflow-state ____ + +This is somewhat overly verbose and also not verbose enough +(i.e. does not include FRE STEM). +If you have suggestions please let the FRE team know. + +2. Will not use unique run directories. +If the run directory exists you will need to remove it before re-installing. + +################################################################################ +What does this script do? +1. If workflow run-dir was previously installed, + start postprocessing for a history file segment: + +- Check if the workflow is running +- Check the task states +- Start cylc scheduler +- Trigger requested processing (-t YYYY) +- Exit + +2. Otherwise, if workflow src-dir does not exist, + configure the postprocessing: + +- Checkout a fresh PP template +- Run the XML converter + +3. Then, install and start the postprocessing for a history file segment +- Run the validation scripts +- Install the workflow +- Start cylc scheduler +- Trigger requested processing (-t YYYY) + +################################################################################ +Recovery steps and scenarios: +1. Something is terribly wrong with PP and you want to reconfigure and try again +- Stop cylc scheduler with "cylc stop --kill " +- Remove run directory with "cylc clean " +- Edit the configuration files in ~/cylc-src/ +- Run frepp again to reinstall and run the updated PP configuration. + +2. Something is terribly wrong and you want a complete fresh start, + or you want an update from the pp template repo. +- Stop cylc scheduler with "cylc stop --kill" +- Remove run directory with "cylc clean " +- Remove src directory with "rm -rf ~/cylc-src/" +- Run frepp again to recheckout pp template, run xml converter, and install/run + +################################################################################ +Specific suggestions to recover from task failures: + +1. refineDiag script failures are likely with a XML-converted configs + for two reasons, so you will probably need to either adjust or remove them. + To disable refineDiag, + - set DO_REFINEDIAG=False, and + - comment out HISTORY_DIR_REFINED + +a. It may use something in the XML, using an xml shell variable that does not + exist now. In these cases, you could rewrite the refineDiag script to + not use the xmlDir shell variable or not use the script. + For "refineDiag_atmos_cmip6.csh", it was included in the postprocessing + template checkout with a small modification. Use this location: + '\$CYLC_WORKFLOW_RUN_DIR/etc/refineDiag/refineDiag_atmos_cmip6.csh'. + - set REFINEDIAG_SCRIPTS to that location + +b. It may be a refineDiag script that does not generate .nc files + as it was expected to do. FRE Bronx allows these side-effect refineDiags, + and instead a new mechanism was invented for these scripts that + do not generate netcdf output: + - set DO_PREANALYSIS=True, and + - PREANALYSIS_SCRIPT="/paath/to/script". + +2. Many PP components in Bronx XMLs are doomed (in terms of failing to + produce output and job failing) caused by using history files that do not + exist, but do not cause problems for the other components. Currently, + the Canopy pp template is not robust in terms of this error mode, + so it's best to not process history files that do not exist. + + In the future, diag manager metadata output will provide a catalog + of history output that the validators will check against. For now, + a simple checker exists, but you must manually generate the + history output list ("history-manifest" file). + + Generate the file with a simple listing of the history tarfile. + You can append a history_refined tarfile as well. Then, the validator + will report on PP components you have specified + (PP_COMPONENTS) but that do not exist in the history-manifest file. + + tar -tf /path/to/history/YYYYMMDD.nc.tar | sort > history-manifest + + To run the configuration validation: + +cd ~/cylc-src/ +rose macro --validate + + It is a good idea to not include pp components (PP_COMPONENTS) that + include history files that do not exist. + + In all cases it is recommended to remove validation errors. + See README.md for general configuration instructions. +EOF + exit 0 +fi + +# check for all options +if [[ $xml ]]; then + xml=$(readlink -f $xml) + if [[ -f $xml ]]; then + echo "using $xml" + else + echo "XML '$xml' does not exist" + exit 1 + fi +else + echo $usage + exit 1 +fi + +if [[ $platform ]]; then + echo "using $platform" +else + echo $usage + exit 1 +fi + +if [[ $target ]]; then + echo "using $target" +else + echo $usage + exit 1 +fi + +if [[ $time ]]; then + echo "using $time" +else + echo $usage + exit 1 +fi + +if [[ $expname ]]; then + echo "using $expname" +else + echo $usage + exit 1 +fi + +cylc --version +if cylc cycle-point $time; then + time_iso=$(cylc cycle-point $time --template CCYYMMDDT0000Z) +else + echo "Time '$time' not a valid ISO8601 date" + exit 1 +fi + +# Start bronx dual-pp +if [[ $dual == true ]]; then + $FRE_COMMANDS_HOME/bin/frepp -x $xml -P $platform -T $target -t $time -D '' $expname -v -s +fi + +# Set the cylc workflow name to __ +# use the default workflow source convention +name=${expname}__${platform}__$target +rundir="$HOME/cylc-run/$name" +srcdir="$HOME/cylc-src/$name" +echo Workflow name: $name +echo Run directory: $rundir +echo Src directory: $srcdir + +# Start postprocessing for a history file segment (workflow was previously installed) +if [[ -d $rundir ]]; then + echo "Run directory '$rundir' exists, so will now try to start it" + cylc scan + cylc workflow-state $name + if cylc workflow-state $name | grep failed; then + cat << EOF +################################################################################ +Unfortunately, there are failed tasks, probably caused by refineDiag errors +or try to use a history file that does not exist. + +While Cylc workflows can be configured to handle failure gracefully, +this workflow is not yet set to do this, so currently it's recommended +to reconfigure your postprocessing to remove task errors. + +For some suggestions to recover from the above most common errors, see: + +frepp --help +################################################################################ +EOF + fi + # sometimes this hangs for unknown reasons + # So for now we'll add --debug to try to diagnose it, and + # use /bin/timeout to exit after 10 min + timeout 10m cylc play --debug $name + sleep 20 + cylc trigger $name//$time_iso/pp-starter + exit 0 +fi + +# Checkout postprocessing template and configure +if [[ ! -d $srcdir ]]; then + echo "Workflow source directory '$srcdir' does not exist, so will now try to checkout template" + + # checkout + mkdir -p $HOME/cylc-src + cd $HOME/cylc-src + # try to reduce checkout size with depth=1 + #git clone --depth=1 --recursive git@gitlab.gfdl.noaa.gov:fre2/workflows/postprocessing.git $name + git clone --depth=1 --recursive https://gitlab.gfdl.noaa.gov/fre2/workflows/postprocessing.git $name + + # xml converter + cd $srcdir + if [[ $dual == true ]]; then + bin/fre-bronx-to-canopy.py -x $xml -p $platform -t $target -e $expname -v --dual + else + bin/fre-bronx-to-canopy.py -x $xml -p $platform -t $target -e $expname -v + fi +fi + +# validate configuration +cd $srcdir +if ! rose macro --validate; then + cat << EOF +################################################################################ +Configuration may not be valid. + +In general, Canopy configurations should pass all available validation scripts. +To run them, + +cd $HOME/cylc-src/$name +rose macro --validate + +Most validation errors reflect configurations problems that should be corrected. +The exceptions are: +1. PP_DIR will be created if it does not exist +2. HISTORY_DIR_REFINED will be created if it does not exist, + assuming DO_REFINEDIAG is also set + +See README.md for general configuration instructions. +################################################################################ +EOF +fi +cylc validate . + +# Install +cylc install --no-run-name $name + +# Start +cylc play $name +sleep 20 +cylc trigger $name//$time_iso/pp-starter +exit 0 diff --git a/lib/fre/pytest.ini b/lib/fre/pytest.ini new file mode 100644 index 00000000..131a2523 --- /dev/null +++ b/lib/fre/pytest.ini @@ -0,0 +1,14 @@ +[pytest] +testpaths = + fre/tests + fre/catalog/tests +# fre/check/tests + fre/cmor/tests +# fre/list/tests + fre/make/tests + fre/pp/tests +# fre/run/tests +# fre/test/tests +# fre/yamltools/tests +# fre/app/tests + fre/app/generate_time_averages/tests diff --git a/lib/fre/run/__init__.py b/lib/fre/run/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/run/frerun.py b/lib/fre/run/frerun.py new file mode 100644 index 00000000..c2c611c3 --- /dev/null +++ b/lib/fre/run/frerun.py @@ -0,0 +1,21 @@ +''' +entry point for fre run subcommands +''' + +import click +from .frerunexample import run_test_function + +@click.group(help=click.style(" - access fre run subcommands", fg=(164,29,132))) +def run_cli(): + ''' entry point to fre run click commands ''' + +@run_cli.command() +@click.option('--uppercase', '-u', is_flag=True, help = 'Print statement in uppercase.') +@click.pass_context +def function(context, uppercase): + # pylint: disable=unused-argument + """ - Execute fre run test """ + context.forward(run_test_function) + +if __name__ == "__main__": + run_cli() diff --git a/lib/fre/run/frerunexample.py b/lib/fre/run/frerunexample.py new file mode 100644 index 00000000..a4335144 --- /dev/null +++ b/lib/fre/run/frerunexample.py @@ -0,0 +1,18 @@ +""" +experimentation file for integrating one file's functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL +""" + +import click + +@click.command() +def run_test_function(uppercase=None): + """Execute fre run run_test_function""" + statement = "testingtestingtestingtesting" + if uppercase: + statement = statement.upper() + click.echo(statement) + +if __name__ == '__main__': + run_test_function() diff --git a/lib/fre/test/__init__.py b/lib/fre/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/test/fretest.py b/lib/fre/test/fretest.py new file mode 100644 index 00000000..172ac855 --- /dev/null +++ b/lib/fre/test/fretest.py @@ -0,0 +1,21 @@ +''' +entry point for fre test subcommands +''' + +import click +from .fretestexample import test_test_function + +@click.group(help=click.style(" - access fre test subcommands", fg=(92,164,29))) +def test_cli(): + ''' entry point to fre test click commands ''' + +@test_cli.command() +@click.option('--uppercase', '-u', is_flag=True, help = 'Print statement in uppercase.') +@click.pass_context +def function(context, uppercase): + # pylint: disable=unused-argument + """ - Execute fre test test """ + context.forward(test_test_function) + +if __name__ == "__main__": + test_cli() diff --git a/lib/fre/test/fretestexample.py b/lib/fre/test/fretestexample.py new file mode 100644 index 00000000..86d0f6ce --- /dev/null +++ b/lib/fre/test/fretestexample.py @@ -0,0 +1,18 @@ +""" +experimentation file for integrating one file's functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL +""" + +import click + +@click.command() +def test_test_function(uppercase=None): + """Execute fre list test_test_function""" + statement = "testingtestingtestingtesting" + if uppercase: + statement = statement.upper() + click.echo(statement) + +if __name__ == '__main__': + test_test_function() diff --git a/lib/fre/tests/README.md b/lib/fre/tests/README.md new file mode 100644 index 00000000..31b2c2c6 --- /dev/null +++ b/lib/fre/tests/README.md @@ -0,0 +1,20 @@ +note- this directory is for unit tests assessing `fre` functionality, and NOT for code corresponding to the command `fre test` + +to run the tests as intended, simply call `pytest` from the root directory of this repository. + +From the root directory of this repository, if you want to... + +Invoke all tests (intended use): + `pytest fre/tests/` + +Invoke all tests in a single file: + `pytest fre/tests/test_fre_cli.py` + +Invoke a single test in a file: + `pytest fre/tests/test_fre_cli.py::test_cli_fre_option_dne` + + +Note that pytest will not print stdout from individual tests. If you want to... + +Print stdout from an invoked test (debugging with print statements): + `pytest fre/tests/test_fre_cli.py::test_cli_fre_option_dne -s` diff --git a/lib/fre/tests/__init__.py b/lib/fre/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/tests/test_files/CMOR_input_example.json b/lib/fre/tests/test_files/CMOR_input_example.json new file mode 100644 index 00000000..3ce8a985 --- /dev/null +++ b/lib/fre/tests/test_files/CMOR_input_example.json @@ -0,0 +1,74 @@ +{ + "#note": "explanation of what source_type is goes here", + "source_type": "AOGCM ISM AER", + + "#note": "CMIP6 valid experiment_ids are found in CMIP6_CV.json", + "experiment_id": "piControl-withism", + "activity_id": "ISMIP6", + "sub_experiment_id": "none", + + "realization_index": "3", + "initialization_index": "1", + "physics_index": "1", + "forcing_index": "1", + + "#note": "Text stored in attribute variant_info (recommended, not required description of run variant)", + "run_variant": "3rd realization", + + "parent_experiment_id": "no parent", + "parent_activity_id": "no parent", + "parent_source_id": "PCMDI-test-1-0", + "parent_variant_label": "r3i1p1f1", + + "parent_time_units": "days since 1850-01-01", + "branch_method": "standard", + "branch_time_in_child": 59400.0, + "branch_time_in_parent": 59400.0, + + "#note": "institution_id must be registered at https://github.com/WCRP-CMIP/CMIP6_CVs/issues/new ", + "institution_id": "PCMDI", + + "#note": "source_id (model name) must be registered at https://github.com/WCRP-CMIP/CMIP6_CVs/issues/new ", + "source_id": "PCMDI-test-1-0", + + "calendar": "360_day", + + "grid": "native atmosphere regular grid (3x4 latxlon)", + "grid_label": "gn", + "nominal_resolution": "10000 km", + + "license": "CMIP6 model data produced by Lawrence Livermore PCMDI is licensed under a Creative Commons Attribution 4.0 International License (https://creativecommons.org/licenses/by/4.0/). Consult https://pcmdi.llnl.gov/CMIP6/TermsOfUse for terms of use governing CMIP6 output, including citation requirements and proper acknowledgment. Further information about this data, including some limitations, can be found via the further_info_url (recorded as a global attribute in this file) and at https:///pcmdi.llnl.gov/. The data producers and data providers make no warranty, either express or implied, including, but not limited to, warranties of merchantability and fitness for a particular purpose. All liabilities arising from the supply of the information (including any liability arising in negligence) are excluded to the fullest extent permitted by law.", + + "#output": "Root directory for output (can be either a relative or full path)", + "outpath": "CMIP6", + + "#note": " **** The following descriptors are optional and may be set to an empty string ", + + "contact ": "Python Coder (coder@a.b.c.com)", + "history": "Output from archivcl_A1.nce/giccm_03_std_2xCO2_2256.", + "comment": "", + "references": "Model described by Koder and Tolkien (J. Geophys. Res., 2001, 576-591). Also see http://www.GICC.su/giccm/doc/index.html. The ssp245 simulation is described in Dorkey et al. '(Clim. Dyn., 2003, 323-357.)'", + + "#note": " **** The following will be obtained from the CV and do not need to be defined here", + + "sub_experiment": "none", + "institution": "", + "source": "PCMDI-test 1.0 (1989)", + + "#note": " **** The following are set correctly for CMIP6 and should not normally need editing", + + "_controlled_vocabulary_file": "CMIP6_CV.json", + "_AXIS_ENTRY_FILE": "CMIP6_coordinate.json", + "_FORMULA_VAR_FILE": "CMIP6_formula_terms.json", + "_cmip6_option": "CMIP6", + + "mip_era": "CMIP6", + "parent_mip_era": "CMIP6", + + "tracking_prefix": "hdl:21.14100", + "_history_template": "%s ;rewrote data to be consistent with for variable found in table .", + + "#output_path_template": "Template for output path directory using tables keys or global attributes, these should follow the relevant data reference syntax", + "output_path_template": "<_member_id>", + "output_file_template": "
<_member_id>", +} diff --git a/lib/fre/tests/test_files/CMORbite_var_list.json b/lib/fre/tests/test_files/CMORbite_var_list.json new file mode 100644 index 00000000..5b75e54f --- /dev/null +++ b/lib/fre/tests/test_files/CMORbite_var_list.json @@ -0,0 +1,11 @@ +{ + "lai": "lai", + "t_ref": "t_ref", + "cl": "cl", + "mc": "mc", + "ta": "ta", + "sos": "sos", + "so": "so", + "ch4global": "ch4global", + "gppLut": "gppLut" +} diff --git a/lib/fre/tests/test_files/ocean_sos_var_file/ocean_monthly_1x1deg.199301-199712.sos.nc b/lib/fre/tests/test_files/ocean_sos_var_file/ocean_monthly_1x1deg.199301-199712.sos.nc new file mode 100644 index 00000000..77c1ec7f Binary files /dev/null and b/lib/fre/tests/test_files/ocean_sos_var_file/ocean_monthly_1x1deg.199301-199712.sos.nc differ diff --git a/lib/fre/tests/test_files/varlist b/lib/fre/tests/test_files/varlist new file mode 100644 index 00000000..6ab9e86a --- /dev/null +++ b/lib/fre/tests/test_files/varlist @@ -0,0 +1,3 @@ +{ + "sos": "sos" +} diff --git a/lib/fre/tests/test_files/varlist_local_target_vars_differ b/lib/fre/tests/test_files/varlist_local_target_vars_differ new file mode 100644 index 00000000..402be0e6 --- /dev/null +++ b/lib/fre/tests/test_files/varlist_local_target_vars_differ @@ -0,0 +1,3 @@ +{ + "sosV2": "sos" +} diff --git a/lib/fre/tests/test_fre_app_cli.py b/lib/fre/tests/test_fre_app_cli.py new file mode 100644 index 00000000..ca14df51 --- /dev/null +++ b/lib/fre/tests/test_fre_app_cli.py @@ -0,0 +1,117 @@ +""" test "fre app" calls """ + +import os +import subprocess +from pathlib import Path + +import click +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + +# fre app +def test_cli_fre_app(capfd): + """ fre app """ + result = runner.invoke(fre.fre, args=["app"]) + assert result.exit_code == 0 + _out, _err = capfd.readouterr() + +def test_cli_fre_app_help(capfd): + """ fre app --help """ + result = runner.invoke(fre.fre, args=["app", "--help"]) + assert result.exit_code == 0 + _out, _err = capfd.readouterr() + +def test_cli_fre_app_opt_dne(capfd): + """ fre app optionDNE """ + result = runner.invoke(fre.fre, args=["app", "optionDNE"]) + assert result.exit_code == 2 + _out, _err = capfd.readouterr() + +# fre app gen-time-averages +def test_cli_fre_app_gen_time_averages(capfd): + """ fre cmor run """ + result = runner.invoke(fre.fre, args=["app", "gen-time-averages"]) + assert result.exit_code == 2 + _out, _err = capfd.readouterr() + +def test_cli_fre_app_gen_time_averages_help(capfd): + """ fre cmor run --help """ + result = runner.invoke(fre.fre, args=["app", "gen-time-averages", "--help"]) + assert result.exit_code == 0 + _out, _err = capfd.readouterr() + +def test_cli_fre_app_gen_time_averages_opt_dne(capfd): + """ fre cmor run optionDNE """ + result = runner.invoke(fre.fre, args=["app", "gen-time-averages", "optionDNE"]) + assert result.exit_code == 2 + _out, _err = capfd.readouterr() + +# fre app regrid +def test_cli_fre_app_regrid(capfd): + """ fre cmor run """ + result = runner.invoke(fre.fre, args=["app", "regrid"]) + assert result.exit_code == 2 + _out, _err = capfd.readouterr() + +def test_cli_fre_app_regrid_help(capfd): + """ fre cmor run --help """ + result = runner.invoke(fre.fre, args=["app", "regrid", "--help"]) + assert result.exit_code == 0 + _out, _err = capfd.readouterr() + +def test_cli_fre_app_regrid_opt_dne(capfd): + """ fre cmor run optionDNE """ + result = runner.invoke(fre.fre, args=["app", "regrid", "optionDNE"]) + assert result.exit_code == 2 + _out, _err = capfd.readouterr() + +def test_cli_fre_app_regrid_test_case_1(capfd): + """ fre cmor run --help """ + + import fre.app.regrid_xy.tests.test_regrid_xy as t_rgxy + assert t_rgxy is not None + + # input files for this test are locked up in here as well + if not Path( t_rgxy.TEST_DIR+'/in-dir' ).exists(): + assert Path(t_rgxy.TAR_IN_DIR).exists() + ex = [ "tar", "-C", t_rgxy.TEST_DIR, "-zxvf", t_rgxy.TAR_IN_DIR ] + sp = subprocess.run( ex ) + assert all ( [ sp.returncode == 0, + Path(IN_DIR).exists() ] ) + + # for the time being, still a little dependent on rose for configuration value passing + if Path(os.getcwd()+'/rose-app-run.conf').exists(): + Path(os.getcwd()+'/rose-app-run.conf').unlink() + + with open(os.getcwd()+'/rose-app-run.conf','a',encoding='utf-8') as rose_app_run_config: + rose_app_run_config.write( '[command]\n' ) + rose_app_run_config.write( 'default=regrid-xy\n' ) + rose_app_run_config.write( '\n' ) + rose_app_run_config.write( f'[{t_rgxy.COMPONENT}]\n' ) + rose_app_run_config.write( f'sources={t_rgxy.SOURCE}\n' ) + rose_app_run_config.write( f'inputGrid={t_rgxy.INPUT_GRID}\n' ) + rose_app_run_config.write( f'inputRealm={t_rgxy.INPUT_REALM}\n' ) + rose_app_run_config.write( f'interpMethod={t_rgxy.INTERP_METHOD}\n' ) + rose_app_run_config.write( f'outputGridLon={t_rgxy.NLON}\n' ) + rose_app_run_config.write( f'outputGridLat={t_rgxy.NLAT}\n' ) + rose_app_run_config.write( '\n' ) + assert Path('./rose-app-run.conf').exists() + + args_list = ["app", "regrid", + "--input_dir", f"{t_rgxy.WORK_YYYYMMDD_DIR}", + "--output_dir", f"{t_rgxy.TEST_OUT_DIR}", + "--begin", f"{t_rgxy.YYYYMMDD}T000000", + "--tmp_dir", f"{t_rgxy.TEST_DIR}", + "--remap_dir", f"{t_rgxy.REMAP_DIR}", + "--source", f"{t_rgxy.SOURCE}", + "--grid_spec", f"{t_rgxy.GOLD_GRID_SPEC_NO_TAR}", + "--def_xy_interp", f'"{t_rgxy.NLON},{t_rgxy.NLAT}"' ] + click.echo(f'args_list = \n {args_list}') + click.echo('fre ' + ' '.join(args_list)) + + result = runner.invoke(fre.fre, args=args_list ) + assert result.exit_code == 0 + _out, _err = capfd.readouterr() diff --git a/lib/fre/tests/test_fre_catalog_cli.py b/lib/fre/tests/test_fre_catalog_cli.py new file mode 100644 index 00000000..79a1d35a --- /dev/null +++ b/lib/fre/tests/test_fre_catalog_cli.py @@ -0,0 +1,50 @@ +''' test "fre catalog" calls ''' + +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + +def test_cli_fre_catalog(): + ''' fre catalog ''' + result = runner.invoke(fre.fre, args=["catalog"]) + assert result.exit_code == 0 + +def test_cli_fre_catalog_help(): + ''' fre catalog --help ''' + result = runner.invoke(fre.fre, args=["catalog", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_catalog_opt_dne(): + ''' fre catalog optionDNE ''' + result = runner.invoke(fre.fre, args=["catalog", "optionDNE"]) + assert result.exit_code == 2 + +def test_cli_fre_catalog_builder(): + ''' fre catalog builder ''' + result = runner.invoke(fre.fre, args=["catalog", "builder"]) + stdout_str = 'Missing: input_path or output_path. ' + \ + 'Pass it in the config yaml or as command-line option' + assert all( [ + result.exit_code == 1, + stdout_str in result.stdout.split('\n') + ] + ) + +def test_cli_fre_catalog_builder_help(): + ''' fre catalog builder --help ''' + result = runner.invoke(fre.fre, args=["catalog", "builder", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_catalog_merge(): + result = runner.invoke(fre.fre, args=["catalog", "merge"]) + expected_stdout = "Error: Missing option '--input'." + assert all( [ + result.exit_code == 2, + expected_stdout in result.stdout.split('\n') + ] ) + +def test_cli_fre_catalog_merge_help(): + result = runner.invoke(fre.fre, args=["catalog", "merge", "--help"]) + assert result.exit_code == 0 diff --git a/lib/fre/tests/test_fre_check_cli.py b/lib/fre/tests/test_fre_check_cli.py new file mode 100644 index 00000000..80657b65 --- /dev/null +++ b/lib/fre/tests/test_fre_check_cli.py @@ -0,0 +1,22 @@ +''' test "fre check" calls ''' + +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + +def test_cli_fre_check(): + ''' fre check ''' + result = runner.invoke(fre.fre, args=["check"]) + assert result.exit_code == 0 + +def test_cli_fre_check_help(): + ''' fre check --help ''' + result = runner.invoke(fre.fre, args=["check", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_check_opt_dne(): + ''' fre check optionDNE ''' + result = runner.invoke(fre.fre, args=["check", "optionDNE"]) + assert result.exit_code == 2 diff --git a/lib/fre/tests/test_fre_cli.py b/lib/fre/tests/test_fre_cli.py new file mode 100644 index 00000000..5c5bdb12 --- /dev/null +++ b/lib/fre/tests/test_fre_cli.py @@ -0,0 +1,22 @@ +''' test "fre" calls ''' + +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + +def test_cli_fre(): + ''' fre ''' + result = runner.invoke(fre.fre) + assert result.exit_code == 0 + +def test_cli_fre_help(): + ''' fre --help ''' + result = runner.invoke(fre.fre, args='--help') + assert result.exit_code == 0 + +def test_cli_fre_option_dne(): + ''' fre optionDNE ''' + result = runner.invoke(fre.fre, args='optionDNE') + assert result.exit_code == 2 diff --git a/lib/fre/tests/test_fre_cmor_cli.py b/lib/fre/tests/test_fre_cmor_cli.py new file mode 100644 index 00000000..b2a09b62 --- /dev/null +++ b/lib/fre/tests/test_fre_cmor_cli.py @@ -0,0 +1,134 @@ +''' test "fre cmor" calls ''' + +from datetime import date +from pathlib import Path + +import click +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + +# fre cmor +def test_cli_fre_cmor(): + ''' fre cmor ''' + result = runner.invoke(fre.fre, args=["cmor"]) + assert result.exit_code == 0 + +def test_cli_fre_cmor_help(): + ''' fre cmor --help ''' + result = runner.invoke(fre.fre, args=["cmor", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_cmor_opt_dne(): + ''' fre cmor optionDNE ''' + result = runner.invoke(fre.fre, args=["cmor", "optionDNE"]) + assert result.exit_code == 2 + +# fre cmor run +def test_cli_fre_cmor_run(): + ''' fre cmor run ''' + result = runner.invoke(fre.fre, args=["cmor", "run"]) + assert result.exit_code == 2 + +def test_cli_fre_cmor_run_help(): + ''' fre cmor run --help ''' + result = runner.invoke(fre.fre, args=["cmor", "run", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_cmor_run_opt_dne(): + ''' fre cmor run optionDNE ''' + result = runner.invoke(fre.fre, args=["cmor", "run", "optionDNE"]) + assert result.exit_code == 2 + + +# these unit tests should be more about the cli, rather than the workload +YYYYMMDD=date.today().strftime('%Y%m%d') +def test_cli_fre_cmor_run_case1(): + ''' fre cmor run, test-use case ''' + + # where are we? we're running pytest from the base directory of this repo + rootdir = 'fre/tests/test_files' + + # explicit inputs to tool + indir = f'{rootdir}/ocean_sos_var_file' + varlist = f'{rootdir}/varlist' + table_config = f'{rootdir}/cmip6-cmor-tables/Tables/CMIP6_Omon.json' + exp_config = f'{rootdir}/CMOR_input_example.json' + outdir = f'{rootdir}/outdir' + + # determined by cmor_run_subtool + cmor_creates_dir = \ + 'CMIP6/CMIP6/ISMIP6/PCMDI/PCMDI-test-1-0/piControl-withism/r3i1p1f1/Omon/sos/gn' + full_outputdir = \ + f"{outdir}/{cmor_creates_dir}/v{YYYYMMDD}" # yay no more 'fre' where it shouldnt be + full_outputfile = \ + f"{full_outputdir}/sos_Omon_PCMDI-test-1-0_piControl-withism_r3i1p1f1_gn_199307-199807.nc" + + # FYI + filename = 'ocean_monthly_1x1deg.199301-199712.sos.nc' # unneeded, this is mostly for reference + full_inputfile=f"{indir}/{filename}" + + # clean up, lest we fool outselves + if Path(full_outputfile).exists(): + Path(full_outputfile).unlink() + + #click.echo('') + result = runner.invoke(fre.fre, args = ["cmor", "run", + "--indir", indir, + "--varlist", varlist, + "--table_config", table_config, + "--exp_config", exp_config, + "--outdir", outdir]) + click.echo(f'stdout = \n {result.stdout}') + #click.echo(f'stderr = \n {result.stderr}') #not captured sep. + assert all ( [ result.exit_code == 0, + Path(full_outputfile).exists(), + Path(full_inputfile).exists() ] ) + + + + + +def test_cli_fre_cmor_run_case2(): + ''' fre cmor run, test-use case ''' + + # where are we? we're running pytest from the base directory of this repo + rootdir = 'fre/tests/test_files' + + # explicit inputs to tool + indir = f'{rootdir}/ocean_sos_var_file' + varlist = f'{rootdir}/varlist_local_target_vars_differ' + table_config = f'{rootdir}/cmip6-cmor-tables/Tables/CMIP6_Omon.json' + exp_config = f'{rootdir}/CMOR_input_example.json' + outdir = f'{rootdir}/outdir' + + # determined by cmor_run_subtool + cmor_creates_dir = \ + 'CMIP6/CMIP6/ISMIP6/PCMDI/PCMDI-test-1-0/piControl-withism/r3i1p1f1/Omon/sos/gn' + full_outputdir = \ + f"{outdir}/{cmor_creates_dir}/v{YYYYMMDD}" # yay no more 'fre' where it shouldnt be + full_outputfile = \ + f"{full_outputdir}/sos_Omon_PCMDI-test-1-0_piControl-withism_r3i1p1f1_gn_199307-199807.nc" + + # FYI + filename = 'ocean_monthly_1x1deg.199301-199712.sosV2.nc' # unneeded, this is mostly for reference + full_inputfile=f"{indir}/{filename}" + + # clean up, lest we fool outselves + if Path(full_outputfile).exists(): + Path(full_outputfile).unlink() + + #click.echo('') + result = runner.invoke(fre.fre, args = ["cmor", "run", + "--indir", indir, + "--varlist", varlist, + "--table_config", table_config, + "--exp_config", exp_config, + "--outdir", outdir]) + click.echo(f'stdout = \n {result.stdout}') + #click.echo(f'stderr = \n {result.stderr}') #not captured sep. + assert all ( [ result.exit_code == 0, + Path(full_outputfile).exists(), + Path(full_inputfile).exists() ] ) diff --git a/lib/fre/tests/test_fre_list_cli.py b/lib/fre/tests/test_fre_list_cli.py new file mode 100644 index 00000000..80c5af2b --- /dev/null +++ b/lib/fre/tests/test_fre_list_cli.py @@ -0,0 +1,22 @@ +''' test "fre list" calls ''' + +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + +def test_cli_fre_list(): + ''' fre list ''' + result = runner.invoke(fre.fre, args=["list"]) + assert result.exit_code == 0 + +def test_cli_fre_list_help(): + ''' fre list --help ''' + result = runner.invoke(fre.fre, args=["list", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_list_opt_dne(): + ''' fre list optionDNE ''' + result = runner.invoke(fre.fre, args=["list", "optionDNE"]) + assert result.exit_code == 2 diff --git a/lib/fre/tests/test_fre_make_cli.py b/lib/fre/tests/test_fre_make_cli.py new file mode 100644 index 00000000..279760b3 --- /dev/null +++ b/lib/fre/tests/test_fre_make_cli.py @@ -0,0 +1,62 @@ +''' test "fre make" calls ''' + +from click.testing import CliRunner +from pathlib import Path +import os +from fre import fre + +runner = CliRunner() + +def test_cli_fre_make(): + ''' fre make ''' + result = runner.invoke(fre.fre, args=["make"]) + assert result.exit_code == 0 + +def test_cli_fre_make_help(): + ''' fre make --help ''' + result = runner.invoke(fre.fre, args=["make", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_make_opt_dne(): + ''' fre make optionDNE ''' + result = runner.invoke(fre.fre, args=["make", "optionDNE"]) + assert result.exit_code == 2 + +def test_cli_fre_make_create_checkout_baremetal(): + ''' fre make create-checkout -y am5.yaml -p ncrc5.intel23 -t debug''' + # Set paths and click options + test_dir = Path("fre/tests") + yamlfile = Path("fre/make/tests/AM5_example/") + platform = "ncrc5.intel23" + target = "debug" + + # Create output path to test that files exist + out_path=f"{test_dir}/fremake_out" + Path(out_path).mkdir(parents=True,exist_ok=True) + + # Set HOME for modelRoot location (output location) in fre make + os.environ["HOME"]=str(Path(out_path)) + + # run create-checkout + result = runner.invoke(fre.fre, args=["make", "create-checkout", "-y", f"{yamlfile}/am5.yaml", "-p", platform, "-t", target]) + + # Check for successful command, creation of checkout script, and that script is executable (os.access - checks is file has specific access mode, os.X_OK - checks executable permission) + assert all ([result.exit_code == 0, + Path(f"{out_path}/fremake_canopy/test/am5/src/checkout.sh").exists(), + os.access(Path(f"{out_path}/fremake_canopy/test/am5/src/checkout.sh"), os.X_OK)]) + +def test_cli_fre_make_create_checkout_container(): + ''' fre make create-checkout -y am5.yaml -p hpcme.2023 -t debug''' + # Set paths and click options + test_dir = Path("fre/tests") + yamlfile = Path("fre/make/tests/AM5_example/") + platform = "hpcme.2023" + target = "debug" + + # run create-checkout + result = runner.invoke(fre.fre, args=["make", "create-checkout", "-y", f"{yamlfile}/am5.yaml", "-p", platform, "-t", target]) + + # Check for successful command, creation of checkout script, and that script is executable (os.access - checks is file has specific access mode, os.X_OK - checks executable permission) + assert all ([result.exit_code == 0, + Path(f"tmp/{platform}/checkout.sh").exists(), + os.access(Path(f"tmp/{platform}/checkout.sh"), os.X_OK) == False ]) diff --git a/lib/fre/tests/test_fre_pp_cli.py b/lib/fre/tests/test_fre_pp_cli.py new file mode 100644 index 00000000..399c8e3e --- /dev/null +++ b/lib/fre/tests/test_fre_pp_cli.py @@ -0,0 +1,152 @@ +''' test "fre pp" calls ''' + +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + + +#-- fre pp +def test_cli_fre_pp(): + ''' fre pp ''' + result = runner.invoke(fre.fre, args=["pp"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_help(): + ''' fre pp --help ''' + result = runner.invoke(fre.fre, args=["pp", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_opt_dne(): + ''' fre pp optionDNE ''' + result = runner.invoke(fre.fre, args=["pp", "optionDNE"]) + assert result.exit_code == 2 + +#-- fre pp checkout +def test_cli_fre_pp_checkout(): + ''' fre pp checkout ''' + result = runner.invoke(fre.fre, args=["pp", "checkout"]) + assert result.exit_code == 2 + +def test_cli_fre_pp_checkout_help(): + ''' fre pp checkout --help ''' + result = runner.invoke(fre.fre, args=["pp", "checkout", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_checkout_opt_dne(): + ''' fre pp checkout optionDNE ''' + result = runner.invoke(fre.fre, args=["pp", "checkout", "optionDNE"]) + assert result.exit_code == 2 + +#-- fre pp configure-xml +def test_cli_fre_pp_configure_xml(): + ''' fre pp configure-xml ''' + result = runner.invoke(fre.fre, args=["pp", "configure-xml"]) + assert result.exit_code == 2 + +def test_cli_fre_pp_configure_xml_help(): + ''' fre pp configure-xml --help ''' + result = runner.invoke(fre.fre, args=["pp", "configure-xml", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_configure_xml_opt_dne(): + ''' fre pp configure-xml optionDNE ''' + result = runner.invoke(fre.fre, args=["pp", "configure-xml", "optionDNE"]) + assert result.exit_code == 2 + +#-- fre pp configure-yaml +def test_cli_fre_pp_configure_yaml(): + ''' fre pp configure-yaml ''' + result = runner.invoke(fre.fre, args=["pp", "configure-yaml"]) + assert result.exit_code == 2 + +def test_cli_fre_pp_configure_yaml_help(): + ''' fre pp configure-yaml --help ''' + result = runner.invoke(fre.fre, args=["pp", "configure-yaml", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_configure_yaml_opt_dne(): + ''' fre pp configure-yaml optionDNE ''' + result = runner.invoke(fre.fre, args=["pp", "configure-yaml", "optionDNE"]) + assert result.exit_code == 2 + +#-- fre pp install +def test_cli_fre_pp_install(): + ''' fre pp install ''' + result = runner.invoke(fre.fre, args=["pp", "install"]) + assert result.exit_code == 2 + +def test_cli_fre_pp_install_help(): + ''' fre pp install --help ''' + result = runner.invoke(fre.fre, args=["pp", "install", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_install_opt_dne(): + ''' fre pp install optionDNE ''' + result = runner.invoke(fre.fre, args=["pp", "install", "optionDNE"]) + assert result.exit_code == 2 + +#-- fre pp run +def test_cli_fre_pp_run(): + ''' fre pp run ''' + result = runner.invoke(fre.fre, args=["pp", "run"]) + assert result.exit_code == 2 + +def test_cli_fre_pp_run_help(): + ''' fre pp run --help ''' + result = runner.invoke(fre.fre, args=["pp", "run", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_run_opt_dne(): + ''' fre pp run optionDNE ''' + result = runner.invoke(fre.fre, args=["pp", "run", "optionDNE"]) + assert result.exit_code == 2 + +#-- fre pp status +def test_cli_fre_pp_status(): + ''' fre pp status ''' + result = runner.invoke(fre.fre, args=["pp", "status"]) + assert result.exit_code == 2 + +def test_cli_fre_pp_status_help(): + ''' fre pp status --help ''' + result = runner.invoke(fre.fre, args=["pp", "status", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_status_opt_dne(): + ''' fre pp status optionDNE ''' + result = runner.invoke(fre.fre, args=["pp", "status", "optionDNE"]) + assert result.exit_code == 2 + +#-- fre pp validate +def test_cli_fre_pp_validate(): + ''' fre pp validate ''' + result = runner.invoke(fre.fre, args=["pp", "validate"]) + assert result.exit_code == 2 + +def test_cli_fre_pp_validate_help(): + ''' fre pp validate --help ''' + result = runner.invoke(fre.fre, args=["pp", "validate", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_validate_opt_dne(): + ''' fre pp validate optionDNE ''' + result = runner.invoke(fre.fre, args=["pp", "validate", "optionDNE"]) + assert result.exit_code == 2 + +#-- fre pp wrapper +def test_cli_fre_pp_wrapper(): + ''' fre pp wrapper ''' + result = runner.invoke(fre.fre, args=["pp", "wrapper"]) + assert result.exit_code == 2 + +def test_cli_fre_pp_wrapper_help(): + ''' fre pp wrapper --help ''' + result = runner.invoke(fre.fre, args=["pp", "wrapper", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_pp_wrapper_opt_dne(): + ''' fre pp wrapper optionDNE ''' + result = runner.invoke(fre.fre, args=["pp", "wrapper", "optionDNE"]) + assert result.exit_code == 2 diff --git a/lib/fre/tests/test_fre_run_cli.py b/lib/fre/tests/test_fre_run_cli.py new file mode 100644 index 00000000..7af6f96f --- /dev/null +++ b/lib/fre/tests/test_fre_run_cli.py @@ -0,0 +1,22 @@ +''' test "fre run" calls ''' + +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + +def test_cli_fre_run(): + ''' fre run ''' + result = runner.invoke(fre.fre, args=["run"]) + assert result.exit_code == 0 + +def test_cli_fre_run_help(): + ''' fre run --help ''' + result = runner.invoke(fre.fre, args=["run", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_run_opt_dne(): + ''' fre run optionDNE ''' + result = runner.invoke(fre.fre, args=["run", "optionDNE"]) + assert result.exit_code == 2 diff --git a/lib/fre/tests/test_fre_test_cli.py b/lib/fre/tests/test_fre_test_cli.py new file mode 100644 index 00000000..faee25d8 --- /dev/null +++ b/lib/fre/tests/test_fre_test_cli.py @@ -0,0 +1,22 @@ +''' test "fre test" calls ''' + +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + +def test_cli_fre_test(): + ''' fre test ''' + result = runner.invoke(fre.fre, args=["test"]) + assert result.exit_code == 0 + +def test_cli_fre_test_help(): + ''' fre test --help ''' + result = runner.invoke(fre.fre, args=["test", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_test_opt_dne(): + ''' fre test optionDNE ''' + result = runner.invoke(fre.fre, args=["test", "optionDNE"]) + assert result.exit_code == 2 diff --git a/lib/fre/tests/test_fre_yamltools_cli.py b/lib/fre/tests/test_fre_yamltools_cli.py new file mode 100644 index 00000000..af9481cd --- /dev/null +++ b/lib/fre/tests/test_fre_yamltools_cli.py @@ -0,0 +1,22 @@ +''' test "fre yamltools" calls ''' + +from click.testing import CliRunner + +from fre import fre + +runner = CliRunner() + +def test_cli_fre_yamltools(): + ''' fre yamltools ''' + result = runner.invoke(fre.fre, args=["yamltools"]) + assert result.exit_code == 0 + +def test_cli_fre_yamltools_help(): + ''' fre yamltools --help ''' + result = runner.invoke(fre.fre, args=["yamltools", "--help"]) + assert result.exit_code == 0 + +def test_cli_fre_yamltools_opt_dne(): + ''' fre yamltools optionDNE ''' + result = runner.invoke(fre.fre, args=["yamltools", "optionDNE"]) + assert result.exit_code == 2 diff --git a/lib/fre/yamltools/README.md b/lib/fre/yamltools/README.md new file mode 100644 index 00000000..f6d896f3 --- /dev/null +++ b/lib/fre/yamltools/README.md @@ -0,0 +1,24 @@ +## FRE yamltools +`fre yamltools` provides subtools that help to manage and perform operations on yaml files. + +## Subtools +- `fre yamltools combine-yamls [options]` + - Purpose: + - Creates combined yaml file in which the [model].yaml, compile.yaml, and platforms.yaml are merged if `--use compile` is specified + - Creates combined yaml file in which the [model].yaml, [experiment].yaml, and [analysis].yaml are merged if `--use pp` is specified + - Options: + - `-y, --yamlfile [experiment yaml] (required)` + - `-p, --platform [platform] (required)` + - `-t, --target [target] (required)` + - `-e, --experiment [experiment name]` + - `--use [compile|pp] (required)` + +### **Tests** + +To run `fre yamltools` test scripts, return to root directory of the fre-cli repo and call those tests with + + python -m pytest fre/yamltools/tests/[test script.py] + +Or run all tests with + + python -m pytest fre/yamltools/tests diff --git a/lib/fre/yamltools/__init__.py b/lib/fre/yamltools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/yamltools/combine_yamls.py b/lib/fre/yamltools/combine_yamls.py new file mode 100644 index 00000000..b2b6540f --- /dev/null +++ b/lib/fre/yamltools/combine_yamls.py @@ -0,0 +1,379 @@ +""" +Script combines the model yaml with the compile, platform, and experiment yamls. +""" + +## TO-DO: +# - figure out way to safe_load (yaml_loader=yaml.SafeLoader?) +# - condition where there are multiple pp and analysis yamls + +import os +import shutil + +from pathlib import Path +import click +import yaml + +def join_constructor(loader, node): + """ + Allows FRE properties defined + in main yaml to be concatenated. + """ + seq = loader.construct_sequence(node) + return ''.join([str(i) for i in seq]) + +def yaml_load(yamlfile): + """ + Load the yamlfile + """ + with open(yamlfile, 'r') as yf: + y = yaml.load(yf,Loader=yaml.Loader) + + return y + +def get_compile_paths(mainyaml_dir,comb): + """ + Extract compile and platform paths from model yaml + """ + comb_model=yaml_load(comb) + + # set platform yaml filepath + if comb_model["build"]["platformYaml"] is not None: + if Path(os.path.join(mainyaml_dir,comb_model["build"]["platformYaml"])).exists(): + py=comb_model["build"]["platformYaml"] + py_path=Path(os.path.join(mainyaml_dir,py)) + else: + raise ValueError("Incorrect platform yaml path given; does not exist.") + else: + raise ValueError("No platform yaml path given!") + #py_path=None + + # set compile yaml filepath + if comb_model["build"]["compileYaml"] is not None: + if Path(os.path.join(mainyaml_dir,comb_model["build"]["compileYaml"])).exists(): + cy=comb_model["build"]["compileYaml"] + cy_path=Path(os.path.join(mainyaml_dir,cy)) + else: + raise ValueError("Incorrect compile yaml path given; does not exist.") + else: + raise ValueError("No compile yaml path given!") + #cy_path=None + + return (py_path,cy_path) + + +def experiment_check(mainyaml_dir,comb,experiment): + """ + Check that the experiment given is an experiment listed in the model yaml. + Extract experiment specific information and file paths. + Arguments: + mainyaml_dir : model yaml file + comb : combined yaml file name + experiment : experiment name + """ + comb_model=yaml_load(comb) + + # Check if exp name given is actually valid experiment listed in combined yaml + exp_list = [] + for i in comb_model.get("experiments"): + exp_list.append(i.get("name")) + + if experiment not in exp_list: + raise NameError(f"{experiment} is not in the list of experiments") + + # Extract compile yaml path for exp. provided + # if experiment matches name in list of experiments in yaml, extract file path + for i in comb_model.get("experiments"): + if experiment == i.get("name"): + expyaml=i.get("pp") + analysisyaml=i.get("analysis") + + if expyaml is not None: + ey_path=[] + for e in expyaml: + if Path(os.path.join(mainyaml_dir,e)).exists(): + ey=Path(os.path.join(mainyaml_dir,e)) + ey_path.append(ey) + else: + raise ValueError("Incorrect experiment yaml path given; does not exist.") + else: + raise ValueError("No experiment yaml path given!") + + if analysisyaml is not None: + ay_path=[] + for a in analysisyaml: + if Path(a).exists(): + ay=Path(os.path.join(mainyaml_dir,a)) + ay_path.append(ay) + else: + raise ValueError("Incorrect analysis yaml ath given; does not exist.") + else: + ay_path=None + + return (ey_path,ay_path) + +## COMPILE CLASS ## +class init_compile_yaml(): + def __init__(self,yamlfile,platform,target): + """ + Process to combine yamls applicable to compilation + """ + self.yml = yamlfile + self.name = yamlfile.split(".")[0] + self.namenopath = self.name.split("/")[-1].split(".")[0] + self.platform = platform + self.target = target + + # Register tag handler + yaml.add_constructor('!join', join_constructor) + + # Path to the main model yaml + self.mainyaml_dir = os.path.dirname(self.yml) + + # Name of the combined yaml + self.combined= f"combined-{self.namenopath}.yaml" if len(self.mainyaml_dir) == 0 else f"{self.mainyaml_dir}/combined-{self.namenopath}.yaml" + + print("Combining yaml files: ") + + def combine_model(self): + """ + Create the combined.yaml and merge it with the model yaml + """ + # copy model yaml info into combined yaml + with open(self.combined,'w+',encoding='UTF-8') as f1: + f1.write(f'name: &name "{self.name}"\n') + f1.write(f'platform: &platform "{self.platform}"\n') + f1.write(f'target: &target "{self.target}"\n\n') + with open(self.yml,'r',encoding='UTF-8') as f2: + f1.write("### MODEL YAML SETTINGS ###\n") + shutil.copyfileobj(f2,f1) + + print(f" model yaml: {self.yml}") + + def combine_compile(self): + """ + Combine compile yaml with the defined combined.yaml + """ + # Get compile info + (py_path,cy_path) = get_compile_paths(self.mainyaml_dir,self.combined) + + # copy compile yaml info into combined yaml + if cy_path is not None: + with open(self.combined,'a',encoding='UTF-8') as f1: + with open(cy_path,'r',encoding='UTF-8') as f2: + f1.write("\n### COMPILE INFO ###\n") + shutil.copyfileobj(f2,f1) + print(f" compile yaml: {cy_path}") + + def combine_platforms(self): + """ + Combine platforms yaml with the defined combined.yaml + """ + # Get compile info + (py_path,cy_path) = get_compile_paths(self.mainyaml_dir,self.combined) + + # combine platform yaml + if py_path is not None: + with open(self.combined,'a',encoding='UTF-8') as f1: + with open(py_path,'r',encoding='UTF-8') as f2: + f1.write("\n### PLATFORM INFO ###\n") + shutil.copyfileobj(f2,f1) + print(f" platforms yaml: {py_path}") + + def clean_yaml(self): + """ + Clean the yaml; remove unnecessary sections in + final combined yaml. + """ + # Load the fully combined yaml + full_yaml = yaml_load(self.combined) + + # Clean the yaml + # If keys exists, delete: + keys_clean=["fre_properties", "shared", "experiments"] + for kc in keys_clean: + if kc in full_yaml.keys(): + del full_yaml[kc] + + with open(self.combined,'w',encoding='UTF-8') as f: + yaml.safe_dump(full_yaml,f,default_flow_style=False,sort_keys=False) + + print(f"Combined yaml located here: {os.path.dirname(self.combined)}/{self.combined}") + return self.combined + +## PP CLASS ## +class init_pp_yaml(): + def __init__(self,yamlfile,experiment,platform,target): + """ + Process to combine the applicable yamls for post-processing + """ + self.yml = yamlfile + self.name = experiment + self.platform = platform + self.target = target + + # Regsiter tag handler + yaml.add_constructor('!join', join_constructor) + + # Path to the main model yaml + self.mainyaml_dir = os.path.dirname(self.yml) + + # Name of the combined yaml + self.combined=f"combined-{self.name}.yaml" + + print("Combining yaml files: ") + + def combine_model(self): + """ + Create the combined.yaml and merge it with the model yaml + """ + # copy model yaml info into combined yaml + with open(self.combined,'w+',encoding='UTF-8') as f1: + f1.write(f'name: &name "{self.name}"\n') + f1.write(f'platform: &platform "{self.platform}"\n') + f1.write(f'target: &target "{self.target}"\n\n') + with open(self.yml,'r',encoding='UTF-8') as f2: + f1.write("### MODEL YAML SETTINGS ###\n") + shutil.copyfileobj(f2,f1) + + print(f" model yaml: {self.yml}") + + def combine_experiment(self): + """ + Combine experiment yamls with the defined combined.yaml + """ + # Experiment Check + (ey_path,ay_path) = experiment_check(self.mainyaml_dir,self.combined,self.name) + + ## COMBINE EXPERIMENT YAML INFO + if ey_path is not None: + for i in ey_path: + #expyaml_path = os.path.join(mainyaml_dir, i) + with open(self.combined,'a',encoding='UTF-8') as f1: + with open(i,'r',encoding='UTF-8') as f2: + #copy expyaml into combined + shutil.copyfileobj(f2,f1) + print(f" experiment yaml: {i}") + + def combine_analysis(self): + """ + Combine analysis yamls with the defined combined.yaml + """ + # Experiment Check + (ey_path,ay_path) = experiment_check(self.mainyaml_dir,self.combined,self.name) + + ## COMBINE EXPERIMENT YAML INFO + if ay_path is not None: + for i in ay_path: + #analysisyaml_path = os.path.join(mainyaml_dir, i) + with open(self.combined,'a',encoding='UTF-8') as f1: + with open(i,'r',encoding='UTF-8') as f2: + #f1.write(f"\n### {i.upper()} settings ###\n") + #copy expyaml into combined + shutil.copyfileobj(f2,f1) + print(f" analysis yaml: {i}") + + def clean_yaml(self): + """ + Clean the yaml; remove unnecessary sections in + final combined yaml. + """ + # Load the fully combined yaml + full_yaml = yaml_load(self.combined) + + # Clean the yaml + # If keys exists, delete: + keys_clean=["fre_properties", "shared", "experiments"] + for kc in keys_clean: + if kc in full_yaml.keys(): + del full_yaml[kc] + + with open(self.combined,'w') as f: + yaml.safe_dump(full_yaml,f,default_flow_style=False,sort_keys=False) + + print(f"Combined yaml located here: {os.path.dirname(self.combined)}/{self.combined}") + return self.combined + +## Functions to combine the yaml files ## +def get_combined_compileyaml(comb): + """ + Combine the model, compile, and platform yamls + Arguments: + comb : combined yaml object + """ + # Merge model into combined file + comb_model = comb.combine_model() + # Merge compile.yaml into combined file + comb_compile = comb.combine_compile() + # Merge platforms.yaml into combined file + full_combined = comb.combine_platforms() + # Clean the yaml + full_combined = comb.clean_yaml() + + return full_combined + +def combined_compile_existcheck(combined,yml,platform,target): + """ + Checks for if combined compile yaml exists already. + If not, combine model, compile, and platform yamls. + """ + cd = Path.cwd() + combined_path=os.path.join(cd,combined) + + # Combine model, compile, and platform yamls + # If fre yammltools combine-yamls tools was used, the combined yaml should exist + if Path(combined_path).exists(): + full_combined = combined_path + print("\nNOTE: Yamls previously merged.") + else: + comb = init_compile_yaml(yml,platform,target) + full_combined = get_combined_compileyaml(comb) + + return full_combined + +def get_combined_ppyaml(comb): + """ + Combine the model, experiment, and analysis yamls + Arguments: + comb : combined yaml object + """ + # Merge model into combined file + comb_model = comb.combine_model() + # Merge pp experiment yamls into combined file + comb_exp = comb.combine_experiment() + # Merge pp analysis yamls, if defined, into combined file + comb_analysis = comb.combine_analysis() + # Clean the yaml + full_combined = comb.clean_yaml() + + return full_combined + +########################################################################################### +def consolidate_yamls(yamlfile,experiment,platform,target,use): + # Regsiter tag handler + yaml.add_constructor('!join', join_constructor) + + # Path to the main model yaml + mainyaml_dir = os.path.dirname(yamlfile) + + if use == "compile": + combined = init_compile_yaml(yamlfile, platform, target) + # Create combined compile yaml + get_combined_compileyaml(combined) + elif use =="pp": + combined = init_pp_yaml(yamlfile,experiment,platform,target) + # Create combined pp yaml + get_combined_ppyaml(combined) + else: + raise ValueError("'use' value is not valid; must be 'compile' or 'pp'") + +@click.command() +def _consolidate_yamls(yamlfile,experiment,platform,target,use): + ''' + Wrapper script for calling yaml_combine - allows the decorated version + of the function to be separate from the undecorated version + ''' + return consolidate_yamls(yamlfile,experiment,platform,target,use) + +# Use parseyaml function to parse created edits.yaml +if __name__ == '__main__': + consolidate_yamls() diff --git a/lib/fre/yamltools/freyamltools.py b/lib/fre/yamltools/freyamltools.py new file mode 100644 index 00000000..3ca3ba93 --- /dev/null +++ b/lib/fre/yamltools/freyamltools.py @@ -0,0 +1,52 @@ +''' fre yamltools ''' + +import click +from .freyamltoolsexample import yamltools_test_function +from .combine_yamls import _consolidate_yamls + +@click.group(help=click.style(" - access fre yamltools subcommands", fg=(202,177,95))) +def yamltools_cli(): + ''' entry point to fre yamltools click commands ''' + +@yamltools_cli.command() +@click.option('--uppercase', '-u', is_flag=True, help = 'Print statement in uppercase.') +@click.pass_context +def function(context, uppercase): + # pylint: disable=unused-argument + """ - Execute fre yamltools test """ + context.forward(yamltools_test_function) + +@yamltools_cli.command() +@click.option("-y", + "--yamlfile", + type=str, + help="YAML file to be used for parsing", + required=True) +@click.option("-e", + "--experiment", + type=str, + help="Experiment name") +@click.option("-p", + "--platform", + type=str, + help="Platform name", + required=True) +@click.option("-t", + "--target", + type=str, + help="Target name", + required=True) +@click.option("--use", + type=click.Choice(['compile','pp']), + help="Process user is combining yamls for. Can pass 'compile' or 'pp'", + required=True) +@click.pass_context +def combine_yamls(context,yamlfile,experiment,platform,target,use): + """ + - Combine the model yaml with the compile, platform, + experiment, and analysis yamls + """ + context.forward(_consolidate_yamls) + +if __name__ == "__main__": + yamltools_cli() diff --git a/lib/fre/yamltools/freyamltoolsexample.py b/lib/fre/yamltools/freyamltoolsexample.py new file mode 100644 index 00000000..e86fb206 --- /dev/null +++ b/lib/fre/yamltools/freyamltoolsexample.py @@ -0,0 +1,18 @@ +""" +experimentation file for integrating one file's functions into main prototype fre file +authored by Bennett.Chang@noaa.gov | bcc2761 +NOAA | GFDL +""" + +import click + +@click.command() +def yamltools_test_function(uppercase=None): + """Execute fre list testfunction2.""" + statement = "testingtestingtestingtesting" + if uppercase: + statement = statement.upper() + click.echo(statement) + +if __name__ == '__main__': + yamltools_test_function() diff --git a/lib/fre/yamltools/tests/AM5_example/am5.yaml b/lib/fre/yamltools/tests/AM5_example/am5.yaml new file mode 100644 index 00000000..c57c997c --- /dev/null +++ b/lib/fre/yamltools/tests/AM5_example/am5.yaml @@ -0,0 +1,72 @@ +# reusable variables +fre_properties: + - &AM5_VERSION "am5f7b12r1" + - &FRE_STEM !join [am5/, *AM5_VERSION] + + # amip + - &EXP_AMIP_START "1979" + - &EXP_AMIP_END "2020" + - &ANA_AMIP_START "1980" + - &ANA_AMIP_END "2020" + + - &PP_AMIP_CHUNK96 "P1Y" + - &PP_AMIP_CHUNK384 "P1Y" + - &PP_XYINTERP96 "180,288" + - &PP_XYINTERP384 "720,1152" + + # climo + - &EXP_CLIMO_START96 "0001" + - &EXP_CLIMO_END96 "0011" + - &ANA_CLIMO_START96 "0002" + - &ANA_CLIMO_END96 "0011" + + - &EXP_CLIMO_START384 "0001" + - &EXP_CLIMO_END384 "0006" + - &ANA_CLIMO_START384 "0002" + - &ANA_CLIMO_END384 "0006" + + # coupled + - &PP_CPLD_CHUNK_A "P5Y" + - &PP_CPLD_CHUNK_B "P20Y" + + # grids + - &GRID_SPEC96 "/archive/oar.gfdl.am5/model_gen5/inputs/c96_grid/c96_OM4_025_grid_No_mg_drag_v20160808.tar" + + # compile information + - &release "f1a1r1" + - &INTEL "intel-classic" + - &FMSincludes "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" + - &momIncludes "-Imom6/MOM6-examples/src/MOM6/pkg/CVMix-src/include" + +build: + # compile information + compileYaml: "compile_yamls/compile.yaml" + platformYaml: "compile_yamls/platforms.yaml" + +shared: + # directories shared across tools + directories: &shared_directories + history_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, history] + pp_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, pp] + analysis_dir: !join [/nbhome/$USER/, *FRE_STEM, /, *name] + ptmp_dir: "/xtmp/$USER/ptmp" + fre_analysis_home: "/home/fms/local/opt/fre-analysis/test" + + # shared pp settings + postprocess: + settings: &shared_settings + history_segment: "P1Y" + site: "ppan" + switches: &shared_switches + do_statics: True + do_timeavgs: True + clean_work: True + do_refinediag: False + do_atmos_plevel_masking: True + do_preanalysis: False + do_analysis: True + +experiments: + - name: "c96L65_am5f7b12r1_amip" + pp: + - "pp_yamls/pp.c96_amip.yaml" diff --git a/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile.yaml b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile.yaml new file mode 100644 index 00000000..5f9a361b --- /dev/null +++ b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile.yaml @@ -0,0 +1,67 @@ +compile: + experiment: "am5" + container_addlibs: + baremetal_linkerflags: + src: + - component: "FMS" + repo: "https://github.com/NOAA-GFDL/FMS.git" + cppdefs: "-DINTERNAL_FILE_NML -Duse_libMPI -Duse_netCDF" + branch: "2022.01" + cppdefs: "-DHAVE_GETTID -Duse_libMPI -Duse_netCDF" + otherFlags: *FMSincludes + - component: "am5_phys" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/am5_phys.git" + branch: "2022.01" + otherFlags: *FMSincludes + - component: "GFDL_atmos_cubed_sphere" + requires: ["FMS", "am5_phys"] + repo: "https://github.com/NOAA-GFDL/GFDL_atmos_cubed_sphere.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE -DINTERNAL_FILE_NML" + branch: "2022.01" + paths: ["GFDL_atmos_cubed_sphere/driver/GFDL", + "GFDL_atmos_cubed_sphere/model", + "GFDL_atmos_cubed_sphere/driver/SHiELD/cloud_diagnosis.F90", + "GFDL_atmos_cubed_sphere/driver/SHiELD/gfdl_cloud_microphys.F90", + "GFDL_atmos_cubed_sphere/tools", + "GFDL_atmos_cubed_sphere/GFDL_tools"] + otherFlags: *FMSincludes + - component: "atmos_drivers" + requires: ["FMS", "am5_phys", "GFDL_atmos_cubed_sphere"] + repo: "https://github.com/NOAA-GFDL/atmos_drivers.git" + cppdefs: "-DSPMD -DCLIMATE_NUDGE" + branch: "2022.01" + paths: ["atmos_drivers/coupled"] + otherFlags: *FMSincludes + - component: "ice_sis" + requires: ["FMS", "ice_param", "mom6"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/ice_sis.git" + branch: "2021.02" + otherFlags: !join [*FMSincludes, " ", *momIncludes] + - component: "ice_param" + repo: "https://github.com/NOAA-GFDL/ice_param.git" + cppdefs: "-Duse_yaml -Duse_libMPI -Duse_netCDF" + branch: "2021.02" + requires: ["FMS", "mom6"] + otherFlags: !join [*FMSincludes," ", *momIncludes] + - component: "land_lad2" + requires: ["FMS"] + repo: "https://gitlab.gfdl.noaa.gov/FMS/land_lad2.git" + branch: "2022.01" + branch: "land_lad2_2021.02" + doF90Cpp: True + cppdefs: "-DINTERNAL_FILE_NML" + otherFlags: *FMSincludes + - component: "mom6" + requires: ["FMS"] + paths: ["mom6/MOM6-examples/src/MOM6/config_src/dynamic", "mom6/MOM6-examples/src/MOM6/config_src/coupled_driver", "mom6/MOM6-examples/src/MOM6/src/*/", "mom6/MOM6-examples/src/MOM6/src/*/*/", "mom6/ocean_BGC/generic_tracers", "mom6/ocean_BGC/mocsy/src"] + branch: ["2021.02","dev/gfdl/2018.04.06"] + repo: ["https://github.com/NOAA-GFDL/ocean_BGC.git","https://github.com/NOAA-GFDL/MOM6-examples.git"] + makeOverrides: 'OPENMP=""' + otherFlags: !join [*FMSincludes, " ", *momIncludes] + - component: "FMScoupler" + paths: ["FMScoupler/full", "FMScoupler/shared"] + repo: "https://github.com/NOAA-GFDL/FMScoupler.git" + branch: "2022.01" + requires: ["FMS", "atmos_drivers", "am5_phys", "land_lad2", "ice_sis", "ice_param", "mom6"] + otherFlags: !join [*FMSincludes, " ", *momIncludes] diff --git a/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/am5-wrong_compilefile.yaml b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/am5-wrong_compilefile.yaml new file mode 100644 index 00000000..86bbb57a --- /dev/null +++ b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/am5-wrong_compilefile.yaml @@ -0,0 +1,72 @@ +# reusable variables +fre_properties: + - &AM5_VERSION "am5f7b12r1" + - &FRE_STEM !join [am5/, *AM5_VERSION] + + # amip + - &EXP_AMIP_START "1979" + - &EXP_AMIP_END "2020" + - &ANA_AMIP_START "1980" + - &ANA_AMIP_END "2020" + + - &PP_AMIP_CHUNK96 "P1Y" + - &PP_AMIP_CHUNK384 "P1Y" + - &PP_XYINTERP96 "180,288" + - &PP_XYINTERP384 "720,1152" + + # climo + - &EXP_CLIMO_START96 "0001" + - &EXP_CLIMO_END96 "0011" + - &ANA_CLIMO_START96 "0002" + - &ANA_CLIMO_END96 "0011" + + - &EXP_CLIMO_START384 "0001" + - &EXP_CLIMO_END384 "0006" + - &ANA_CLIMO_START384 "0002" + - &ANA_CLIMO_END384 "0006" + + # coupled + - &PP_CPLD_CHUNK_A "P5Y" + - &PP_CPLD_CHUNK_B "P20Y" + + # grids + - &GRID_SPEC96 "/archive/oar.gfdl.am5/model_gen5/inputs/c96_grid/c96_OM4_025_grid_No_mg_drag_v20160808.tar" + + # compile information + - &release "f1a1r1" + - &INTEL "intel-classic" + - &FMSincludes "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" + - &momIncludes "-Imom6/MOM6-examples/src/MOM6/pkg/CVMix-src/include" + +build: + # compile information + compileYaml: "compiile.yaml" + platformYaml: "wrong_platforms.yaml" + +shared: + # directories shared across tools + directories: &shared_directories + history_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, history] + pp_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, pp] + analysis_dir: !join [/nbhome/$USER/, *FRE_STEM, /, *name] + ptmp_dir: "/xtmp/$USER/ptmp" + fre_analysis_home: "/home/fms/local/opt/fre-analysis/test" + + # shared pp settings + postprocess: + settings: &shared_settings + history_segment: "P1Y" + site: "ppan" + switches: &shared_switches + do_statics: True + do_timeavgs: True + clean_work: True + do_refinediag: False + do_atmos_plevel_masking: True + do_preanalysis: False + do_analysis: True + +experiments: + - name: "c96L65_am5f7b12r1_amip" + pp: + - diff --git a/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/am5-wrong_datatype.yaml b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/am5-wrong_datatype.yaml new file mode 100644 index 00000000..e65f3bd2 --- /dev/null +++ b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/am5-wrong_datatype.yaml @@ -0,0 +1,72 @@ +# reusable variables +fre_properties: + - &AM5_VERSION "am5f7b12r1" + - &FRE_STEM !join [am5/, *AM5_VERSION] + + # amip + - &EXP_AMIP_START "1979" + - &EXP_AMIP_END "2020" + - &ANA_AMIP_START "1980" + - &ANA_AMIP_END "2020" + + - &PP_AMIP_CHUNK96 "P1Y" + - &PP_AMIP_CHUNK384 "P1Y" + - &PP_XYINTERP96 "180,288" + - &PP_XYINTERP384 "720,1152" + + # climo + - &EXP_CLIMO_START96 "0001" + - &EXP_CLIMO_END96 "0011" + - &ANA_CLIMO_START96 "0002" + - &ANA_CLIMO_END96 "0011" + + - &EXP_CLIMO_START384 "0001" + - &EXP_CLIMO_END384 "0006" + - &ANA_CLIMO_START384 "0002" + - &ANA_CLIMO_END384 "0006" + + # coupled + - &PP_CPLD_CHUNK_A "P5Y" + - &PP_CPLD_CHUNK_B "P20Y" + + # grids + - &GRID_SPEC96 "/archive/oar.gfdl.am5/model_gen5/inputs/c96_grid/c96_OM4_025_grid_No_mg_drag_v20160808.tar" + + # compile information + - &release "f1a1r1" + - &INTEL "intel-classic" + - &FMSincludes "-IFMS/fms2_io/include -IFMS/include -IFMS/mpp/include" + - &momIncludes "-Imom6/MOM6-examples/src/MOM6/pkg/CVMix-src/include" + +build: + # compile information + compileYaml: "wrong_compile.yaml" + platformYaml: "wrong_platforms.yaml" + +shared: + # directories shared across tools + directories: &shared_directories + history_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, history] + pp_dir: !join [/archive/$USER/, *FRE_STEM, /, *name, /, *platform, -, *target, /, pp] + analysis_dir: !join [/nbhome/$USER/, *FRE_STEM, /, *name] + ptmp_dir: "/xtmp/$USER/ptmp" + fre_analysis_home: "/home/fms/local/opt/fre-analysis/test" + + # shared pp settings + postprocess: + settings: &shared_settings + history_segment: "P1Y" + site: "ppan" + switches: &shared_switches + do_statics: True + do_timeavgs: True + clean_work: True + do_refinediag: False + do_atmos_plevel_masking: True + do_preanalysis: False + do_analysis: True + +experiments: + - name: "c96L65_am5f7b12r1_amip" + pp: + - diff --git a/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/wrong_compile.yaml b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/wrong_compile.yaml new file mode 100644 index 00000000..c122764a --- /dev/null +++ b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/wrong_compile.yaml @@ -0,0 +1,11 @@ +compile: + experiment: "am5" + container_addlibs: + baremetal_linkerflags: + src: + - component: + repo: "https://github.com/NOAA-GFDL/FMS.git" + cppdefs: "-DINTERNAL_FILE_NML -Duse_libMPI -Duse_netCDF" + branch: 2022.01 + cppdefs: "-DHAVE_GETTID -Duse_libMPI -Duse_netCDF" + otherFlags: *FMSincludes diff --git a/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/wrong_platforms.yaml b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/wrong_platforms.yaml new file mode 100644 index 00000000..7e1b9f49 --- /dev/null +++ b/lib/fre/yamltools/tests/AM5_example/compile_yamls/compile_fail/wrong_platforms.yaml @@ -0,0 +1,26 @@ +platforms: + - name: ncrc5.intel + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [ !join [*INTEL, "/2022.2.1"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/test + - name: ncrc5.intel23 + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [!join [*INTEL, "/2023.1.0"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/test + - name: hpcme.2023 + compiler: intel + RUNenv: [". /spack/share/spack/setup-env.sh", "spack load libyaml", "spack load netcdf-fortran@4.5.4", "spack load hdf5@1.14.0"] + modelRoot: /apps + fc: mpiifort + cc: mpiicc + container: True + containerBuild: "podman" + containerRun: "apptainer" diff --git a/lib/fre/yamltools/tests/AM5_example/compile_yamls/platforms.yaml b/lib/fre/yamltools/tests/AM5_example/compile_yamls/platforms.yaml new file mode 100644 index 00000000..7e1b9f49 --- /dev/null +++ b/lib/fre/yamltools/tests/AM5_example/compile_yamls/platforms.yaml @@ -0,0 +1,26 @@ +platforms: + - name: ncrc5.intel + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [ !join [*INTEL, "/2022.2.1"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/test + - name: ncrc5.intel23 + compiler: intel + modulesInit: [" module use -a /ncrc/home2/fms/local/modulefiles \n","source $MODULESHOME/init/sh \n"] + modules: [!join [*INTEL, "/2023.1.0"],"fre/bronx-20",cray-hdf5/1.12.2.3, cray-netcdf/4.9.0.3] + fc: ftn + cc: cc + mkTemplate: "/ncrc/home2/fms/local/opt/fre-commands/bronx-20/site/ncrc5/$(INTEL).mk" + modelRoot: ${HOME}/fremake_canopy/test + - name: hpcme.2023 + compiler: intel + RUNenv: [". /spack/share/spack/setup-env.sh", "spack load libyaml", "spack load netcdf-fortran@4.5.4", "spack load hdf5@1.14.0"] + modelRoot: /apps + fc: mpiifort + cc: mpiicc + container: True + containerBuild: "podman" + containerRun: "apptainer" diff --git a/lib/fre/yamltools/tests/AM5_example/pp_yamls/pp.c96_amip.yaml b/lib/fre/yamltools/tests/AM5_example/pp_yamls/pp.c96_amip.yaml new file mode 100644 index 00000000..117c66c6 --- /dev/null +++ b/lib/fre/yamltools/tests/AM5_example/pp_yamls/pp.c96_amip.yaml @@ -0,0 +1,88 @@ +# local reusable variable overrides +fre_properties: + - &custom_interp "180,360" + +# directory overrides +#c96_amip_directories: +directories: + <<: *shared_directories + ptmp_dir: "/ptmp/$USER" + pp_grid_spec: *GRID_SPEC96 + +#c96_amip_postprocess: +postprocess: + # pp setting overrides + settings: + <<: *shared_settings + pp_start: *ANA_AMIP_START + pp_stop: *ANA_AMIP_END + pp_chunk_a: *PP_AMIP_CHUNK96 + pp_components: "atmos atmos_scalar" + switches: + <<: *shared_switches + do_statics: False + + # main pp instructions + components: + - type: "atmos_cmip" + sources: "atmos_month_cmip atmos_8xdaily_cmip atmos_daily_cmip" + sourceGrid: "cubedsphere" + xyInterp: *custom_interp + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos" + sources: "atmos_month" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_level_cmip" + sources: "atmos_level_cmip" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_level" + sources: "atmos_month" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_month_aer" + sources: "atmos_month_aer" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'atmos' + - type: "atmos_diurnal" + sources: "atmos_diurnal" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order2" + inputRealm: 'atmos' + - type: "atmos_scalar" + sources: "atmos_scalar" + - type: "aerosol_cmip" + xyInterp: *PP_XYINTERP96 + sources: "aerosol_month_cmip" + sourceGrid: "cubedsphere" + interpMethod: "conserve_order1" + inputRealm: 'atmos' + - type: "land" + sources: "land_month" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'land' + - type: "land_cmip" + sources: "land_month_cmip" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'land' + - type: "tracer_level" + sources: "atmos_tracer" + sourceGrid: "cubedsphere" + xyInterp: *PP_XYINTERP96 + interpMethod: "conserve_order1" + inputRealm: 'atmos' diff --git a/lib/fre/yamltools/tests/AM5_example/pp_yamls/schema.json b/lib/fre/yamltools/tests/AM5_example/pp_yamls/schema.json new file mode 100644 index 00000000..dfb9cff5 --- /dev/null +++ b/lib/fre/yamltools/tests/AM5_example/pp_yamls/schema.json @@ -0,0 +1,75 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Schema for PP Yaml", + "type": "object", + "properties": { + "name": {"type": "string"}, + "platform": {"type": "string"}, + "target": {"type": "string"}, + "directories": { + "description": "FRE shared directories", + "type": "object", + "items":{"$ref": "#/$defs/dirs" } + }, + "postprocess": { + "description": "FRE post-processing information", + "type": "object", + "items":{"$ref": "#/$defs/pp" } + } + }, + "$defs": { + "dirs": { + "history_dir": {"type":"string"}, + "pp_dir": {"type":"string"}, + "ptmp_dir": {"type":"string"}, + "refinediag_scripts":{"type":["string","null"]}, + "preanalysis_script":{"type":["string","null"]}, + "history_refined":{"type":["string","null"]}, + "analysis_dir":{"type":["string","null"]}, + "pp_grid_spec": {"type":"string"}, + "fre_analysis_home": {"type":["string","null"]} + }, + "pp": { + "type": "object", + "properties": { + "settings": { + "type:": "object", + "properties": { + "history_segment": {"type":"string"}, + "site": {"type":"string"}, + "pp_chunk_a": {"type":"string"}, + "pp_chunk_b": {"type":"string"}, + "pp_start": {"type":"string"}, + "pp_stop": {"type":"string"}, + "pp_components": {"type":"string"} + } + }, + "switches": { + "type": "object", + "properties": { + "clean_work": {"type":"boolean"}, + "do_mdtf": {"type":"boolean"}, + "do_statics": {"type":"boolean"}, + "do_timeavgs": {"type":"boolean"}, + "do_refinediag": {"type":"boolean"}, + "do_atmos_plevel_masking": {"type":"boolean"}, + "do_preanalysis": {"type":"boolean"}, + "do_analysis": {"type":"boolean"}, + "do_analysis_only": {"type":"boolean"} + } + }, + "components": { + "type": "array", + "properties": { + "type": {"type":"string"}, + "sources": {"type":"string"}, + "sourceGrid": {"type":"string"}, + "xyInterp": {"type":"string"}, + "interpMethod": {"type":"string"}, + "inputRealm": {"type":"string"} + } + } + } + } + } +} diff --git a/lib/fre/yamltools/tests/__init__.py b/lib/fre/yamltools/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/fre/yamltools/tests/test_combine_yamls.py b/lib/fre/yamltools/tests/test_combine_yamls.py new file mode 100644 index 00000000..7df6eb36 --- /dev/null +++ b/lib/fre/yamltools/tests/test_combine_yamls.py @@ -0,0 +1,202 @@ +""" +tests routines in fre.yamltools.combine_yamls +""" +import os +from pathlib import Path +import pytest +import shutil +import json +import yaml +from jsonschema import validate +from fre.yamltools import combine_yamls as cy + + +## SET-UP +# Set example yaml paths, input directory, output directory +#CWD = Path.cwd() +TEST_DIR = Path("fre/yamltools/tests") +IN_DIR = Path(f"{TEST_DIR}/AM5_example") +SCHEMA_DIR = Path("fre/gfdl_msd_schemas/FRE") + +# Create output directories +COMP_OUT_DIR = Path(f"{TEST_DIR}/combine_yamls_out/compile") +PP_OUT_DIR = Path(f"{TEST_DIR}/combine_yamls_out/pp") + +# If output directory exists, remove and create again +for out in [COMP_OUT_DIR, PP_OUT_DIR]: + if out.exists(): + shutil.rmtree(out) + Path(out).mkdir(parents=True,exist_ok=True) + else: + Path(out).mkdir(parents=True,exist_ok=True) + +## Set what would be click options +# Compile +COMP_EXPERIMENT = "am5" +COMP_PLATFORM = "ncrc5.intel23" +COMP_TARGET = "prod" + +# Post-processing +PP_EXPERIMENT = "c96L65_am5f7b12r1_amip" +PP_PLATFORM = "gfdl.ncrc5-intel22-classic" +PP_TARGET = "prod" + +def test_modelyaml_exists(): + """ + Make sure main yaml file exists + """ + assert Path(f"{IN_DIR}/am5.yaml").exists() + +def test_compileyaml_exists(): + """ + Make sure experiment yaml file exists + """ + assert Path(f"{IN_DIR}/compile_yamls/compile.yaml").exists() + +def test_platformyaml_exists(): + """ + Make sure experiment yaml file exists + """ + assert Path(f"{IN_DIR}/compile_yamls/platforms.yaml").exists() + +def test_merged_compile_yamls(): + """ + Check for the creation of the combined-[experiment] yaml + Check that the model yaml was merged into the combined yaml + """ + # Model yaml path + modelyaml = str(Path(f"{IN_DIR}/am5.yaml")) + use = "compile" + + # Merge the yamls + cy.consolidate_yamls(modelyaml, COMP_EXPERIMENT, COMP_PLATFORM, COMP_TARGET, use) + + # Move combined yaml to output location + shutil.move(f"{IN_DIR}/combined-am5.yaml", COMP_OUT_DIR) + + # Check that the combined yaml exists + assert Path(f"{COMP_OUT_DIR}/combined-{COMP_EXPERIMENT}.yaml").exists() + +def test_combined_compileyaml_validation(): + """ + Validate the combined compile yaml + """ + combined_yamlfile =f"{COMP_OUT_DIR}/combined-{COMP_EXPERIMENT}.yaml" + schema_file = os.path.join(SCHEMA_DIR, "fre_make.json") + + with open(combined_yamlfile,'r') as cf: + yml = yaml.safe_load(cf) + + with open(schema_file,'r') as f: + s = f.read() + schema = json.loads(s) + + # If the yaml is valid, no issues + # If the yaml is not valid, error + try: + validate(instance=yml,schema=schema) + except: + assert False + +def test_combined_compileyaml_combinefail(): + """ + Check to test if compile yaml is incorrect/does not exist, + the combine fails. (compile yaml path misspelled) + """ + # Model yaml path + modelyaml = str(Path(f"{IN_DIR}/compile_yamls/compile_fail/am5-wrong_compilefile.yaml")) + use = "compile" + + # Merge the yamls - should fail since there is no compile yaml specified in the model yaml + try: + cy.consolidate_yamls(modelyaml, COMP_EXPERIMENT, COMP_PLATFORM, COMP_TARGET, use) + # Move combined yaml to output location + shutil.move(f"{IN_DIR}/compile_yamls/compile_fail/combined-am5-wrong_compilefile.yaml", COMP_OUT_DIR) + except: + print("EXPECTED FAILURE") + # Move combined yaml to output location + shutil.move(f"{IN_DIR}/compile_yamls/compile_fail/combined-am5-wrong_compilefile.yaml", COMP_OUT_DIR) + assert True + +def test_combined_compileyaml_validatefail(): + """ + Check if the schema is validating correctly + Branch should be string + """ + # Model yaml path + modelyaml = str(Path(f"{IN_DIR}/compile_yamls/compile_fail/am5-wrong_datatype.yaml")) + use = "compile" + + # Merge the yamls + cy.consolidate_yamls(modelyaml, COMP_EXPERIMENT, COMP_PLATFORM, COMP_TARGET, use) + + # Move combined yaml to output location + shutil.move(f"{IN_DIR}/compile_yamls/compile_fail/combined-am5-wrong_datatype.yaml", COMP_OUT_DIR) + + # Validate against schema; should fail + wrong_combined = Path(f"{COMP_OUT_DIR}/combined-am5-wrong_datatype.yaml") + schema_file = os.path.join(SCHEMA_DIR, "fre_make.json") + + # Open/load combined yaml file + with open(wrong_combined,'r') as cf: + yml = yaml.safe_load(cf) + + # Open/load schema.jaon + with open(schema_file,'r') as f: + s = f.read() + schema = json.loads(s) + + # Validation should fail + try: + validate(instance=yml,schema=schema) + except: + assert True + +############ PP ############ +def test_expyaml_exists(): + """ + Make sure experiment yaml file exists + """ + assert Path(f"{IN_DIR}/pp_yamls/pp.c96_amip.yaml").exists() + +@pytest.mark.skip(reason='analysis scripts might not be defined yet') +def test_analysisyaml_exists(): + """ + Make sure experiment yaml file exists + """ + assert Path(f"{IN_DIR}/pp_yamls/analysis.yaml").exists() + +def test_merged_pp_yamls(): + """ + Check for the creation of the combined-[experiment] yaml + Check that the model yaml was merged into the combined yaml + """ + # Model yaml path + modelyaml = Path(f"{IN_DIR}/am5.yaml") + use = "pp" + + # Merge the yamls + cy.consolidate_yamls(modelyaml, PP_EXPERIMENT, PP_PLATFORM, PP_TARGET, use) + + # Move combined yaml to output location + shutil.move(f"combined-{PP_EXPERIMENT}.yaml", PP_OUT_DIR) + + # Check that the combined yaml exists + assert Path(f"{PP_OUT_DIR}/combined-{PP_EXPERIMENT}.yaml").exists() + +def test_combined_ppyaml_validation(): + """ + Validate the combined compile yaml + """ + combined_yamlfile =f"{PP_OUT_DIR}/combined-{PP_EXPERIMENT}.yaml" + schema_dir = Path(f"{IN_DIR}/pp_yamls") + schema_file = os.path.join(schema_dir, 'schema.json') + + with open(combined_yamlfile,'r') as cf: + yml = yaml.safe_load(cf) + + with open(schema_file,'r') as f: + s = f.read() + schema = json.loads(s) + + validate(instance=yml,schema=schema) diff --git a/modules.html b/modules.html new file mode 100644 index 00000000..85fc00c7 --- /dev/null +++ b/modules.html @@ -0,0 +1,190 @@ + + + + + + + + + fre — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 00000000..dee9b859 Binary files /dev/null and b/objects.inv differ diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 00000000..f327111b --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,506 @@ + + + + + + + + Python Module Index — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ f +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ f
+ fre +
    + fre.app +
    + fre.app.freapp +
    + fre.app.generate_time_averages +
    + fre.app.generate_time_averages.cdoTimeAverager +
    + fre.app.generate_time_averages.frenctoolsTimeAverager +
    + fre.app.generate_time_averages.frepytoolsTimeAverager +
    + fre.app.generate_time_averages.generate_time_averages +
    + fre.app.generate_time_averages.tests +
    + fre.app.generate_time_averages.tests.test_generate_time_averages +
    + fre.app.generate_time_averages.timeAverager +
    + fre.app.mask_atmos_plevel +
    + fre.app.regrid_xy +
    + fre.app.regrid_xy.regrid_xy +
    + fre.catalog +
    + fre.catalog.frecatalog +
    + fre.catalog.tests +
    + fre.catalog.tests.test_fre_catalog +
    + fre.check +
    + fre.check.frecheck +
    + fre.check.frecheckexample +
    + fre.cmor +
    + fre.cmor.cmor_mixer +
    + fre.cmor.frecmor +
    + fre.cmor.tests +
    + fre.cmor.tests.test_cmor_run_subtool +
    + fre.fre +
    + fre.lazy_group +
    + fre.list +
    + fre.list.frelist +
    + fre.list.frelistexample +
    + fre.make +
    + fre.make.createCheckout +
    + fre.make.createCompile +
    + fre.make.createDocker +
    + fre.make.createMakefile +
    + fre.make.fremake +
    + fre.make.runFremake +
    + fre.make.tests +
    + fre.make.tests.test_create_makefile +
    + fre.pp +
    + fre.pp.checkoutScript +
    + fre.pp.configure_script_xml +
    + fre.pp.configure_script_yaml +
    + fre.pp.frepp +
    + fre.pp.install +
    + fre.pp.run +
    + fre.pp.status +
    + fre.pp.tests +
    + fre.pp.tests.test_configure_script_yaml +
    + fre.pp.tests.test_rose_quoting +
    + fre.pp.validate +
    + fre.pp.wrapper +
    + fre.run +
    + fre.run.frerun +
    + fre.run.frerunexample +
    + fre.test +
    + fre.test.fretest +
    + fre.test.fretestexample +
    + fre.tests +
    + fre.tests.test_fre_app_cli +
    + fre.tests.test_fre_catalog_cli +
    + fre.tests.test_fre_check_cli +
    + fre.tests.test_fre_cli +
    + fre.tests.test_fre_cmor_cli +
    + fre.tests.test_fre_list_cli +
    + fre.tests.test_fre_make_cli +
    + fre.tests.test_fre_pp_cli +
    + fre.tests.test_fre_run_cli +
    + fre.tests.test_fre_test_cli +
    + fre.tests.test_fre_yamltools_cli +
    + fre.yamltools +
    + fre.yamltools.combine_yamls +
    + fre.yamltools.freyamltools +
    + fre.yamltools.freyamltoolsexample +
    + fre.yamltools.tests +
    + fre.yamltools.tests.test_combine_yamls +
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 00000000..709a273b --- /dev/null +++ b/search.html @@ -0,0 +1,126 @@ + + + + + + + + Search — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 00000000..7deb46c7 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"Adding Documentation": [[2, "adding-documentation"]], "Adding New Tools": [[2, "adding-new-tools"]], "Badges": [[0, null]], "Calling fre": [[83, "calling-fre"]], "Checklist": [[2, "checklist"]], "Contents:": [[80, null]], "Create New Conda environment": [[82, "create-new-conda-environment"]], "Developer Usage": [[2, null]], "Documentation-Documentation": [[1, null]], "Example fre/ Directory Structure": [[2, "example-fre-directory-structure"]], "From Other Repositories": [[2, "from-other-repositories"]], "How to Contribute to fre-cli\u2019s documentation": [[1, "how-to-contribute-to-fre-cli-s-documentation"]], "If on workstation": [[82, "if-on-workstation"]], "Indices": [[80, "indices"]], "MANIFEST.in": [[2, "manifest-in"]], "Module contents": [[3, "module-fre"], [4, "module-fre.app"], [6, "module-fre.app.generate_time_averages"], [11, "module-fre.app.generate_time_averages.tests"], [15, "module-fre.app.regrid_xy"], [17, "module-fre.catalog"], [19, "module-fre.catalog.tests"], [21, "module-fre.check"], [24, "module-fre.cmor"], [27, "module-fre.cmor.tests"], [31, "module-fre.list"], [34, "module-fre.make"], [41, "module-fre.make.tests"], [43, "module-fre.pp"], [51, "module-fre.pp.tests"], [56, "module-fre.run"], [59, "module-fre.test"], [62, "module-fre.tests"], [74, "module-fre.yamltools"], [78, "module-fre.yamltools.tests"]], "Set up Conda environment": [[82, "set-up-conda-environment"]], "Setup": [[82, null]], "Submodules": [[3, "submodules"], [4, "submodules"], [6, "submodules"], [11, "submodules"], [15, "submodules"], [17, "submodules"], [19, "submodules"], [21, "submodules"], [24, "submodules"], [27, "submodules"], [31, "submodules"], [34, "submodules"], [41, "submodules"], [43, "submodules"], [51, "submodules"], [56, "submodules"], [59, "submodules"], [62, "submodules"], [74, "submodules"], [78, "submodules"]], "Subpackages": [[3, "subpackages"], [4, "subpackages"], [6, "subpackages"], [17, "subpackages"], [24, "subpackages"], [34, "subpackages"], [43, "subpackages"], [74, "subpackages"]], "Tools": [[83, "tools"]], "Usage": [[83, null]], "Welcome to Fre-Cli\u2019s documentation!": [[80, null]], "fre": [[81, null]], "fre app": [[83, "fre-app"]], "fre catalog": [[83, "fre-catalog"]], "fre cmor": [[83, "fre-cmor"]], "fre make": [[83, "fre-make"]], "fre package": [[3, null]], "fre pp": [[83, "fre-pp"]], "fre yamltools": [[83, "fre-yamltools"]], "fre.app package": [[4, null]], "fre.app.freapp module": [[5, null]], "fre.app.generate_time_averages package": [[6, null]], "fre.app.generate_time_averages.cdoTimeAverager module": [[7, null]], "fre.app.generate_time_averages.frenctoolsTimeAverager module": [[8, null]], "fre.app.generate_time_averages.frepytoolsTimeAverager module": [[9, null]], "fre.app.generate_time_averages.generate_time_averages module": [[10, null]], "fre.app.generate_time_averages.tests package": [[11, null]], "fre.app.generate_time_averages.tests.test_generate_time_averages module": [[12, null]], "fre.app.generate_time_averages.timeAverager module": [[13, null]], "fre.app.mask_atmos_plevel module": [[14, null]], "fre.app.regrid_xy package": [[15, null]], "fre.app.regrid_xy.regrid_xy module": [[16, null]], "fre.catalog package": [[17, null]], "fre.catalog.frecatalog module": [[18, null]], "fre.catalog.tests package": [[19, null]], "fre.catalog.tests.test_fre_catalog module": [[20, null]], "fre.check package": [[21, null]], "fre.check.frecheck module": [[22, null]], "fre.check.frecheckexample module": [[23, null]], "fre.cmor package": [[24, null]], "fre.cmor.cmor_mixer module": [[25, null]], "fre.cmor.frecmor module": [[26, null]], "fre.cmor.tests package": [[27, null]], "fre.cmor.tests.test_cmor_run_subtool module": [[28, null]], "fre.fre module": [[29, null]], "fre.lazy_group module": [[30, null]], "fre.list package": [[31, null]], "fre.list.frelist module": [[32, null]], "fre.list.frelistexample module": [[33, null]], "fre.make package": [[34, null]], "fre.make.createCheckout module": [[35, null]], "fre.make.createCompile module": [[36, null]], "fre.make.createDocker module": [[37, null]], "fre.make.createMakefile module": [[38, null]], "fre.make.fremake module": [[39, null]], "fre.make.runFremake module": [[40, null]], "fre.make.tests package": [[41, null]], "fre.make.tests.test_create_makefile module": [[42, null]], "fre.pp package": [[43, null]], "fre.pp.checkoutScript module": [[44, null]], "fre.pp.configure_script_xml module": [[45, null]], "fre.pp.configure_script_yaml module": [[46, null]], "fre.pp.frepp module": [[47, null]], "fre.pp.install module": [[48, null]], "fre.pp.run module": [[49, null]], "fre.pp.status module": [[50, null]], "fre.pp.tests package": [[51, null]], "fre.pp.tests.test_configure_script_yaml module": [[52, null]], "fre.pp.tests.test_rose_quoting module": [[53, null]], "fre.pp.validate module": [[54, null]], "fre.pp.wrapper module": [[55, null]], "fre.run package": [[56, null]], "fre.run.frerun module": [[57, null]], "fre.run.frerunexample module": [[58, null]], "fre.test package": [[59, null]], "fre.test.fretest module": [[60, null]], "fre.test.fretestexample module": [[61, null]], "fre.tests package": [[62, null]], "fre.tests.test_fre_app_cli module": [[63, null]], "fre.tests.test_fre_catalog_cli module": [[64, null]], "fre.tests.test_fre_check_cli module": [[65, null]], "fre.tests.test_fre_cli module": [[66, null]], "fre.tests.test_fre_cmor_cli module": [[67, null]], "fre.tests.test_fre_list_cli module": [[68, null]], "fre.tests.test_fre_make_cli module": [[69, null]], "fre.tests.test_fre_pp_cli module": [[70, null]], "fre.tests.test_fre_run_cli module": [[71, null]], "fre.tests.test_fre_test_cli module": [[72, null]], "fre.tests.test_fre_yamltools_cli module": [[73, null]], "fre.yamltools package": [[74, null]], "fre.yamltools.combine_yamls module": [[75, null]], "fre.yamltools.freyamltools module": [[76, null]], "fre.yamltools.freyamltoolsexample module": [[77, null]], "fre.yamltools.tests package": [[78, null]], "fre.yamltools.tests.test_combine_yamls module": [[79, null]], "not-yet-implemented": [[83, "not-yet-implemented"]]}, "docnames": ["badges", "contributing_to_doc", "developer_usage", "fre", "fre.app", "fre.app.freapp", "fre.app.generate_time_averages", "fre.app.generate_time_averages.cdoTimeAverager", "fre.app.generate_time_averages.frenctoolsTimeAverager", "fre.app.generate_time_averages.frepytoolsTimeAverager", "fre.app.generate_time_averages.generate_time_averages", "fre.app.generate_time_averages.tests", "fre.app.generate_time_averages.tests.test_generate_time_averages", "fre.app.generate_time_averages.timeAverager", "fre.app.mask_atmos_plevel", "fre.app.regrid_xy", "fre.app.regrid_xy.regrid_xy", "fre.catalog", "fre.catalog.frecatalog", "fre.catalog.tests", "fre.catalog.tests.test_fre_catalog", "fre.check", "fre.check.frecheck", "fre.check.frecheckexample", "fre.cmor", "fre.cmor.cmor_mixer", "fre.cmor.frecmor", "fre.cmor.tests", "fre.cmor.tests.test_cmor_run_subtool", "fre.fre", "fre.lazy_group", "fre.list", "fre.list.frelist", "fre.list.frelistexample", "fre.make", "fre.make.createCheckout", "fre.make.createCompile", "fre.make.createDocker", "fre.make.createMakefile", "fre.make.fremake", "fre.make.runFremake", "fre.make.tests", "fre.make.tests.test_create_makefile", "fre.pp", "fre.pp.checkoutScript", "fre.pp.configure_script_xml", "fre.pp.configure_script_yaml", "fre.pp.frepp", "fre.pp.install", "fre.pp.run", "fre.pp.status", "fre.pp.tests", "fre.pp.tests.test_configure_script_yaml", "fre.pp.tests.test_rose_quoting", "fre.pp.validate", "fre.pp.wrapper", "fre.run", "fre.run.frerun", "fre.run.frerunexample", "fre.test", "fre.test.fretest", "fre.test.fretestexample", "fre.tests", "fre.tests.test_fre_app_cli", "fre.tests.test_fre_catalog_cli", "fre.tests.test_fre_check_cli", "fre.tests.test_fre_cli", "fre.tests.test_fre_cmor_cli", "fre.tests.test_fre_list_cli", "fre.tests.test_fre_make_cli", "fre.tests.test_fre_pp_cli", "fre.tests.test_fre_run_cli", "fre.tests.test_fre_test_cli", "fre.tests.test_fre_yamltools_cli", "fre.yamltools", "fre.yamltools.combine_yamls", "fre.yamltools.freyamltools", "fre.yamltools.freyamltoolsexample", "fre.yamltools.tests", "fre.yamltools.tests.test_combine_yamls", "index", "modules", "setup", "usage"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["badges.rst", "contributing_to_doc.rst", "developer_usage.rst", "fre.rst", "fre.app.rst", "fre.app.freapp.rst", "fre.app.generate_time_averages.rst", "fre.app.generate_time_averages.cdoTimeAverager.rst", "fre.app.generate_time_averages.frenctoolsTimeAverager.rst", "fre.app.generate_time_averages.frepytoolsTimeAverager.rst", "fre.app.generate_time_averages.generate_time_averages.rst", "fre.app.generate_time_averages.tests.rst", "fre.app.generate_time_averages.tests.test_generate_time_averages.rst", "fre.app.generate_time_averages.timeAverager.rst", "fre.app.mask_atmos_plevel.rst", "fre.app.regrid_xy.rst", "fre.app.regrid_xy.regrid_xy.rst", "fre.catalog.rst", "fre.catalog.frecatalog.rst", "fre.catalog.tests.rst", "fre.catalog.tests.test_fre_catalog.rst", "fre.check.rst", "fre.check.frecheck.rst", "fre.check.frecheckexample.rst", "fre.cmor.rst", "fre.cmor.cmor_mixer.rst", "fre.cmor.frecmor.rst", "fre.cmor.tests.rst", "fre.cmor.tests.test_cmor_run_subtool.rst", "fre.fre.rst", "fre.lazy_group.rst", "fre.list.rst", "fre.list.frelist.rst", "fre.list.frelistexample.rst", "fre.make.rst", "fre.make.createCheckout.rst", "fre.make.createCompile.rst", "fre.make.createDocker.rst", "fre.make.createMakefile.rst", "fre.make.fremake.rst", "fre.make.runFremake.rst", "fre.make.tests.rst", "fre.make.tests.test_create_makefile.rst", "fre.pp.rst", "fre.pp.checkoutScript.rst", "fre.pp.configure_script_xml.rst", "fre.pp.configure_script_yaml.rst", "fre.pp.frepp.rst", "fre.pp.install.rst", "fre.pp.run.rst", "fre.pp.status.rst", "fre.pp.tests.rst", "fre.pp.tests.test_configure_script_yaml.rst", "fre.pp.tests.test_rose_quoting.rst", "fre.pp.validate.rst", "fre.pp.wrapper.rst", "fre.run.rst", "fre.run.frerun.rst", "fre.run.frerunexample.rst", "fre.test.rst", "fre.test.fretest.rst", "fre.test.fretestexample.rst", "fre.tests.rst", "fre.tests.test_fre_app_cli.rst", "fre.tests.test_fre_catalog_cli.rst", "fre.tests.test_fre_check_cli.rst", "fre.tests.test_fre_cli.rst", "fre.tests.test_fre_cmor_cli.rst", "fre.tests.test_fre_list_cli.rst", "fre.tests.test_fre_make_cli.rst", "fre.tests.test_fre_pp_cli.rst", "fre.tests.test_fre_run_cli.rst", "fre.tests.test_fre_test_cli.rst", "fre.tests.test_fre_yamltools_cli.rst", "fre.yamltools.rst", "fre.yamltools.combine_yamls.rst", "fre.yamltools.freyamltools.rst", "fre.yamltools.freyamltoolsexample.rst", "fre.yamltools.tests.rst", "fre.yamltools.tests.test_combine_yamls.rst", "index.rst", "modules.rst", "setup.rst", "usage.rst"], "indexentries": {"avg_type (fre.app.generate_time_averages.timeaverager.timeaverager attribute)": [[13, "fre.app.generate_time_averages.timeAverager.timeAverager.avg_type", false]], "cdotimeaverager (class in fre.app.generate_time_averages.cdotimeaverager)": [[7, "fre.app.generate_time_averages.cdoTimeAverager.cdoTimeAverager", false]], "check_dataset_for_ocean_grid() (in module fre.cmor.cmor_mixer)": [[25, "fre.cmor.cmor_mixer.check_dataset_for_ocean_grid", false]], "check_interp_method() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.check_interp_method", false]], "check_per_component_settings() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.check_per_component_settings", false]], "checkout_create() (in module fre.make.createcheckout)": [[35, "fre.make.createCheckout.checkout_create", false]], "chunk_from_legacy() (in module fre.pp.configure_script_xml)": [[45, "fre.pp.configure_script_xml.chunk_from_legacy", false]], "clean_yaml() (fre.yamltools.combine_yamls.init_compile_yaml method)": [[75, "fre.yamltools.combine_yamls.init_compile_yaml.clean_yaml", false]], "clean_yaml() (fre.yamltools.combine_yamls.init_pp_yaml method)": [[75, "fre.yamltools.combine_yamls.init_pp_yaml.clean_yaml", false]], "cmor_run_subtool() (in module fre.cmor.cmor_mixer)": [[25, "fre.cmor.cmor_mixer.cmor_run_subtool", false]], "cmorize_target_var_files() (in module fre.cmor.cmor_mixer)": [[25, "fre.cmor.cmor_mixer.cmorize_target_var_files", false]], "combine_analysis() (fre.yamltools.combine_yamls.init_pp_yaml method)": [[75, "fre.yamltools.combine_yamls.init_pp_yaml.combine_analysis", false]], "combine_compile() (fre.yamltools.combine_yamls.init_compile_yaml method)": [[75, "fre.yamltools.combine_yamls.init_compile_yaml.combine_compile", false]], "combine_experiment() (fre.yamltools.combine_yamls.init_pp_yaml method)": [[75, "fre.yamltools.combine_yamls.init_pp_yaml.combine_experiment", false]], "combine_model() (fre.yamltools.combine_yamls.init_compile_yaml method)": [[75, "fre.yamltools.combine_yamls.init_compile_yaml.combine_model", false]], "combine_model() (fre.yamltools.combine_yamls.init_pp_yaml method)": [[75, "fre.yamltools.combine_yamls.init_pp_yaml.combine_model", false]], "combine_platforms() (fre.yamltools.combine_yamls.init_compile_yaml method)": [[75, "fre.yamltools.combine_yamls.init_compile_yaml.combine_platforms", false]], "combined_compile_existcheck() (in module fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.combined_compile_existcheck", false]], "compile_create() (in module fre.make.createcompile)": [[36, "fre.make.createCompile.compile_create", false]], "consolidate_yamls() (in module fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.consolidate_yamls", false]], "copy_nc() (in module fre.cmor.cmor_mixer)": [[25, "fre.cmor.cmor_mixer.copy_nc", false]], "create_tmp_dir() (in module fre.cmor.cmor_mixer)": [[25, "fre.cmor.cmor_mixer.create_tmp_dir", false]], "dockerfile_create() (in module fre.make.createdocker)": [[37, "fre.make.createDocker.dockerfile_create", false]], "duration_to_seconds() (in module fre.pp.configure_script_xml)": [[45, "fre.pp.configure_script_xml.duration_to_seconds", false]], "experiment_check() (in module fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.experiment_check", false]], "fre": [[3, "module-fre", false]], "fre.app": [[4, "module-fre.app", false]], "fre.app.freapp": [[5, "module-fre.app.freapp", false]], "fre.app.generate_time_averages": [[6, "module-fre.app.generate_time_averages", false]], "fre.app.generate_time_averages.cdotimeaverager": [[7, "module-fre.app.generate_time_averages.cdoTimeAverager", false]], "fre.app.generate_time_averages.frenctoolstimeaverager": [[8, "module-fre.app.generate_time_averages.frenctoolsTimeAverager", false]], "fre.app.generate_time_averages.frepytoolstimeaverager": [[9, "module-fre.app.generate_time_averages.frepytoolsTimeAverager", false]], "fre.app.generate_time_averages.generate_time_averages": [[10, "module-fre.app.generate_time_averages.generate_time_averages", false]], "fre.app.generate_time_averages.tests": [[11, "module-fre.app.generate_time_averages.tests", false]], "fre.app.generate_time_averages.tests.test_generate_time_averages": [[12, "module-fre.app.generate_time_averages.tests.test_generate_time_averages", false]], "fre.app.generate_time_averages.timeaverager": [[13, "module-fre.app.generate_time_averages.timeAverager", false]], "fre.app.mask_atmos_plevel": [[14, "module-fre.app.mask_atmos_plevel", false]], "fre.app.regrid_xy": [[15, "module-fre.app.regrid_xy", false]], "fre.app.regrid_xy.regrid_xy": [[16, "module-fre.app.regrid_xy.regrid_xy", false]], "fre.catalog": [[17, "module-fre.catalog", false]], "fre.catalog.frecatalog": [[18, "module-fre.catalog.frecatalog", false]], "fre.catalog.tests": [[19, "module-fre.catalog.tests", false]], "fre.catalog.tests.test_fre_catalog": [[20, "module-fre.catalog.tests.test_fre_catalog", false]], "fre.check": [[21, "module-fre.check", false]], "fre.check.frecheck": [[22, "module-fre.check.frecheck", false]], "fre.check.frecheckexample": [[23, "module-fre.check.frecheckexample", false]], "fre.cmor": [[24, "module-fre.cmor", false]], "fre.cmor.cmor_mixer": [[25, "module-fre.cmor.cmor_mixer", false]], "fre.cmor.frecmor": [[26, "module-fre.cmor.frecmor", false]], "fre.cmor.tests": [[27, "module-fre.cmor.tests", false]], "fre.cmor.tests.test_cmor_run_subtool": [[28, "module-fre.cmor.tests.test_cmor_run_subtool", false]], "fre.fre": [[29, "module-fre.fre", false]], "fre.lazy_group": [[30, "module-fre.lazy_group", false]], "fre.list": [[31, "module-fre.list", false]], "fre.list.frelist": [[32, "module-fre.list.frelist", false]], "fre.list.frelistexample": [[33, "module-fre.list.frelistexample", false]], "fre.make": [[34, "module-fre.make", false]], "fre.make.createcheckout": [[35, "module-fre.make.createCheckout", false]], "fre.make.createcompile": [[36, "module-fre.make.createCompile", false]], "fre.make.createdocker": [[37, "module-fre.make.createDocker", false]], "fre.make.createmakefile": [[38, "module-fre.make.createMakefile", false]], "fre.make.fremake": [[39, "module-fre.make.fremake", false]], "fre.make.runfremake": [[40, "module-fre.make.runFremake", false]], "fre.make.tests": [[41, "module-fre.make.tests", false]], "fre.make.tests.test_create_makefile": [[42, "module-fre.make.tests.test_create_makefile", false]], "fre.pp": [[43, "module-fre.pp", false]], "fre.pp.checkoutscript": [[44, "module-fre.pp.checkoutScript", false]], "fre.pp.configure_script_xml": [[45, "module-fre.pp.configure_script_xml", false]], "fre.pp.configure_script_yaml": [[46, "module-fre.pp.configure_script_yaml", false]], "fre.pp.frepp": [[47, "module-fre.pp.frepp", false]], "fre.pp.install": [[48, "module-fre.pp.install", false]], "fre.pp.run": [[49, "module-fre.pp.run", false]], "fre.pp.status": [[50, "module-fre.pp.status", false]], "fre.pp.tests": [[51, "module-fre.pp.tests", false]], "fre.pp.tests.test_configure_script_yaml": [[52, "module-fre.pp.tests.test_configure_script_yaml", false]], "fre.pp.tests.test_rose_quoting": [[53, "module-fre.pp.tests.test_rose_quoting", false]], "fre.pp.validate": [[54, "module-fre.pp.validate", false]], "fre.pp.wrapper": [[55, "module-fre.pp.wrapper", false]], "fre.run": [[56, "module-fre.run", false]], "fre.run.frerun": [[57, "module-fre.run.frerun", false]], "fre.run.frerunexample": [[58, "module-fre.run.frerunexample", false]], "fre.test": [[59, "module-fre.test", false]], "fre.test.fretest": [[60, "module-fre.test.fretest", false]], "fre.test.fretestexample": [[61, "module-fre.test.fretestexample", false]], "fre.tests": [[62, "module-fre.tests", false]], "fre.tests.test_fre_app_cli": [[63, "module-fre.tests.test_fre_app_cli", false]], "fre.tests.test_fre_catalog_cli": [[64, "module-fre.tests.test_fre_catalog_cli", false]], "fre.tests.test_fre_check_cli": [[65, "module-fre.tests.test_fre_check_cli", false]], "fre.tests.test_fre_cli": [[66, "module-fre.tests.test_fre_cli", false]], "fre.tests.test_fre_cmor_cli": [[67, "module-fre.tests.test_fre_cmor_cli", false]], "fre.tests.test_fre_list_cli": [[68, "module-fre.tests.test_fre_list_cli", false]], "fre.tests.test_fre_make_cli": [[69, "module-fre.tests.test_fre_make_cli", false]], "fre.tests.test_fre_pp_cli": [[70, "module-fre.tests.test_fre_pp_cli", false]], "fre.tests.test_fre_run_cli": [[71, "module-fre.tests.test_fre_run_cli", false]], "fre.tests.test_fre_test_cli": [[72, "module-fre.tests.test_fre_test_cli", false]], "fre.tests.test_fre_yamltools_cli": [[73, "module-fre.tests.test_fre_yamltools_cli", false]], "fre.yamltools": [[74, "module-fre.yamltools", false]], "fre.yamltools.combine_yamls": [[75, "module-fre.yamltools.combine_yamls", false]], "fre.yamltools.freyamltools": [[76, "module-fre.yamltools.freyamltools", false]], "fre.yamltools.freyamltoolsexample": [[77, "module-fre.yamltools.freyamltoolsexample", false]], "fre.yamltools.tests": [[78, "module-fre.yamltools.tests", false]], "fre.yamltools.tests.test_combine_yamls": [[79, "module-fre.yamltools.tests.test_combine_yamls", false]], "frelist_xpath() (in module fre.pp.configure_script_xml)": [[45, "fre.pp.configure_script_xml.frelist_xpath", false]], "fremake_run() (in module fre.make.runfremake)": [[40, "fre.make.runFremake.fremake_run", false]], "frenctoolstimeaverager (class in fre.app.generate_time_averages.frenctoolstimeaverager)": [[8, "fre.app.generate_time_averages.frenctoolsTimeAverager.frenctoolsTimeAverager", false]], "frepytoolstimeaverager (class in fre.app.generate_time_averages.frepytoolstimeaverager)": [[9, "fre.app.generate_time_averages.frepytoolsTimeAverager.frepytoolsTimeAverager", false]], "freq_from_legacy() (in module fre.pp.configure_script_xml)": [[45, "fre.pp.configure_script_xml.freq_from_legacy", false]], "freq_to_date_format() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.freq_to_date_format", false]], "generate_timavg() (fre.app.generate_time_averages.cdotimeaverager.cdotimeaverager method)": [[7, "fre.app.generate_time_averages.cdoTimeAverager.cdoTimeAverager.generate_timavg", false]], "generate_timavg() (fre.app.generate_time_averages.frenctoolstimeaverager.frenctoolstimeaverager method)": [[8, "fre.app.generate_time_averages.frenctoolsTimeAverager.frenctoolsTimeAverager.generate_timavg", false]], "generate_timavg() (fre.app.generate_time_averages.frepytoolstimeaverager.frepytoolstimeaverager method)": [[9, "fre.app.generate_time_averages.frepytoolsTimeAverager.frepytoolsTimeAverager.generate_timavg", false]], "generate_timavg() (fre.app.generate_time_averages.timeaverager.timeaverager method)": [[13, "fre.app.generate_time_averages.timeAverager.timeAverager.generate_timavg", false]], "generate_time_average() (in module fre.app.generate_time_averages.generate_time_averages)": [[10, "fre.app.generate_time_averages.generate_time_averages.generate_time_average", false]], "get_combined_compileyaml() (in module fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.get_combined_compileyaml", false]], "get_combined_ppyaml() (in module fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.get_combined_ppyaml", false]], "get_command() (fre.lazy_group.lazygroup method)": [[30, "fre.lazy_group.LazyGroup.get_command", false]], "get_compile_paths() (in module fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.get_compile_paths", false]], "get_iso_datetimes() (in module fre.cmor.cmor_mixer)": [[25, "fre.cmor.cmor_mixer.get_iso_datetimes", false]], "get_mosaic_file_name() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.get_mosaic_file_name", false]], "get_mosaic_grid_file_name() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.get_mosaic_grid_file_name", false]], "get_var_filenames() (in module fre.cmor.cmor_mixer)": [[25, "fre.cmor.cmor_mixer.get_var_filenames", false]], "get_vertical_dimension() (in module fre.cmor.cmor_mixer)": [[25, "fre.cmor.cmor_mixer.get_vertical_dimension", false]], "init_compile_yaml (class in fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.init_compile_yaml", false]], "init_pp_yaml (class in fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.init_pp_yaml", false]], "join_constructor() (in module fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.join_constructor", false]], "lazygroup (class in fre.lazy_group)": [[30, "fre.lazy_group.LazyGroup", false]], "list_commands() (fre.lazy_group.lazygroup method)": [[30, "fre.lazy_group.LazyGroup.list_commands", false]], "main() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.main", false]], "main() (in module fre.pp.configure_script_xml)": [[45, "fre.pp.configure_script_xml.main", false]], "make_component_list() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.make_component_list", false]], "make_regrid_var_list() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.make_regrid_var_list", false]], "makefile_create() (in module fre.make.createmakefile)": [[38, "fre.make.createMakefile.makefile_create", false]], "mask_field_above_surface_pressure() (in module fre.app.mask_atmos_plevel)": [[14, "fre.app.mask_atmos_plevel.mask_field_above_surface_pressure", false]], "module": [[3, "module-fre", false], [4, "module-fre.app", false], [5, "module-fre.app.freapp", false], [6, "module-fre.app.generate_time_averages", false], [7, "module-fre.app.generate_time_averages.cdoTimeAverager", false], [8, "module-fre.app.generate_time_averages.frenctoolsTimeAverager", false], [9, "module-fre.app.generate_time_averages.frepytoolsTimeAverager", false], [10, "module-fre.app.generate_time_averages.generate_time_averages", false], [11, "module-fre.app.generate_time_averages.tests", false], [12, "module-fre.app.generate_time_averages.tests.test_generate_time_averages", false], [13, "module-fre.app.generate_time_averages.timeAverager", false], [14, "module-fre.app.mask_atmos_plevel", false], [15, "module-fre.app.regrid_xy", false], [16, "module-fre.app.regrid_xy.regrid_xy", false], [17, "module-fre.catalog", false], [18, "module-fre.catalog.frecatalog", false], [19, "module-fre.catalog.tests", false], [20, "module-fre.catalog.tests.test_fre_catalog", false], [21, "module-fre.check", false], [22, "module-fre.check.frecheck", false], [23, "module-fre.check.frecheckexample", false], [24, "module-fre.cmor", false], [25, "module-fre.cmor.cmor_mixer", false], [26, "module-fre.cmor.frecmor", false], [27, "module-fre.cmor.tests", false], [28, "module-fre.cmor.tests.test_cmor_run_subtool", false], [29, "module-fre.fre", false], [30, "module-fre.lazy_group", false], [31, "module-fre.list", false], [32, "module-fre.list.frelist", false], [33, "module-fre.list.frelistexample", false], [34, "module-fre.make", false], [35, "module-fre.make.createCheckout", false], [36, "module-fre.make.createCompile", false], [37, "module-fre.make.createDocker", false], [38, "module-fre.make.createMakefile", false], [39, "module-fre.make.fremake", false], [40, "module-fre.make.runFremake", false], [41, "module-fre.make.tests", false], [42, "module-fre.make.tests.test_create_makefile", false], [43, "module-fre.pp", false], [44, "module-fre.pp.checkoutScript", false], [45, "module-fre.pp.configure_script_xml", false], [46, "module-fre.pp.configure_script_yaml", false], [47, "module-fre.pp.frepp", false], [48, "module-fre.pp.install", false], [49, "module-fre.pp.run", false], [50, "module-fre.pp.status", false], [51, "module-fre.pp.tests", false], [52, "module-fre.pp.tests.test_configure_script_yaml", false], [53, "module-fre.pp.tests.test_rose_quoting", false], [54, "module-fre.pp.validate", false], [55, "module-fre.pp.wrapper", false], [56, "module-fre.run", false], [57, "module-fre.run.frerun", false], [58, "module-fre.run.frerunexample", false], [59, "module-fre.test", false], [60, "module-fre.test.fretest", false], [61, "module-fre.test.fretestexample", false], [62, "module-fre.tests", false], [63, "module-fre.tests.test_fre_app_cli", false], [64, "module-fre.tests.test_fre_catalog_cli", false], [65, "module-fre.tests.test_fre_check_cli", false], [66, "module-fre.tests.test_fre_cli", false], [67, "module-fre.tests.test_fre_cmor_cli", false], [68, "module-fre.tests.test_fre_list_cli", false], [69, "module-fre.tests.test_fre_make_cli", false], [70, "module-fre.tests.test_fre_pp_cli", false], [71, "module-fre.tests.test_fre_run_cli", false], [72, "module-fre.tests.test_fre_test_cli", false], [73, "module-fre.tests.test_fre_yamltools_cli", false], [74, "module-fre.yamltools", false], [75, "module-fre.yamltools.combine_yamls", false], [76, "module-fre.yamltools.freyamltools", false], [77, "module-fre.yamltools.freyamltoolsexample", false], [78, "module-fre.yamltools.tests", false], [79, "module-fre.yamltools.tests.test_combine_yamls", false]], "pkg (fre.app.generate_time_averages.timeaverager.timeaverager attribute)": [[13, "fre.app.generate_time_averages.timeAverager.timeAverager.pkg", false]], "post_write() (in module fre.app.mask_atmos_plevel)": [[14, "fre.app.mask_atmos_plevel.post_write", false]], "preprocess() (in module fre.app.mask_atmos_plevel)": [[14, "fre.app.mask_atmos_plevel.preprocess", false]], "pressure_coordinate() (in module fre.app.mask_atmos_plevel)": [[14, "fre.app.mask_atmos_plevel.pressure_coordinate", false]], "quote_rose_values() (in module fre.pp.configure_script_yaml)": [[46, "fre.pp.configure_script_yaml.quote_rose_values", false]], "regrid_xy() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.regrid_xy", false]], "rewrite_netcdf_file_var() (in module fre.cmor.cmor_mixer)": [[25, "fre.cmor.cmor_mixer.rewrite_netcdf_file_var", false]], "rose_init() (in module fre.pp.configure_script_yaml)": [[46, "fre.pp.configure_script_yaml.rose_init", false]], "run_avgtype_pkg_calculations() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.run_avgtype_pkg_calculations", false]], "safe_rose_config_get() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.safe_rose_config_get", false]], "set_netcdf_encoding() (in module fre.app.mask_atmos_plevel)": [[14, "fre.app.mask_atmos_plevel.set_netcdf_encoding", false]], "set_rose_apps() (in module fre.pp.configure_script_yaml)": [[46, "fre.pp.configure_script_yaml.set_rose_apps", false]], "set_rose_suite() (in module fre.pp.configure_script_yaml)": [[46, "fre.pp.configure_script_yaml.set_rose_suite", false]], "stddev_type (fre.app.generate_time_averages.timeaverager.timeaverager attribute)": [[13, "fre.app.generate_time_averages.timeAverager.timeAverager.stddev_type", false]], "test_analysisyaml_exists() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_analysisyaml_exists", false]], "test_bm_makefile_creation() (in module fre.make.tests.test_create_makefile)": [[42, "fre.make.tests.test_create_makefile.test_bm_makefile_creation", false]], "test_boolean() (in module fre.pp.tests.test_rose_quoting)": [[53, "fre.pp.tests.test_rose_quoting.test_boolean", false]], "test_cdo_time_avgs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_cdo_time_avgs", false]], "test_cdo_time_unwgt_avgs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_cdo_time_unwgt_avgs", false]], "test_cdo_time_unwgt_stddevs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_cdo_time_unwgt_stddevs", false]], "test_cli_fre() (in module fre.tests.test_fre_cli)": [[66, "fre.tests.test_fre_cli.test_cli_fre", false]], "test_cli_fre_app() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app", false]], "test_cli_fre_app_gen_time_averages() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app_gen_time_averages", false]], "test_cli_fre_app_gen_time_averages_help() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app_gen_time_averages_help", false]], "test_cli_fre_app_gen_time_averages_opt_dne() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app_gen_time_averages_opt_dne", false]], "test_cli_fre_app_help() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app_help", false]], "test_cli_fre_app_opt_dne() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app_opt_dne", false]], "test_cli_fre_app_regrid() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app_regrid", false]], "test_cli_fre_app_regrid_help() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app_regrid_help", false]], "test_cli_fre_app_regrid_opt_dne() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app_regrid_opt_dne", false]], "test_cli_fre_app_regrid_test_case_1() (in module fre.tests.test_fre_app_cli)": [[63, "fre.tests.test_fre_app_cli.test_cli_fre_app_regrid_test_case_1", false]], "test_cli_fre_catalog() (in module fre.tests.test_fre_catalog_cli)": [[64, "fre.tests.test_fre_catalog_cli.test_cli_fre_catalog", false]], "test_cli_fre_catalog_builder() (in module fre.tests.test_fre_catalog_cli)": [[64, "fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_builder", false]], "test_cli_fre_catalog_builder_help() (in module fre.tests.test_fre_catalog_cli)": [[64, "fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_builder_help", false]], "test_cli_fre_catalog_help() (in module fre.tests.test_fre_catalog_cli)": [[64, "fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_help", false]], "test_cli_fre_catalog_merge() (in module fre.tests.test_fre_catalog_cli)": [[64, "fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_merge", false]], "test_cli_fre_catalog_merge_help() (in module fre.tests.test_fre_catalog_cli)": [[64, "fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_merge_help", false]], "test_cli_fre_catalog_opt_dne() (in module fre.tests.test_fre_catalog_cli)": [[64, "fre.tests.test_fre_catalog_cli.test_cli_fre_catalog_opt_dne", false]], "test_cli_fre_check() (in module fre.tests.test_fre_check_cli)": [[65, "fre.tests.test_fre_check_cli.test_cli_fre_check", false]], "test_cli_fre_check_help() (in module fre.tests.test_fre_check_cli)": [[65, "fre.tests.test_fre_check_cli.test_cli_fre_check_help", false]], "test_cli_fre_check_opt_dne() (in module fre.tests.test_fre_check_cli)": [[65, "fre.tests.test_fre_check_cli.test_cli_fre_check_opt_dne", false]], "test_cli_fre_cmor() (in module fre.tests.test_fre_cmor_cli)": [[67, "fre.tests.test_fre_cmor_cli.test_cli_fre_cmor", false]], "test_cli_fre_cmor_help() (in module fre.tests.test_fre_cmor_cli)": [[67, "fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_help", false]], "test_cli_fre_cmor_opt_dne() (in module fre.tests.test_fre_cmor_cli)": [[67, "fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_opt_dne", false]], "test_cli_fre_cmor_run() (in module fre.tests.test_fre_cmor_cli)": [[67, "fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run", false]], "test_cli_fre_cmor_run_case1() (in module fre.tests.test_fre_cmor_cli)": [[67, "fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run_case1", false]], "test_cli_fre_cmor_run_case2() (in module fre.tests.test_fre_cmor_cli)": [[67, "fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run_case2", false]], "test_cli_fre_cmor_run_help() (in module fre.tests.test_fre_cmor_cli)": [[67, "fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run_help", false]], "test_cli_fre_cmor_run_opt_dne() (in module fre.tests.test_fre_cmor_cli)": [[67, "fre.tests.test_fre_cmor_cli.test_cli_fre_cmor_run_opt_dne", false]], "test_cli_fre_help() (in module fre.tests.test_fre_cli)": [[66, "fre.tests.test_fre_cli.test_cli_fre_help", false]], "test_cli_fre_list() (in module fre.tests.test_fre_list_cli)": [[68, "fre.tests.test_fre_list_cli.test_cli_fre_list", false]], "test_cli_fre_list_help() (in module fre.tests.test_fre_list_cli)": [[68, "fre.tests.test_fre_list_cli.test_cli_fre_list_help", false]], "test_cli_fre_list_opt_dne() (in module fre.tests.test_fre_list_cli)": [[68, "fre.tests.test_fre_list_cli.test_cli_fre_list_opt_dne", false]], "test_cli_fre_make() (in module fre.tests.test_fre_make_cli)": [[69, "fre.tests.test_fre_make_cli.test_cli_fre_make", false]], "test_cli_fre_make_create_checkout_baremetal() (in module fre.tests.test_fre_make_cli)": [[69, "fre.tests.test_fre_make_cli.test_cli_fre_make_create_checkout_baremetal", false]], "test_cli_fre_make_create_checkout_container() (in module fre.tests.test_fre_make_cli)": [[69, "fre.tests.test_fre_make_cli.test_cli_fre_make_create_checkout_container", false]], "test_cli_fre_make_help() (in module fre.tests.test_fre_make_cli)": [[69, "fre.tests.test_fre_make_cli.test_cli_fre_make_help", false]], "test_cli_fre_make_opt_dne() (in module fre.tests.test_fre_make_cli)": [[69, "fre.tests.test_fre_make_cli.test_cli_fre_make_opt_dne", false]], "test_cli_fre_option_dne() (in module fre.tests.test_fre_cli)": [[66, "fre.tests.test_fre_cli.test_cli_fre_option_dne", false]], "test_cli_fre_pp() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp", false]], "test_cli_fre_pp_checkout() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_checkout", false]], "test_cli_fre_pp_checkout_help() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_checkout_help", false]], "test_cli_fre_pp_checkout_opt_dne() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_checkout_opt_dne", false]], "test_cli_fre_pp_configure_xml() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_xml", false]], "test_cli_fre_pp_configure_xml_help() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_xml_help", false]], "test_cli_fre_pp_configure_xml_opt_dne() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_xml_opt_dne", false]], "test_cli_fre_pp_configure_yaml() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_yaml", false]], "test_cli_fre_pp_configure_yaml_help() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_yaml_help", false]], "test_cli_fre_pp_configure_yaml_opt_dne() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_configure_yaml_opt_dne", false]], "test_cli_fre_pp_help() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_help", false]], "test_cli_fre_pp_install() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_install", false]], "test_cli_fre_pp_install_help() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_install_help", false]], "test_cli_fre_pp_install_opt_dne() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_install_opt_dne", false]], "test_cli_fre_pp_opt_dne() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_opt_dne", false]], "test_cli_fre_pp_run() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_run", false]], "test_cli_fre_pp_run_help() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_run_help", false]], "test_cli_fre_pp_run_opt_dne() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_run_opt_dne", false]], "test_cli_fre_pp_status() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_status", false]], "test_cli_fre_pp_status_help() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_status_help", false]], "test_cli_fre_pp_status_opt_dne() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_status_opt_dne", false]], "test_cli_fre_pp_validate() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_validate", false]], "test_cli_fre_pp_validate_help() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_validate_help", false]], "test_cli_fre_pp_validate_opt_dne() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_validate_opt_dne", false]], "test_cli_fre_pp_wrapper() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_wrapper", false]], "test_cli_fre_pp_wrapper_help() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_wrapper_help", false]], "test_cli_fre_pp_wrapper_opt_dne() (in module fre.tests.test_fre_pp_cli)": [[70, "fre.tests.test_fre_pp_cli.test_cli_fre_pp_wrapper_opt_dne", false]], "test_cli_fre_run() (in module fre.tests.test_fre_run_cli)": [[71, "fre.tests.test_fre_run_cli.test_cli_fre_run", false]], "test_cli_fre_run_help() (in module fre.tests.test_fre_run_cli)": [[71, "fre.tests.test_fre_run_cli.test_cli_fre_run_help", false]], "test_cli_fre_run_opt_dne() (in module fre.tests.test_fre_run_cli)": [[71, "fre.tests.test_fre_run_cli.test_cli_fre_run_opt_dne", false]], "test_cli_fre_test() (in module fre.tests.test_fre_test_cli)": [[72, "fre.tests.test_fre_test_cli.test_cli_fre_test", false]], "test_cli_fre_test_help() (in module fre.tests.test_fre_test_cli)": [[72, "fre.tests.test_fre_test_cli.test_cli_fre_test_help", false]], "test_cli_fre_test_opt_dne() (in module fre.tests.test_fre_test_cli)": [[72, "fre.tests.test_fre_test_cli.test_cli_fre_test_opt_dne", false]], "test_cli_fre_yamltools() (in module fre.tests.test_fre_yamltools_cli)": [[73, "fre.tests.test_fre_yamltools_cli.test_cli_fre_yamltools", false]], "test_cli_fre_yamltools_help() (in module fre.tests.test_fre_yamltools_cli)": [[73, "fre.tests.test_fre_yamltools_cli.test_cli_fre_yamltools_help", false]], "test_cli_fre_yamltools_opt_dne() (in module fre.tests.test_fre_yamltools_cli)": [[73, "fre.tests.test_fre_yamltools_cli.test_cli_fre_yamltools_opt_dne", false]], "test_combined_compileyaml_combinefail() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_combined_compileyaml_combinefail", false]], "test_combined_compileyaml_validatefail() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_combined_compileyaml_validatefail", false]], "test_combined_compileyaml_validation() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_combined_compileyaml_validation", false]], "test_combined_ppyaml_validation() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_combined_ppyaml_validation", false]], "test_combinedyaml_exists() (in module fre.pp.tests.test_configure_script_yaml)": [[52, "fre.pp.tests.test_configure_script_yaml.test_combinedyaml_exists", false]], "test_compare_cdo_to_fre_nctools() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_compare_cdo_to_fre_nctools", false]], "test_compare_fre_python_tools_to_cdo() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_compare_fre_python_tools_to_cdo", false]], "test_compare_fre_python_tools_to_fre_nctools() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_compare_fre_python_tools_to_fre_nctools", false]], "test_compare_unwgt_fre_python_tools_to_unwgt_cdo() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_compare_unwgt_fre_python_tools_to_unwgt_cdo", false]], "test_compileyaml_exists() (in module fre.make.tests.test_create_makefile)": [[42, "fre.make.tests.test_create_makefile.test_compileyaml_exists", false]], "test_compileyaml_exists() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_compileyaml_exists", false]], "test_configure_script() (in module fre.pp.tests.test_configure_script_yaml)": [[52, "fre.pp.tests.test_configure_script_yaml.test_configure_script", false]], "test_container_makefile_creation() (in module fre.make.tests.test_create_makefile)": [[42, "fre.make.tests.test_create_makefile.test_container_makefile_creation", false]], "test_expyaml_exists() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_expyaml_exists", false]], "test_fre_catalog_import() (in module fre.catalog.tests.test_fre_catalog)": [[20, "fre.catalog.tests.test_fre_catalog.test_fre_catalog_import", false]], "test_fre_cmor_run_subtool_case1() (in module fre.cmor.tests.test_cmor_run_subtool)": [[28, "fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case1", false]], "test_fre_cmor_run_subtool_case1_output_compare_data() (in module fre.cmor.tests.test_cmor_run_subtool)": [[28, "fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case1_output_compare_data", false]], "test_fre_cmor_run_subtool_case1_output_compare_metadata() (in module fre.cmor.tests.test_cmor_run_subtool)": [[28, "fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case1_output_compare_metadata", false]], "test_fre_cmor_run_subtool_case2() (in module fre.cmor.tests.test_cmor_run_subtool)": [[28, "fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case2", false]], "test_fre_cmor_run_subtool_case2_output_compare_data() (in module fre.cmor.tests.test_cmor_run_subtool)": [[28, "fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case2_output_compare_data", false]], "test_fre_cmor_run_subtool_case2_output_compare_metadata() (in module fre.cmor.tests.test_cmor_run_subtool)": [[28, "fre.cmor.tests.test_cmor_run_subtool.test_fre_cmor_run_subtool_case2_output_compare_metadata", false]], "test_fre_python_tools_time_avgs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_fre_python_tools_time_avgs", false]], "test_fre_python_tools_time_avgs_stddevs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_fre_python_tools_time_avgs_stddevs", false]], "test_fre_python_tools_time_unwgt_avgs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_fre_python_tools_time_unwgt_avgs", false]], "test_fre_python_tools_time_unwgt_avgs_stddevs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_fre_python_tools_time_unwgt_avgs_stddevs", false]], "test_import() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.test_import", false]], "test_merged_compile_yamls() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_merged_compile_yamls", false]], "test_merged_pp_yamls() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_merged_pp_yamls", false]], "test_modelyaml_exists() (in module fre.make.tests.test_create_makefile)": [[42, "fre.make.tests.test_create_makefile.test_modelyaml_exists", false]], "test_modelyaml_exists() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_modelyaml_exists", false]], "test_monthly_cdo_time_unwgt_avgs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_monthly_cdo_time_unwgt_avgs", false]], "test_monthly_cdo_time_unwgt_stddevs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_monthly_cdo_time_unwgt_stddevs", false]], "test_platformyaml_exists() (in module fre.make.tests.test_create_makefile)": [[42, "fre.make.tests.test_create_makefile.test_platformyaml_exists", false]], "test_platformyaml_exists() (in module fre.yamltools.tests.test_combine_yamls)": [[79, "fre.yamltools.tests.test_combine_yamls.test_platformyaml_exists", false]], "test_seasonal_cdo_time_unwgt_avgs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_seasonal_cdo_time_unwgt_avgs", false]], "test_seasonal_cdo_time_unwgt_stddevs() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_seasonal_cdo_time_unwgt_stddevs", false]], "test_setup_cmor_cmip_table_repo() (in module fre.cmor.tests.test_cmor_run_subtool)": [[28, "fre.cmor.tests.test_cmor_run_subtool.test_setup_cmor_cmip_table_repo", false]], "test_setup_fre_cmor_run_subtool() (in module fre.cmor.tests.test_cmor_run_subtool)": [[28, "fre.cmor.tests.test_cmor_run_subtool.test_setup_fre_cmor_run_subtool", false]], "test_setup_fre_cmor_run_subtool_case2() (in module fre.cmor.tests.test_cmor_run_subtool)": [[28, "fre.cmor.tests.test_cmor_run_subtool.test_setup_fre_cmor_run_subtool_case2", false]], "test_string() (in module fre.pp.tests.test_rose_quoting)": [[53, "fre.pp.tests.test_rose_quoting.test_string", false]], "test_time_avg_file_dir_exists() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_time_avg_file_dir_exists", false]], "test_time_avg_input_file_exists() (in module fre.app.generate_time_averages.tests.test_generate_time_averages)": [[12, "fre.app.generate_time_averages.tests.test_generate_time_averages.test_time_avg_input_file_exists", false]], "timeaverager (class in fre.app.generate_time_averages.timeaverager)": [[13, "fre.app.generate_time_averages.timeAverager.timeAverager", false]], "truncate_date() (in module fre.app.regrid_xy.regrid_xy)": [[16, "fre.app.regrid_xy.regrid_xy.truncate_date", false]], "unwgt (fre.app.generate_time_averages.timeaverager.timeaverager attribute)": [[13, "fre.app.generate_time_averages.timeAverager.timeAverager.unwgt", false]], "validate_yaml() (in module fre.pp.configure_script_yaml)": [[46, "fre.pp.configure_script_yaml.validate_yaml", false]], "var (fre.app.generate_time_averages.timeaverager.timeaverager attribute)": [[13, "fre.app.generate_time_averages.timeAverager.timeAverager.var", false]], "var_has_time_units() (fre.app.generate_time_averages.timeaverager.timeaverager method)": [[13, "fre.app.generate_time_averages.timeAverager.timeAverager.var_has_time_units", false]], "write_dataset() (in module fre.app.mask_atmos_plevel)": [[14, "fre.app.mask_atmos_plevel.write_dataset", false]], "yaml_load() (in module fre.pp.configure_script_yaml)": [[46, "fre.pp.configure_script_yaml.yaml_load", false]], "yaml_load() (in module fre.yamltools.combine_yamls)": [[75, "fre.yamltools.combine_yamls.yaml_load", false]], "yamlinfo() (in module fre.pp.configure_script_yaml)": [[46, "fre.pp.configure_script_yaml.yamlInfo", false]]}, "objects": {"": [[3, 0, 0, "-", "fre"]], "fre": [[4, 0, 0, "-", "app"], [17, 0, 0, "-", "catalog"], [21, 0, 0, "-", "check"], [24, 0, 0, "-", "cmor"], [29, 0, 0, "-", "fre"], [30, 0, 0, "-", "lazy_group"], [31, 0, 0, "-", "list"], [34, 0, 0, "-", "make"], [43, 0, 0, "-", "pp"], [56, 0, 0, "-", "run"], [59, 0, 0, "-", "test"], [62, 0, 0, "-", "tests"], [74, 0, 0, "-", "yamltools"]], "fre.app": [[5, 0, 0, "-", "freapp"], [6, 0, 0, "-", "generate_time_averages"], [14, 0, 0, "-", "mask_atmos_plevel"], [15, 0, 0, "-", "regrid_xy"]], "fre.app.generate_time_averages": [[7, 0, 0, "-", "cdoTimeAverager"], [8, 0, 0, "-", "frenctoolsTimeAverager"], [9, 0, 0, "-", "frepytoolsTimeAverager"], [10, 0, 0, "-", "generate_time_averages"], [11, 0, 0, "-", "tests"], [13, 0, 0, "-", "timeAverager"]], "fre.app.generate_time_averages.cdoTimeAverager": [[7, 1, 1, "", "cdoTimeAverager"]], "fre.app.generate_time_averages.cdoTimeAverager.cdoTimeAverager": [[7, 2, 1, "", "generate_timavg"]], "fre.app.generate_time_averages.frenctoolsTimeAverager": [[8, 1, 1, "", "frenctoolsTimeAverager"]], "fre.app.generate_time_averages.frenctoolsTimeAverager.frenctoolsTimeAverager": [[8, 2, 1, "", "generate_timavg"]], "fre.app.generate_time_averages.frepytoolsTimeAverager": [[9, 1, 1, "", "frepytoolsTimeAverager"]], "fre.app.generate_time_averages.frepytoolsTimeAverager.frepytoolsTimeAverager": [[9, 2, 1, "", "generate_timavg"]], "fre.app.generate_time_averages.generate_time_averages": [[10, 3, 1, "", "generate_time_average"]], "fre.app.generate_time_averages.tests": [[12, 0, 0, "-", "test_generate_time_averages"]], "fre.app.generate_time_averages.tests.test_generate_time_averages": [[12, 3, 1, "", "run_avgtype_pkg_calculations"], [12, 3, 1, "", "test_cdo_time_avgs"], [12, 3, 1, "", "test_cdo_time_unwgt_avgs"], [12, 3, 1, "", "test_cdo_time_unwgt_stddevs"], [12, 3, 1, "", "test_compare_cdo_to_fre_nctools"], [12, 3, 1, "", "test_compare_fre_python_tools_to_cdo"], [12, 3, 1, "", "test_compare_fre_python_tools_to_fre_nctools"], [12, 3, 1, "", "test_compare_unwgt_fre_python_tools_to_unwgt_cdo"], [12, 3, 1, "", "test_fre_python_tools_time_avgs"], [12, 3, 1, "", "test_fre_python_tools_time_avgs_stddevs"], [12, 3, 1, "", "test_fre_python_tools_time_unwgt_avgs"], [12, 3, 1, "", "test_fre_python_tools_time_unwgt_avgs_stddevs"], [12, 3, 1, "", "test_monthly_cdo_time_unwgt_avgs"], [12, 3, 1, "", "test_monthly_cdo_time_unwgt_stddevs"], [12, 3, 1, "", "test_seasonal_cdo_time_unwgt_avgs"], [12, 3, 1, "", "test_seasonal_cdo_time_unwgt_stddevs"], [12, 3, 1, "", "test_time_avg_file_dir_exists"], [12, 3, 1, "", "test_time_avg_input_file_exists"]], "fre.app.generate_time_averages.timeAverager": [[13, 1, 1, "", "timeAverager"]], "fre.app.generate_time_averages.timeAverager.timeAverager": [[13, 4, 1, "", "avg_type"], [13, 2, 1, "", "generate_timavg"], [13, 4, 1, "", "pkg"], [13, 4, 1, "", "stddev_type"], [13, 4, 1, "", "unwgt"], [13, 4, 1, "", "var"], [13, 2, 1, "", "var_has_time_units"]], "fre.app.mask_atmos_plevel": [[14, 3, 1, "", "mask_field_above_surface_pressure"], [14, 3, 1, "", "post_write"], [14, 3, 1, "", "preprocess"], [14, 3, 1, "", "pressure_coordinate"], [14, 3, 1, "", "set_netcdf_encoding"], [14, 3, 1, "", "write_dataset"]], "fre.app.regrid_xy": [[16, 0, 0, "-", "regrid_xy"]], "fre.app.regrid_xy.regrid_xy": [[16, 3, 1, "", "check_interp_method"], [16, 3, 1, "", "check_per_component_settings"], [16, 3, 1, "", "freq_to_date_format"], [16, 3, 1, "", "get_mosaic_file_name"], [16, 3, 1, "", "get_mosaic_grid_file_name"], [16, 3, 1, "", "main"], [16, 3, 1, "", "make_component_list"], [16, 3, 1, "", "make_regrid_var_list"], [16, 3, 1, "", "regrid_xy"], [16, 3, 1, "", "safe_rose_config_get"], [16, 3, 1, "", "test_import"], [16, 3, 1, "", "truncate_date"]], "fre.catalog": [[18, 0, 0, "-", "frecatalog"], [19, 0, 0, "-", "tests"]], "fre.catalog.tests": [[20, 0, 0, "-", "test_fre_catalog"]], "fre.catalog.tests.test_fre_catalog": [[20, 3, 1, "", "test_fre_catalog_import"]], "fre.check": [[22, 0, 0, "-", "frecheck"], [23, 0, 0, "-", "frecheckexample"]], "fre.cmor": [[25, 0, 0, "-", "cmor_mixer"], [26, 0, 0, "-", "frecmor"], [27, 0, 0, "-", "tests"]], "fre.cmor.cmor_mixer": [[25, 3, 1, "", "check_dataset_for_ocean_grid"], [25, 3, 1, "", "cmor_run_subtool"], [25, 3, 1, "", "cmorize_target_var_files"], [25, 3, 1, "", "copy_nc"], [25, 3, 1, "", "create_tmp_dir"], [25, 3, 1, "", "get_iso_datetimes"], [25, 3, 1, "", "get_var_filenames"], [25, 3, 1, "", "get_vertical_dimension"], [25, 3, 1, "", "rewrite_netcdf_file_var"]], "fre.cmor.tests": [[28, 0, 0, "-", "test_cmor_run_subtool"]], "fre.cmor.tests.test_cmor_run_subtool": [[28, 3, 1, "", "test_fre_cmor_run_subtool_case1"], [28, 3, 1, "", "test_fre_cmor_run_subtool_case1_output_compare_data"], [28, 3, 1, "", "test_fre_cmor_run_subtool_case1_output_compare_metadata"], [28, 3, 1, "", "test_fre_cmor_run_subtool_case2"], [28, 3, 1, "", "test_fre_cmor_run_subtool_case2_output_compare_data"], [28, 3, 1, "", "test_fre_cmor_run_subtool_case2_output_compare_metadata"], [28, 3, 1, "", "test_setup_cmor_cmip_table_repo"], [28, 3, 1, "", "test_setup_fre_cmor_run_subtool"], [28, 3, 1, "", "test_setup_fre_cmor_run_subtool_case2"]], "fre.lazy_group": [[30, 1, 1, "", "LazyGroup"]], "fre.lazy_group.LazyGroup": [[30, 2, 1, "", "get_command"], [30, 2, 1, "", "list_commands"]], "fre.list": [[32, 0, 0, "-", "frelist"], [33, 0, 0, "-", "frelistexample"]], "fre.make": [[35, 0, 0, "-", "createCheckout"], [36, 0, 0, "-", "createCompile"], [37, 0, 0, "-", "createDocker"], [38, 0, 0, "-", "createMakefile"], [39, 0, 0, "-", "fremake"], [40, 0, 0, "-", "runFremake"], [41, 0, 0, "-", "tests"]], "fre.make.createCheckout": [[35, 3, 1, "", "checkout_create"]], "fre.make.createCompile": [[36, 3, 1, "", "compile_create"]], "fre.make.createDocker": [[37, 3, 1, "", "dockerfile_create"]], "fre.make.createMakefile": [[38, 3, 1, "", "makefile_create"]], "fre.make.runFremake": [[40, 3, 1, "", "fremake_run"]], "fre.make.tests": [[42, 0, 0, "-", "test_create_makefile"]], "fre.make.tests.test_create_makefile": [[42, 3, 1, "", "test_bm_makefile_creation"], [42, 3, 1, "", "test_compileyaml_exists"], [42, 3, 1, "", "test_container_makefile_creation"], [42, 3, 1, "", "test_modelyaml_exists"], [42, 3, 1, "", "test_platformyaml_exists"]], "fre.pp": [[44, 0, 0, "-", "checkoutScript"], [45, 0, 0, "-", "configure_script_xml"], [46, 0, 0, "-", "configure_script_yaml"], [47, 0, 0, "-", "frepp"], [48, 0, 0, "-", "install"], [49, 0, 0, "-", "run"], [50, 0, 0, "-", "status"], [51, 0, 0, "-", "tests"], [54, 0, 0, "-", "validate"], [55, 0, 0, "-", "wrapper"]], "fre.pp.configure_script_xml": [[45, 3, 1, "", "chunk_from_legacy"], [45, 3, 1, "", "duration_to_seconds"], [45, 3, 1, "", "frelist_xpath"], [45, 3, 1, "", "freq_from_legacy"], [45, 3, 1, "", "main"]], "fre.pp.configure_script_yaml": [[46, 3, 1, "", "quote_rose_values"], [46, 3, 1, "", "rose_init"], [46, 3, 1, "", "set_rose_apps"], [46, 3, 1, "", "set_rose_suite"], [46, 3, 1, "", "validate_yaml"], [46, 3, 1, "", "yamlInfo"], [46, 3, 1, "", "yaml_load"]], "fre.pp.tests": [[52, 0, 0, "-", "test_configure_script_yaml"], [53, 0, 0, "-", "test_rose_quoting"]], "fre.pp.tests.test_configure_script_yaml": [[52, 3, 1, "", "test_combinedyaml_exists"], [52, 3, 1, "", "test_configure_script"]], "fre.pp.tests.test_rose_quoting": [[53, 3, 1, "", "test_boolean"], [53, 3, 1, "", "test_string"]], "fre.run": [[57, 0, 0, "-", "frerun"], [58, 0, 0, "-", "frerunexample"]], "fre.test": [[60, 0, 0, "-", "fretest"], [61, 0, 0, "-", "fretestexample"]], "fre.tests": [[63, 0, 0, "-", "test_fre_app_cli"], [64, 0, 0, "-", "test_fre_catalog_cli"], [65, 0, 0, "-", "test_fre_check_cli"], [66, 0, 0, "-", "test_fre_cli"], [67, 0, 0, "-", "test_fre_cmor_cli"], [68, 0, 0, "-", "test_fre_list_cli"], [69, 0, 0, "-", "test_fre_make_cli"], [70, 0, 0, "-", "test_fre_pp_cli"], [71, 0, 0, "-", "test_fre_run_cli"], [72, 0, 0, "-", "test_fre_test_cli"], [73, 0, 0, "-", "test_fre_yamltools_cli"]], "fre.tests.test_fre_app_cli": [[63, 3, 1, "", "test_cli_fre_app"], [63, 3, 1, "", "test_cli_fre_app_gen_time_averages"], [63, 3, 1, "", "test_cli_fre_app_gen_time_averages_help"], [63, 3, 1, "", "test_cli_fre_app_gen_time_averages_opt_dne"], [63, 3, 1, "", "test_cli_fre_app_help"], [63, 3, 1, "", "test_cli_fre_app_opt_dne"], [63, 3, 1, "", "test_cli_fre_app_regrid"], [63, 3, 1, "", "test_cli_fre_app_regrid_help"], [63, 3, 1, "", "test_cli_fre_app_regrid_opt_dne"], [63, 3, 1, "", "test_cli_fre_app_regrid_test_case_1"]], "fre.tests.test_fre_catalog_cli": [[64, 3, 1, "", "test_cli_fre_catalog"], [64, 3, 1, "", "test_cli_fre_catalog_builder"], [64, 3, 1, "", "test_cli_fre_catalog_builder_help"], [64, 3, 1, "", "test_cli_fre_catalog_help"], [64, 3, 1, "", "test_cli_fre_catalog_merge"], [64, 3, 1, "", "test_cli_fre_catalog_merge_help"], [64, 3, 1, "", "test_cli_fre_catalog_opt_dne"]], "fre.tests.test_fre_check_cli": [[65, 3, 1, "", "test_cli_fre_check"], [65, 3, 1, "", "test_cli_fre_check_help"], [65, 3, 1, "", "test_cli_fre_check_opt_dne"]], "fre.tests.test_fre_cli": [[66, 3, 1, "", "test_cli_fre"], [66, 3, 1, "", "test_cli_fre_help"], [66, 3, 1, "", "test_cli_fre_option_dne"]], "fre.tests.test_fre_cmor_cli": [[67, 3, 1, "", "test_cli_fre_cmor"], [67, 3, 1, "", "test_cli_fre_cmor_help"], [67, 3, 1, "", "test_cli_fre_cmor_opt_dne"], [67, 3, 1, "", "test_cli_fre_cmor_run"], [67, 3, 1, "", "test_cli_fre_cmor_run_case1"], [67, 3, 1, "", "test_cli_fre_cmor_run_case2"], [67, 3, 1, "", "test_cli_fre_cmor_run_help"], [67, 3, 1, "", "test_cli_fre_cmor_run_opt_dne"]], "fre.tests.test_fre_list_cli": [[68, 3, 1, "", "test_cli_fre_list"], [68, 3, 1, "", "test_cli_fre_list_help"], [68, 3, 1, "", "test_cli_fre_list_opt_dne"]], "fre.tests.test_fre_make_cli": [[69, 3, 1, "", "test_cli_fre_make"], [69, 3, 1, "", "test_cli_fre_make_create_checkout_baremetal"], [69, 3, 1, "", "test_cli_fre_make_create_checkout_container"], [69, 3, 1, "", "test_cli_fre_make_help"], [69, 3, 1, "", "test_cli_fre_make_opt_dne"]], "fre.tests.test_fre_pp_cli": [[70, 3, 1, "", "test_cli_fre_pp"], [70, 3, 1, "", "test_cli_fre_pp_checkout"], [70, 3, 1, "", "test_cli_fre_pp_checkout_help"], [70, 3, 1, "", "test_cli_fre_pp_checkout_opt_dne"], [70, 3, 1, "", "test_cli_fre_pp_configure_xml"], [70, 3, 1, "", "test_cli_fre_pp_configure_xml_help"], [70, 3, 1, "", "test_cli_fre_pp_configure_xml_opt_dne"], [70, 3, 1, "", "test_cli_fre_pp_configure_yaml"], [70, 3, 1, "", "test_cli_fre_pp_configure_yaml_help"], [70, 3, 1, "", "test_cli_fre_pp_configure_yaml_opt_dne"], [70, 3, 1, "", "test_cli_fre_pp_help"], [70, 3, 1, "", "test_cli_fre_pp_install"], [70, 3, 1, "", "test_cli_fre_pp_install_help"], [70, 3, 1, "", "test_cli_fre_pp_install_opt_dne"], [70, 3, 1, "", "test_cli_fre_pp_opt_dne"], [70, 3, 1, "", "test_cli_fre_pp_run"], [70, 3, 1, "", "test_cli_fre_pp_run_help"], [70, 3, 1, "", "test_cli_fre_pp_run_opt_dne"], [70, 3, 1, "", "test_cli_fre_pp_status"], [70, 3, 1, "", "test_cli_fre_pp_status_help"], [70, 3, 1, "", "test_cli_fre_pp_status_opt_dne"], [70, 3, 1, "", "test_cli_fre_pp_validate"], [70, 3, 1, "", "test_cli_fre_pp_validate_help"], [70, 3, 1, "", "test_cli_fre_pp_validate_opt_dne"], [70, 3, 1, "", "test_cli_fre_pp_wrapper"], [70, 3, 1, "", "test_cli_fre_pp_wrapper_help"], [70, 3, 1, "", "test_cli_fre_pp_wrapper_opt_dne"]], "fre.tests.test_fre_run_cli": [[71, 3, 1, "", "test_cli_fre_run"], [71, 3, 1, "", "test_cli_fre_run_help"], [71, 3, 1, "", "test_cli_fre_run_opt_dne"]], "fre.tests.test_fre_test_cli": [[72, 3, 1, "", "test_cli_fre_test"], [72, 3, 1, "", "test_cli_fre_test_help"], [72, 3, 1, "", "test_cli_fre_test_opt_dne"]], "fre.tests.test_fre_yamltools_cli": [[73, 3, 1, "", "test_cli_fre_yamltools"], [73, 3, 1, "", "test_cli_fre_yamltools_help"], [73, 3, 1, "", "test_cli_fre_yamltools_opt_dne"]], "fre.yamltools": [[75, 0, 0, "-", "combine_yamls"], [76, 0, 0, "-", "freyamltools"], [77, 0, 0, "-", "freyamltoolsexample"], [78, 0, 0, "-", "tests"]], "fre.yamltools.combine_yamls": [[75, 3, 1, "", "combined_compile_existcheck"], [75, 3, 1, "", "consolidate_yamls"], [75, 3, 1, "", "experiment_check"], [75, 3, 1, "", "get_combined_compileyaml"], [75, 3, 1, "", "get_combined_ppyaml"], [75, 3, 1, "", "get_compile_paths"], [75, 1, 1, "", "init_compile_yaml"], [75, 1, 1, "", "init_pp_yaml"], [75, 3, 1, "", "join_constructor"], [75, 3, 1, "", "yaml_load"]], "fre.yamltools.combine_yamls.init_compile_yaml": [[75, 2, 1, "", "clean_yaml"], [75, 2, 1, "", "combine_compile"], [75, 2, 1, "", "combine_model"], [75, 2, 1, "", "combine_platforms"]], "fre.yamltools.combine_yamls.init_pp_yaml": [[75, 2, 1, "", "clean_yaml"], [75, 2, 1, "", "combine_analysis"], [75, 2, 1, "", "combine_experiment"], [75, 2, 1, "", "combine_model"]], "fre.yamltools.tests": [[79, 0, 0, "-", "test_combine_yamls"]], "fre.yamltools.tests.test_combine_yamls": [[79, 3, 1, "", "test_analysisyaml_exists"], [79, 3, 1, "", "test_combined_compileyaml_combinefail"], [79, 3, 1, "", "test_combined_compileyaml_validatefail"], [79, 3, 1, "", "test_combined_compileyaml_validation"], [79, 3, 1, "", "test_combined_ppyaml_validation"], [79, 3, 1, "", "test_compileyaml_exists"], [79, 3, 1, "", "test_expyaml_exists"], [79, 3, 1, "", "test_merged_compile_yamls"], [79, 3, 1, "", "test_merged_pp_yamls"], [79, 3, 1, "", "test_modelyaml_exists"], [79, 3, 1, "", "test_platformyaml_exists"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "function", "Python function"], "4": ["py", "attribute", "Python attribute"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:function", "4": "py:attribute"}, "terms": {"": [2, 8, 12, 13, 23, 25, 28, 33, 40, 45, 58, 61, 77, 83], "01": 82, "1": 30, "2023": [29, 40, 69], "2024": [29, 82], "3": [1, 45], "31": 2, "8": 30, "8601": 16, "A": 83, "At": 2, "Be": 2, "By": 16, "For": 2, "If": [1, 75, 80, 83], "In": [1, 2], "It": [14, 16], "On": [1, 2], "TO": 52, "The": [1, 2, 25, 45, 46], "To": 1, "_": 82, "__init__": 2, "_unmsk": 14, "abl": 1, "about": [1, 83], "abov": [2, 10], "abstract": [7, 8, 9, 13], "accept": [14, 25], "access": 2, "action": 1, "activ": 82, "ad": 80, "add": [2, 14], "addit": [2, 25], "after": [2, 25], "aka": 8, "albedo": 14, "alert": 83, "all": [2, 25, 83], "allow": [29, 75, 83], "almost": 25, "along": 2, "alreadi": 75, "also": [25, 28, 46, 83], "alwai": 25, "am5": [69, 83], "am5f3b1r0": 83, "amongst": 25, "an": [2, 12, 25, 75, 82, 83], "an_nc_var": 13, "anaconda": [2, 82], "analysi": 75, "ani": [1, 2, 14, 83], "anoth": 13, "answer": 12, "anyth": 1, "anywai": 25, "api": 25, "app": [2, 3, 45, 46, 52, 63, 80, 81], "appear": [16, 30], "append": [25, 82], "appli": 83, "approach": [2, 7, 9], "ar": [2, 13, 14, 16, 25, 46, 83], "archiv": 83, "arg": [16, 25, 30, 45], "argpars": 45, "argument": [2, 25, 45, 75, 83], "around": 1, "arrai": 16, "assoc": [1, 25], "associ": 25, "attach": 25, "attribut": [14, 16], "author": [23, 29, 33, 40, 55, 58, 61, 77], "automag": 1, "automat": 2, "avail": 83, "averag": [7, 8, 9, 10, 12, 13, 83], "avg_typ": [6, 7, 8, 9, 10, 12, 13], "avoid": 9, "back": 1, "badboi": 25, "badg": 80, "bare": 42, "base": [7, 8, 9, 13, 25, 30, 75], "bash": 55, "bcc2761": [23, 29, 33, 58, 61, 77], "befor": 25, "begin": [2, 16], "being": 2, "below": 83, "bennett": [23, 29, 33, 40, 58, 61, 77], "better": [2, 52], "bin": 55, "bind": 7, "bit": 1, "blob": 55, "bool": [13, 16], "boolean": 46, "both": 25, "bounds_vari": 14, "branch": [1, 2, 79], "break": [2, 52], "brief": 83, "bronx": [16, 45], "build": [1, 83], "build_conda": 1, "builder": [64, 83], "c96l65_am5f3b1r0_pdclim1850f": 83, "c96l65_am5f4b4r0_amip": 83, "call": [5, 8, 12, 16, 25, 29, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 80], "can": [1, 2, 14, 25, 83], "canopi": 45, "capabl": 16, "capfd": [28, 63], "carolyn": 55, "case": [2, 28, 46, 67], "case1": 28, "case2": 28, "catalog": [3, 64, 80, 81], "cdf": 16, "cdo": [7, 12], "cdotimeaverag": [4, 6], "chang": [1, 2, 23, 29, 33, 40, 58, 61, 77, 83], "channel": [2, 82], "check": [3, 13, 14, 16, 25, 28, 42, 65, 75, 79, 81, 83], "check_dataset_for_ocean_grid": [3, 24, 25], "check_interp_method": [4, 15, 16], "check_per_component_set": [4, 15, 16], "checklist": 80, "checkout": [40, 69, 70, 83], "checkout_cr": [3, 34, 35], "checkoutscript": [3, 43], "chunk": 45, "chunk_from_legaci": [3, 43, 45], "clash": 16, "class": [7, 8, 9, 13, 30, 75], "clean": 75, "clean_yaml": [74, 75], "cli": [2, 8, 29, 82], "click": [1, 2, 25, 29, 30, 40], "clone": [2, 28, 83], "cmd_name": 30, "cmip": 25, "cmip6": 28, "cmor": [3, 63, 67, 80, 81], "cmor_mix": [3, 24], "cmor_run_subtool": [3, 24, 25, 28], "cmoriz": 25, "cmorize_target_var_fil": [3, 24, 25], "code": [2, 40], "com": [2, 30], "comb": 75, "combin": [52, 75, 79, 83], "combine_analysi": [74, 75], "combine_compil": [74, 75], "combine_experi": [74, 75], "combine_model": [74, 75], "combine_platform": [74, 75], "combine_yaml": [3, 74, 79], "combined_compile_existcheck": [3, 74, 75], "command": [2, 30, 45, 83], "comment": 1, "commit": 1, "compar": 12, "comparison": 28, "compil": [40, 42, 75, 79], "compile_cr": [3, 34, 36], "complex": 30, "compliant": 25, "compon": [2, 16, 25, 45], "componenet": 16, "component_list": 16, "comput": 14, "concaten": 75, "conda": [2, 80], "conf": [45, 46], "confgur": 52, "config": [16, 82], "configur": [16, 25, 46, 70, 83], "configure_script_xml": [3, 43], "configure_script_yaml": [3, 43, 52], "conserv": 16, "consid": 2, "consolidate_yaml": [3, 74, 75], "contain": [2, 14, 25, 42], "content": [2, 45, 81], "context": [2, 30], "contribut": 80, "convent": 25, "convers": 45, "convert": 45, "coordin": 14, "copi": [2, 25, 28, 46], "copy_nc": [3, 24, 25], "core": 13, "correct": 2, "correctli": 79, "creat": [1, 2, 16, 25, 40, 42, 46, 52, 69, 75, 80], "create_test_conda_env": 1, "create_tmp_dir": [3, 24, 25], "createcheckout": [3, 34], "createcompil": [3, 34], "createdock": [3, 34], "createmakefil": [3, 34], "creation": 79, "criteria": 2, "crunch": 9, "csh": 8, "csv": 83, "ctx": 30, "current": [2, 83], "cylc": 46, "d": [14, 16, 25], "dana": 40, "dang": 25, "data": [14, 25, 28], "dataarrai": 14, "dataset": [14, 25], "date": [16, 40], "datetim": 25, "deactiv": 83, "debug": [16, 69], "def_xy_interp": 16, "default": 16, "defin": [2, 13, 30, 75, 83], "deliber": 9, "depend": [1, 2, 25], "deploi": [1, 83], "deploy": 1, "deprec": 2, "desc": 25, "design": [9, 13], "desir": 28, "determin": 25, "develop": [80, 83], "dictionari": 25, "did": [1, 14], "differ": [2, 16, 25, 28], "dimens": 25, "dimension": 25, "dir": 25, "directli": [1, 83], "directori": [12, 25, 46, 80, 83], "disabl": 1, "discov": 25, "do": [1, 2, 14, 46, 52], "do_analysi": 45, "do_refinediag": 45, "doc": 1, "dockerfile_cr": [3, 34, 37], "doe": [16, 79], "doesnt": 28, "don": 16, "done": 2, "ds_p": 14, "dual": 45, "due": 2, "dump": 14, "durat": [16, 45], "duration_to_second": [3, 43, 45], "e": [1, 2, 16, 25, 29, 45, 83], "each": 25, "edit": [1, 83], "effect": 25, "effici": 2, "element": 45, "empti": [2, 25, 82], "en": 30, "enabl": 1, "encod": 14, "encount": 2, "end": [14, 25], "ensur": 2, "enter": 83, "entir": 25, "entri": [2, 18, 25, 29, 57, 60, 83], "environ": [2, 80, 83], "environmentnam": 82, "equal": 25, "error": [16, 25], "essenti": 25, "etc": [2, 83], "even": 2, "everi": 2, "everyth": 2, "ex": 8, "exampl": [80, 83], "execut": [35, 36, 37, 83], "exist": [2, 28, 30, 42, 52, 75, 79], "exp": 45, "experi": [45, 46, 75, 79, 83], "experiment": [23, 33, 58, 61, 77], "experiment_check": [3, 74, 75], "expos": 25, "extens": 25, "extract": 75, "facillit": 2, "fail": 79, "fals": 10, "familiar": 2, "faster": 1, "few": [2, 83], "field": [14, 16], "file": [2, 12, 14, 16, 23, 25, 28, 29, 33, 45, 46, 52, 58, 61, 75, 77, 79, 83], "fileextens": 2, "filenam": [2, 14, 25], "filepath": 45, "final": [25, 75], "find": [1, 25], "first": 1, "fit": 14, "fix": 14, "flag": 83, "folder": 2, "follow": [2, 83], "forg": 82, "fork": [1, 2], "format": [16, 46, 83], "fortran": 8, "forward": 2, "found": [1, 25], "fre": 82, "fre2": [55, 83], "fre_nctool": 12, "fre_python_tool": 12, "freapp": [3, 4], "frecatalog": [3, 17], "frecheck": [3, 21], "frecheckexampl": [3, 21], "frecmor": [3, 24], "free": 2, "fregrid": 16, "frelist": [3, 31, 45], "frelist_xpath": [3, 43, 45], "frelistexampl": [3, 31], "fremak": [3, 34, 40, 83], "fremake_run": [3, 34, 40], "frenc": 8, "frenctoolstimeaverag": [4, 6], "frepp": [3, 43, 55], "frepytool": 9, "frepytoolstimeaverag": [4, 6], "freq": 16, "freq_from_legaci": [3, 43, 45], "freq_to_date_format": [4, 15, 16], "frequenc": [14, 16, 45], "frerun": [3, 56], "frerunexampl": [3, 56], "fretest": [3, 59], "fretestexampl": [3, 59], "freyamltool": [3, 74], "freyamltoolsexampl": [3, 74], "from": [1, 7, 8, 9, 10, 13, 16, 25, 28, 46, 75, 80, 82, 83], "full": [2, 25], "fulli": 13, "function": [2, 6, 7, 9, 10, 12, 13, 23, 25, 29, 33, 58, 61, 77], "futur": 2, "g": [16, 25], "gain": 2, "gener": [7, 8, 9, 10, 12, 13, 25, 45, 83], "generate_timavg": [6, 7, 8, 9, 13], "generate_time_averag": [3, 4], "get": [1, 16, 25], "get_combined_compileyaml": [3, 74, 75], "get_combined_ppyaml": [3, 74, 75], "get_command": [3, 30], "get_compile_path": [3, 74, 75], "get_iso_datetim": [3, 24, 25], "get_mosaic_file_nam": [4, 15, 16], "get_mosaic_grid_file_nam": [4, 15, 16], "get_var_filenam": [3, 24, 25], "get_vertical_dimens": [3, 24, 25], "gfdl": [2, 23, 25, 29, 33, 55, 58, 61, 77, 82, 83], "gh": 1, "git": [2, 83], "github": [1, 2], "gitlab": [55, 83], "given": [16, 25, 30, 45, 46, 75], "gov": [23, 29, 33, 55, 58, 61, 77, 83], "grid": [16, 25], "grid_spec": 16, "grid_spec_fil": 16, "group": [2, 29, 30, 83], "guid": 2, "guidanc": 83, "ha": [2, 14, 25], "har": 12, "have": [2, 25], "head": 1, "header": 25, "heavili": 2, "help": [25, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 83], "here": [2, 13, 25], "hint": 13, "historydir": 45, "hold": [25, 28], "home": 83, "hopelessli": 25, "host": 29, "hous": 25, "how": 80, "howev": 2, "hpcme": 69, "http": [1, 2, 30, 55, 82], "i": [1, 2, 13, 16, 25, 28, 29, 40, 42, 46, 75, 79, 83], "ideal": 2, "ident": 25, "identifi": 25, "implement": [2, 80], "implemet": 2, "import": [2, 6, 16, 24], "in_nc": 25, "includ": [2, 25], "incorrect": 79, "index": 80, "indir": 25, "infil": [7, 8, 9, 10, 12, 13], "inform": [25, 75], "inherit": [7, 8, 9, 13], "init_compile_yaml": [3, 74, 75], "init_pp_yaml": [3, 74, 75], "initi": 46, "input": [12, 14, 16, 25, 28, 45, 83], "input_dir": 16, "input_mosa": 16, "insid": [2, 25], "instal": [2, 3, 43, 70, 82], "instead": 83, "integr": [23, 33, 58, 61, 77], "intel23": 69, "interact": 1, "interp_method": 16, "intput": 25, "io": 1, "iso": [16, 25], "iso8601": 45, "iso_datetime_arr": 25, "iso_freq": 16, "issu": 2, "item": 46, "its": 2, "job": [35, 36, 40], "join_constructor": [3, 74, 75], "json": [25, 46, 83], "json_exp_config": 25, "json_table_config": 25, "json_var_list": 25, "just": 83, "kei": [2, 25, 45], "kwarg": 30, "label": 25, "larger": 14, "last": [1, 2], "lazi": 30, "lazy_group": [3, 81], "lazy_subcommand": 30, "lazygroup": [3, 30, 81], "left": 1, "legaci": 16, "legacy_chunk": 45, "legacy_freq": 45, "length": 16, "level": 14, "lib": 14, "like": [2, 16, 83], "line": [2, 45, 83], "link": 2, "list": [3, 16, 25, 30, 68, 75, 81, 83], "list_command": [3, 30], "load": [30, 46, 75, 82], "loader": 75, "local": [2, 16, 25], "local_var": 25, "locat": [2, 25, 55], "long": 83, "look": 12, "loop": 16, "lord": 25, "low": 14, "macro": 25, "made": 2, "mai": [1, 2, 83], "main": [1, 2, 3, 4, 15, 16, 23, 25, 29, 33, 43, 45, 55, 58, 61, 75, 77, 79], "mainfunctionoftoolcommand": 2, "maintain": 2, "mainyaml_dir": 75, "make": [1, 2, 3, 16, 25, 28, 52, 69, 79, 80, 81], "make_component_list": [4, 15, 16], "make_regrid_var_list": [4, 15, 16], "makefil": 42, "makefile_cr": [3, 34, 38], "manifest": 80, "manipul": 25, "manner": [9, 25], "mask": [14, 16], "mask_atmos_plevel": [3, 4], "mask_field_above_surface_pressur": [3, 4, 14], "match": [2, 25], "md": 25, "meat": 45, "merg": [75, 79], "messag": 83, "meta": 2, "metadata": [25, 28], "metal": 42, "min": 1, "miniforg": 82, "minim": 83, "mip": 25, "miss": 83, "misspel": 79, "model": [40, 42, 75, 79], "modul": [2, 80, 81, 82, 83], "monthli": 12, "more": 2, "mosaic": 16, "mosaic_typ": 16, "most": [16, 25], "mostli": 7, "move": 25, "movement": 25, "multipl": [2, 16], "must": [2, 13, 46], "n": [82, 83], "name": [1, 2, 16, 25, 28, 30, 75, 83], "name_of_set": 25, "namespac": 45, "nativ": 9, "navig": 1, "nc": [16, 25], "nc_fl": 25, "nc_variabl": 16, "ncrc5": [69, 83], "nctool": [8, 16], "necessari": 82, "need": [2, 25, 83], "needs_atmos_mask": 14, "nest": 16, "net": 16, "netcdf": [14, 25], "netcdf4": [9, 25], "netcdf_fil": 25, "never": 13, "new": [1, 80], "newest": 2, "nine": 25, "no_parallel_checkout": [35, 40], "noaa": [2, 23, 29, 33, 55, 58, 61, 77, 82, 83], "node": 75, "non": [2, 25], "none": [7, 8, 9, 10, 12, 13, 16, 25, 30], "nor": 1, "normal": 2, "notat": 45, "note": 1, "now": 2, "numpi": [9, 16], "o": [28, 83], "object": [13, 25, 30, 75], "objet": 25, "ocean": 25, "often": 25, "omon": 25, "onc": [2, 83], "one": [1, 2, 13, 16, 23, 25, 33, 58, 61, 77], "onli": [1, 14, 25, 28], "onto": 16, "opaqu": 25, "open": 25, "openmp": 83, "oper": 25, "opt_var_nam": 25, "option": [2, 14, 16, 25, 82, 83], "optiondn": [63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73], "order": [30, 83], "org": [2, 82], "origin": [25, 45], "other": [9, 12, 80], "out": [1, 2, 25], "out_nc": 25, "outdir": 25, "outfil": [7, 8, 9, 10, 12, 13, 14], "output": [2, 16, 25, 83], "output_dir": 16, "outputfil": 28, "overwrit": [45, 83], "p": [45, 69, 83], "packag": [2, 7, 9, 10, 81], "page": [1, 80], "pair": [25, 45], "palletsproject": 30, "parallel": [36, 40], "paramet": [2, 83], "part": 2, "parti": 9, "pass": 25, "pass_context": 2, "past": 2, "path": [25, 45, 75, 79, 83], "per": [16, 25], "pip": 2, "pkg": [6, 7, 8, 9, 10, 12, 13], "placehold": 83, "platform": [35, 36, 37, 38, 40, 42, 45, 46, 75, 83], "pleas": 2, "point": [2, 18, 25, 29, 57, 60, 83], "poke": 1, "posit": 83, "post": 25, "post_writ": [3, 4, 14], "posteriori": 14, "postprocess": 83, "potenti": 2, "pp": [3, 70, 80, 81], "pp_start": 45, "pp_stop": 45, "ppdir": 45, "pre": 9, "prefer": [2, 14], "prepar": 14, "preprocess": [3, 4, 14], "present": [2, 14, 16, 28], "pressur": 14, "pressure_coordin": [3, 4, 14], "pressure_var": 14, "presum": 25, "prev": 28, "primari": [25, 45], "princip": 29, "print": 16, "process": 25, "prod": 83, "produc": 14, "program": 29, "proj_table_var": 25, "properli": 14, "properti": 75, "prototyp": [23, 33, 58, 61, 77], "provid": [2, 83], "publish": 2, "publish_conda": 2, "push": [1, 2], "py": [2, 25, 55, 83], "pylint": 1, "pytest": [1, 16, 28], "python": [2, 7, 9, 25, 83], "quantiti": 13, "quickli": 16, "quiet": 45, "quit": 82, "quot": 46, "quote_rose_valu": [3, 43, 46], "ran": 83, "re": [1, 82], "reactiv": 83, "read": [16, 25], "readm": 25, "recommend": 2, "recurs": 2, "reduct": 14, "ref": [1, 16], "refer": [2, 25], "refinediag": 14, "refinedir": 45, "reflect": 2, "regardless": 2, "regrid": [16, 45, 46, 52], "regrid_xi": [3, 4, 83], "relat": 13, "relev": 16, "remain": 2, "remap": [16, 45, 46, 52], "remap_dir": 16, "remot": 1, "remov": [28, 75], "replac": 55, "replic": 2, "repo": [1, 28], "repositori": [80, 83], "repres": 25, "represent": 25, "represnt": 25, "request": 25, "requir": [2, 6, 14, 25, 45, 83], "resid": 2, "respect": 2, "rest": 83, "return": [14, 25, 30, 45], "rewrit": 25, "rewrite_netcdf_file_var": [3, 24, 25], "right": 25, "robinson": 40, "root": [2, 25], "rose": [16, 45, 46, 52], "rose_app_cfg": 16, "rose_init": [3, 43, 46], "rose_regrid": 46, "rose_remap": 46, "rose_suit": 46, "routin": [7, 9, 25, 28, 79], "rsa": 2, "rule": 25, "run": [1, 2, 3, 25, 28, 40, 43, 63, 67, 70, 71, 81, 83], "run_avgtype_pkg_calcul": [6, 11, 12], "rundown": 83, "runfremak": [3, 34], "safe_rose_config_get": [4, 15, 16], "sai": 2, "sake": 2, "same": [2, 14, 16, 25], "satisfi": 2, "save": 2, "scalar": 16, "schema": [46, 79], "scheme": 16, "script": [2, 8, 14, 16, 29, 46, 52, 55, 75], "search": 80, "season": 12, "second": 45, "section": [2, 16, 75, 83], "see": [2, 7, 25, 30, 83], "select": 1, "separ": 2, "serv": 2, "set": [1, 14, 16, 46, 55, 80], "set_netcdf_encod": [3, 4, 14], "set_rose_app": [3, 43, 46], "set_rose_suit": [3, 43, 46], "setup": [2, 28, 80, 83], "should": [1, 2, 30, 79], "shown": 83, "shutil": 25, "singh": 40, "six": 25, "slightli": [25, 28], "small": 1, "so": [1, 16, 25], "solut": 2, "some": 25, "someth": [2, 83], "somewher": 1, "sourc": [1, 16], "specif": [1, 25, 75], "specifi": [2, 25, 83], "spheric": 16, "src": 46, "ssh": 2, "start": 82, "statist": [9, 13], "statu": [3, 43, 70], "stddev_typ": [6, 7, 8, 9, 10, 12, 13], "steer": [10, 16, 25], "step": [1, 2], "str": [13, 45], "string": [16, 25, 79], "structur": [13, 25, 80], "style": [30, 45], "subcommand": [2, 18, 30, 57, 60, 83], "subdirectori": 2, "subgroup": 29, "subject": 2, "submodul": 81, "subpackag": 81, "subprocess": [8, 45], "subtool": [2, 83], "success": [1, 52], "suffici": 25, "suit": [45, 46, 52], "sure": [1, 2, 25, 28, 52, 79], "surfac": 14, "syntax": [2, 83], "system": 55, "t": [2, 16, 45, 69, 83], "tabl": [25, 28], "target": [16, 25, 35, 36, 37, 38, 40, 45, 46, 75, 83], "target_fil": 16, "target_var": 25, "task": [2, 45], "tavg": 8, "templat": [2, 14, 16, 46, 83], "test": [2, 3, 4, 6, 16, 17, 24, 34, 43, 74, 81, 83], "test_analysisyaml_exist": [74, 78, 79], "test_bm_makefile_cr": [34, 41, 42], "test_boolean": [43, 51, 53], "test_cdo_time_avg": [6, 11, 12], "test_cdo_time_unwgt_avg": [6, 11, 12], "test_cdo_time_unwgt_stddev": [6, 11, 12], "test_cli_fr": [3, 62, 66], "test_cli_fre_app": [3, 62, 63], "test_cli_fre_app_gen_time_averag": [3, 62, 63], "test_cli_fre_app_gen_time_averages_help": [3, 62, 63], "test_cli_fre_app_gen_time_averages_opt_dn": [3, 62, 63], "test_cli_fre_app_help": [3, 62, 63], "test_cli_fre_app_opt_dn": [3, 62, 63], "test_cli_fre_app_regrid": [3, 62, 63], "test_cli_fre_app_regrid_help": [3, 62, 63], "test_cli_fre_app_regrid_opt_dn": [3, 62, 63], "test_cli_fre_app_regrid_test_case_1": [3, 62, 63], "test_cli_fre_catalog": [3, 62, 64], "test_cli_fre_catalog_build": [3, 62, 64], "test_cli_fre_catalog_builder_help": [3, 62, 64], "test_cli_fre_catalog_help": [3, 62, 64], "test_cli_fre_catalog_merg": [3, 62, 64], "test_cli_fre_catalog_merge_help": [3, 62, 64], "test_cli_fre_catalog_opt_dn": [3, 62, 64], "test_cli_fre_check": [3, 62, 65], "test_cli_fre_check_help": [3, 62, 65], "test_cli_fre_check_opt_dn": [3, 62, 65], "test_cli_fre_cmor": [3, 62, 67], "test_cli_fre_cmor_help": [3, 62, 67], "test_cli_fre_cmor_opt_dn": [3, 62, 67], "test_cli_fre_cmor_run": [3, 62, 67], "test_cli_fre_cmor_run_case1": [3, 62, 67], "test_cli_fre_cmor_run_case2": [3, 62, 67], "test_cli_fre_cmor_run_help": [3, 62, 67], "test_cli_fre_cmor_run_opt_dn": [3, 62, 67], "test_cli_fre_help": [3, 62, 66], "test_cli_fre_list": [3, 62, 68], "test_cli_fre_list_help": [3, 62, 68], "test_cli_fre_list_opt_dn": [3, 62, 68], "test_cli_fre_mak": [3, 62, 69], "test_cli_fre_make_create_checkout_baremet": [3, 62, 69], "test_cli_fre_make_create_checkout_contain": [3, 62, 69], "test_cli_fre_make_help": [3, 62, 69], "test_cli_fre_make_opt_dn": [3, 62, 69], "test_cli_fre_option_dn": [3, 62, 66], "test_cli_fre_pp": [3, 62, 70], "test_cli_fre_pp_checkout": [3, 62, 70], "test_cli_fre_pp_checkout_help": [3, 62, 70], "test_cli_fre_pp_checkout_opt_dn": [3, 62, 70], "test_cli_fre_pp_configure_xml": [3, 62, 70], "test_cli_fre_pp_configure_xml_help": [3, 62, 70], "test_cli_fre_pp_configure_xml_opt_dn": [3, 62, 70], "test_cli_fre_pp_configure_yaml": [3, 62, 70], "test_cli_fre_pp_configure_yaml_help": [3, 62, 70], "test_cli_fre_pp_configure_yaml_opt_dn": [3, 62, 70], "test_cli_fre_pp_help": [3, 62, 70], "test_cli_fre_pp_instal": [3, 62, 70], "test_cli_fre_pp_install_help": [3, 62, 70], "test_cli_fre_pp_install_opt_dn": [3, 62, 70], "test_cli_fre_pp_opt_dn": [3, 62, 70], "test_cli_fre_pp_run": [3, 62, 70], "test_cli_fre_pp_run_help": [3, 62, 70], "test_cli_fre_pp_run_opt_dn": [3, 62, 70], "test_cli_fre_pp_statu": [3, 62, 70], "test_cli_fre_pp_status_help": [3, 62, 70], "test_cli_fre_pp_status_opt_dn": [3, 62, 70], "test_cli_fre_pp_valid": [3, 62, 70], "test_cli_fre_pp_validate_help": [3, 62, 70], "test_cli_fre_pp_validate_opt_dn": [3, 62, 70], "test_cli_fre_pp_wrapp": [3, 62, 70], "test_cli_fre_pp_wrapper_help": [3, 62, 70], "test_cli_fre_pp_wrapper_opt_dn": [3, 62, 70], "test_cli_fre_run": [3, 62, 71], "test_cli_fre_run_help": [3, 62, 71], "test_cli_fre_run_opt_dn": [3, 62, 71], "test_cli_fre_test": [3, 62, 72], "test_cli_fre_test_help": [3, 62, 72], "test_cli_fre_test_opt_dn": [3, 62, 72], "test_cli_fre_yamltool": [3, 62, 73], "test_cli_fre_yamltools_help": [3, 62, 73], "test_cli_fre_yamltools_opt_dn": [3, 62, 73], "test_cmor_run_subtool": [24, 27], "test_combine_yaml": [74, 78], "test_combined_compileyaml_combinefail": [74, 78, 79], "test_combined_compileyaml_valid": [74, 78, 79], "test_combined_compileyaml_validatefail": [74, 78, 79], "test_combined_ppyaml_valid": [74, 78, 79], "test_combinedyaml_exist": [43, 51, 52], "test_compare_cdo_to_fre_nctool": [6, 11, 12], "test_compare_fre_python_tools_to_cdo": [6, 11, 12], "test_compare_fre_python_tools_to_fre_nctool": [6, 11, 12], "test_compare_unwgt_fre_python_tools_to_unwgt_cdo": [6, 11, 12], "test_compileyaml_exist": [34, 41, 42, 74, 78, 79], "test_configure_script": [43, 51, 52], "test_configure_script_yaml": [43, 51], "test_container_makefile_cr": [34, 41, 42], "test_create_makefil": [34, 41], "test_expyaml_exist": [74, 78, 79], "test_fre_app_cli": [3, 62], "test_fre_catalog": [17, 19], "test_fre_catalog_cli": [3, 62], "test_fre_catalog_import": [17, 19, 20], "test_fre_check_cli": [3, 62], "test_fre_cli": [3, 62], "test_fre_cmor_cli": [3, 62], "test_fre_cmor_run_subtool_case1": [24, 27, 28], "test_fre_cmor_run_subtool_case1_output_compare_data": [24, 27, 28], "test_fre_cmor_run_subtool_case1_output_compare_metadata": [24, 27, 28], "test_fre_cmor_run_subtool_case2": [24, 27, 28], "test_fre_cmor_run_subtool_case2_output_compare_data": [24, 27, 28], "test_fre_cmor_run_subtool_case2_output_compare_metadata": [24, 27, 28], "test_fre_list_cli": [3, 62], "test_fre_make_cli": [3, 62], "test_fre_pp_cli": [3, 62], "test_fre_python_tools_time_avg": [6, 11, 12], "test_fre_python_tools_time_avgs_stddev": [6, 11, 12], "test_fre_python_tools_time_unwgt_avg": [6, 11, 12], "test_fre_python_tools_time_unwgt_avgs_stddev": [6, 11, 12], "test_fre_run_cli": [3, 62], "test_fre_test_cli": [3, 62], "test_fre_yamltools_cli": [3, 62], "test_generate_time_averag": [6, 11], "test_import": [4, 15, 16], "test_merged_compile_yaml": [74, 78, 79], "test_merged_pp_yaml": [74, 78, 79], "test_modelyaml_exist": [34, 41, 42, 74, 78, 79], "test_monthly_cdo_time_unwgt_avg": [6, 11, 12], "test_monthly_cdo_time_unwgt_stddev": [6, 11, 12], "test_platformyaml_exist": [34, 41, 42, 74, 78, 79], "test_rose_quot": [43, 51], "test_seasonal_cdo_time_unwgt_avg": [6, 11, 12], "test_seasonal_cdo_time_unwgt_stddev": [6, 11, 12], "test_setup_cmor_cmip_table_repo": [24, 27, 28], "test_setup_fre_cmor_run_subtool": [24, 27, 28], "test_setup_fre_cmor_run_subtool_case2": [24, 27, 28], "test_str": [43, 51, 53], "test_time_avg_file_dir_exist": [6, 11, 12], "test_time_avg_input_file_exist": [6, 11, 12], "than": 14, "thei": [30, 46, 83], "them": 46, "theres": 1, "thi": [1, 2, 13, 14, 16, 25, 28, 29, 30, 52], "think": 25, "third": 9, "those": 25, "three": 25, "throw": 25, "timavg": 8, "time": [1, 2, 7, 8, 9, 10, 12, 13, 16, 83], "timeaverag": [4, 6, 7, 8, 9], "tmp": 25, "tmp_dir": [16, 25], "to_netcdf": 14, "tom": 40, "tool": [8, 10, 25, 80], "toolcommandscript": 2, "top": 1, "tripolar": 16, "trivial": [1, 25], "truncat": 16, "truncate_d": [4, 15, 16], "try": 2, "turn": 1, "two": 25, "type": 2, "typic": 2, "u": 25, "ue2": 83, "under": [1, 2], "uniqu": [16, 25], "unit": 13, "unless": 46, "unnecessari": 75, "unweight": 12, "unwgt": [6, 7, 8, 9, 10, 12, 13], "up": [52, 80], "updat": 2, "upon": 25, "us": [2, 7, 8, 9, 12, 14, 16, 25, 28, 30, 40, 42, 45, 46, 67, 75], "usag": [25, 45, 80], "user": [1, 2, 45, 83], "usu": 9, "util": [8, 25], "valid": [3, 16, 43, 45, 46, 70, 79, 83], "validate_yaml": [3, 43, 46], "valu": [16, 25, 45, 46], "var": [6, 7, 8, 9, 10, 13, 14], "var_filenam": 25, "var_has_time_unit": [6, 13], "var_with_bound": 14, "variabl": [13, 16, 25, 46], "variou": [10, 82], "varnam": 14, "vector": 16, "verbos": [35, 36, 40, 45], "version": 12, "vertic": 25, "via": [7, 29, 40, 45], "wa": 79, "wai": 2, "warn": 16, "we": [1, 25], "webpag": 1, "weight": [7, 12], "welcom": 1, "well": 82, "were": 25, "what": 2, "when": [1, 2, 42], "where": [1, 2, 16], "which": [2, 25, 46], "while": 30, "whitlock": 55, "whole": 25, "why": 16, "wish": 25, "within": [2, 16, 25], "without": 83, "won": 2, "work": 82, "workflow": [1, 2, 46, 83], "workstat": 80, "would": [2, 25, 83], "wrapper": [3, 43, 70], "write_dataset": [3, 4, 14], "x": [30, 45], "xarrai": 14, "xml": [45, 70], "xpath": 45, "xy": 45, "y": [69, 83], "yaml": [2, 42, 46, 52, 69, 70, 75, 79, 83], "yaml_load": [3, 43, 46, 74, 75], "yamlfil": [35, 36, 37, 38, 40, 46, 75], "yamlinfo": [3, 43, 46], "yamltool": [3, 73, 80, 81], "yet": [2, 80], "yml": [1, 2, 75], "you": [1, 2, 82], "your": [1, 2], "your_usernam": 1}, "titles": ["Badges", "Documentation-Documentation", "Developer Usage", "fre package", "fre.app package", "fre.app.freapp module", "fre.app.generate_time_averages package", "fre.app.generate_time_averages.cdoTimeAverager module", "fre.app.generate_time_averages.frenctoolsTimeAverager module", "fre.app.generate_time_averages.frepytoolsTimeAverager module", "fre.app.generate_time_averages.generate_time_averages module", "fre.app.generate_time_averages.tests package", "fre.app.generate_time_averages.tests.test_generate_time_averages module", "fre.app.generate_time_averages.timeAverager module", "fre.app.mask_atmos_plevel module", "fre.app.regrid_xy package", "fre.app.regrid_xy.regrid_xy module", "fre.catalog package", "fre.catalog.frecatalog module", "fre.catalog.tests package", "fre.catalog.tests.test_fre_catalog module", "fre.check package", "fre.check.frecheck module", "fre.check.frecheckexample module", "fre.cmor package", "fre.cmor.cmor_mixer module", "fre.cmor.frecmor module", "fre.cmor.tests package", "fre.cmor.tests.test_cmor_run_subtool module", "fre.fre module", "fre.lazy_group module", "fre.list package", "fre.list.frelist module", "fre.list.frelistexample module", "fre.make package", "fre.make.createCheckout module", "fre.make.createCompile module", "fre.make.createDocker module", "fre.make.createMakefile module", "fre.make.fremake module", "fre.make.runFremake module", "fre.make.tests package", "fre.make.tests.test_create_makefile module", "fre.pp package", "fre.pp.checkoutScript module", "fre.pp.configure_script_xml module", "fre.pp.configure_script_yaml module", "fre.pp.frepp module", "fre.pp.install module", "fre.pp.run module", "fre.pp.status module", "fre.pp.tests package", "fre.pp.tests.test_configure_script_yaml module", "fre.pp.tests.test_rose_quoting module", "fre.pp.validate module", "fre.pp.wrapper module", "fre.run package", "fre.run.frerun module", "fre.run.frerunexample module", "fre.test package", "fre.test.fretest module", "fre.test.fretestexample module", "fre.tests package", "fre.tests.test_fre_app_cli module", "fre.tests.test_fre_catalog_cli module", "fre.tests.test_fre_check_cli module", "fre.tests.test_fre_cli module", "fre.tests.test_fre_cmor_cli module", "fre.tests.test_fre_list_cli module", "fre.tests.test_fre_make_cli module", "fre.tests.test_fre_pp_cli module", "fre.tests.test_fre_run_cli module", "fre.tests.test_fre_test_cli module", "fre.tests.test_fre_yamltools_cli module", "fre.yamltools package", "fre.yamltools.combine_yamls module", "fre.yamltools.freyamltools module", "fre.yamltools.freyamltoolsexample module", "fre.yamltools.tests package", "fre.yamltools.tests.test_combine_yamls module", "Welcome to Fre-Cli\u2019s documentation!", "fre", "Setup", "Usage"], "titleterms": {"": [1, 80], "If": 82, "ad": 2, "app": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 83], "badg": 0, "call": 83, "catalog": [17, 18, 19, 20, 83], "cdotimeaverag": 7, "check": [21, 22, 23], "checklist": 2, "checkoutscript": 44, "cli": [1, 80], "cmor": [24, 25, 26, 27, 28, 83], "cmor_mix": 25, "combine_yaml": 75, "conda": 82, "configure_script_xml": 45, "configure_script_yaml": 46, "content": [3, 4, 6, 11, 15, 17, 19, 21, 24, 27, 31, 34, 41, 43, 51, 56, 59, 62, 74, 78, 80], "contribut": 1, "creat": 82, "createcheckout": 35, "createcompil": 36, "createdock": 37, "createmakefil": 38, "develop": 2, "directori": 2, "document": [1, 2, 80], "environ": 82, "exampl": 2, "fre": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83], "freapp": 5, "frecatalog": 18, "frecheck": 22, "frecheckexampl": 23, "frecmor": 26, "frelist": 32, "frelistexampl": 33, "fremak": 39, "frenctoolstimeaverag": 8, "frepp": 47, "frepytoolstimeaverag": 9, "frerun": 57, "frerunexampl": 58, "fretest": 60, "fretestexampl": 61, "freyamltool": 76, "freyamltoolsexampl": 77, "from": 2, "generate_time_averag": [6, 7, 8, 9, 10, 11, 12, 13], "how": 1, "implement": 83, "indic": 80, "instal": 48, "lazy_group": 30, "list": [31, 32, 33], "make": [34, 35, 36, 37, 38, 39, 40, 41, 42, 83], "manifest": 2, "mask_atmos_plevel": 14, "modul": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79], "new": [2, 82], "other": 2, "packag": [3, 4, 6, 11, 15, 17, 19, 21, 24, 27, 31, 34, 41, 43, 51, 56, 59, 62, 74, 78], "pp": [43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 83], "regrid_xi": [15, 16], "repositori": 2, "run": [49, 56, 57, 58], "runfremak": 40, "set": 82, "setup": 82, "statu": 50, "structur": 2, "submodul": [3, 4, 6, 11, 15, 17, 19, 21, 24, 27, 31, 34, 41, 43, 51, 56, 59, 62, 74, 78], "subpackag": [3, 4, 6, 17, 24, 34, 43, 74], "test": [11, 12, 19, 20, 27, 28, 41, 42, 51, 52, 53, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 78, 79], "test_cmor_run_subtool": 28, "test_combine_yaml": 79, "test_configure_script_yaml": 52, "test_create_makefil": 42, "test_fre_app_cli": 63, "test_fre_catalog": 20, "test_fre_catalog_cli": 64, "test_fre_check_cli": 65, "test_fre_cli": 66, "test_fre_cmor_cli": 67, "test_fre_list_cli": 68, "test_fre_make_cli": 69, "test_fre_pp_cli": 70, "test_fre_run_cli": 71, "test_fre_test_cli": 72, "test_fre_yamltools_cli": 73, "test_generate_time_averag": 12, "test_rose_quot": 53, "timeaverag": 13, "tool": [2, 83], "up": 82, "usag": [2, 83], "valid": 54, "welcom": 80, "workstat": 82, "wrapper": 55, "yamltool": [74, 75, 76, 77, 78, 79, 83], "yet": 83}}) \ No newline at end of file diff --git a/setup.html b/setup.html new file mode 100644 index 00000000..26a2059f --- /dev/null +++ b/setup.html @@ -0,0 +1,143 @@ + + + + + + + + + Setup — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Setup

+
+

Set up Conda environment

+

various options work quite well:

+
+

If on workstation

+

module load fre/2024.01

+
+
+

Create New Conda environment

+

if you’re at GFDL: +module load miniforge

+

create an empty environment to start +conda create -n [environmentName]

+

activate the empty environment: +conda activate [environmentName]

+

append necessary channels: +conda config --append channels noaa-gfdl; conda config --append channels conda-forge;

+

install fre-cli into the activated environment from `the GFDL conda channel https://anaconda.org/NOAA-GFDL/fre-cli`_ : +conda install noaa-gfdl::fre-cli

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/usage.html b/usage.html new file mode 100644 index 00000000..04672851 --- /dev/null +++ b/usage.html @@ -0,0 +1,229 @@ + + + + + + + + + Usage — Fre-Cli 1.0 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Usage

+

for setup, see the setup section.

+
+

Calling fre

+

Brief rundown of commands also provided below:

+
    +
  • Enter commands and follow --help messages for guidance

  • +
  • If the user just runs fre, it will list all the command groups following fre, such as +run, make, pp, etc. and once the user specifies a command group, the list of available +subcommands for that group will be shown

  • +
  • Commands that require arguments to run will alert user about missing arguments, and will also list +the rest of the optional parameters if --help is executed

  • +
  • Argument flags are not positional, can be specified in any order as long as they are specified

  • +
  • Can run directly from any directory, no need to clone repository

  • +
  • May need to deactivate environment and reactivate it in order for changes to apply

  • +
  • fre/setup.py allows fre/fre.py to be ran as fre on the command line by defining it as an +entry point. Without it, the call would be instead, something like python fre/fre.py

  • +
+
+
+

Tools

+

A few subtools are currently in development:

+
+

fre app

+
    +
  1. generate-time-averages

  2. +
  3. regrid_xy

  4. +
+
+
+

fre catalog

+
    +
  1. builder Generate a catalog

  2. +
+
    +
  • Builds json and csv format catalogs from user input directory path

  • +
  • Minimal Syntax: fre catalog builder -i [input path] -o [output path]

  • +
  • Module(s) needed: n/a

  • +
  • Example: fre catalog builder -i /archive/am5/am5/am5f3b1r0/c96L65_am5f3b1r0_pdclim1850F/gfdl.ncrc5-deploy-prod-openmp/pp -o ~/output --overwrite

  • +
+
    +
  1. validate Validate the catalog

  2. +
+
+
+

fre cmor

+
    +
  1. run

  2. +
+
    +
  • placehold

  • +
+
+
+

fre make

+
    +
  1. run-fremake

  2. +
+
    +
  • placehold

  • +
+
+
+

fre pp

+
    +
  1. configure

  2. +
+
    +
  • Postprocessing yaml configuration

  • +
  • Minimal Syntax: fre pp configure -y [user-edit yaml file]

  • +
  • Module(s) needed: n/a

  • +
  • Example: fre pp configure -y /home/$user/pp/ue2/user-edits/edits.yaml

  • +
+
    +
  1. checkout

  2. +
+
    +
  • Checkout template file and clone gitlab.gfdl.noaa.gov/fre2/workflows/postprocessing.git repository

  • +
  • Minimal Syntax: fre pp checkout -e [experiment name] -p [platform name] -t [target name]

  • +
  • Module(s) needed: n/a

  • +
  • Example: fre pp checkout -e c96L65_am5f4b4r0_amip -p gfdl.ncrc5-deploy -t prod-openmp

  • +
+
+
+

fre yamltools

+
    +
  1. combine-yamls

  2. +
+
    +
  • placehold

  • +
+
+
+

not-yet-implemented

+
    +
  1. fre check

  2. +
  3. fre list

  4. +
  5. fre run

  6. +
  7. fre test

  8. +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file