Library / SDK
SPFlow/SPFlow avatar
SPFlow/SPFlow

The SPFlow quickstart mixes the same distribution twice with different weights

Sum Product Flow: An Easy and Extensible Library for Sum-Product Networks

313 stars83 forksPythonApache-2.0

At a glance

What is it?
SPFlow is a PyTorch library for sum-product networks and probabilistic circuits, offering exact inference and both manual and learned structure. It is worth reading closely because its packaging carries small tells, from a stray classifier from another framework to a container image whose default command is the test suite.
Who is it for?
SPFlow suits a probabilistic-programming user who wants tractable exact inference with autograd on the same device as the rest of a PyTorch stack, since leaves, products, and sums are all torch modules and the zoo ships ready-made models. It does not suit someone who needs the original research-era API, since version 1.0.0 was a rewrite.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The DSL example builds a mixture of two identical branches

The quickstart is a small DSL block, and the example inside it is worth reading closely.

python
with dsl():
    terms = 0.4 * Normal(0) * Normal(1) + 0.6 * Normal(0) * Normal(1)

pc = terms.build()
ll = pc.log_likelihood(torch.randn(8, 2))
print(ll.shape)
visualize(pc, output_path="dsl-structure", format="svg")

The two mixture components are the same product of the same two normal leaves. Only the weights differ, at 0.4 and 0.6, so the sum node mixes two identical distributions. As a density that is indistinguishable from a single branch, and it does not demonstrate that the sum layer works.

That is a documentation shortcut rather than a bug in the library, and the following example is the one that builds a real structure. It declares feature indices for three variables, seeds the random number generator explicitly so the example stays reproducible, and constructs a categorical leaf with an explicit number of categories on one branch and normal leaves on the other.

One more detail carries over into the second example: both snippets import a filesystem cleanup helper and never use it. Copying either block into your own file leaves you with an unused import.

The DSL writes an expression and build materializes it

The interface has two stages and the distinction matters when you are debugging.

Inside the context manager you write arithmetic: leaves are multiplied into products, products are added into sums, and the result is a symbolic expression. Nothing has been allocated and no tensors exist yet. The context manager is what captures the operator tree as you write rather than executing each line.

Then one method materializes it into an executable circuit. After that call the object behaves like any other module in the library and supports the operations the rest of the API offers, including log-likelihood evaluation on a batch and sampling.

The last line of the example exports the structure to an SVG file with an explicit output path and format, which is the practical way to check whether the circuit you described is the circuit you got. The visualization helper is imported from a utilities module and sits on top of a graph export dependency in the package metadata.

The longer example reaches for the module classes directly, importing from the leaves, products, and sums modules, and passing an explicit scope object to each leaf. So both routes exist: the DSL for quick work, the explicit classes when you need to name each node.

A Robot Framework classifier sits in the package metadata

The classifier list is long and mostly correct, with one entry that does not belong.

The project declares production stability, an intended audience of both science and research and developers, typing support, and topic classifiers for scientific and engineering work, artificial intelligence, information analysis, and software development libraries. It also requires Python 3.10 or newer with no upper bound.

Sitting among those is a framework classifier naming a testing automation framework as a library this project plugs into. Nothing in the description, the examples, or the dependency list involves that framework. It is the kind of entry that arrives when a classifier list is assembled from a template or copied from another project, and it is harmless on a package index while being wrong.

The rest of the metadata is unusually careful. The license is declared with its file, keywords are limited to two, package discovery is restricted to the library namespace, and the version is read from an attribute inside the package rather than written in the manifest.

Package data inclusion is switched off globally while the typing marker file is listed explicitly, which is the combination you want when a typed library has to ship its marker but nothing else from the source tree.

psutil is pinned because a clustering dependency needs it

The runtime dependency list is seven entries, and one of them is there for someone else's reasons.

The core is a numeric stack, the deep learning framework, a tensor rearrangement helper, and a graph export library. The interesting entry is a fast clustering package, pinned with a lower bound, and next to it a process utility library carrying an explicit comment saying it is required by that clustering package.

So a system-monitoring dependency is a direct requirement of this library for one transitive reason. That is not wrong, and it has a defensible logic: pinning it yourself means you control the version rather than inheriting whatever the clustering package resolves.

The clustering dependency is not incidental either. Structure learning and parameter initialization in probabilistic circuits both use clustering, which is why a k-means implementation appears in a library whose stated inference cost is polynomial.

The remaining pins are floors rather than ceilings, with a notably old lower bound on the scientific computing library and a recent one on the numeric and tensor libraries. Nothing in the manifest caps the deep learning framework, so the resolver's newest version is the one you get.

