1 Random choices create worlds
if 30% gives its two branches weights of 0.3 and 0.7. A discrete draw splits too: let r ~ 2d6 gives eleven possible totals, each with its probability.
Probl is a probabilistic programming language for probabilities, simulations and forecasts. Write models with variables, functions and loops. It reports distributions over their possible outcomes. Use it to calculate game odds, compare decisions, forecast uncertain quantities or update a model with evidence.
The playground runs models locally in your browser using WebAssembly. No installation is required.
# Craps, pass line bet: what's the chance of winning?let come_out ~ 2d6 # one world per totalvar win = falseif come_out in [7, 11] { win = true} else if come_out not in [2, 3, 12] { loop { # keep rolling for the point let r ~ 2d6 if r == come_out { win = true; break } if r == 7 { break } }}report win
enumeratedwin 49.29%
This craps model has no fixed number of rolls. Probl solves its 6 repeating states as a Markov chain, giving 49.29%. The mathematical probability is 244/495; the engine uses floating-point arithmetic.
How it works
In enumeration mode, a world is a program state with a weight. Random choices split worlds, equivalent states merge, and reports summarize the remaining population. Boolean conditions follow the matching branch; probability conditions explore both.
if 30% gives its two branches weights of 0.3 and 0.7. A discrete draw splits too: let r ~ 2d6 gives eleven possible totals, each with its probability.
let weather = if 30% { "rain" } else { "sun" }report weather
"rain"0.3"sun"0.7Worlds with the same state needed by later code combine, adding their weights. Here two paths reach 0 after two steps and combine into one world with weight ½.
A thousand coin-flip steps have 21000 paths but only 1,001 final positions. Keeping only the current position lets those paths merge; storing the whole history would prevent that.
var pos = 0repeat 3 { pos += if 50% { 1 } else { -1 } }report pos
report collects values across worlds. It displays probabilities for events and summaries for numeric quantities. Ordinary expressions operate within each world; a report performs the aggregation.
enumeratedweather sun 70.00% · rain 30.00%
Games
Dice, card draws and turn-based rules can be written directly. Merging equivalent states keeps many models manageable, although models that retain many distinct histories can still grow exponentially.
# Risk: 10 attacking armies against 8 defenders, fought to the end. enumeratevar attackers = 10var defenders = 8while attackers > 1 and defenders > 0 { # The attacker rolls up to three dice but must leave one army at home; # the defender rolls up to two. roll(n, d6) gives the dice sorted high to low. let attack ~ roll(min(3, attackers - 1), d6) let defend ~ roll(min(2, defenders), d6) # Highest against highest, second against second. Ties go to the defender. for i in 0..<min(attack.len(), defend.len()) { if attack[i] > defend[i] { defenders -= 1 } else { attackers -= 1 } }}# A full round has 56 × 21 distinct dice outcomes. But once it's over, the# dice are never read again, so the only state left is the two army counts:# the worlds merge back into a handful after every round.report defenders == 0 as "attacker conquers"report attackers - 1 as "armies left to move in"
enumeratedattacker conquers 64.64%armies left to move in mean 3.05 · sd 2.78 · 5% 0 · median 3 · 95% 8 · 0 █▁▂▃▃▃▃▂▂▁ 9
Forecasting
Select sampling for large state spaces or continuous calculations that enumeration does not support. Each run follows one random path, and reports include sampling uncertainty. Enumeration is the default; it does not automatically switch modes.
90% of runs 50% median
3% to 7% specifies a lognormal distribution whose 5th and 95th percentiles are 3% and 7%.normal_range(-8%, 0%) uses a normal distribution with those percentiles, allowing negative values.report … by month produces a table of monthly distributions. This chart plots its median and central intervals.sample · 50,000 runs · seed 11MRR ≥ $100k after 18 months 33.3% ± 0.2%
let price = 49 # $ per customer per monthlet churn ~ 3% to 7% # share of customers who leave each month: uncertain, but fixedvar market ~ one_of([Boom: 20%, Steady: 60%, Slump: 20%])var signups ~ 60 to 150 # new customers per month at launchvar customers = 0for month in 1..18 { let new ~ poisson(signups) let lost ~ binomial(customers, churn) customers += new - lost report customers * price by month as "MRR ($)" market = next_month(market) let growth ~ match market { # month-on-month growth in sign-ups Boom => 8% to 20% Steady => 1% to 6% Slump => normal_range(-8%, 0%) # growth can be negative: say which shape you mean } signups *= 1 + growth}report customers * price >= 100_000 as "MRR ≥ $100k after 18 months"
# A condition affects 1% of the population.let sick ~ bernoulli(1%)# Sensitivity is 95%; the false-positive rate is 8%.observe true from bernoulli(if sick { 95% } else { 8% })report sick
enumerated · evidence 8.87%sick 10.71%
Evidence
observe conditions the model on evidence. A boolean observation removes incompatible worlds; an observation from a distribution weights worlds by the likelihood of the observed value.
In this example, prevalence is 1%, sensitivity is 95%, and the false-positive rate is 8%. Given a positive test, the posterior probability is 10.71%. The run also reports the probability of observing a positive result.
Models can read typed CSV, JSON and text inputs. Supported conjugate pairs, such as a beta prior with binomial observations, update analytically in enumeration and sampling.
Continuous models
Enumeration can keep a continuous draw as a distribution through affine calculations, thresholds, abs, min, max and clamp. Supported conjugate observations update its posterior without sampling individual values.
let arrival ~ uniform(0, 30)observe arrival > 10let wait = max(arrival - 15, 0)report wait as "wait (minutes)"report wait > 10 as "waiting more than 10 minutes"
enumerated · evidence 66.67%wait (minutes) mean 5.62 · sd 4.96 · 5% 0.00 · median 5.00 · 95% 14.00waiting more than 10 minutes 25.00%
Condition an arrival time on being later than 10 minutes, then calculate the remaining wait. The result includes a 25% chance of no wait.
let rate ~ beta(2, 2)observe 3 from binomial(5, rate)report ratereport rate > 50%
enumerated · evidence 21.43%rate mean 0.56 · sd 0.16 · 5% 0.29 · median 0.56 · 95% 0.81rate > 50% 63.67%
Three successes in five trials update a beta(2, 2) prior to beta(5, 4). Later uses of the same draw see that posterior.
Interpreting results
Reports identify enumeration or sampling and show evidence and unresolved weight where applicable. Enumeration uses floating-point weights; fractions displayed with ≈ are approximations.
enumerated · evidence 8.87% sample · 50,000 runs · seed 11
Pin the engine version, program, inputs, date and random seed to reproduce a successful run. Sampled results do not depend on the number of worker threads. Native and WebAssembly implementations are checked for matching results.
An independent interpreter using rational arithmetic checks the finite discrete subset on generated programs. Known-answer tests and sampling comparisons cover additional behavior. See the testing guide for coverage and remaining gaps.
= can bind a distribution recipe. Combining that recipe with itself describes independent draws. ~ binds one outcome per world; using that value twice refers to the same event. Here the probabilities are 9% and 30%.
let storm = bernoulli(30%)report storm and storm as "two independent storms"let happened ~ stormreport happened and happened as "the same storm twice"
enumeratedtwo independent storms 9.00%the same storm twice 30.00%
Get started
The browser editor provides completion, hover help, definitions and renaming. It includes a guide, reference and runnable examples. Shared links contain the program.
Open the playgroundInstall the CLI from crates.io with Rust 1.85 or later. The package is probl-cli; the command is probl:
$ cargo install probl-cli --locked $ probl run model.probl $ probl run model.probl --runs 100000 --seed 3 $ probl check model.probl $ probl repl
Examples
Examples cover games, forecasting, calendars, decisions, monitoring and risk. Each includes assumptions and expected results, and opens in the playground.
simulate, a balancing sweep
report … by
a to b estimates, regime switching, a fan chart over months
observe … from, learning a rate, then forecasting with it
today, weekends, explicit holidays and deadline probability
Probl 0.2.1 is early-stage software. The language and Rust API are evolving, and some type errors are detected at runtime. General nonlinear continuous inference, modules and quantum-amplitude simulation are not implemented. Review the reference semantics for current boundaries.
Probl draws on ideas from Squiggle, AnyDice and WebPPL. Source code is available on GitHub under MIT or Apache-2.0, without warranty.