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.
- {gridpack-0.5.2 → gridpack-0.5.4}/PKG-INFO +34 -34
- {gridpack-0.5.2 → gridpack-0.5.4}/gridpack/__init__.py +1 -1
- {gridpack-0.5.2 → gridpack-0.5.4}/gridpack/cli.py +2 -2
- {gridpack-0.5.2 → gridpack-0.5.4}/gridpack/lattice.py +191 -191
- gridpack-0.5.4/gridpack.egg-info/PKG-INFO +365 -0
- gridpack-0.5.4/gridpack.egg-info/SOURCES.txt +20 -0
- gridpack-0.5.4/gridpack.egg-info/dependency_links.txt +1 -0
- gridpack-0.5.4/gridpack.egg-info/entry_points.txt +5 -0
- gridpack-0.5.4/gridpack.egg-info/requires.txt +17 -0
- gridpack-0.5.4/gridpack.egg-info/top_level.txt +1 -0
- {gridpack-0.5.2 → gridpack-0.5.4}/pyproject.toml +1 -1
- gridpack-0.5.4/setup.cfg +4 -0
- gridpack-0.5.4/tests/test_roundtrip.py +168 -0
- gridpack-0.5.4/tests/test_xarray.py +134 -0
- gridpack-0.5.2/CHANGELOG.md +0 -97
- gridpack-0.5.2/setup.py +0 -2
- {gridpack-0.5.2 → gridpack-0.5.4}/LICENSE +0 -0
- {gridpack-0.5.2 → gridpack-0.5.4}/README.md +0 -0
- {gridpack-0.5.2 → gridpack-0.5.4}/gridpack/__main__.py +0 -0
- {gridpack-0.5.2 → gridpack-0.5.4}/gridpack/codec.py +0 -0
- {gridpack-0.5.2 → gridpack-0.5.4}/gridpack/container.py +0 -0
- {gridpack-0.5.2 → gridpack-0.5.4}/gridpack/entropy.py +0 -0
- {gridpack-0.5.2 → gridpack-0.5.4}/gridpack/netcdf_io.py +0 -0
- {gridpack-0.5.2 → gridpack-0.5.4}/gridpack/xarray_backend.py +0 -0
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: gridpack
|
|
3
|
-
Version: 0.5.
|
|
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).**
|
|
@@ -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
|
|
167
|
-
args.file_chunk = args
|
|
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
|