gridpack 0.5.2__tar.gz → 0.5.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,37 +1,37 @@
1
- Metadata-Version: 2.4
2
- Name: gridpack
3
- Version: 0.5.2
4
- Summary: Lossless compression for gridded scientific data (ERA5 and friends)
5
- Author: gridpack contributors
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/showkeyjar/gridpack
8
- Project-URL: Source, https://github.com/showkeyjar/gridpack
9
- Project-URL: Issues, https://github.com/showkeyjar/gridpack/issues
10
- Keywords: era5,netcdf,hdf5,lossless-compression,gridded-data,climate,meteorology,xarray
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Intended Audience :: Science/Research
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
16
- Classifier: Topic :: System :: Archiving :: Compression
17
- Requires-Python: >=3.9
18
- Description-Content-Type: text/markdown
19
- License-File: LICENSE
20
- Requires-Dist: numpy>=1.21
21
- Provides-Extra: io
22
- Requires-Dist: h5py>=3.0; extra == "io"
23
- Provides-Extra: zstd
24
- Requires-Dist: zstandard>=0.18; extra == "zstd"
25
- Provides-Extra: xarray
26
- Requires-Dist: xarray>=2023.1; extra == "xarray"
27
- Requires-Dist: h5netcdf>=1.0; extra == "xarray"
28
- Provides-Extra: all
29
- Requires-Dist: h5py>=3.0; extra == "all"
30
- Requires-Dist: zstandard>=0.18; extra == "all"
31
- Requires-Dist: xarray>=2023.1; extra == "all"
32
- Requires-Dist: h5netcdf>=1.0; extra == "all"
33
- Dynamic: license-file
34
-
1
+ Metadata-Version: 2.4
2
+ Name: gridpack
3
+ Version: 0.5.4
4
+ Summary: Lossless compression for gridded scientific data (ERA5 and friends)
5
+ Author: gridpack contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/showkeyjar/gridpack
8
+ Project-URL: Source, https://github.com/showkeyjar/gridpack
9
+ Project-URL: Issues, https://github.com/showkeyjar/gridpack/issues
10
+ Keywords: era5,netcdf,hdf5,lossless-compression,gridded-data,climate,meteorology,xarray
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
16
+ Classifier: Topic :: System :: Archiving :: Compression
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: numpy>=1.21
21
+ Provides-Extra: io
22
+ Requires-Dist: h5py>=3.0; extra == "io"
23
+ Provides-Extra: zstd
24
+ Requires-Dist: zstandard>=0.18; extra == "zstd"
25
+ Provides-Extra: xarray
26
+ Requires-Dist: xarray>=2023.1; extra == "xarray"
27
+ Requires-Dist: h5netcdf>=1.0; extra == "xarray"
28
+ Provides-Extra: all
29
+ Requires-Dist: h5py>=3.0; extra == "all"
30
+ Requires-Dist: zstandard>=0.18; extra == "all"
31
+ Requires-Dist: xarray>=2023.1; extra == "all"
32
+ Requires-Dist: h5netcdf>=1.0; extra == "all"
33
+ Dynamic: license-file
34
+
35
35
  # gridpack
36
36
 
37
37
  **Lossless compression for gridded scientific data (ERA5 and friends).**
@@ -43,7 +43,7 @@ except Exception: # pragma: no cover
43
43
  from_xarray = None
44
44
  HAVE_XARRAY = False
45
45
 
46
- __version__ = "0.5.2"
46
+ __version__ = "0.5.4"
47
47
 
48
48
  __all__ = [
49
49
  "codec", "container", "entropy", "lattice", "netcdf_io",
@@ -163,8 +163,8 @@ def main(argv=None):
163
163
  b.set_defaults(func=cmd_bench)
164
164
 
165
165
  args = p.parse_args(argv)
166
- args.chunk_t = args.chunk_t or None
167
- args.file_chunk = args.file_chunk or None
166
+ args.chunk_t = getattr(args, "chunk_t", None) or None
167
+ args.file_chunk = getattr(args, "file_chunk", None) or None
168
168
  return args.func(args) or 0
169
169
 
170
170
 
@@ -1,191 +1,191 @@
1
- """gridpack.lattice -- 精确格点表示层。
2
-
3
- 很多格点数据(如 ERA5 各要素)的浮点值并不是任意的 IEEE-754 数,而是**全部落在同一个
4
- 二进制格点 ``2**E`` 上**(例如 10m 风场量化到 0.0625 = 2**-4,总降水量化到 2**-14)。
5
- 此时可以把浮点无损改写成整数 ``k = v / 2**E``:整数的动态范围比原始 32/64 位位型小得多,
6
- 后续的时间/空间预测残差更小、字节平面更干净,熵编码收益很大。
7
-
8
- 判定是**精确**的,不做任何近似:
9
-
10
- * float32 非零值可写成 ``v = sig * 2**(exp-150)``,``sig`` 是 24 位有效数(正规数为
11
- ``mant | 0x800000``,次正规数为 ``mant``,此时指数固定为 -149)。注意 ``exp`` 必须
12
- 先掩掉符号位(``(u >> 23) & 0xFF``),否则负值的指数会被多算 256。
13
- * ``v`` 是 ``2**E`` 的整数倍 ⟺ ``tz(sig) + exp - 150 >= E``(``tz`` 为二进制尾零个数)。
14
- * 因此全体取值可用的**最大**格点指数就是 ``E_max = min(tz(sig) + exp - 150)``。
15
-
16
- 取 ``E = E_max`` 是精确无损的;取更小的 ``E`` 同样无损(只是整数更大)。本模块在返回
17
- ``E`` 之前还会做两道闸门:整数必须能安全放进 int32(给 zig-zag 与一阶差分留余量),
18
- 并且会**逐块回代验证** ``v / 2**E`` 确实是整数;任一不满足就返回 ``None``,
19
- 由调用方回退到保序映射路径。
20
-
21
- 内存:按 ``block``(默认 400 万个值)分块统计,峰值与整个数组大小无关。
22
-
23
- 负零(``-0.0``)与正零位型不同而整数表示无法区分,故单独用稀疏位图记录,
24
- 保证解码后**逐位一致**。
25
- """
26
-
27
- from __future__ import annotations
28
-
29
- import zlib
30
-
31
- import numpy as np
32
-
33
- __all__ = ["detect_lattice", "verify_lattice", "lattice_encode", "lattice_decode",
34
- "negative_zero_mask", "restore_negative_zero", "INT_WIDTH"]
35
-
36
- # 格点整数一律用 int32 承载(动态范围已由 detect_lattice 限定在 2**29 以内)
37
- INT_WIDTH = 4
38
- INT_DTYPE = np.dtype("i4")
39
- UINT_DTYPE = np.dtype("u4")
40
-
41
- # 整数绝对值上限:给 zig-zag(翻倍)与一阶差分(再翻倍)留出余量
42
- _K_LIMIT = 2.0 ** 29
43
-
44
- # 分块大小(元素个数)。400 万 int64 约 32 MB,避免大文件上出现 GB 级瞬时峰值。
45
- BLOCK = 4_000_000
46
-
47
-
48
- _TZ16 = np.zeros(65536, dtype=np.int32)
49
- _IDX16 = np.arange(65536, dtype=np.uint32)
50
- for _k in range(16):
51
- _TZ16[(_IDX16 & ((1 << (_k + 1)) - 1)) == (1 << _k)] = _k
52
- del _IDX16
53
-
54
-
55
- def _trailing_zeros(x: np.ndarray, nbits: int) -> np.ndarray:
56
- """向量化二进制尾零个数(x 为非零非负整数数组)。
57
-
58
- nbits<=32 走 16 位查表(两级),否则退回逐级折半。
59
- 两条路径结果完全一致;查表版实测快约 3 倍。
60
- """
61
- if nbits <= 32:
62
- lo = x & np.array(0xFFFF, dtype=x.dtype)
63
- hi = (x >> np.array(16, dtype=x.dtype)) & np.array(0xFFFF, dtype=x.dtype)
64
- return np.where(lo != 0, _TZ16[lo],
65
- np.where(hi != 0, _TZ16[hi] + np.array(16, dtype=np.int32),
66
- np.array(nbits, dtype=np.int32)))
67
- n = np.zeros(x.shape, dtype=np.int64)
68
- y = x.astype(np.int64).copy()
69
- steps = []
70
- k = 1
71
- while k < nbits:
72
- steps.append(k)
73
- k *= 2
74
- for k in reversed(steps):
75
- mask = (1 << k) - 1
76
- z = (y & mask) == 0
77
- n += np.where(z, k, 0)
78
- y = np.where(z, y >> k, y)
79
- return n
80
-
81
-
82
- def _block_exp(part: np.ndarray) -> int:
83
- """单个子块(全为非零有限值)的 min(tz(sig) + base)。"""
84
- if part.dtype == np.float32:
85
- u = part.view(np.uint32)
86
- exp = ((u >> np.uint32(23)) & np.uint32(0xFF)).astype(np.int32)
87
- mant = (u & np.uint32(0x7FFFFF)).astype(np.int32)
88
- sig = np.where(exp == 0, mant, mant | np.int32(0x800000)).astype(np.int32)
89
- base = np.where(exp == 0, np.int32(-149), exp - np.int32(150)).astype(np.int32)
90
- nbits = 24
91
- else:
92
- u = part.view(np.uint64)
93
- exp = ((u >> np.uint64(52)) & np.uint64(0x7FF)).astype(np.int64)
94
- mant = (u & np.uint64(0xFFFFFFFFFFFFF)).astype(np.int64)
95
- sig = np.where(exp == 0, mant, mant | np.int64(1 << 52))
96
- base = np.where(exp == 0, np.int64(-1074), exp - np.int64(1075)).astype(np.int64)
97
- nbits = 53
98
- return int((_trailing_zeros(sig, nbits) + base).min())
99
-
100
-
101
- def verify_lattice(flat: np.ndarray, e: int, block: int = BLOCK) -> bool:
102
- """逐块确认每个值都精确等于整数 k * 2**e(2 的幂缩放本身无舍入误差)。
103
-
104
- 这是防止"指数算大、静默丢精度"这类错误的最后一道闸门。
105
- """
106
- scale = float(2.0 ** (-e))
107
- for s in range(0, flat.size, block):
108
- p = flat[s:s + block].astype(np.float64) * scale
109
- if not np.array_equal(np.rint(p), p):
110
- return False
111
- return True
112
-
113
-
114
- def detect_lattice(a: np.ndarray, block: int = BLOCK, verify: bool = True):
115
- """返回全体取值共同格点的最大指数 E(int),无法使用格点表示时返回 None。"""
116
- v = np.ascontiguousarray(a)
117
- if v.dtype not in (np.float32, np.float64):
118
- return None
119
- flat = v.reshape(-1)
120
- n = flat.size
121
- if n == 0:
122
- return None
123
-
124
- e_best = None
125
- maxabs = 0.0
126
- for s in range(0, n, block):
127
- part = flat[s:s + block]
128
- if not np.isfinite(part).all():
129
- return None
130
- nz = part != 0
131
- if not nz.any():
132
- continue
133
- p = np.ascontiguousarray(part[nz])
134
- m = float(np.abs(p).max())
135
- if m > maxabs:
136
- maxabs = m
137
- e = _block_exp(p)
138
- e_best = e if e_best is None else min(e_best, e)
139
- del p, nz, part
140
-
141
- if e_best is None:
142
- return None # 全零场:整数表示无意义
143
- if not (-1074 <= e_best <= 40):
144
- return None
145
- if maxabs / (2.0 ** e_best) > _K_LIMIT:
146
- return None # 整数会溢出 int32,放弃格点路径
147
- if verify and not verify_lattice(flat, e_best, block):
148
- return None # 回代不通过,宁可退保序映射也不出错
149
- return e_best
150
-
151
-
152
- def lattice_encode(a: np.ndarray, e: int) -> np.ndarray:
153
- """浮点 -> 整数 k = v / 2**e(精确,无舍入误差)。"""
154
- v = np.ascontiguousarray(a)
155
- scale = float(2.0 ** (-e))
156
- if v.dtype == np.float32:
157
- q = np.rint(v.astype(np.float64) * scale)
158
- else:
159
- q = np.rint(v * scale)
160
- return q.astype(np.int32)
161
-
162
-
163
- def lattice_decode(k: np.ndarray, e: int, dtype) -> np.ndarray:
164
- """整数 -> 浮点 v = k * 2**e。2 的幂缩放与 int32 取值范围在 float64 中都是精确的。"""
165
- scale = float(2.0 ** e)
166
- q = np.ascontiguousarray(k).astype(np.int64).astype(np.float64) * scale
167
- return q.astype(dtype)
168
-
169
-
170
- def negative_zero_mask(a: np.ndarray) -> bytes:
171
- """记录 -0.0 的位置(稀疏位图,zlib 压缩);没有 -0.0 时返回 b""。"""
172
- v = np.ascontiguousarray(a)
173
- if v.dtype == np.float32:
174
- m = v.view(np.uint32) == np.uint32(0x80000000)
175
- elif v.dtype == np.float64:
176
- m = v.view(np.uint64) == np.uint64(0x8000000000000000)
177
- else:
178
- return b""
179
- m = m.reshape(-1)
180
- if not m.any():
181
- return b""
182
- return zlib.compress(np.packbits(m).tobytes(), 6)
183
-
184
-
185
- def restore_negative_zero(v: np.ndarray, nz: bytes, dtype) -> np.ndarray:
186
- """按位图把 -0.0 的符号位恢复回去(原地)。"""
187
- if not nz:
188
- return v
189
- mask = np.unpackbits(np.frombuffer(zlib.decompress(nz), dtype=np.uint8))[:v.size].astype(bool)
190
- v.reshape(-1)[mask] = np.dtype(dtype).type(-0.0)
191
- return v
1
+ """gridpack.lattice -- 精确格点表示层。
2
+
3
+ 很多格点数据(如 ERA5 各要素)的浮点值并不是任意的 IEEE-754 数,而是**全部落在同一个
4
+ 二进制格点 ``2**E`` 上**(例如 10m 风场量化到 0.0625 = 2**-4,总降水量化到 2**-14)。
5
+ 此时可以把浮点无损改写成整数 ``k = v / 2**E``:整数的动态范围比原始 32/64 位位型小得多,
6
+ 后续的时间/空间预测残差更小、字节平面更干净,熵编码收益很大。
7
+
8
+ 判定是**精确**的,不做任何近似:
9
+
10
+ * float32 非零值可写成 ``v = sig * 2**(exp-150)``,``sig`` 是 24 位有效数(正规数为
11
+ ``mant | 0x800000``,次正规数为 ``mant``,此时指数固定为 -149)。注意 ``exp`` 必须
12
+ 先掩掉符号位(``(u >> 23) & 0xFF``),否则负值的指数会被多算 256。
13
+ * ``v`` 是 ``2**E`` 的整数倍 ⟺ ``tz(sig) + exp - 150 >= E``(``tz`` 为二进制尾零个数)。
14
+ * 因此全体取值可用的**最大**格点指数就是 ``E_max = min(tz(sig) + exp - 150)``。
15
+
16
+ 取 ``E = E_max`` 是精确无损的;取更小的 ``E`` 同样无损(只是整数更大)。本模块在返回
17
+ ``E`` 之前还会做两道闸门:整数必须能安全放进 int32(给 zig-zag 与一阶差分留余量),
18
+ 并且会**逐块回代验证** ``v / 2**E`` 确实是整数;任一不满足就返回 ``None``,
19
+ 由调用方回退到保序映射路径。
20
+
21
+ 内存:按 ``block``(默认 400 万个值)分块统计,峰值与整个数组大小无关。
22
+
23
+ 负零(``-0.0``)与正零位型不同而整数表示无法区分,故单独用稀疏位图记录,
24
+ 保证解码后**逐位一致**。
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import zlib
30
+
31
+ import numpy as np
32
+
33
+ __all__ = ["detect_lattice", "verify_lattice", "lattice_encode", "lattice_decode",
34
+ "negative_zero_mask", "restore_negative_zero", "INT_WIDTH"]
35
+
36
+ # 格点整数一律用 int32 承载(动态范围已由 detect_lattice 限定在 2**29 以内)
37
+ INT_WIDTH = 4
38
+ INT_DTYPE = np.dtype("i4")
39
+ UINT_DTYPE = np.dtype("u4")
40
+
41
+ # 整数绝对值上限:给 zig-zag(翻倍)与一阶差分(再翻倍)留出余量
42
+ _K_LIMIT = 2.0 ** 29
43
+
44
+ # 分块大小(元素个数)。400 万 int64 约 32 MB,避免大文件上出现 GB 级瞬时峰值。
45
+ BLOCK = 4_000_000
46
+
47
+
48
+ _TZ16 = np.zeros(65536, dtype=np.int32)
49
+ _IDX16 = np.arange(65536, dtype=np.uint32)
50
+ for _k in range(16):
51
+ _TZ16[(_IDX16 & ((1 << (_k + 1)) - 1)) == (1 << _k)] = _k
52
+ del _IDX16
53
+
54
+
55
+ def _trailing_zeros(x: np.ndarray, nbits: int) -> np.ndarray:
56
+ """向量化二进制尾零个数(x 为非零非负整数数组)。
57
+
58
+ nbits<=32 走 16 位查表(两级),否则退回逐级折半。
59
+ 两条路径结果完全一致;查表版实测快约 3 倍。
60
+ """
61
+ if nbits <= 32:
62
+ lo = x & np.array(0xFFFF, dtype=x.dtype)
63
+ hi = (x >> np.array(16, dtype=x.dtype)) & np.array(0xFFFF, dtype=x.dtype)
64
+ return np.where(lo != 0, _TZ16[lo],
65
+ np.where(hi != 0, _TZ16[hi] + np.array(16, dtype=np.int32),
66
+ np.array(nbits, dtype=np.int32)))
67
+ n = np.zeros(x.shape, dtype=np.int64)
68
+ y = x.astype(np.int64).copy()
69
+ steps = []
70
+ k = 1
71
+ while k < nbits:
72
+ steps.append(k)
73
+ k *= 2
74
+ for k in reversed(steps):
75
+ mask = (1 << k) - 1
76
+ z = (y & mask) == 0
77
+ n += np.where(z, k, 0)
78
+ y = np.where(z, y >> k, y)
79
+ return n
80
+
81
+
82
+ def _block_exp(part: np.ndarray) -> int:
83
+ """单个子块(全为非零有限值)的 min(tz(sig) + base)。"""
84
+ if part.dtype == np.float32:
85
+ u = part.view(np.uint32)
86
+ exp = ((u >> np.uint32(23)) & np.uint32(0xFF)).astype(np.int32)
87
+ mant = (u & np.uint32(0x7FFFFF)).astype(np.int32)
88
+ sig = np.where(exp == 0, mant, mant | np.int32(0x800000)).astype(np.int32)
89
+ base = np.where(exp == 0, np.int32(-149), exp - np.int32(150)).astype(np.int32)
90
+ nbits = 24
91
+ else:
92
+ u = part.view(np.uint64)
93
+ exp = ((u >> np.uint64(52)) & np.uint64(0x7FF)).astype(np.int64)
94
+ mant = (u & np.uint64(0xFFFFFFFFFFFFF)).astype(np.int64)
95
+ sig = np.where(exp == 0, mant, mant | np.int64(1 << 52))
96
+ base = np.where(exp == 0, np.int64(-1074), exp - np.int64(1075)).astype(np.int64)
97
+ nbits = 53
98
+ return int((_trailing_zeros(sig, nbits) + base).min())
99
+
100
+
101
+ def verify_lattice(flat: np.ndarray, e: int, block: int = BLOCK) -> bool:
102
+ """逐块确认每个值都精确等于整数 k * 2**e(2 的幂缩放本身无舍入误差)。
103
+
104
+ 这是防止"指数算大、静默丢精度"这类错误的最后一道闸门。
105
+ """
106
+ scale = float(2.0 ** (-e))
107
+ for s in range(0, flat.size, block):
108
+ p = flat[s:s + block].astype(np.float64) * scale
109
+ if not np.array_equal(np.rint(p), p):
110
+ return False
111
+ return True
112
+
113
+
114
+ def detect_lattice(a: np.ndarray, block: int = BLOCK, verify: bool = True):
115
+ """返回全体取值共同格点的最大指数 E(int),无法使用格点表示时返回 None。"""
116
+ v = np.ascontiguousarray(a)
117
+ if v.dtype not in (np.float32, np.float64):
118
+ return None
119
+ flat = v.reshape(-1)
120
+ n = flat.size
121
+ if n == 0:
122
+ return None
123
+
124
+ e_best = None
125
+ maxabs = 0.0
126
+ for s in range(0, n, block):
127
+ part = flat[s:s + block]
128
+ if not np.isfinite(part).all():
129
+ return None
130
+ nz = part != 0
131
+ if not nz.any():
132
+ continue
133
+ p = np.ascontiguousarray(part[nz])
134
+ m = float(np.abs(p).max())
135
+ if m > maxabs:
136
+ maxabs = m
137
+ e = _block_exp(p)
138
+ e_best = e if e_best is None else min(e_best, e)
139
+ del p, nz, part
140
+
141
+ if e_best is None:
142
+ return None # 全零场:整数表示无意义
143
+ if not (-1074 <= e_best <= 40):
144
+ return None
145
+ if maxabs / (2.0 ** e_best) > _K_LIMIT:
146
+ return None # 整数会溢出 int32,放弃格点路径
147
+ if verify and not verify_lattice(flat, e_best, block):
148
+ return None # 回代不通过,宁可退保序映射也不出错
149
+ return e_best
150
+
151
+
152
+ def lattice_encode(a: np.ndarray, e: int) -> np.ndarray:
153
+ """浮点 -> 整数 k = v / 2**e(精确,无舍入误差)。"""
154
+ v = np.ascontiguousarray(a)
155
+ scale = float(2.0 ** (-e))
156
+ if v.dtype == np.float32:
157
+ q = np.rint(v.astype(np.float64) * scale)
158
+ else:
159
+ q = np.rint(v * scale)
160
+ return q.astype(np.int32)
161
+
162
+
163
+ def lattice_decode(k: np.ndarray, e: int, dtype) -> np.ndarray:
164
+ """整数 -> 浮点 v = k * 2**e。2 的幂缩放与 int32 取值范围在 float64 中都是精确的。"""
165
+ scale = float(2.0 ** e)
166
+ q = np.ascontiguousarray(k).astype(np.int64).astype(np.float64) * scale
167
+ return q.astype(dtype)
168
+
169
+
170
+ def negative_zero_mask(a: np.ndarray) -> bytes:
171
+ """记录 -0.0 的位置(稀疏位图,zlib 压缩);没有 -0.0 时返回 b""。"""
172
+ v = np.ascontiguousarray(a)
173
+ if v.dtype == np.float32:
174
+ m = v.view(np.uint32) == np.uint32(0x80000000)
175
+ elif v.dtype == np.float64:
176
+ m = v.view(np.uint64) == np.uint64(0x8000000000000000)
177
+ else:
178
+ return b""
179
+ m = m.reshape(-1)
180
+ if not m.any():
181
+ return b""
182
+ return zlib.compress(np.packbits(m).tobytes(), 6)
183
+
184
+
185
+ def restore_negative_zero(v: np.ndarray, nz: bytes, dtype) -> np.ndarray:
186
+ """按位图把 -0.0 的符号位恢复回去(原地)。"""
187
+ if not nz:
188
+ return v
189
+ mask = np.unpackbits(np.frombuffer(zlib.decompress(nz), dtype=np.uint8))[:v.size].astype(bool)
190
+ v.reshape(-1)[mask] = np.dtype(dtype).type(-0.0)
191
+ return v