zfp 0.1.0
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.
- checksums.yaml +7 -0
- data/README.md +346 -0
- data/lib/zfp/codec.rb +80 -0
- data/lib/zfp/errors.rb +13 -0
- data/lib/zfp/ffi.rb +45 -0
- data/lib/zfp/field.rb +54 -0
- data/lib/zfp/packer.rb +63 -0
- data/lib/zfp/stream.rb +61 -0
- data/lib/zfp/type_coercion.rb +97 -0
- data/lib/zfp/version.rb +5 -0
- data/lib/zfp.rb +34 -0
- data/sig/zfp.rbs +4 -0
- metadata +109 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 74703e564c8c5a5b8f5acae9b29aeb8faaf494769a2be01216060f54f51e91d9
|
|
4
|
+
data.tar.gz: 84c36071e18794d6c8a174c6fe7508a7f5acad282dc7d574a8565cb3cad88082
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4bd205cf6669f1d47c9bdb16328b3d23558626bcdeb8066440ec5ef4ee1195822379bf2e13272aefd5ed9fba286873d0f4b796e260f7b0fa157cd521e3d582bd
|
|
7
|
+
data.tar.gz: 58c293a2b8d1d69740f56b82f6811819d59c50d017d4190d23ad3679e448a0099d2e966c264b5792b2f6dd65dd26470dab0b4c9f3d748148b63c55c9eab828fb
|
data/README.md
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# zfp — ZFP Floating-Point Compression for Ruby
|
|
2
|
+
|
|
3
|
+
> _Because your floats deserve better than Base64._
|
|
4
|
+
|
|
5
|
+
[](https://rubygems.org/gems/zfp)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
**`zfp`** brings [LLNL's battle-hardened ZFP compression library](https://computing.llnl.gov/projects/zfp) to Ruby. ZFP was built by national-lab scientists to compress petabytes of floating-point simulation data without losing the ability to do science on it. Now it's in a Ruby gem. You're welcome, science.
|
|
9
|
+
|
|
10
|
+
Whether you're cramming ten years of OHLCV market data into Redis, shipping a million embedding vectors over the wire, or just deeply offended by how wasteful `Array#pack("E*")` is, this gem is for you.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What ZFP Actually Does
|
|
15
|
+
|
|
16
|
+
ZFP compresses *n*-dimensional arrays of floats, doubles, int32s, and int64s — up to 4 dimensions — using a floating-point-aware transform that exploits spatial correlation across array elements. Unlike general-purpose compressors, it understands the structure of numeric data.
|
|
17
|
+
|
|
18
|
+
It offers four compression modes:
|
|
19
|
+
|
|
20
|
+
| Mode | What it does | Good for |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `:reversible` | Bit-exact lossless | Audit trails, exact P&L, storing anything you'll diff |
|
|
23
|
+
| `:fixed_rate` | Guaranteed bits-per-value | Streaming, fixed-size storage slots |
|
|
24
|
+
| `:fixed_precision` | Guaranteed significant bits | Scientific reproducibility |
|
|
25
|
+
| `:fixed_accuracy` | Guaranteed absolute error bound | Financial data, ML embeddings, anything with a tolerance |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
You need `libzfp` installed first. Install it with [upkg](https://github.com/seuros/upkg):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
upkg install zfp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then add the gem:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
bundle add zfp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or install directly:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
gem install zfp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
No native compilation. No waiting. The binding uses [ruby-ffi](https://github.com/ffi/ffi), so `gem install` is instant and the library loads at runtime.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Five-Minute Quickstart
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
require "zfp"
|
|
57
|
+
|
|
58
|
+
prices = [174.21, 174.85, 173.40, 175.10, 176.33] # ... 10,000 more of these
|
|
59
|
+
|
|
60
|
+
# Lossless round-trip — bit-exact, no questions asked
|
|
61
|
+
compressed = Zfp.compress(prices, type: :double, shape: [prices.size], mode: :reversible)
|
|
62
|
+
restored = Zfp.decompress(compressed, type: :double, shape: [prices.size], mode: :reversible)
|
|
63
|
+
|
|
64
|
+
prices == restored # => true
|
|
65
|
+
|
|
66
|
+
# Self-describing pack — no metadata bookkeeping required
|
|
67
|
+
packed = Zfp.pack(prices, type: :double, shape: [prices.size], mode: :reversible)
|
|
68
|
+
restored = Zfp.unpack(packed) # type, shape, mode all embedded — no args needed
|
|
69
|
+
|
|
70
|
+
prices == restored # => true
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
That's it. The rest is just choosing how hard you want to squeeze.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## API Reference
|
|
78
|
+
|
|
79
|
+
There are three ways to use the gem. Pick whichever fits your architecture.
|
|
80
|
+
|
|
81
|
+
### Module methods — the easy path
|
|
82
|
+
|
|
83
|
+
**Raw bytes** (`compress` / `decompress`)
|
|
84
|
+
|
|
85
|
+
The caller manages metadata (type, shape, mode). Returns a plain `String` of compressed bytes.
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
bytes = Zfp.compress(data, type: :double, shape: [1000], mode: :reversible)
|
|
89
|
+
data = Zfp.decompress(bytes, type: :double, shape: [1000], mode: :reversible)
|
|
90
|
+
|
|
91
|
+
# Lossy — absolute error bounded to $0.001 per element
|
|
92
|
+
bytes = Zfp.compress(prices, type: :double, shape: [1000], mode: :fixed_accuracy, tolerance: 0.001)
|
|
93
|
+
data = Zfp.decompress(bytes, type: :double, shape: [1000], mode: :fixed_accuracy, tolerance: 0.001)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Self-describing bytes** (`pack` / `unpack`)
|
|
97
|
+
|
|
98
|
+
Metadata is embedded in a 32-byte header. Pass bytes anywhere — Redis, S3, a message queue — and unpack without needing a schema.
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
# Pack: type/shape/mode/params stored in the bytes themselves
|
|
102
|
+
packed = Zfp.pack(data, type: :double, shape: [1000], mode: :fixed_accuracy, tolerance: 0.001)
|
|
103
|
+
|
|
104
|
+
# Unpack: zero arguments needed
|
|
105
|
+
restored = Zfp.unpack(packed)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `Zfp::Codec` — when you compress many arrays with the same config
|
|
109
|
+
|
|
110
|
+
Build a codec once. Compress forever.
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
codec = Zfp::Codec.new(type: :double, shape: [252], mode: :fixed_accuracy, tolerance: 0.001)
|
|
114
|
+
|
|
115
|
+
# Same codec compresses each security's yearly price history
|
|
116
|
+
securities.each do |ticker|
|
|
117
|
+
store[ticker] = codec.compress(daily_closes[ticker])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Retrieve and decompress
|
|
121
|
+
prices = codec.decompress(store["AAPL"])
|
|
122
|
+
|
|
123
|
+
# Or pack (self-describing) via codec
|
|
124
|
+
packed = codec.pack(daily_closes["AAPL"])
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Multi-Dimensional Arrays
|
|
130
|
+
|
|
131
|
+
ZFP natively understands 1-D through 4-D structure. Providing the actual shape (not just total element count) lets it exploit correlation across all axes simultaneously — the more structure you describe, the better it compresses.
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
# 1-D time series
|
|
135
|
+
Zfp.compress(prices, type: :double, shape: [252], mode: :reversible)
|
|
136
|
+
|
|
137
|
+
# 2-D matrix — e.g. 50 securities × 252 days
|
|
138
|
+
matrix = securities.flat_map { |s| daily_closes[s] }
|
|
139
|
+
Zfp.compress(matrix, type: :double, shape: [50, 252], mode: :fixed_accuracy, tolerance: 0.001)
|
|
140
|
+
|
|
141
|
+
# 3-D tensor — e.g. 10 portfolios × 30 securities × 252 days
|
|
142
|
+
Zfp.compress(tensor, type: :double, shape: [10, 30, 252], mode: :reversible)
|
|
143
|
+
|
|
144
|
+
# 4-D — ZFP supports up to 4 dimensions
|
|
145
|
+
Zfp.compress(data4d, type: :double, shape: [4, 8, 16, 16], mode: :reversible)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## All Four Scalar Types
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# Floating-point — all modes available
|
|
154
|
+
Zfp.compress(floats, type: :float, shape: [n], mode: :reversible)
|
|
155
|
+
Zfp.compress(doubles, type: :double, shape: [n], mode: :fixed_accuracy, tolerance: 1e-6)
|
|
156
|
+
|
|
157
|
+
# Integer — reversible mode only (already lossless by nature)
|
|
158
|
+
Zfp.compress(counts, type: :int32, shape: [n], mode: :reversible)
|
|
159
|
+
Zfp.compress(ids, type: :int64, shape: [n], mode: :reversible)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Numo::NArray Support
|
|
165
|
+
|
|
166
|
+
If [numo-narray](https://github.com/ruby-numo/numo-narray) is loaded, the gem auto-detects type and shape from Numo arrays. No `type:` or `shape:` arguments needed on compress or pack.
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
require "numo/narray"
|
|
170
|
+
|
|
171
|
+
# Input: Numo::DFloat — type (:double) and shape inferred automatically
|
|
172
|
+
closes = Numo::DFloat.cast(daily_prices).reshape(50, 252)
|
|
173
|
+
bytes = Zfp.compress(closes, mode: :fixed_accuracy, tolerance: 0.001)
|
|
174
|
+
packed = Zfp.pack(closes, mode: :reversible)
|
|
175
|
+
|
|
176
|
+
# Output: get a Numo array back with numo: true
|
|
177
|
+
result = Zfp.decompress(bytes, type: :double, shape: [50, 252], mode: :fixed_accuracy,
|
|
178
|
+
tolerance: 0.001, numo: true)
|
|
179
|
+
# => Numo::DFloat[50, 252]
|
|
180
|
+
|
|
181
|
+
# pack/unpack preserves Numo in → Numo out automatically
|
|
182
|
+
result = Zfp.unpack(packed)
|
|
183
|
+
# => Numo::DFloat[50, 252]
|
|
184
|
+
|
|
185
|
+
# All four Numo types supported:
|
|
186
|
+
# Numo::SFloat → :float
|
|
187
|
+
# Numo::DFloat → :double
|
|
188
|
+
# Numo::Int32 → :int32
|
|
189
|
+
# Numo::Int64 → :int64
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Compression Mode Guide
|
|
195
|
+
|
|
196
|
+
### `:reversible` — lossless, bit-exact
|
|
197
|
+
|
|
198
|
+
Use this when correctness is non-negotiable. Compression ratios depend on data entropy:
|
|
199
|
+
sequential integers and smooth time series compress well; high-entropy noise barely shrinks.
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
bytes = Zfp.compress(data, type: :double, shape: [n], mode: :reversible)
|
|
203
|
+
# Typical ratios: 2x–6x on financial/scientific data
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### `:fixed_accuracy` — absolute error bound
|
|
207
|
+
|
|
208
|
+
The workhorse for financial and ML workloads. You set a maximum per-element error;
|
|
209
|
+
ZFP uses as few bits as needed to honor it.
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
# Error on any element guaranteed ≤ tolerance
|
|
213
|
+
bytes = Zfp.compress(prices, type: :double, shape: [n], mode: :fixed_accuracy, tolerance: 0.001)
|
|
214
|
+
# $0.001 per price point — indistinguishable for P&L calculations
|
|
215
|
+
# Typical ratios: 3x–8x
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `:fixed_precision` — significant bits
|
|
219
|
+
|
|
220
|
+
Useful when you want to preserve a specific number of significant bits rather than
|
|
221
|
+
an absolute error bound. Handy for scientific data where relative precision matters more than absolute.
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
bytes = Zfp.compress(data, type: :double, shape: [n], mode: :fixed_precision, precision: 20)
|
|
225
|
+
# 20 significant bits preserved (of 52 available in a double)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### `:fixed_rate` — guaranteed bytes per value
|
|
229
|
+
|
|
230
|
+
Use when you need fixed-size storage slots — e.g., each block in a columnar store
|
|
231
|
+
must be exactly the same size. The `rate` is bits per scalar value.
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
bytes = Zfp.compress(data, type: :double, shape: [n], mode: :fixed_rate, rate: 16.0)
|
|
235
|
+
# 16 bits per value → 4x smaller than raw double
|
|
236
|
+
# WARNING: aggressive rates (< 8) can produce large errors on high-dynamic-range data.
|
|
237
|
+
# Always validate max_err against your tolerance before committing to a rate.
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Real-World Performance
|
|
243
|
+
|
|
244
|
+
Benchmarked on a portfolio of 5 × ~20 securities × 252 trading days of close prices
|
|
245
|
+
(synthetic GBM data, see `examples/portfolio_performance.rb`):
|
|
246
|
+
|
|
247
|
+
| Mode | Ratio | Max Error |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| `:reversible` | 1.1×–1.2× | 0 (exact) |
|
|
250
|
+
| `:fixed_accuracy, tolerance: 0.001` | 3.0×–3.4× | < $0.001 |
|
|
251
|
+
| `:fixed_accuracy, tolerance: 0.01` | 3.4× | < $0.01 |
|
|
252
|
+
|
|
253
|
+
ZFP shines brightest on correlated data — real market prices (correlated sectors, macro
|
|
254
|
+
moves, mean reversion) compress significantly better than the GBM baseline above.
|
|
255
|
+
|
|
256
|
+
For ML embedding vectors (1536-dim float32, high spatial correlation):
|
|
257
|
+
expect 4×–10× with `:fixed_accuracy` and a tolerance tuned to preserve cosine similarity.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Error Handling
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
Zfp::LibraryNotFound # libzfp not installed or not found
|
|
265
|
+
Zfp::InvalidType # unrecognized :type symbol
|
|
266
|
+
Zfp::InvalidMode # unrecognized :mode symbol
|
|
267
|
+
Zfp::InvalidShape # empty shape, > 4 dimensions, or non-positive dimension
|
|
268
|
+
Zfp::InvalidParams # missing or invalid mode-specific param (rate/precision/tolerance)
|
|
269
|
+
Zfp::CompressionFailed # libzfp returned an error during compress
|
|
270
|
+
Zfp::DecompressionFailed # libzfp returned an error during decompress
|
|
271
|
+
Zfp::PackerError # corrupt or truncated pack header
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
All inherit from `Zfp::Error < StandardError`.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## When to Use This Gem
|
|
279
|
+
|
|
280
|
+
- **In-memory caches of numerical time series** — compress to a byte string, store in Redis,
|
|
281
|
+
decompress on demand. Transparent to the rest of your code.
|
|
282
|
+
- **Columnar financial data** — years of OHLCV for thousands of tickers at a fraction of raw size.
|
|
283
|
+
- **ML embedding storage** — vectors stay geometrically faithful under `:fixed_accuracy` at
|
|
284
|
+
tolerances that preserve cosine similarity.
|
|
285
|
+
- **Message queues** — pack/unpack means the consumer needs no schema; bytes are self-describing.
|
|
286
|
+
- **Checkpointing numerical computation** — save intermediate Numo arrays between steps.
|
|
287
|
+
|
|
288
|
+
## When Not to Use This Gem
|
|
289
|
+
|
|
290
|
+
- **Random or already-compressed data** — ZFP assumes spatial correlation. Noise, encrypted data,
|
|
291
|
+
and random floats will barely shrink and may grow.
|
|
292
|
+
- **Text, blobs, or non-numeric data** — use MessagePack, zstd, or zlib.
|
|
293
|
+
- **Tiny arrays (< ~50 elements)** — the 32-byte pack header plus ZFP block overhead dominates.
|
|
294
|
+
- **Integer data needing lossy compression** — integer types support `:reversible` only.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Examples
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
bundle exec ruby examples/01_basic_usage.rb
|
|
302
|
+
bundle exec ruby examples/portfolio_performance.rb
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Requirements
|
|
308
|
+
|
|
309
|
+
- Ruby ≥ 3.0
|
|
310
|
+
- libzfp 1.0+ — install with [upkg](https://github.com/seuros/upkg): `upkg install zfp`
|
|
311
|
+
- [ffi](https://github.com/ffi/ffi) ~> 1.0 (installed automatically)
|
|
312
|
+
- [numo-narray](https://github.com/ruby-numo/numo-narray) _(optional — loaded automatically if present)_
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Development
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
git clone https://github.com/madbomber/zfp
|
|
320
|
+
cd zfp
|
|
321
|
+
bundle install
|
|
322
|
+
bundle exec rake test
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Tests that require libzfp are automatically skipped if the library isn't found,
|
|
326
|
+
so the suite is safe to run anywhere.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Contributing
|
|
331
|
+
|
|
332
|
+
Bug reports and pull requests are welcome at https://github.com/madbomber/zfp.
|
|
333
|
+
Please include a failing test for bugs and a passing test for features.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## References
|
|
338
|
+
|
|
339
|
+
- [ZFP — Compressed Floating-Point and Integer Arrays](https://computing.llnl.gov/projects/zfp) — official project page at Lawrence Livermore National Laboratory
|
|
340
|
+
- [ZFP on GitHub](https://github.com/LLNL/zfp) — source, documentation, and compression algorithm details
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## License
|
|
345
|
+
|
|
346
|
+
MIT. Go nuts.
|
data/lib/zfp/codec.rb
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zfp
|
|
4
|
+
class Codec
|
|
5
|
+
VALID_TYPES = %i[float double int32 int64].freeze
|
|
6
|
+
VALID_MODES = %i[fixed_rate fixed_precision fixed_accuracy reversible].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(type:, shape:, mode:, numo: false, **params)
|
|
9
|
+
validate_type!(type)
|
|
10
|
+
validate_mode!(mode)
|
|
11
|
+
validate_shape!(shape)
|
|
12
|
+
validate_params!(mode, params)
|
|
13
|
+
@type = type
|
|
14
|
+
@shape = shape
|
|
15
|
+
@mode = mode
|
|
16
|
+
@params = params
|
|
17
|
+
@numo = numo
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def compress(data)
|
|
21
|
+
buf = TypeCoercion.to_buffer(data, @type)
|
|
22
|
+
field = Field.new(@type, @shape, buf)
|
|
23
|
+
Stream.new(@mode, @params).compress(field)
|
|
24
|
+
ensure
|
|
25
|
+
field&.free
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def decompress(bytes)
|
|
29
|
+
count = @shape.reduce(:*)
|
|
30
|
+
elem_size = TypeCoercion::ELEMENT_SIZE[@type]
|
|
31
|
+
out_buf = ::FFI::MemoryPointer.new(:uint8, count * elem_size)
|
|
32
|
+
field = Field.new(@type, @shape, out_buf)
|
|
33
|
+
Stream.new(@mode, @params).decompress(field, bytes)
|
|
34
|
+
TypeCoercion.from_buffer(out_buf, @type, @shape, @numo)
|
|
35
|
+
ensure
|
|
36
|
+
field&.free
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def pack(data)
|
|
40
|
+
as_numo = @numo || TypeCoercion.numo?(data)
|
|
41
|
+
compressed = compress(data)
|
|
42
|
+
Packer.encode(compressed, type: @type, shape: @shape, mode: @mode,
|
|
43
|
+
params: @params, numo: as_numo)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def validate_type!(type)
|
|
49
|
+
raise Zfp::InvalidType, "Unknown type: #{type.inspect}. Valid: #{VALID_TYPES}" \
|
|
50
|
+
unless VALID_TYPES.include?(type)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate_mode!(mode)
|
|
54
|
+
raise Zfp::InvalidMode, "Unknown mode: #{mode.inspect}. Valid: #{VALID_MODES}" \
|
|
55
|
+
unless VALID_MODES.include?(mode)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def validate_shape!(shape)
|
|
59
|
+
unless shape.is_a?(Array) && shape.length.between?(1, 4) &&
|
|
60
|
+
shape.all? { |d| d.is_a?(Integer) && d > 0 }
|
|
61
|
+
raise Zfp::InvalidShape,
|
|
62
|
+
"shape must be Array of 1–4 positive integers, got #{shape.inspect}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def validate_params!(mode, params)
|
|
67
|
+
case mode
|
|
68
|
+
when :fixed_rate
|
|
69
|
+
raise Zfp::InvalidParams, "fixed_rate requires rate: (Float > 0)" \
|
|
70
|
+
unless params[:rate]&.to_f&.positive?
|
|
71
|
+
when :fixed_precision
|
|
72
|
+
raise Zfp::InvalidParams, "fixed_precision requires precision: (Integer > 0)" \
|
|
73
|
+
unless params[:precision].is_a?(Integer) && params[:precision] > 0
|
|
74
|
+
when :fixed_accuracy
|
|
75
|
+
raise Zfp::InvalidParams, "fixed_accuracy requires tolerance: (Float > 0)" \
|
|
76
|
+
unless params[:tolerance]&.to_f&.positive?
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/zfp/errors.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zfp
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class LibraryNotFound < Error; end
|
|
6
|
+
class InvalidType < Error; end
|
|
7
|
+
class InvalidMode < Error; end
|
|
8
|
+
class InvalidShape < Error; end
|
|
9
|
+
class InvalidParams < Error; end
|
|
10
|
+
class CompressionFailed < Error; end
|
|
11
|
+
class DecompressionFailed < Error; end
|
|
12
|
+
class PackerError < Error; end
|
|
13
|
+
end
|
data/lib/zfp/ffi.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module Zfp
|
|
6
|
+
module FFI
|
|
7
|
+
extend ::FFI::Library
|
|
8
|
+
|
|
9
|
+
begin
|
|
10
|
+
ffi_lib "zfp"
|
|
11
|
+
rescue LoadError
|
|
12
|
+
raise Zfp::LibraryNotFound,
|
|
13
|
+
"ZFP library not found. Install with: brew install zfp"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
ZFP_TYPE_INT32 = 1
|
|
17
|
+
ZFP_TYPE_INT64 = 2
|
|
18
|
+
ZFP_TYPE_FLOAT = 3
|
|
19
|
+
ZFP_TYPE_DOUBLE = 4
|
|
20
|
+
|
|
21
|
+
attach_function :zfp_stream_open, [:pointer], :pointer
|
|
22
|
+
attach_function :zfp_stream_close, [:pointer], :void
|
|
23
|
+
attach_function :zfp_stream_set_bit_stream, [:pointer, :pointer], :void
|
|
24
|
+
attach_function :zfp_stream_rewind, [:pointer], :void
|
|
25
|
+
attach_function :zfp_stream_set_rate, [:pointer, :double, :int, :uint, :int], :double
|
|
26
|
+
attach_function :zfp_stream_set_precision, [:pointer, :uint], :uint
|
|
27
|
+
attach_function :zfp_stream_set_accuracy, [:pointer, :double], :double
|
|
28
|
+
attach_function :zfp_stream_set_reversible, [:pointer], :void
|
|
29
|
+
attach_function :zfp_stream_maximum_size, [:pointer, :pointer], :size_t
|
|
30
|
+
attach_function :zfp_compress, [:pointer, :pointer], :size_t
|
|
31
|
+
attach_function :zfp_decompress, [:pointer, :pointer], :size_t
|
|
32
|
+
|
|
33
|
+
attach_function :zfp_field_alloc, [], :pointer
|
|
34
|
+
attach_function :zfp_field_free, [:pointer], :void
|
|
35
|
+
attach_function :zfp_field_set_pointer, [:pointer, :pointer], :void
|
|
36
|
+
attach_function :zfp_field_set_type, [:pointer, :int], :void
|
|
37
|
+
attach_function :zfp_field_set_size_1d, [:pointer, :size_t], :void
|
|
38
|
+
attach_function :zfp_field_set_size_2d, [:pointer, :size_t, :size_t], :void
|
|
39
|
+
attach_function :zfp_field_set_size_3d, [:pointer, :size_t, :size_t, :size_t], :void
|
|
40
|
+
attach_function :zfp_field_set_size_4d, [:pointer, :size_t, :size_t, :size_t, :size_t], :void
|
|
41
|
+
|
|
42
|
+
attach_function :stream_open, [:pointer, :size_t], :pointer
|
|
43
|
+
attach_function :stream_close, [:pointer], :void
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/zfp/field.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zfp
|
|
4
|
+
class Field
|
|
5
|
+
attr_reader :zfp_type_int, :shape
|
|
6
|
+
|
|
7
|
+
def initialize(type, shape, buffer)
|
|
8
|
+
validate_shape!(shape)
|
|
9
|
+
@type = type
|
|
10
|
+
@shape = shape
|
|
11
|
+
@buffer = buffer # hold reference — prevents GC while field is alive
|
|
12
|
+
@zfp_type_int = Zfp::TypeCoercion::RUBY_TO_ZFP_TYPE[type]
|
|
13
|
+
@ptr = Zfp::FFI.zfp_field_alloc
|
|
14
|
+
raise Zfp::Error, "Failed to allocate zfp_field" if @ptr.null?
|
|
15
|
+
|
|
16
|
+
Zfp::FFI.zfp_field_set_pointer(@ptr, buffer)
|
|
17
|
+
Zfp::FFI.zfp_field_set_type(@ptr, @zfp_type_int)
|
|
18
|
+
set_shape(shape)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def pointer
|
|
22
|
+
@ptr
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def dims
|
|
26
|
+
@shape.length
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def free
|
|
30
|
+
return if @ptr.nil? || @ptr.null?
|
|
31
|
+
Zfp::FFI.zfp_field_free(@ptr)
|
|
32
|
+
@ptr = ::FFI::Pointer::NULL
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def validate_shape!(shape)
|
|
38
|
+
unless shape.is_a?(Array) && shape.length.between?(1, 4) &&
|
|
39
|
+
shape.all? { |d| d.is_a?(Integer) && d > 0 }
|
|
40
|
+
raise Zfp::InvalidShape,
|
|
41
|
+
"shape must be an Array of 1–4 positive integers, got #{shape.inspect}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def set_shape(shape)
|
|
46
|
+
case shape.length
|
|
47
|
+
when 1 then Zfp::FFI.zfp_field_set_size_1d(@ptr, shape[0])
|
|
48
|
+
when 2 then Zfp::FFI.zfp_field_set_size_2d(@ptr, shape[0], shape[1])
|
|
49
|
+
when 3 then Zfp::FFI.zfp_field_set_size_3d(@ptr, shape[0], shape[1], shape[2])
|
|
50
|
+
when 4 then Zfp::FFI.zfp_field_set_size_4d(@ptr, shape[0], shape[1], shape[2], shape[3])
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/zfp/packer.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zfp
|
|
4
|
+
module Packer
|
|
5
|
+
MAGIC = "ZFP\x01"
|
|
6
|
+
HEADER_LEN = 32
|
|
7
|
+
PACK_FMT = "a4CCCCVVVVE"
|
|
8
|
+
|
|
9
|
+
TYPE_TO_BYTE = { float: 0, double: 1, int32: 2, int64: 3 }.freeze
|
|
10
|
+
BYTE_TO_TYPE = TYPE_TO_BYTE.invert.freeze
|
|
11
|
+
|
|
12
|
+
MODE_TO_BYTE = { fixed_rate: 0, fixed_precision: 1, fixed_accuracy: 2, reversible: 3 }.freeze
|
|
13
|
+
BYTE_TO_MODE = MODE_TO_BYTE.invert.freeze
|
|
14
|
+
|
|
15
|
+
def self.encode(compressed_bytes, type:, shape:, mode:, params: {}, numo: false)
|
|
16
|
+
flags = numo ? 1 : 0
|
|
17
|
+
dims = shape.length
|
|
18
|
+
dim0, dim1, dim2, dim3 = Array.new(4) { |i| shape[i].to_i }
|
|
19
|
+
param = extract_param(mode, params)
|
|
20
|
+
header = [MAGIC, TYPE_TO_BYTE[type], MODE_TO_BYTE[mode], dims, flags,
|
|
21
|
+
dim0, dim1, dim2, dim3, param].pack(PACK_FMT)
|
|
22
|
+
header + compressed_bytes
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.decode(bytes)
|
|
26
|
+
if bytes.bytesize < HEADER_LEN
|
|
27
|
+
raise Zfp::PackerError, "truncated header (#{bytes.bytesize} < #{HEADER_LEN} bytes)"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
magic, type_b, mode_b, rank, flags, d0, d1, d2, d3, param =
|
|
31
|
+
bytes[0, HEADER_LEN].unpack(PACK_FMT)
|
|
32
|
+
|
|
33
|
+
raise Zfp::PackerError, "bad magic bytes (expected ZFP\\x01)" unless magic == MAGIC
|
|
34
|
+
|
|
35
|
+
type = BYTE_TO_TYPE.fetch(type_b) { raise Zfp::PackerError, "unknown type byte #{type_b}" }
|
|
36
|
+
mode = BYTE_TO_MODE.fetch(mode_b) { raise Zfp::PackerError, "unknown mode byte #{mode_b}" }
|
|
37
|
+
shape = [d0, d1, d2, d3].first(rank)
|
|
38
|
+
numo = flags & 1 == 1
|
|
39
|
+
params = decode_params(mode, param)
|
|
40
|
+
data = bytes[HEADER_LEN..]
|
|
41
|
+
|
|
42
|
+
[type, shape, mode, params, numo, data]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private_class_method def self.extract_param(mode, params)
|
|
46
|
+
case mode
|
|
47
|
+
when :fixed_rate then params[:rate].to_f
|
|
48
|
+
when :fixed_precision then params[:precision].to_f
|
|
49
|
+
when :fixed_accuracy then params[:tolerance].to_f
|
|
50
|
+
else 0.0
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private_class_method def self.decode_params(mode, param)
|
|
55
|
+
case mode
|
|
56
|
+
when :fixed_rate then { rate: param }
|
|
57
|
+
when :fixed_precision then { precision: param.round }
|
|
58
|
+
when :fixed_accuracy then { tolerance: param }
|
|
59
|
+
else {}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/zfp/stream.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zfp
|
|
4
|
+
class Stream
|
|
5
|
+
def initialize(mode, params)
|
|
6
|
+
@mode = mode
|
|
7
|
+
@params = params
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def compress(field)
|
|
11
|
+
zfp_ptr = bitstream = output_buf = nil
|
|
12
|
+
zfp_ptr = Zfp::FFI.zfp_stream_open(nil)
|
|
13
|
+
apply_mode(zfp_ptr, field)
|
|
14
|
+
bufsize = Zfp::FFI.zfp_stream_maximum_size(zfp_ptr, field.pointer)
|
|
15
|
+
output_buf = ::FFI::MemoryPointer.new(:uint8, bufsize)
|
|
16
|
+
bitstream = Zfp::FFI.stream_open(output_buf, bufsize)
|
|
17
|
+
Zfp::FFI.zfp_stream_set_bit_stream(zfp_ptr, bitstream)
|
|
18
|
+
Zfp::FFI.zfp_stream_rewind(zfp_ptr)
|
|
19
|
+
written = Zfp::FFI.zfp_compress(zfp_ptr, field.pointer)
|
|
20
|
+
raise Zfp::CompressionFailed, "zfp_compress returned 0" if written == 0
|
|
21
|
+
output_buf.read_bytes(written)
|
|
22
|
+
ensure
|
|
23
|
+
Zfp::FFI.stream_close(bitstream) if bitstream && !bitstream.null?
|
|
24
|
+
Zfp::FFI.zfp_stream_close(zfp_ptr) if zfp_ptr && !zfp_ptr.null?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def decompress(field, compressed_bytes)
|
|
28
|
+
zfp_ptr = bitstream = input_buf = nil
|
|
29
|
+
input_buf = ::FFI::MemoryPointer.new(:uint8, compressed_bytes.bytesize)
|
|
30
|
+
input_buf.put_bytes(0, compressed_bytes)
|
|
31
|
+
zfp_ptr = Zfp::FFI.zfp_stream_open(nil)
|
|
32
|
+
apply_mode(zfp_ptr, field)
|
|
33
|
+
bitstream = Zfp::FFI.stream_open(input_buf, compressed_bytes.bytesize)
|
|
34
|
+
Zfp::FFI.zfp_stream_set_bit_stream(zfp_ptr, bitstream)
|
|
35
|
+
Zfp::FFI.zfp_stream_rewind(zfp_ptr)
|
|
36
|
+
read = Zfp::FFI.zfp_decompress(zfp_ptr, field.pointer)
|
|
37
|
+
raise Zfp::DecompressionFailed, "zfp_decompress returned 0" if read == 0
|
|
38
|
+
ensure
|
|
39
|
+
Zfp::FFI.stream_close(bitstream) if bitstream && !bitstream.null?
|
|
40
|
+
Zfp::FFI.zfp_stream_close(zfp_ptr) if zfp_ptr && !zfp_ptr.null?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def apply_mode(zfp_ptr, field)
|
|
46
|
+
case @mode
|
|
47
|
+
when :fixed_rate
|
|
48
|
+
Zfp::FFI.zfp_stream_set_rate(zfp_ptr, @params[:rate].to_f,
|
|
49
|
+
field.zfp_type_int, field.dims, 0)
|
|
50
|
+
when :fixed_precision
|
|
51
|
+
Zfp::FFI.zfp_stream_set_precision(zfp_ptr, @params[:precision])
|
|
52
|
+
when :fixed_accuracy
|
|
53
|
+
Zfp::FFI.zfp_stream_set_accuracy(zfp_ptr, @params[:tolerance].to_f)
|
|
54
|
+
when :reversible
|
|
55
|
+
Zfp::FFI.zfp_stream_set_reversible(zfp_ptr)
|
|
56
|
+
else
|
|
57
|
+
raise Zfp::InvalidMode, "Unknown mode: #{@mode}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zfp
|
|
4
|
+
module TypeCoercion
|
|
5
|
+
RUBY_TO_ZFP_TYPE = {
|
|
6
|
+
int32: Zfp::FFI::ZFP_TYPE_INT32,
|
|
7
|
+
int64: Zfp::FFI::ZFP_TYPE_INT64,
|
|
8
|
+
float: Zfp::FFI::ZFP_TYPE_FLOAT,
|
|
9
|
+
double: Zfp::FFI::ZFP_TYPE_DOUBLE
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
PACK_FORMAT = {
|
|
13
|
+
float: "e*",
|
|
14
|
+
double: "E*",
|
|
15
|
+
int32: "l<*",
|
|
16
|
+
int64: "q<*"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
ELEMENT_SIZE = {
|
|
20
|
+
float: 4,
|
|
21
|
+
double: 8,
|
|
22
|
+
int32: 4,
|
|
23
|
+
int64: 8
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
def detect_type(data)
|
|
28
|
+
return nil unless numo?(data)
|
|
29
|
+
case data
|
|
30
|
+
when Numo::SFloat then :float
|
|
31
|
+
when Numo::DFloat then :double
|
|
32
|
+
when Numo::Int32 then :int32
|
|
33
|
+
when Numo::Int64 then :int64
|
|
34
|
+
else raise Zfp::InvalidType, "Unsupported Numo type: #{data.class}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def detect_shape(data)
|
|
39
|
+
return nil unless numo?(data)
|
|
40
|
+
data.shape
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def numo?(data)
|
|
44
|
+
defined?(Numo::NArray) && data.is_a?(Numo::NArray)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_buffer(data, type)
|
|
48
|
+
numo?(data) ? numo_to_buffer(data, type) : array_to_buffer(data, type)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def from_buffer(ptr, type, shape, as_numo)
|
|
52
|
+
as_numo ? buffer_to_numo(ptr, type, shape) : buffer_to_array(ptr, type, shape)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def array_to_buffer(array, type)
|
|
58
|
+
fmt = PACK_FORMAT.fetch(type) { raise Zfp::InvalidType, "Unknown type: #{type}" }
|
|
59
|
+
packed = array.flatten.pack(fmt)
|
|
60
|
+
ptr = ::FFI::MemoryPointer.new(:uint8, packed.bytesize)
|
|
61
|
+
ptr.put_bytes(0, packed)
|
|
62
|
+
ptr
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def numo_to_buffer(narray, type)
|
|
66
|
+
fmt = PACK_FORMAT.fetch(type) { raise Zfp::InvalidType, "Unknown type: #{type}" }
|
|
67
|
+
packed = narray.to_a.flatten.pack(fmt)
|
|
68
|
+
ptr = ::FFI::MemoryPointer.new(:uint8, packed.bytesize)
|
|
69
|
+
ptr.put_bytes(0, packed)
|
|
70
|
+
ptr
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def buffer_to_array(ptr, type, shape)
|
|
74
|
+
count = shape.reduce(:*)
|
|
75
|
+
bytes = ptr.read_bytes(count * ELEMENT_SIZE[type])
|
|
76
|
+
bytes.unpack(PACK_FORMAT[type])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def buffer_to_numo(ptr, type, shape)
|
|
80
|
+
count = shape.reduce(:*)
|
|
81
|
+
bytes = ptr.read_bytes(count * ELEMENT_SIZE[type])
|
|
82
|
+
array = bytes.unpack(PACK_FORMAT[type])
|
|
83
|
+
numo_class_for(type).cast(array).reshape(*shape)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def numo_class_for(type)
|
|
87
|
+
case type
|
|
88
|
+
when :float then Numo::SFloat
|
|
89
|
+
when :double then Numo::DFloat
|
|
90
|
+
when :int32 then Numo::Int32
|
|
91
|
+
when :int64 then Numo::Int64
|
|
92
|
+
else raise Zfp::InvalidType, "Unknown type: #{type}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
data/lib/zfp/version.rb
ADDED
data/lib/zfp.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "zfp/version"
|
|
4
|
+
require_relative "zfp/errors"
|
|
5
|
+
require_relative "zfp/ffi"
|
|
6
|
+
require_relative "zfp/type_coercion"
|
|
7
|
+
require_relative "zfp/field"
|
|
8
|
+
require_relative "zfp/stream"
|
|
9
|
+
require_relative "zfp/packer"
|
|
10
|
+
require_relative "zfp/codec"
|
|
11
|
+
|
|
12
|
+
module Zfp
|
|
13
|
+
def self.compress(data, type: nil, shape: nil, mode:, **params)
|
|
14
|
+
type ||= TypeCoercion.detect_type(data) or raise InvalidType, "type: required for Ruby Array input"
|
|
15
|
+
shape ||= TypeCoercion.detect_shape(data) or raise InvalidShape, "shape: required for Ruby Array input"
|
|
16
|
+
Codec.new(type: type, shape: shape, mode: mode, **params).compress(data)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.decompress(bytes, type:, shape:, mode:, numo: false, **params)
|
|
20
|
+
Codec.new(type: type, shape: shape, mode: mode, numo: numo, **params).decompress(bytes)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.pack(data, type: nil, shape: nil, mode:, **params)
|
|
24
|
+
type ||= TypeCoercion.detect_type(data) or raise InvalidType, "type: required for Ruby Array input"
|
|
25
|
+
shape ||= TypeCoercion.detect_shape(data) or raise InvalidShape, "shape: required for Ruby Array input"
|
|
26
|
+
as_numo = TypeCoercion.numo?(data)
|
|
27
|
+
Codec.new(type: type, shape: shape, mode: mode, numo: as_numo, **params).pack(data)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.unpack(bytes)
|
|
31
|
+
type, shape, mode, params, numo, compressed = Packer.decode(bytes)
|
|
32
|
+
Codec.new(type: type, shape: shape, mode: mode, numo: numo, **params).decompress(compressed)
|
|
33
|
+
end
|
|
34
|
+
end
|
data/sig/zfp.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: zfp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dewayne VanHoozer
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ffi
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: numo-narray
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: minitest
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: debug_me
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
description: |
|
|
69
|
+
Compress and decompress arrays of float/double/int32/int64 in 1-4
|
|
70
|
+
dimensions using ZFP's fixed-rate, fixed-precision, fixed-accuracy,
|
|
71
|
+
or lossless reversible modes. Accepts Ruby Array and Numo::NArray.
|
|
72
|
+
email:
|
|
73
|
+
- dvanhoozer@gmail.com
|
|
74
|
+
executables: []
|
|
75
|
+
extensions: []
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
files:
|
|
78
|
+
- README.md
|
|
79
|
+
- lib/zfp.rb
|
|
80
|
+
- lib/zfp/codec.rb
|
|
81
|
+
- lib/zfp/errors.rb
|
|
82
|
+
- lib/zfp/ffi.rb
|
|
83
|
+
- lib/zfp/field.rb
|
|
84
|
+
- lib/zfp/packer.rb
|
|
85
|
+
- lib/zfp/stream.rb
|
|
86
|
+
- lib/zfp/type_coercion.rb
|
|
87
|
+
- lib/zfp/version.rb
|
|
88
|
+
- sig/zfp.rbs
|
|
89
|
+
homepage: https://github.com/madbomber/zfp
|
|
90
|
+
licenses: []
|
|
91
|
+
metadata: {}
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
require_paths:
|
|
94
|
+
- lib
|
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: 3.0.0
|
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
requirements: []
|
|
106
|
+
rubygems_version: 4.0.10
|
|
107
|
+
specification_version: 4
|
|
108
|
+
summary: Ruby FFI bindings for the ZFP floating-point compression library
|
|
109
|
+
test_files: []
|