Repository navigation
feat: read and write npz methods to replace read_pickle - #1908
Conversation
Adds: - `read_zarr` and `write_zarr` methods which fall back to pickle if no zarr store is available. - Warnings that read_pickle methods will be deprecated for future GlacierDirectories. - Zarr as a core dependency. Refs: OGGM#1903
|
Thanks! Looking promising A few quick thoughts:
|
|
Notes based on internal meeting:
Next on agenda.
Agreed. model_flowlines: Centerline = Centerline(...)
write_store(model_flowlines, "model_flowlines")
model_flowlines: Centerline = read_store("model_flowlines")where internally def write_store(self, obj, filename, filesuffix, **kwargs):
data: xr.DataTree = _validate_store(obj) # converts shapely objects etc. into zarr-compatible types
write_zarr(data: xr.DataTree=data, group:str=filename, **kwargs)
Zarr-related code will be placed under |
Adds:
- Validation for different zarr/pickle stores to maintain backwards
compatibility.
- Dedicated module for zarr-related operations.
- Conversion between existing pickles and zarr.
- Reconstruction of OGGM objects from minimum possible data.
Refactors:
- Replaces all instances of `read_pickle` with `read_store`.
Compatibility is maintained
Currently writes each data set to a separate file instead of a larger data store
ON-60 write data to zarr
Falls back to pickle if zarr r/w fails Refs: ON-60
… into ON-60-write-data-to-zarr
|
Failing tests:
|
|
@fmaussion this is ready for deployment. |
Workaround for datetime64 at ns, which uses an int that can't be decoded without a unit.
| "scipy", | ||
| "shapely", | ||
| "xarray", | ||
| "xvec>=0.5.2", |
| ``read_store`` and ``write_store``. The latter uses npz files inside glacier | ||
| directories instead of pickles. Users should migrate to the new functions, as | ||
| compatibility with pickles is not guaranteed in the future. (:pull:`1908`). | ||
| By `Nicolas Gampierakis <https://github.com/gampnico>`_. |
There was a problem hiding this comment.
This needs to be in the 1.X section above now
|
Excellent work, thanks @gampnico - two minor things in the code, and one last medium thing. Unless my AI is mistaken, we need two additional small checks: 1. 2. fls = gdir.read_store('model_flowlines')
gdir.write_pickle(fls[:1], 'model_flowlines', filesuffix='_test') # pre-existing pickle
gdir.write_store(fls, 'model_flowlines', filesuffix='_test') # npz written, .pkl kept
len(gdir.read_store('model_flowlines', '_test')) # 3
len(gdir.read_pickle('model_flowlines', filesuffix='_test')) # 1 <- staleThe missing file at least raises an error; the stale pickle is the issue Suggested fix, mirroring what
A regression test for the snippet above would be good too. Almost ready! |
|
For point 1:
This is intentional behaviour, and I've already opened a PR for the tutorials: OGGM/tutorials#208 . If you like I could add a message with We can maybe chat about point 2 later today. If the default is if os.path.isfile(npz_fp) and delete:
# npz write succeeded, drop the now-redundant pickle
os.remove(fp)But this is done with an explicit argument, and I'm not sure if it's safe to do so silently. What do you think?
I'm not sure what this is referring to, My interpretation with This depends on how you want this phased out, and where you want to introduce a breaking change in OGGM's release cycle. If you want something like:
|
|
Good points! Thanks Nic. I guess my first reaction when I thought about it was: "Since the objects in the background didn't change, why not keep read_pickle working". Thinking about PyGEM and other "wild" OGGM code in particular, the change to npz is just a small annoyance for them. But PyGEM uses read_pickle only 9 times: https://github.com/search?q=repo%3APyGEM-Community%2FPyGEM+read_pickle&type=code So (1) and (2) could be solved in one go if we say read_pickle simply is meant to disappear: it errors if any npz file is available in the dir, and write_pickle can simply disappear entirely? We can discuss later today. |
|
Based on our discussion today, |
|
We are good to go here, right? |
|
Yes, all good to go! |
|
Apologies for the accidental push, this is now ready to merge. |
|
Thanks @gampnico ! Great to see this in |
* allow prcp fac fixed with new temp_bias system * revert unnecessary changes in PR #1908 --------- Co-authored-by: Fabien Maussion <Fabien Maussion>
This PR currently adds:
read_store,read_npzandwrite_npzmethods which fall back to pickle if no npz files are available.transcoder.pythat handles transcoding between existing pickles and the new npz files._compat.py, which would be re-used across Feat: incremental glacier directory creation #1951 and feat: stream glacier directories #1966 for reproducible conversion.NB: This PR's design has changed substantially and has moved to using npz instead of zarr for performance.
Closes: #1903
Points for discussion
All pickles are now stored as npz files in a glacier directory's subdirectory, which I'm currently naming "data_store". Zarr was too inefficient for such small data.
The default replacement for
read_pickleis nowread_store, which handles both pickle (read_pickle) and numpy (read_npz).read_storefalls back toread_pickleif no npz file exists. This preserves backwards compatibility with existing gdirs if a user selects an older URL.I've designed this as a type registry, rather than mapping to basenames, as it's less brittle, and I don't think it would prevent basename mapping later on. This way we only need to make changes when Python/numpy introduces or deprecates a type (so basically never), rather than when we change or add a new OGGM object.
Zarr does not support all the data structures used by certain pickles (e.g. shapely objects). Previous zarr commits converted these to a compatible type. When using
read_store, objects are converted back into the type expected by OGGM. This should minimise rewrites across the rest of the codebase. However, zarr-dependent packages (e.g. DTCG) will require compatibility wrappers.Closes #1903
whats-new.rstKeeping this section for posterity, to help explain how the design decisions evolved.
All pickles are now stored as
xr.DataTreesin a single zarr file, which I'm currently naming "data_store". The filename inread_zarr(filename)therefore reads the group, rather than entire zarr file. This avoids having multiple small zarr files, and we can maintain cross-compatibility with DTCG.The default replacement for
read_pickleis nowread_store, which handles both pickle (read_pickle) and zarr (read_zarr).read_storefalls back toread_pickleif no zarr store exists. This preserves backwards compatibility with existing gdirs if a user selects an older URL.Zarr does not support all the data structures used by certain pickles (e.g. shapely objects). oggm-zarr converts these to a compatible type. When using
read_store, these objects are converted back into the type expected by OGGM. This should minimise rewrites across the rest of the codebase.I'd like to upload a small sample zarr to oggm-sample-data, so this can be included in
init_hef, and I can then add tests for reading a zarr from a gdir directly.I'm currently keeping code for converting from pickle to zarr as a dual-licensed package, as some of this includes code from DTCG which is not compatible with OGGM's license. I'll see if I can rework this, but it may be simpler to handle conversion via
dtcg, since this is already set up and we can build directly on the existingGeoZarrHandler.You can view a conversion workflow here.