Skip to content
Niclas edited this page Jul 18, 2026 · 6 revisions

regret: Counterfactual Regret Minimization for imperfect-information games

regret is a general-purpose, game-agnostic Rust implementation of the Counterfactual Regret Minimization (CFR / MCCFR) family for imperfect-information extensive-form games. The solver knows nothing about poker, dice, or cards: you bring a game that implements the Game trait and the trainer learns a Profile (a strategy).

New to the crate? Start with Getting Started for a minimal compiling example, then come back here for orientation.

Where to start

Guides (the learning path, in order):

Reference:

  • Profiles: Profile, the versioned checkpoint format, provenance, shape tags, and the Policy trait.
  • Analytics: exact exploitability, best response, and NashConv, plus the sampled estimator for large games and its limits.
  • Subgames: safe subgame solving, blueprints, depth-limited and compositional re-solving.
  • Abstraction: action abstraction.
  • Interior-perturbed strategies: the rtcfr module for near-Nash strategies that keep every action strictly positive.
  • Deep CFR: the optional deep feature (Deep CFR, ESCHER, DREAM, and more).
  • Reference games: the example games used by the tests.

Defaults

Trainer::new(game) defaults to external-sampling MCCFR with Discounted CFR+ (DCFR+), a general-purpose configuration that works well across game sizes. The single-call Profile::train(game, iters) covers the common case; Getting Started walks through it end to end.

The DCFR+ default is not the right blueprint variant for safe subgame solving, where strict idempotency needs a CFR-family variant. See Subgames.

Workspace layout

  • regret (root crate): the solver, with Game (src/game.rs), Trainer (src/trainer/mod.rs), and Profile (src/profile/mod.rs). src/lib.rs re-exports the prelude.
  • regret-games (workspace member, publish = false): reference games (Kuhn, RPS, Goofspiel, Liar's Dice, Leduc, and others). It is a dev-dependency, so cargo test compiles every reference game and the first build is slow.
  • regret-py is excluded from the workspace (it pulls in pyo3 / Python). Do not expect it to build under plain cargo, and do not run cargo from inside it without a Python toolchain.

Features (Cargo.toml)

  • parallel (default): rayon multi-worker ensemble training in the tabular Trainer.
  • deep: neural-net / sampled Deep CFR (src/deep/, tests/deep.rs), not in default. cargo test --features deep compiles and runs it.
  • f32-tables: store accumulators as f32 (memory-bound games); the math stays f64.

Edition 2024, MSRV 1.85.

CFR semantics you must not misread

  • exploitability_2p_zerosum is a Nash proof (goes to 0) for 2-player zero-sum games only.
  • For N > 2 or non-zero-sum games, best_responses / exploitability_* are a quality signal and do not prove Nash. Averaging independent parallel runs is only exact for 2p zero-sum; for N > 2 treat it as variance reduction.
  • regret::sampled gives a sampled best response / exploitability with a confidence interval for games too large for an exact full-tree traversal.

See Analytics for the dispatcher logic and its caveats.

Conventions

  • The crate uses Greek/math glyphs in its formulas (σ, π, Δ, R⁺). This wiki keeps that convention rather than ASCII-fying it.
  • Doc comments in the source state behavior, panics, and complexity, including where a function panics.
  • For a current, version-pinned API, check docs.rs for the dependency versions actually in Cargo.lock, not whatever is latest.

Building and testing

cargo test                        # default features (parallel on); unit + proptest
cargo test --features deep        # also compiles + runs the Deep CFR path
cargo test --no-default-features  # parallel OFF: CI checks this and --all-features
cargo test --test unit            # one focused test file
cargo test --release -- --ignored --test-threads=1   # convergence suite (slow)
cargo clippy --all-targets --all-features -- -W clippy::all
cargo fmt --all -- --check        # formatting gate (CI fails on this)
cargo bench --all-features        # benches/suite.rs

The convergence tests in tests/convergence.rs are #[ignore]d: they train real games to equilibrium, so run them only with --ignored --release --test-threads=1. Plain cargo test does not compile the deep path; add --features deep.

regret-py is excluded from the workspace, so a plain cargo at the root will not build it; it needs a Python toolchain.

Build gotcha: a git stash / stash pop shifts file mtimes and can leave a stale compiled build, so a following cargo test may report against old objects (seen with the sampled_* tests). Force a fresh rebuild before trusting results after any mtime-shifting operation.

Clone this wiki locally