|
| 1 | +####################################################################### |
| 2 | +# Copyright (c) 2019-present, Blosc Development Team <blosc@blosc.org> |
| 3 | +# All rights reserved. |
| 4 | +# |
| 5 | +# SPDX-License-Identifier: BSD-3-Clause |
| 6 | +####################################################################### |
| 7 | + |
| 8 | +import numpy as np |
| 9 | + |
| 10 | +import blosc2 |
| 11 | +from blosc2 import where |
| 12 | + |
| 13 | + |
| 14 | +def show(label, value): |
| 15 | + print(f"{label}: {value}") |
| 16 | + |
| 17 | + |
| 18 | +@blosc2.dsl_kernel |
| 19 | +def masked_energy(a, b, mask): |
| 20 | + return where(mask > 0, a * a + 2 * b, 0.0) |
| 21 | + |
| 22 | + |
| 23 | +bundle_path = "example_b2o_bundle.b2z" |
| 24 | +blosc2.remove_urlpath(bundle_path) |
| 25 | + |
| 26 | +# Build a portable bundle with ordinary arrays plus persisted lazy recipes. |
| 27 | +with blosc2.DictStore(bundle_path, mode="w", threshold=1) as store: |
| 28 | + store["/data/a"] = np.linspace(0.0, 1.0, 10, dtype=np.float32) |
| 29 | + store["/data/b"] = np.linspace(1.0, 2.0, 10, dtype=np.float32) |
| 30 | + store["/data/mask"] = np.asarray([0, 1, 1, 0, 1, 0, 1, 1, 0, 1], dtype=np.int8) |
| 31 | + |
| 32 | + # Reopen the array members through the store so operand refs point back to |
| 33 | + # the .b2z container via dictstore_key payloads. |
| 34 | + a = store["/data/a"] |
| 35 | + b = store["/data/b"] |
| 36 | + mask = store["/data/mask"] |
| 37 | + |
| 38 | + expr = blosc2.lazyexpr("(a - b) / (a + b + 1e-6)", operands={"a": a, "b": b}) |
| 39 | + udf = blosc2.lazyudf(masked_energy, (a, b, mask), dtype=np.float32, shape=a.shape) |
| 40 | + |
| 41 | + # DictStore currently stores array-like external leaves, so persist the |
| 42 | + # logical lazy objects through their b2o NDArray carriers. |
| 43 | + store["/recipes/expr"] = blosc2.ndarray_from_cframe(expr.to_cframe()) |
| 44 | + store["/recipes/udf"] = blosc2.ndarray_from_cframe(udf.to_cframe()) |
| 45 | + |
| 46 | +show("Bundle created", bundle_path) |
| 47 | + |
| 48 | +# Reopen the bundle read-only. The persisted LazyExpr and LazyUDF can be |
| 49 | +# evaluated directly without re-saving, rebuilding, or re-deploying the .b2z. |
| 50 | +with blosc2.open(bundle_path, mode="r") as store: |
| 51 | + show("Read-only keys", sorted(store.keys())) |
| 52 | + |
| 53 | + expr = store["/recipes/expr"] |
| 54 | + udf = store["/recipes/udf"] |
| 55 | + |
| 56 | + expr_result = expr.compute() |
| 57 | + udf_result = udf.compute() |
| 58 | + |
| 59 | + show("Reopened expr type", type(expr).__name__) |
| 60 | + show("Reopened udf type", type(udf).__name__) |
| 61 | + show("Expr operand refs", expr.array.schunk.vlmeta["b2o"]["operands"]) |
| 62 | + show("UDF operand refs", udf.array.schunk.vlmeta["b2o"]["operands"]) |
| 63 | + show("Expr values", np.round(expr_result[:], 4)) |
| 64 | + show("UDF values", udf_result[:]) |
| 65 | + |
| 66 | + expected_expr = (store["/data/a"][:] - store["/data/b"][:]) / ( |
| 67 | + store["/data/a"][:] + store["/data/b"][:] + 1e-6 |
| 68 | + ) |
| 69 | + expected_udf = np.where( |
| 70 | + store["/data/mask"][:] > 0, |
| 71 | + store["/data/a"][:] ** 2 + 2 * store["/data/b"][:], |
| 72 | + 0.0, |
| 73 | + ).astype(np.float32) |
| 74 | + np.testing.assert_allclose(expr_result[:], expected_expr, rtol=1e-6, atol=1e-6) |
| 75 | + np.testing.assert_allclose(udf_result[:], expected_udf, rtol=1e-6, atol=1e-6) |
| 76 | + show("Read-only evaluation", "ok") |
0 commit comments