Skip to main content

Deferred boost-histogram/hist filling on graphed task graphs (the dask-histogram analogue)

Project description

graphed-histogram

Deferred boost-histogram / hist filling on graphed task graphs — the dask-histogram analogue, built on graphed's own evaluation idiom (milestone M23; P0.1 of the ADL-benchmarks port).

A .fill(...) records instead of executing. Each fill becomes an External node in the graphed IR (the same M3 family as correctionlib and ONNX nodes): a call into foreign machinery, carried in the IR with reproducibility metadata, evaluated later by a registered evaluator. Backends know nothing about histograms — fills record through the frontend's record_external(descriptor=, form=) seam and resolve through evaluate_ir's externals= registry.

The deferred histogram in one example

import boost_histogram as bh
import graphed_histogram as gh
from graphed_core.execution import SequentialRunner

h = gh.boost.Histogram(bh.axis.Regular(20, 0.0, 10.0), storage=bh.storage.Int64())
h.fill(x)                 # x is a graphed Array: RECORDS a fill node, returns h
h.fill(x * 0.5 + 1.0)     # fills accumulate — more nodes, same histogram

plan   = h.plan(steps_per_file=4)            # the deferred task graph
result = SequentialRunner().run(plan).value  # a CONCRETE boost histogram
# any R7 executor accepts the same plan:
#   ProcessExecutor(max_workers=4, persistent=True).run(plan).value

The eager boost API stays available on h (axes, storage, views of the empty state); what changes is that filling stages graph nodes and evaluation belongs to executors.

Why it is built this way

  • Fills are External nodes. The package supplies a PayloadDescriptor (kind="histogram", content_hash=sha256(spec), io_schema="uhi") and an opaque histogram form; the backend is never consulted. Nothing in graphed-core, graphed, or any backend mentions histograms.
  • The canonical spec is the identity. A histogram's identity is the SHA-256 of its canonical, versioned axes/storage spec — key-sorted JSON covering every supported axis and storage (declarative params, never cloudpickle; UHI in, UHI out, no invented formats). Identical fills intern to one graph node; the spec string is the fill's preservation payload; a plan re-run on another machine resolves its evaluator by the same hash. spec_of(h) reads it; zero_of(spec) rebuilds the empty histogram anywhere.
  • Aggregation is plans and executors, not compute(). There is deliberately no compute() method — evaluation is graphed's machinery. h.plan(...) builds a Plan(process=fill-partition-through-the-compiled-IR, combine=native +, empty=zero), and any R7 executor's run(plan).value is the aggregated histogram. Histograms form a monoid under native + for every standard storage, so the executor's fixed combine tree applies unchanged: Int64 counts are exact under any tree, float storages are deterministic per fixed-tree executor configuration. The reference path for in-memory sources is session.materialize(fill_node).

Public surface

graphed_histogram.boost.Histogram deferred boost_histogram.Histogram; .fill() records and returns self (fills accumulate), .plan() exports the task graph
factory(*arrays, histref=, weight=, sample=) a deferred histogram from a reference histogram's axes/storage plus one staged fill (the dask-histogram factory shape)
histogram / histogram2d / histogramdd numpy-like deferred entry points (explicit bins + range)
plan(histograms, ...) one plan aggregating several deferred histograms that share a source in a single pass (the compute(dict_of_hists) analogue)
spec_of / zero_of / content_hash the canonical-spec helpers
evaluators(*histograms) merged content-hash -> evaluator registry for evaluate_ir(externals=...)
add_histograms native-+ combine helper for multi-fill sums

All standard boost storages (combine is native +); axes Regular/Variable/Integer/IntCategory/StrCategory/Boolean. Sources implementing graphed.write.PartitionedSource are filled partition by partition — their whole-dataset loader is never invoked. One source family per histogram (PartitionedSource or in-memory; mixtures rejected). Ragged fill values flatten completely at fill time.

Multiplicative weights (M29)

HEP event weights arrive as several factors (generator weight x pileup x trigger SFs ...). fill(weight=...) accepts weight= as a sequence of graphed Arrays, a first-class fill signature: each weight is recorded as a real graph input and evaluation multiplies them elementwise into the single fill weight.

h.fill(g.pt, weight=[g.genweight, g.pileup_sf, g.trigger_sf])

A single weight records exactly as before — no n_weights param — so pre-M29 node identities, specs, and preservation bundles are untouched.

One pass over several histograms

plan(histograms, ...) compiles all the fills of several histograms into one multi-output IR, so a sub-graph feeding multiple histograms (e.g. a trijet selection feeding both a pT and a b-tag histogram) is read and evaluated once — not once per histogram. run(plan).value is the matching {label: histogram} mapping (string keys for a Mapping, "0", "1", ... for a plain sequence). Column projection covers the union of all the histograms' fills.

Worker backends

plan(backend=...) accepts a zero-arg factory/class or an importable "module:attr" string resolved in the worker — the required form for behavior-carrying backends, because behavior dicts contain lambdas and do not pickle. A worker built without required behaviors fails loudly; it never silently fills the wrong thing.

The hist integration

hist.graphed (in the hist fork) supplies Hist/NamedHist as thin MRO sandwiches over this package's Histogram: the familiar QuickConstruct (Hist.new.Reg(100, 0, 200, name="met").Double()) and named-axis fills record deferred; executor results wrap back into in-memory hist.Hist objects with names and labels intact (they ride the canonical spec). The eight ADL benchmark queries run on exactly this surface.

Phase 2 (deliberately not built)

Growth axes (combining grown category axes across partitions needs a category-union merge, rejected at spec time for now); dask-style collection protocols (persist, to_delayed) — the durable artifact is the compiled IR / Plan; behavior-reference forwarding by default.

Status and gates

Frozen tests under tests/frozen/m23/ and tests/frozen/m29/ — never weakened. Gates: ruff + ruff format · mypy --strict · pytest (>= 90% branch coverage) · sphinx -W. See CLAUDE.md for the milestone digest, docs/design.rst for the engineering walkthrough, and .graphed/state.json for current status.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

graphed_histogram-0.0.1.tar.gz (28.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

graphed_histogram-0.0.1-py3-none-any.whl (12.1 kB view details)

Uploaded Python 3

File details

Details for the file graphed_histogram-0.0.1.tar.gz.

File metadata

  • Download URL: graphed_histogram-0.0.1.tar.gz
  • Upload date:
  • Size: 28.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for graphed_histogram-0.0.1.tar.gz
Algorithm Hash digest
SHA256 947675d72e02a01beacc1b7f679275536b7ac6768238502c3426bdca6720c789
MD5 33793ead86c2f5dbfeb335155736b3d8
BLAKE2b-256 623d553447cb3e1517e28b5d65f90dc60b0f2215653cb34907440e96b4b306ff

See more details on using hashes here.

File details

Details for the file graphed_histogram-0.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for graphed_histogram-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f3eabed004114d5e389e9a2ef5bbda9cdd14dd624fadc685729bf0f62432b9aa
MD5 0d647c78e5dbb5a3a45ceb32f021c37d
BLAKE2b-256 da1dba0f348a1737061ca5b6dc99afda686645c20293bbfd1d763144b81db0ba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page