Two type checkers are installed side by side in the dev extra

The development extra is where the project's habits show.

It installs two formatters' worth of tooling in practice: a code formatter and a linter, both with narrow compatible-release pins. Then it installs two independent static type checkers, one established and one much newer, at separate versions.

Running two type checkers means the codebase satisfies two different inference engines, which is a stronger guarantee than either alone and a real cost in configuration and in pull request time. For a library that ships a typing marker and advertises typed support, that choice is coherent.

Testing comes next, with the test runner, the coverage plugin, and pre-commit. An imaging library is also in the extra, which suggests images are compared during tests, consistent with the visualization helper being a feature rather than a debugging aid.

The repository root carries the files that match. A benchmarking document, a changelog, a citation file, a versioning policy describing semantic versioning and deprecation, a release runbook for maintainers, a contributing guide, an agent instructions file, and a root-level pytest configuration.

That is a lot of governance for a library of this size, and most of it exists because the 1.0 rewrite changed enough that deprecation policy became a real question.

The container image runs the test suite as its default command

The Dockerfile describes a CI image, not an application image, and the default command is what makes that obvious.

It starts from a long-term-support Ubuntu release, installs the certificate, build, and virtual environment tooling, and then installs a Python package manager by fetching its install script over the network and piping it into a shell. The repository is copied in and the development extra is synced into a virtual environment.

The final command runs the test runner with four parallel workers. There is no server, no documentation build, and no inference entry point.

So the container is for reproducing the test run rather than for using the library. That is a reasonable choice for a project whose primary artifact is a library, and it also means the documented container route exercises the development dependencies rather than the runtime ones.

Two smaller things are worth noting. The installation of the package manager through a piped script is a common convenience and a common supply-chain trade-off, since the image trusts whatever that URL serves at build time. And the test parallelism is fixed in the image rather than configurable, so a machine with fewer cores will run four workers regardless.

The citation still carries the 2019 subtitle

The citation block predates the current codebase by seven years, and its title no longer matches the repository.

It points at a paper from 2019 with a title describing a library for deep probabilistic learning using sum-product networks. The current project description names the same library as being for sum-product networks, while the README heading describes it as a library for probabilistic circuits. Three names for one project across three places.

There is also a machine-readable citation file at the repository root, so a tool reading that gets a different answer from a reader copying the README block.

What changed is documented in the development status section rather than in the citation. Version 1.0.0 is described as introducing a rewrite onto a modern deep learning framework, and 1.1.0 builds on it, adding a ready-made naive Bayes model in the zoo for density estimation and classification along with performance work across learning, leaf, tensor-rearrangement, and layer modules.

The release dates support that story. Three tags landed within two days: two patch releases of 1.0.0 and then 1.1.0, which is the pattern of a rewrite being stabilized and then cut in sequence.

Editorial conclusion

SPFlow suits a probabilistic-programming user who wants tractable exact inference with autograd on the same device as the rest of a PyTorch stack, since leaves, products, and sums are all torch modules and the zoo ships ready-made models. It does not suit someone who needs the original research-era API, since version 1.0.0 was a rewrite. Before you build on it, read the versioning policy and changelog for what changed in the rewrite, and note that the citation still points at the 2019 paper with the old subtitle.

Frequently asked questions

What is a sum-product network?

In SPFlow's terms, sum-product networks and probabilistic circuits are deep generative and discriminative models that enable tractable polynomial-time probabilistic inference while keeping expressive power. The library exposes that exact inference as marginals, conditionals, and most probable explanations.

How do I install SPFlow?

From PyPI with `pip install spflow`. Contributor setup from source is described in CONTRIBUTING.md, and the container route installs the development extra and then runs pytest with four parallel workers as its default command.

What is in the spflow.zoo package?

Ready-made probabilistic circuit models. Version 1.1.0 added a Naive Bayes model there, used for density estimation and classification, alongside the library's general support for missing data and multiple distribution types.

Which learning methods does SPFlow support?

Gradient descent, expectation-maximization, and structure learning. Circuits can be built by manual design or by automatic structure learning, and inference is exact, with GPU acceleration through PyTorch.

What does the SPFlow DSL do?

You write a weighted mixture expression inside a context manager imported from the dsl module, then call build on the result to materialize an executable circuit. From there the circuit can be scored with log-likelihood, sampled from, or exported as SVG with the visualization helper.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. SPFlow/SPFlow on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/spflow-spflow.svg)](https://hysenlabs.com/projects/spflow-spflow)