Skip to content

gdejohn/monty

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

83 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

monty

A Java 25 library for estimating equity and expected value in Texas hold 'em using Monte Carlo simulation.

Usage

Tell Monty your hole cards, the community cards, and the number of players including yourself:

Monty monty = Monty.pocket(EIGHT.of(CLUBS), NINE.of(CLUBS))
                   .flop(SEVEN.of(CLUBS), TEN.of(CLUBS), ACE.of(HEARTS))
                   .players(4);

Get a lazy, parallel stream that pseudorandomly samples the possible outcomes given those constraints, running independent trials on every available thread:

IntStream trials = monty.limit(1_000_000);

The result of a trial is a nonnegative integer indicating the number of players that you split the pot with, including yourself: 0 means you lost, 1 means you won, and n > 1 means an n-way tie. Or Monty can determine how many trials to run and summarize the outcomes for you given an error magnitude and confidence level:

double error = 0.001d;
double confidence = 0.999_999d;
Equity equity = monty.equity(error, confidence);

Your equity is the fraction of the pot that you won on average across every trial, which converges to the true average across every possible outcome:

assert Math.abs(0.52279d - equity.estimate()) < error;

The expected value of a call is the ratio of your estimated winnings to the size of the raise:

var pot = 100;
var raise = 50;

assert equity.expectedValue(pot, raise) > 1.0d;

Accuracy tends to increase with further trials, subject to diminishing returns.

Monte Carlo simulation

The Monte Carlo simulation uses the Deck class to represent a deck of cards that can be lazily shuffled (dealing and shuffling are interleaved, producing each card on demand, drawn uniformly at random from the remaining cards in the deck with fast bounded random number generation using Lemire's method). A deck can be split to run independent simulations in a parallel stream, optionally providing a custom SplittableGenerator implementation. Throughput scales linearly with the number of threads.

Hand evaluation

The evaluator directly computes the value of the best five-card hand made from seven given cards (without checking each of the 21 combinations) using bitwise logical operators, shifts, some integer arithmetic (no multiplication, division, or modulo), and five reads from a 16KB lookup table (comfortably fits in L1 cache). It is garbage free, does not use the standard library, performs no pairwise card comparisons, and has no loops or conditional statements. The only branch is a switch expression that compiles to a tableswitch, indexed by a hash value to flatten what would otherwise be a dense tree of unpredictable conditional branches into one small jump table.

Partial evaluations are represented with a persistent data structure (two long bit vectors in a value class, built up one card at a time) that can be reused in the evaluation of each player's hand for the community cards they all have in common.

Benchmarks

On an AMD Ryzen 9 9950X3D at 5.7GHz, Monty can evaluate over 60 million random seven-card hands per second per core (less than 100 CPU cycles per hand).

./mvnw clean package && java -jar benchmarks/target/benchmarks.jar -prof perfnorm -prof gc
Benchmark Mode Score Error Units
Monty.evaluateRandom thrpt 61340979.020 ± 398049.638 ops/s
Monty.evaluateRandom:CPI thrpt 0.390 ± 0.014 clks/insn
Monty.evaluateRandom:IPC thrpt 2.564 ± 0.093 insns/clk
Monty.evaluateRandom:L1-dcache-load-misses:u thrpt 0.001 ± 0.001 #/op
Monty.evaluateRandom:L1-dcache-loads:u thrpt 56.644 ± 0.174 #/op
Monty.evaluateRandom:L1-icache-load-misses:u thrpt 0.001 ± 0.002 #/op
Monty.evaluateRandom:L1-icache-loads:u thrpt 0.008 ± 0.022 #/op
Monty.evaluateRandom:branch-misses:u thrpt 0.742 ± 0.005 #/op
Monty.evaluateRandom:branches:u thrpt 9.565 ± 0.002 #/op
Monty.evaluateRandom:cycles:u thrpt 92.897 ± 3.329 #/op
Monty.evaluateRandom:dTLB-load-misses:u thrpt ≈ 10⁻⁵ #/op
Monty.evaluateRandom:dTLB-loads:u thrpt ≈ 10⁻⁴ #/op
Monty.evaluateRandom:gc.alloc.rate thrpt 0.001 ± 0.001 MB/sec
Monty.evaluateRandom:gc.alloc.rate.norm thrpt ≈ 10⁻⁵ B/op
Monty.evaluateRandom:gc.count thrpt ≈ 0 counts
Monty.evaluateRandom:iTLB-load-misses:u thrpt ≈ 10⁻⁵ #/op
Monty.evaluateRandom:iTLB-loads:u thrpt ≈ 10⁻⁴ #/op
Monty.evaluateRandom:instructions:u thrpt 238.167 ± 0.151 #/op
Monty.evaluateRandom:stalled-cycles-frontend:u thrpt 9.308 ± 3.170 #/op

About

Fast hand evaluation and equity estimation for Texas hold 'em

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages