feat: integrate earthaccess for cloud-native NASA Earthdata streaming - #92
Draft
drnimbusrain wants to merge 7 commits into
Draft
Conversation
Adds a Python-layer bridge that lets CECE source any input data stream directly from NASA Earthdata Cloud via earthaccess/fsspec, with no local file staging required. New files: - src/python/earthaccess_resolver.py -- EarthAccessStreamConfig dataclass and EarthAccessStreamResolver; authenticates against NASA EDL once then opens granules as fsspec file-like objects via earthaccess.open(). - src/python/stream_bridge.py -- EarthAccessStreamBridge; slices remote xr.Datasets by nearest timestep and injects float64 arrays into CeceImportState via the existing pybind11 set_field path. - examples/cece_config_earthaccess.yaml -- worked example running MEGAN3, BDSNP, and Fengsha from MODIS LAI, SMAP soil moisture, CERES PAR, ERA5 wind, and MCD12Q1 land-cover; all streams use source: earthaccess. Modified files: - src/python/config.py -- _from_dict now routes source: earthaccess streams into _cece_data["earthaccess_streams"] instead of AMIO; adds earthaccess_streams property and parse_earthaccess_streams() module-level helper. - src/python/__init__.py -- exports EarthAccessStreamConfig, EarthAccessStreamResolver, EarthAccessStreamBridge, parse_earthaccess_streams. - pyproject.toml -- adds [cloud] optional-dependency group: earthaccess>=0.12, xarray, h5netcdf, fsspec, s3fs. Backward compatible: existing file-path streams continue to use the unmodified AMIO/netcdf4 path. Cloud deps are optional (pip install 'cece-tools[cloud]'); a clear ImportError is raised if missing.
37 tests, 5 skipped (5 require live EDL credentials or pybind11 build)
0 failures.
New files:
- tests/test_earthaccess_stream_bdsnp_megan3.py
11 test classes covering the full earthaccess streaming stack:
1. EarthAccessStreamConfig dataclass unit tests
2. parse_earthaccess_streams() helper
3. CeceConfig routing (source:earthaccess vs AMIO path)
4. EarthAccessStreamResolver with mocked earthaccess
5. EarthAccessStreamBridge.inject_at_time() end-to-end
6. BDSNP soil-NO fields on HEMCO 72x46 4x5 grid (mirrors PR ufs-community#85)
7. MEGAN3 isoprene fields on HEMCO 72x46 4x5 grid (mirrors PR ufs-community#90)
8. Combined BDSNP+MEGAN3 simultaneous injection
9. ImportError guard for missing cloud extras
10. cece_config_earthaccess_4x5_test.yaml round-trip parse
11. Live Earthdata smoke tests (skipped without EDL credentials)
- tests/cece_config_earthaccess_4x5_test.yaml
Test config matching the exact HEMCO 4x5 grid from PRs ufs-community#85/ufs-community#90:
72 lon x 46 lat, +-89 polar centres, one 3600-s step.
MODIS MCD15A2H (LAI) + SMAP SPL4SMGP (soil) + CERES PAR streams.
Modified:
- src/python/stream_bridge.py -- try/except for relative import so
module loads standalone without package context (required by tests)
- src/python/config.py -- same try/except on earthaccess_resolver
import for standalone test loading
- examples/cece_config_earthaccess.yaml -- grid updated from F360 to
HEMCO_4x5 to align with parity grids from PRs ufs-community#85 and ufs-community#90
Collaborator
|
I like the idea of this. I think we should also build in methods utilizing grib2io (followup PR) to directly stream met data from NOAA forecasts. |
…1 oracle Adds scripts/run_bdsnp_megan3_4x5_global_test.py, a driver that evaluates CECE's checked-in native MEGAN3 (cece_megan3.cpp, cece_megan.hpp, cece_emission_activity.cpp) and BDSNP (cece_bdsnp.cpp) equations directly in NumPy on the 72x46 HEMCO 4x5 grid from tests/cece_config_earthaccess_4x5_test.yaml, using synthetic global inputs standing in for the MODIS/SMAP/CERES earthaccess streams (no Kokkos/ESMF build or live NASA EDL credentials available in this environment). Produces global isoprene and soil-NO maps, a percent-difference map and zonal-mean comparison against the HEMCO 3.12.1 MEGAN source oracle from PR ufs-community#90, a 16-case scalar validation table, and a summary report (results/bdsnp_megan3_4x5_test/). The oracle self-check reproduces PR ufs-community#90's reference CSV exactly (0 error); against it, CECE's current MEGAN3 kernel diverges substantially because it hard-codes T_AVG_15=297K, PAR_AVG=400, DOY=180, and a 30-day LAI age interval instead of HEMCO's cold-start convention, and uses LDF=0.9996 vs HEMCO's 1.0. Also carries in tests/data/hemco_megan/{README.md, hemco_3_12_1_megan_reference.csv} from PR ufs-community#90 (feat/hemco-megan-3121-parity) as comparison reference data only; that PR is not merged here. No HEMCO reference exists for BDSNP anywhere in the repo, so BDSNP is reported on its own, cross-checked only against its documented freezing behavior (soil T <= 0C -> 0 emission).
…list Addresses all five open items from the PR ufs-community#92 conversation: - Validate short_name against CMR at config-parse time: new validate_short_name()/validate_short_names() in earthaccess_resolver.py query earthaccess.search_datasets() and warn (never raise) before the run starts instead of failing at the first timestep. Opt-in via cece_data.validate_earthaccess_short_names: true so config parsing never requires network access by default. - Auto-derive bounding_box from driver grid extents: CeceConfig now parses driver.grid and both CeceConfig._from_dict and parse_earthaccess_streams() fill in an unset stream bounding_box from it; explicit bounding_box values are left untouched. - Evaluated earthaccess.virtualize()/open_virtual_mfdataset (VirtualiZarr / DMR++) as an open_mfdataset alternative: added opt-in EarthAccessStreamConfig.use_virtual (virtual: true in YAML). Not made the default because DMR++ sidecars aren't guaranteed for the DAAC collections this module targets; open_as_xarray falls back to open_mfdataset with a warning if it's unsupported or fails. - Added block_size/cache_type fsspec tuning knobs to EarthAccessStreamConfig, forwarded to earthaccess.open() with a graceful fallback (+ warning) for earthaccess versions that reject them. - Added unit tests that mock only earthaccess.login/search_data/open and read genuine local NetCDF4 fixture files through the real xr.open_mfdataset(engine="h5netcdf") path, so CI can exercise real file I/O without live EDL credentials. Also exports validate_short_name/validate_short_names from the cece package __init__, and adds dask to the [cloud] extras (required by open_mfdataset).
…ntax) - scripts/run_bdsnp_megan3_4x5_global_test.py: csv.DictWriter now sets lineterminator="\n" explicitly, since csv's default "excel" dialect always writes CRLF regardless of file mode; regenerated scalar_case_validation.csv with LF endings. - tests/test_earthaccess_stream_bdsnp_megan3.py: replaced parenthesized multi-context-manager `with (...)` statements (Python 3.9+ syntax) with backslash-continued `with a, b:` form, since ruff's configured target is Python 3.8 (pyproject.toml requires-python>=3.8) and rejected the newer syntax as invalid; added `# noqa: E402` to two intentionally late imports required by the module-loading bootstrap. - Applied ruff-format's auto-formatting across the touched Python files. Verified with `pre-commit run` on all touched files (all hooks pass) and `pytest tests/test_earthaccess_stream_bdsnp_megan3.py` (63 passed, 5 skipped).
examples/cece_config_earthaccess.yaml and tests/cece_config_earthaccess_4x5_test.yaml had manually aligned colons and inline-comment spacing that prettier's YAML formatter normalizes (single space after ':' and before trailing '#'). Ran `pre-commit run prettier --all-files` and applied its output; whitespace-only, no semantic change. Verified with `pre-commit run --all-files` (all hooks pass) and `pytest tests/test_earthaccess_stream_bdsnp_megan3.py` (63 passed, 5 skipped).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
CECE's current data pipeline requires every input stream (LAI, meteorology, soil properties, PAR, etc.) to be pre-staged as local NetCDF files before a run can start. This creates three compounding operational burdens:
earthaccessis a NASA-supported Python library that authenticates against NASA Earthdata Login (EDL) once and returnsfsspecfile-like objects backed by S3 (in-region) or HTTPS (anywhere).netCDF4/h5netcdfandxarraycan read these objects identically to local files — zero bytes hit the local drive unless the user explicitly asks for a download.This PR adds a thin Python-layer bridge that lets any
cece_datastream declaresource: earthaccess, offloading granule discovery and credentialed streaming to earthaccess while leaving the C++ core and AMIO path completely unchanged.Architecture
The pybind11
set_fieldinjection path already exists; this PR adds nothing to the C++ layer.Files Changed
New files
src/python/earthaccess_resolver.pyEarthAccessStreamConfigdataclass (CMR search params) andEarthAccessStreamResolver(auth → search → open → xr.Dataset)src/python/stream_bridge.pyEarthAccessStreamBridge— per-timestep nearest-time slice and field injection intoCeceImportStateexamples/cece_config_earthaccess.yamlModified files
src/python/config.py_from_dictroutessource: earthaccessstreams to_cece_data["earthaccess_streams"]; addsearthaccess_streamsproperty andparse_earthaccess_streams()module-level helpersrc/python/__init__.pyEarthAccessStreamConfig,EarthAccessStreamResolver,EarthAccessStreamBridge,parse_earthaccess_streamspyproject.toml[cloud]optional-dependency group (earthaccess,xarray,h5netcdf,fsspec,s3fs)Example:
cece_config_earthaccess.yamlDemonstrates running MEGAN3, BDSNP, and Fengsha driven entirely by live NASA Earthdata Cloud streams — no local NetCDF files required.
One-time credential setup
Install cloud extras
pip install 'cece-tools[cloud]'Stream-to-physics-scheme mapping
modis_laileaf_area_indexsmap_soilsoil_moisture_root,soil_moisturesmap_soiltempsoil_temperatureceres_parpar_direct,par_diffuse,solar_cosineera5_windu_wind,v_windsoil_textureland_use_typeKey config section
Python usage (pybind11 path)
Design decisions
set_fieldpybind11 injection path already existsengine="h5netcdf"scipyrequires seekable local pathsearthaccess.open()notdownload()source: earthaccessopt-in key[cloud]optional-dep groupnp.asfortranarraybeforeset_fieldLayoutLeft(column-major) convention used by all CECE import state fieldsOpen items / follow-up
short_nameagainst CMR at config-parse time (warn before run starts rather than at first timestep)bounding_boxfrom driver grid extents when not set explicitlyearthaccess.virtualize()(VirtualiZarr / DMR++) as an alternative toopen_mfdatasetfor large granule countsblock_size/cache_typefsspec tuning knobs toEarthAccessStreamConfigearthaccess.open()with local fixture files to allow CI without live EDL credentialsTests added (
tests/test_earthaccess_stream_bdsnp_megan3.py)Result: 37 passed, 5 skipped, 0 failures (run:
pytest tests/test_earthaccess_stream_bdsnp_megan3.py -v)All tests run without NASA EDL credentials and without a CECE build. The 5 skipped tests are annotated: 2 require the pybind11 build (
requires_cece_core), 3 require live EDL credentials (live_earthdata).Test classes
TestEarthAccessStreamConfigTestParseEarthAccessStreamsparse_earthaccess_streams()— empty, AMIO-only, mixed, earthaccess-onlyTestCeceConfigEarthAccessRouting_from_dictsendssource:earthaccesstoearthaccess_streams; AMIO streams untouchedTestEarthAccessStreamResolverMockedopen_as_xarray()with mocked earthaccess; no-granuleRuntimeErrorTestEarthAccessStreamBridgeMockedinject_at_time()populates state; Fortran-contiguous; float64; multi-streamTestBDSNPFieldInjectionOnHemcoGrid(46, 72), T∈[200–340 K], SM∈[0–1], 24-h timestep advance. Mirrors PR #85 parity grid.TestMEGAN3FieldInjectionOnHemcoGrid(46, 72); Fortran-contiguous; 5-timestep advance. Mirrors PR #90 parity grid.TestCombinedBdsnpMegan3Injectioninject_at_timecallTestEarthAccessImportErrorImportError+pip install 'cece-tools[cloud]'hint when cloud extras absentTestExampleConfigParsingtests/cece_config_earthaccess_4x5_test.yamlround-trip: file exists, parses, yields earthaccess streams, has bothbdsnpandmegan3schemesTestLiveEarthDataIntegrationpytest -m live_earthdataNew test config:
tests/cece_config_earthaccess_4x5_test.yamlMatches the exact HEMCO 4°×5° reference grid from PR #85 (SoilNOx parity) and PR #90 (MEGAN isoprene parity):
megan3+bdsnp(soil_no_method: hemco_3_12_1)Cross-over with PRs #85 and #90
The BDSNP grid tests assert input field shape
(46, 72)and physical ranges matching the HEMCO 3.12.1 MERRA-2 soil inputs used in PR #85. The MEGAN3 grid tests assert all six import fields used by the PR #90 parity simulation are present, correctly shaped, and within physical range before reaching the C++ kernel. The combined injection test verifies all schemes can be fed simultaneously from a singleinject_at_timecall — the same execution pattern as a live CECE driver timestep.Source fixes required for standalone test loading
src/python/stream_bridge.py:try/excepton relativefrom .earthaccess_resolver importso the module loads without package context in the test runnersrc/python/config.py: sametry/exceptonfrom .earthaccess_resolver import EarthAccessStreamConfigRunning