Quick Start Guide
This guide follows PropFlow’s recommended top-down flow: build a graph with
FGBuilder, run an engine, compare engine variants, and optionally dig into
analysis tooling. Use the PropFlow User Guide when you need deeper detail on each
layer.
Prepare the Environment
Install PropFlow and its development extras (for docs/tests) into an activated virtual environment:
pip install -e '.[dev]'
Step 1 — Build a Factor Graph (FGBuilder First)
FGBuilder gives you a fully initialised propflow.bp.factor_graph.FactorGraph
without hand-wiring agents. Pick a topology, choose a cost-table factory, and
specify parameters.
from propflow import FGBuilder
from propflow.configs import create_random_int_table
graph = FGBuilder.build_cycle_graph(
num_vars=6,
domain_size=3,
ct_factory=create_random_int_table,
ct_params={"low": 0, "high": 25},
)
FGBuilder ensures factors, variables, and edges are consistent, and it
initialises cost tables via the provided factory.
Step 2 — Run Belief Propagation
Instantiate an engine (BPEngine by default) with the graph. Engines attach
computators, seed mailboxes, and iterate until convergence or a max iteration
count.
from propflow import BPEngine
engine = BPEngine(graph)
engine.run(max_iter=50)
print("Assignments:", engine.assignments)
print("Global cost:", engine.graph.global_cost)
Step 3 — Try Engine Variants and Policies
Swap computators or engines to experiment with different BP behaviours. Here we compare plain BP to a damped variant.
from copy import deepcopy
from propflow import DampingEngine, FGBuilder, MinSumComputator
from propflow.configs import create_random_int_table
graph_for_comparison = FGBuilder.build_cycle_graph(
num_vars=6,
domain_size=3,
ct_factory=create_random_int_table,
ct_params={"low": 0, "high": 25},
)
baseline_graph = deepcopy(graph_for_comparison)
damped_graph = deepcopy(graph_for_comparison)
baseline = BPEngine(baseline_graph, computator=MinSumComputator())
baseline.run(max_iter=50)
damped = DampingEngine(damped_graph, damping_factor=0.85)
damped.run(max_iter=50)
print("Baseline cost:", baseline.history.costs[-1])
print("Damped cost:", damped.history.costs[-1])
Step 4 — Scale Out with the Simulator
Run several engine configurations across a batch of graphs using
propflow.simulator.Simulator. It will execute runs in parallel when
possible and aggregate cost histories.
from propflow import Simulator, FGBuilder, BPEngine, DampingEngine
from propflow.configs import CTFactories
configs = {
"baseline": {"class": BPEngine},
"damped": {"class": DampingEngine, "damping_factor": 0.85},
}
graphs = [
FGBuilder.build_random_graph(
num_vars=12,
domain_size=3,
ct_factory=CTFactories.RANDOM_INT,
ct_params={"low": 5, "high": 30},
density=0.3,
)
for _ in range(5)
]
simulator = Simulator(configs)
results = simulator.run_simulations(graphs, max_iter=200)
simulator.plot_results()
Optional — Build a Custom Graph Manually
When you need a structure that the helpers do not cover, create agents directly
and pass an explicit edges mapping into propflow.bp.factor_graph.FactorGraph.
Remember that the list of variables for each factor is ordered—the position in
the list matches the axis in the cost table.
from propflow import FactorGraph, VariableAgent, FactorAgent, BPEngine
from propflow.configs import create_uniform_float_table
x1 = VariableAgent("x1", domain=2)
x2 = VariableAgent("x2", domain=2)
parity = FactorAgent(
name="f12",
domain=2,
ct_creation_func=create_uniform_float_table,
)
graph = FactorGraph(
variable_li=[x1, x2],
factor_li=[parity],
edges={parity: [x1, x2]},
)
engine = BPEngine(graph)
engine.run(max_iter=25)
Checklist for manual builds:
Every factor appears exactly once in
factor_liand as a key inedges.Each value in
edgesis an ordered list of variables; the order defines tensor dimensions.Cost-table factories must accept
num_varsanddomain_size(PropFlow passes both arguments automatically).Use deterministic parameters (seeds, bounds) when you want reproducibility.
Inspecting Runs with Analyzer Tooling
Every engine step captures an EngineSnapshot automatically. Persist a compact
trace manually or hand the in-memory snapshots to the analyzer/visualizer.
import json
from pathlib import Path
from propflow import BPEngine, FGBuilder
from propflow.configs import create_random_int_table
fg = FGBuilder.build_cycle_graph(
num_vars=8,
domain_size=3,
ct_factory=create_random_int_table,
ct_params={"low": 1, "high": 20},
)
engine = BPEngine(fg)
engine.run(max_iter=80)
payload = [
{
"step": snap.step,
"assignments": snap.assignments,
"global_cost": snap.global_cost,
"metadata": snap.metadata,
}
for snap in engine.snapshots
]
out_path = Path("results/demo/run.json")
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(payload, indent=2))
latest = engine.latest_snapshot()
Where to Go Next
Read the PropFlow User Guide for a deeper explanation of each layer.
Explore Examples for more advanced scenarios and patterns.
Consult the PropFlow Handbook when you need operational practices or deployment guidance.
Browse API Reference for the full API reference.