Read and iterate an ensemble

A .bendl file is self-describing, so opening one with BendlDecoder hands you the whole package at once: the assignment stream and the graph, metadata, and any custom assets stored beside it. There is no separate graph file to track down and no node order to remember, because the bundle already carries both.

Inspect before you iterate

It is worth a quick look at what a bundle holds before committing to a full pass over it. Each of these reads comes straight from the header or the directory table, so none of them touch the assignment stream:

from binary_ensemble import BendlDecoder

decoder = BendlDecoder("ensemble.bendl")

print(len(decoder))                 # number of samples (expanded count)
print(decoder.assignment_format())  # 'ben' or 'xben'
print(decoder.asset_names())        # e.g. ['graph.json', 'metadata.json']
print(decoder.read_metadata())      # the metadata.json payload, or None

len() reads the sample count from the bundle header rather than scanning the stream, so it is cheap enough to use for a progress-bar total.

Iterate the assignments

Iterating a decoder yields each plan in turn as a list[int]: one district id per node, in graph order.

for assignment in decoder:
    # assignment is a list[int]: the district id of each node, in graph order
    ...

A decoder is reusable. Each for loop rewinds to the start of the stream automatically, so there is no need to reopen the file between passes:

total = len(decoder)
first = next(iter(decoder))      # peek the first plan
all_plans = list(decoder)        # full pass again, from the start

That shared cursor is also the one thing to watch: iteration is strictly sequential, so don’t drive two loops over the same decoder at once. When you genuinely need two positions in the stream simultaneously, open a second BendlDecoder on the file.

Recover the dual graph

Because the graph travels inside the bundle, you can rebuild full plan objects without a separate graph file. read_graph() returns a live networkx.Graph whose node order matches the order the assignments were written in, so a plan and its graph line up with no extra bookkeeping:

import pandas as pd
from gerrychain import Partition

decoder = BendlDecoder("ensemble.bendl")
graph = decoder.read_graph()
node_order = pd.Index(graph.nodes)

for assignment in decoder:
    series = pd.Series(assignment, index=node_order)
    partition = Partition(graph, assignment=series)
    # ... analyze the partition (cut edges, population, scores, ...)

Get the raw graph or permutation map

read_graph() rebuilds a NetworkX object, which is usually what you want. When you need the underlying JSON instead, or the permutation map that a reordered bundle carries, reach for the asset directly:

raw_graph = decoder.read_json_asset("graph.json")      # parsed adjacency dict
permutation_map = decoder.read_node_permutation_map()  # None if the graph wasn't reordered

See Custom assets and appending for reading arbitrary blobs.