Imp
Imprecise probabilistic programming via BDDs in Haskell.
Imp is a domain-specific language for discrete probabilistic programs with Knightian uncertainty, where some probabilities are not precisely known.
Programs compile to binary decision diagrams (BDDs) for inference via weighted model counting.
Unlike standard probabilistic programming, Imp computes credal sets.
These are convex sets of distributions that capture all possibilities consistent with the specified uncertainty,
enabling reasoning under ambiguity in settings like decision-making, planning, and robust inference.
Key ideas
| Concept |
Description |
| Graded Monad DSL |
Programs have type Imp (g :: [Symbol]) a, where g is a type-level set of Knightian names tracking sources of adversarial uncertainty. Uses QualifiedDo for ergonomic do-notation. |
| BDD Compilation |
Both probabilistic flips and Knightian choices compile to BDD variables. Probabilistic variables get weights for WMC whereas Knightian variables are left free. |
| Semiring-parametric Inference |
Inference is performed via semiring-parametric WMC enabling different inference methods based on the instantiation. |
| Enumeration via Probabilities |
Computes the full credal set by enumerating Knightian valuations, giving exact lower and upper probabilities. |
| Optimization via Dual Numbers |
Performs gradient ascent over the Knightian weights to search for probability bounds. |
| Approximation via Intervals |
Uses interval arithmetic to give a sound outer approximation of interval bounds in one WMC pass. |
| Symbolic via Polynomials |
Recovers polynomial representation of the credal set for later optimization (i.e. by a corner search). |
Quick start
{-# LANGUAGE DataKinds, QualifiedDo, RebindableSyntax, TypeApplications #-}
import Imp
data Ball = Red | Black | Yellow deriving (Eq, Ord, Show)
-- Ellsberg's urn: 30 red, 60 either black or yellow
ellsberg :: Imp '["split"] Ball
ellsberg = Imp.do
isRed <- flip (1/3)
isBlack <- interval @"split" 0.0 1.0
Imp.return $ if isRed then Red
else if isBlack then Black
else Yellow
Running inference
-- Per-value marginal bounds (exact enumeration)
marginal ellsberg
-- fromList [(Red,(0.333,0.333)),(Black,(0.0,0.667)),(Yellow,(0.0,0.667))]
-- Lower/upper probability of an event
intervalProbability ellsberg (== Red)
-- (0.333, 0.333)
intervalProbability ellsberg (\b -> b == Red || b == Yellow)
-- (0.333, 1.0)
Precise marginals can be calculated for programs with no Knightian uncertainty:
fairCoin :: Imp '[] Bool
fairCoin = flip 0.5
preciseMarginal fairCoin
-- fromList [(False,0.5),(True,0.5)]
Building and running
Requires GHC 9.10+ and Cabal. Add imp-ppl to build-depends, then import Imp.
# Build the library
cabal build
# Run the test suite
cabal test
# Dev tooling below (requires the `dev` flag)
# Benchmarks (each emits CSV to stdout: N,method,time_s):
cabal run -f dev bench-ellsberg # n-way Ellsberg, N = 2..15
cabal run -f dev bench-robot # n-step robot IMDP, N = 1..8
# Visualization tool (generates index.html with credal set plots and BDD diagrams)
cabal run -f dev viz
open index.html
Modules
| Module |
Description |
Imp |
Single-import entry point: custom prelude, DSL combinators, and the inference API |
Imp.Prelude |
The standard Prelude minus the six names the DSL replaces |
Imp.DSL |
Graded monad GADT, flip, knight, interval, observe, tag |
Imp.DSL.Grade |
Type-level grade algebra: Merge, Union, TagAll |
Imp.DSL.Combinators |
Iteration combinators: mapName, intervalMap, foldN, scanN, foldMN |
Imp.Semiring |
Semiring class, ProbS, DualS, IntervalS, PolyS |
Imp.BDD |
Core BDD types |
Imp.BDD.Builder |
Hash-consed BDD manager, ITE/And/Or operations |
Imp.BDD.Compile |
Compilation from Imp programs to BDDs |
Imp.BDD.WMC |
Semiring-parametric weighted model counting |
Imp.Inference |
Re-exports the four inference backends |
Imp.Inference.Enumerate |
Exact inference by Knightian valuation enumeration |
Imp.Inference.Approx |
Sound outer bounds via interval WMC |
Imp.Inference.Optimize |
Gradient ascent via dual number WMC |
Imp.Inference.Symbolic |
Exact inference via polynomial WMC and corner search |
Imp.Examples.Basic |
Simple coin-flip programs with no Knightian uncertainty |
Imp.Examples.Ellsberg |
The Ellsberg paradox: 30 Red balls, 60 Black or Yellow in unknown proportion |
Imp.Examples.IMDP |
Interval MDP: robot navigation on a line |
Imp.Examples.Iteration |
Random walks with imprecise step probability |
Imp.Examples.Knightian |
Knightian names controlling correlation between choices |
Imp.Examples.MontyHall |
Monty Hall problem with imprecise host behavior |
Imp.Examples.Polytope |
Polytope credal sets from composed intervals |
Imp.Examples.TwoChild |
The imprecise two-child problem |
GHC extensions
Users need only four pragmas plus one import:
{-# LANGUAGE DataKinds, QualifiedDo, RebindableSyntax, TypeApplications #-}
import Imp
The single import provides both unqualified access (DSL combinators, ifThenElse for RebindableSyntax, and the inference API) and the Imp. qualifier that QualifiedDo desugaring uses (Imp.do, Imp.return).
RebindableSyntax also rebinds ordinary monadic do, so put driver code in a separate module importing Imp.Inference (not Imp, which shadows return, flip, and fmap), or keep one module and use import qualified Prelude as P with P.do for IO.
Internally, the library uses DataKinds, DerivingStrategies, GADTs, and TypeFamilies as cabal default-extensions, plus per-file UndecidableInstances and AllowAmbiguousTypes where needed.
Main features
Probabilistic and Knightian choices
flip p creates a coin flip that returns True with probability p.
knight @"name" creates a Knightian binary choice returning True with unknown probability.
interval @"name" lo hi is shorthand for a Bernoulli with unknown probability in [lo, hi].
Conditioning
observe conditions on a Boolean being True:
conditioned :: Imp '["bias"] Bool
conditioned = Imp.do
biased <- interval @"bias" 0.3 0.7
observe biased
Imp.return biased
Tagging and iteration
tag @"name" scopes all Knightian choices in a subprogram under a tag, enabling reuse.
Combinators like foldN, scanN, and foldMN iterate over numbered tags for multi-step models:
-- Robot taking 5 steps with interval transition probabilities
trajectory :: Imp '["step1.d", "step2.d", "step3.d", "step4.d", "step5.d"] [Position]
trajectory = scanN @5 @"step" dynamics startPos step
Inference modes
| Function |
Description |
Complexity |
preciseMarginal |
Exact marginals for programs with no Knightian uncertainty |
O(|BDD|) |
marginal |
Exact per-value lower/upper bounds via enumeration |
O(2^k x |BDD|) |
intervalProbability |
Exact lower/upper P(event) via enumeration |
O(2^k x |BDD|) |
intervalExpectation |
Exact lower/upper E[f] via enumeration |
O(2^k x |BDD|) |
marginalApprox |
Sound outer approximation via interval WMC |
O(|BDD|) |
intervalProbabilityApprox |
Sound outer approximation of P(event) |
O(|BDD|) |
intervalExpectationApprox |
Sound outer approximation of E[f] |
O(|BDD|) |
marginalSymbolic |
Exact per-value bounds via symbolic WMC |
O(2^k x |BDD|) |
intervalProbabilitySymbolic |
Exact lower/upper P(event) via symbolic WMC |
O(2^k x |BDD|) |
intervalExpectationSymbolic |
Exact lower/upper E[f] via symbolic WMC |
O(2^k x |BDD|) |
credalVertices |
The distributions per feasible Knightian valuation |
O(2^k x |BDD|) |
optimizeProbability |
Gradient-based optimization over the credal set |
O(steps x k x |BDD|) |
optimizeExpectation |
Gradient-based optimization of expectations |
O(steps x k x |BDD|) |
Where k = number of Knightian variables.
Examples
See Imp.Examples.* for worked examples:
- Basic: simple coin flips
- Ellsberg: the classic ambiguity-aversion paradox (30 red, 60 black/yellow unknown split)
- Knightian:
dependent/independent showing how Knightian names control correlation
- IMDP: robot navigation on a line with interval transition probabilities, including compositional reuse via
tag
- Iteration: random walks using
intervalMap
- MontyHall: Monty Hall problem with Knightian host bias
- Polytope: composed interval choices forming higher-dimensional credal sets
- TwoChild: an imprecise variant of the two-child problem, demonstrating
observe
Accompanying paper
The library accompanies the paper Imprecise Probabilistic Programming, Precisely (Functional Pearl) (to appear).
The corresponding code is tagged icfp2026.
The current version differs from the paper as follows.
| Name |
Change |
compile |
Returns a tuple of BDD manager, variable weights, Knightian variable indices, and worlds as a map rather than an association list |
wmc, wmcBatch |
Take a Weight -> (s, s) interpretation of the probability/Knightian weights into the semiring, rather than WMCParams; added wmcBatch for Traversables |
ProbS |
Renamed from RealS |
foldMN |
Renamed from foldmN |
preciseMarginal, credalVertices |
Return maps instead of association lists |
marginal, marginalApprox, marginalSymbolic |
Return maps of lower/upper pairs instead of lists of triples |
optimizeExpectation, optimizeProbability |
Renamed from credalOptimize*, take the program as the first argument, and return maps of Knightian weights rather than association lists |
(>>=), (>>) |
Carry an Ord constraint, permitting intermediate grouping of worlds in compile, which reduces the branching factor |
Imp.Inference.Symbolic |
Implemented the polynomial semiring and corner search that the paper left for future work |
| Empty credal set |
Now uniformly throws an error when inferring bounds |