Skip to content

Commit 4f5d4e2

Browse files
authored
First prototype: ESMValTool integration via access_moppy.esmval (#345)
* ESMValTool Integration skeleton * feat: update esmvalcore dependency to >=2.14 and enhance recipe support for ACCESS-ESM1-5 * Refactor ESMValTool integration for ACCESS-MOPPy - Update documentation to reflect changes in ESMValCore config handling. - Modify ESMValCore config generation to create a data-source config file instead of an overlay. - Adjust CLI commands and examples to remove the need for the `--config` flag. - Enhance unit tests for config generation to validate new structure and templates. - Update recipe reader tests to ensure only valid dataset names are accepted. - Add dependencies for `esmpy` and `rdflib` in `pyproject.toml`. * ESMValTool Integration skeleton * feat: update esmvalcore dependency to >=2.14 and enhance recipe support for ACCESS-ESM1-5 * Refactor ESMValTool integration for ACCESS-MOPPy - Update documentation to reflect changes in ESMValCore config handling. - Modify ESMValCore config generation to create a data-source config file instead of an overlay. - Adjust CLI commands and examples to remove the need for the `--config` flag. - Enhance unit tests for config generation to validate new structure and templates. - Update recipe reader tests to ensure only valid dataset names are accepted. - Add dependencies for `esmpy` and `rdflib` in `pyproject.toml`. * fix: resolve duplicate esmvalcore dep causing pixi install failure The esmval feature's pypi-dependencies listed esmvalcore = '>=2.14' explicitly, which duplicated the same requirement already declared via access_moppy's [project.optional-dependencies].esmval. Pixi sees both during lock-file resolution and raises: Unsupported pep508 requirement: 'esmvalcore>=2.14' ╰─▶ esmvalcore is already a dependency. Fix: replace the explicit esmvalcore line with access_moppy carrying the esmval extra. Pixi resolves esmvalcore through the extras mechanism only, so there is a single declaration and no conflict.
1 parent eb96e61 commit 4f5d4e2

17 files changed

Lines changed: 3664 additions & 0 deletions
Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
ESMValTool Integration
2+
======================
3+
4+
This guide explains how to use ACCESS-MOPPy as a transparent CMORisation
5+
pre-processor for `ESMValTool <https://www.esmvaltool.org/>`_ and
6+
`ESMValCore <https://github.com/ESMValGroup/ESMValCore>`_.
7+
8+
.. contents:: Table of Contents
9+
:local:
10+
:depth: 2
11+
12+
Overview
13+
--------
14+
15+
ESMValTool assumes that the data it reads is already in CMIP-compliant
16+
format. Raw ACCESS-ESM1.6 output uses UM STASH codes, MOM5 variable
17+
names, and a non-standard directory structure, so ESMValTool cannot use it
18+
directly.
19+
20+
The ``access_moppy.esmval`` subpackage bridges this gap **without
21+
modifying ESMValCore or ESMValTool**. It:
22+
23+
1. Parses an ESMValTool recipe YAML to find which variables and time ranges
24+
are needed.
25+
2. Locates the corresponding raw ACCESS-ESM1.6 files on disk.
26+
3. Runs ACCESS-MOPPy's CMORisation pipeline and writes CMIP DRS-structured
27+
NetCDF output to a local cache directory.
28+
4. Generates an ESMValCore 2.14+ ``LocalDataSource`` config file and places
29+
it in the ESMValCore user config directory
30+
(``~/.config/esmvaltool/``) so ESMValTool finds the data automatically
31+
— no ``--config`` flag required.
32+
it is simply reading well-formed CMIP6 data.
33+
34+
Architecture
35+
------------
36+
37+
.. code-block:: text
38+
39+
┌──────────────────────────────────────────────────────────────┐
40+
│ User runs: moppy-esmval-run my_recipe.yml │
41+
└──────────────────────────┬───────────────────────────────────┘
42+
43+
┌────────────────▼──────────────────┐
44+
│ CMORiseOrchestrator │
45+
│ │
46+
│ 1. Parse recipe YAML │
47+
│ 2. Extract ACCESS-ESM1-6 datasets│
48+
│ 3. Map (mip, short_name) → │
49+
│ compound_name │
50+
│ 4. Locate raw ACCESS files │
51+
│ 5. Run ACCESS_ESM_CMORiser │
52+
│ (skip if cached & current) │
53+
│ 6. Write CMIP DRS output tree │
54+
└────────────────┬──────────────────┘
55+
56+
┌────────────────▼──────────────────┐
57+
│ ~/.config/esmvaltool/ │
58+
│ moppy-esmval-data.yml │
59+
│ │
60+
│ projects: │
61+
│ CMIP6: │
62+
│ data: │
63+
│ moppy-cache: │
64+
│ type: LocalDataSource │
65+
│ rootpath: ~/.cache/… │
66+
└────────────────┬──────────────────┘
67+
│ (auto-loaded by ESMValCore)
68+
┌────────────────▼──────────────────┐
69+
│ esmvaltool run my_recipe.yml │
70+
│ (no --config flag needed) │
71+
└───────────────────────────────────┘
72+
73+
Installation
74+
------------
75+
76+
Install ACCESS-MOPPy with the ESMValTool integration extras::
77+
78+
pip install "access_moppy[esmval]"
79+
80+
This pulls in ``esmvalcore>=2.14`` as an optional dependency. ESMValTool
81+
itself is not strictly required (only ESMValCore is needed to run recipes
82+
using the generated config overlay); install it separately if needed::
83+
84+
pip install ESMValTool
85+
86+
Quick Start
87+
-----------
88+
89+
**Step 1 — Prepare a recipe**
90+
91+
Write or obtain a normal ESMValTool recipe that references
92+
``dataset: ACCESS-ESM1-6`` and ``project: CMIP6``:
93+
94+
.. code-block:: yaml
95+
96+
# my_recipe.yml
97+
documentation:
98+
title: ACCESS-ESM1-6 surface temperature example
99+
description: Minimal recipe demonstrating ACCESS-MOPPy ESMValTool integration.
100+
authors:
101+
- anonymous
102+
103+
datasets:
104+
- {dataset: ACCESS-ESM1-6, project: CMIP6, exp: historical,
105+
ensemble: r1i1p1f1, grid: gn, timerange: '2000/2005'}
106+
107+
diagnostics:
108+
temperature_bias:
109+
variables:
110+
tas:
111+
mip: Amon
112+
scripts:
113+
plot:
114+
script: examples/plot_map.py
115+
116+
**Step 2 — CMORise and run (two-step)**
117+
118+
.. code-block:: bash
119+
120+
# CMORise required data and write ESMValCore config
121+
# (written to ~/.config/esmvaltool/moppy-esmval-data.yml automatically):
122+
moppy-esmval-prepare my_recipe.yml \
123+
--input-root /g/data/p73/archive/.../MyRun \
124+
--cache-dir ~/.cache/moppy-esmval
125+
126+
# Run ESMValTool — config is picked up automatically, no --config flag:
127+
esmvaltool run my_recipe.yml
128+
129+
**Step 3 — Or use the one-step wrapper**
130+
131+
.. code-block:: bash
132+
133+
moppy-esmval-run my_recipe.yml \
134+
--input-root /g/data/p73/archive/.../MyRun \
135+
--cache-dir ~/.cache/moppy-esmval
136+
137+
This is equivalent to running both commands above sequentially.
138+
139+
**Step 4 — Or via the esmvaltool CLI**
140+
141+
Once ACCESS-MOPPy is installed alongside ESMValCore, it registers a new
142+
sub-command on the ``esmvaltool`` executable::
143+
144+
esmvaltool cmorise my_recipe.yml \
145+
--input-root /g/data/p73/archive/.../MyRun \
146+
--cache-dir ~/.cache/moppy-esmval
147+
148+
esmvaltool run my_recipe.yml
149+
150+
Command Reference
151+
-----------------
152+
153+
``moppy-esmval-prepare``
154+
~~~~~~~~~~~~~~~~~~~~~~~~
155+
156+
CMORise the data required by a recipe without invoking ESMValTool.
157+
158+
.. code-block:: text
159+
160+
usage: moppy-esmval-prepare [-h]
161+
RECIPE
162+
--input-root PATH
163+
--cache-dir PATH
164+
[--model-id ID]
165+
[--config FILE]
166+
[--output-config FILE]
167+
[--workers N]
168+
[--dry-run]
169+
[--pattern COMPOUND_NAME:GLOB]
170+
[-v]
171+
172+
.. list-table::
173+
:header-rows: 1
174+
:widths: 25 75
175+
176+
* - Argument
177+
- Description
178+
* - ``RECIPE``
179+
- Path to the ESMValTool YAML recipe (required).
180+
* - ``--input-root``
181+
- Root directory of the raw ACCESS-ESM1.6 archive (required).
182+
* - ``--cache-dir``
183+
- Directory where CMORised files will be written in CMIP DRS structure (required).
184+
* - ``--model-id``
185+
- ACCESS-MOPPy model identifier. Default: ``ACCESS-ESM1.6``.
186+
* - ``--config``
187+
- Path to any existing file in the user's ESMValCore config directory.
188+
The MOPPy data-source file is written into the same directory.
189+
* - ``--output-config``
190+
- Where to write the generated ESMValCore data-source config file
191+
(default: ``~/.config/esmvaltool/moppy-esmval-data.yml``).
192+
* - ``--workers``
193+
- Number of parallel CMORisation workers. Default: ``1``.
194+
* - ``--dry-run``
195+
- Log what would be done without running CMORisation.
196+
* - ``--pattern``
197+
- Raw-file glob pattern override for a specific variable, e.g.
198+
``Amon.tas:/output*/atmosphere/netCDF/*mon.nc``. Can be repeated.
199+
* - ``-v / --verbose``
200+
- Enable DEBUG-level logging.
201+
202+
``moppy-esmval-run``
203+
~~~~~~~~~~~~~~~~~~~~
204+
205+
CMORise and immediately invoke ``esmvaltool run``. Accepts all the same
206+
arguments as ``moppy-esmval-prepare`` plus:
207+
208+
.. list-table::
209+
:header-rows: 1
210+
:widths: 25 75
211+
212+
* - Argument
213+
- Description
214+
* - ``--esmvaltool-args``
215+
- Extra arguments forwarded verbatim to ``esmvaltool run`` (quoted string).
216+
217+
``esmvaltool cmorise``
218+
~~~~~~~~~~~~~~~~~~~~~~
219+
220+
Registered via the ``esmvaltool_commands`` entry-point group. Performs
221+
the same preparation step as ``moppy-esmval-prepare``. All parameters
222+
are the same, passed as keyword arguments because the ``esmvaltool`` CLI
223+
uses Google `fire <https://github.com/google/python-fire>`_ for dispatch.
224+
225+
Python API
226+
----------
227+
228+
All components are importable directly for use in scripts and notebooks:
229+
230+
.. code-block:: python
231+
232+
from access_moppy.esmval import CMORiseOrchestrator, RecipeReader, VariableIndex
233+
234+
# Parse recipe
235+
reader = RecipeReader("my_recipe.yml")
236+
print(reader.tasks) # list of CMORTask objects
237+
238+
# Check which variables are supported
239+
idx = VariableIndex()
240+
print(idx.is_supported("Amon", "tas")) # True
241+
242+
# Run orchestrator
243+
orch = CMORiseOrchestrator(
244+
input_root="/g/data/p73/archive/.../MyRun",
245+
cache_dir="~/.cache/moppy-esmval",
246+
)
247+
results = orch.prepare_recipe("my_recipe.yml")
248+
CMORiseOrchestrator.summarise(results)
249+
250+
# Write ESMValCore 2.14+ config (placed in ~/.config/esmvaltool/ by default)
251+
from access_moppy.esmval.config_gen import write_esmval_config
252+
cfg = write_esmval_config("~/.cache/moppy-esmval")
253+
print(f"Config written to: {cfg}")
254+
# esmvaltool run my_recipe.yml # no --config flag needed
255+
256+
File Pattern Overrides
257+
----------------------
258+
259+
By default the file finder uses broad component-level glob patterns
260+
relative to ``--input-root``. When the default patterns do not match
261+
your archive layout you can override them per variable:
262+
263+
.. code-block:: bash
264+
265+
moppy-esmval-prepare my_recipe.yml \
266+
--input-root /data/archive/MyRun \
267+
--cache-dir ~/.cache/moppy-esmval \
268+
--pattern "Amon.tas:/output[0-4]*/atmosphere/netCDF/*mon.nc" \
269+
--pattern "Omon.tos:/output[0-4]*/ocean/ocean-2d-surface_temp*.nc"
270+
271+
Or in Python:
272+
273+
.. code-block:: python
274+
275+
orch = CMORiseOrchestrator(
276+
input_root="/data/archive/MyRun",
277+
cache_dir="~/.cache/moppy-esmval",
278+
pattern_overrides={
279+
"Amon.tas": "output[0-4]*/atmosphere/netCDF/*mon.nc",
280+
"Omon.tos": "output[0-4]*/ocean/ocean-2d-surface_temp*.nc",
281+
},
282+
)
283+
284+
The default patterns for each component are:
285+
286+
.. list-table::
287+
:header-rows: 1
288+
:widths: 20 80
289+
290+
* - Component
291+
- Default glob patterns (relative to ``--input-root``)
292+
* - ``atmosphere``, ``land``, ``aerosol``
293+
- ``output[0-9]*/atmosphere/netCDF/*mon.nc`` (plus ``*dai.nc``, ``*3hr.nc``, ``*6hr.nc``)
294+
* - ``ocean``
295+
- ``output[0-9]*/ocean/ocean-{1,2,3}d-*.nc``
296+
* - ``sea_ice``
297+
- ``output[0-9]*/ice/iceh-1monthly-mean*.nc``
298+
299+
Caching
300+
-------
301+
302+
The orchestrator caches results: if a CMORised file already exists under
303+
``--cache-dir`` *and* is newer than all raw input files, the variable is
304+
skipped and reported as ``"cached"``. To force re-CMORisation, either
305+
delete the relevant files from the cache or use ``--dry-run`` first to
306+
inspect what is cached.
307+
308+
HPC Usage (NCI Gadi)
309+
---------------------
310+
311+
On NCI Gadi the raw ACCESS-ESM1.6 output typically lives under
312+
``/g/data/p73/archive/`` or ``/g/data/access/ACCESS-ESM1-6/``. A typical
313+
workflow uses the ``--workers`` flag together with a pre-existing PBS
314+
interactive session:
315+
316+
.. code-block:: bash
317+
318+
# Inside a PBS interactive session or a Gadi login node
319+
moppy-esmval-run my_recipe.yml \
320+
--input-root /g/data/p73/archive/CMIP7/ACCESS-ESM1-6/.../MyRun \
321+
--cache-dir /scratch/tm70/$USER/moppy-esmval-cache \
322+
--workers 8 \
323+
--esmvaltool-args "--max-parallel-tasks 4"
324+
325+
For very large variable sets, combine with the existing ``moppy-cmorise``
326+
PBS batch system to CMORise first, then run ESMValTool against the output:
327+
328+
.. code-block:: bash
329+
330+
# 1. CMORise with PBS (schedules a job per variable)
331+
moppy-cmorise my_batch_config.yml
332+
333+
# 2. Once jobs finish, point ESMValTool at the output
334+
moppy-esmval-prepare my_recipe.yml \
335+
--input-root /g/data/.../MyRun \
336+
--cache-dir /scratch/tm70/$USER/moppy-output \
337+
--dry-run # nothing to CMORise — will just write config file
338+
339+
esmvaltool run my_recipe.yml
340+
341+
Troubleshooting
342+
---------------
343+
344+
**"No supported ACCESS-ESM datasets found in recipe"**
345+
346+
Check that your recipe includes ``project: CMIP6`` and
347+
``dataset: ACCESS-ESM1-5`` or ``dataset: ACCESS-ESM1-6`` in the datasets block.
348+
349+
**"No raw files found for 'Amon.xxx'"**
350+
351+
The default glob patterns may not match your archive layout. Use
352+
``--pattern`` to override, and add ``-v`` to see the exact paths being
353+
searched.
354+
355+
**"No mapping found for 'Amon.xxx'"**
356+
357+
The variable is not yet supported in the ACCESS-ESM1.6 mapping file.
358+
Check ``access_moppy.esmval.VariableIndex().all_compound_names()`` for
359+
the full list of supported variables.
360+
361+
**ESMValTool cannot find the CMORised data**
362+
363+
Verify that ``--cache-dir`` in the prepare step matches the ``rootpath``
364+
in ``~/.config/esmvaltool/moppy-esmval-data.yml``. If you wrote the
365+
config file to a non-default location, set the ``ESMVALTOOL_CONFIG_DIR``
366+
environment variable to that directory before calling
367+
``esmvaltool run``.
368+
369+
API Reference
370+
-------------
371+
372+
.. autoclass:: access_moppy.esmval.recipe_reader.RecipeReader
373+
:members:
374+
:undoc-members:
375+
376+
.. autoclass:: access_moppy.esmval.recipe_reader.CMORTask
377+
:members:
378+
379+
.. autoclass:: access_moppy.esmval.variable_mapper.VariableIndex
380+
:members:
381+
:undoc-members:
382+
383+
.. autoclass:: access_moppy.esmval.file_finder.RawFileFinder
384+
:members:
385+
:undoc-members:
386+
387+
.. autoclass:: access_moppy.esmval.orchestrator.CMORiseOrchestrator
388+
:members:
389+
:undoc-members:
390+
391+
.. autofunction:: access_moppy.esmval.config_gen.write_esmval_config
392+
393+
.. autofunction:: access_moppy.esmval.config_gen.merge_into_existing_config

0 commit comments

Comments
 (0)