Package: zarpy
Zarpy: formally verified biased coin and n-sided die.See thepaper(to appear in
PLDI'23) andGithub repository.Why use Zarpy?Probabilistic choiceA basic operation in randomized algorithms isprobabilistic choice:
for somep ∈ [0,1], execute actiona1with probabilitypor
actiona2with probability1-p(i.e., flip a biased coin to
determine the path of execution). A common method for performing
probabilistic choice is as follows:ifrandom()<p:executea1else:executea2wherepis a float in the range[0,1]andrandom()produces a
random float in the range[0,1). While good enough for many
applications, this approach is not always correct due to float
roundoff error. We can only expecta1to be executed with
probabilityp + ϵfor some small error termϵ, which technically
invalidates any correctness guarantees of our overall system that
depend on the correctness of its probabilistic choices.Zarpy provides an alternative that is guaranteed (by formal proof in
Coq) to executea1with probabilityp(wherenanddare
integers such thatp = n/d):fromzarpyimportbuild_coin,flipbuild_coin((n,d))# Build and cache coin with bias p = n/difflip():# Generate a Boolean value with Pr(True) = pexecutea1else:executea2Uniform samplingAnother common operation is to randomly draw from a finite collection
of values with equal (uniform) probability of each. An old trick for
drawing an integer uniformly from the range[0, n)is to generate a
random integer from[0, RAND_MAX]and take the modulus wrt.n:x=rand()%n# Assign x random value from [0,n)but this method suffers from modulo bias whennis not a power of 2,
causing some values to occur with higher probability than others (see,
e.g.,this
articlefor more information on modulo bias). Zarpy provides a uniform sampler
that is guaranteed for any integer0 < nto generate samples from[0,n)with probability1/neach:fromzarpyimportbuild_die,rollbuild_die(n)x=roll()Although the Python functionrandom.randintis ostensibly free from
modulo bias, our implementation guarantees so by aformal proof of
correctnessin Coq.Trusted Computing BaseThe samplers provided by Zarpy have been implemented and verified in
Coq and extracted to OCaml and bundled into Python package viapythonlib. Validity of the
correctness proofs is thus dependent on the correctness of Coq's
extraction mechanism, the OCaml compiler and runtime, a small amount
of OCaml shim code (viewablehere),
and the pythonlib library.Proofs of correctnessThe coin and die samplers are implemented as probabilistic programs in
theZarsystem and compiled tointeraction treesimplementing them via reduction to sequences of fair coin flips. See
Section 3 of thepaperfor details
and the filezarpy.vfor their
implementations and proofs of correctness.Correctness is two-fold. For biased coin with biasp, we prove:coin_itree_correct:
the probability of producingtrueaccording to the formal probabilistic
semantics of the constructed interaction tree is equal top, andcoin_samples_equidistributed:
when the source of random bits is uniformly distributed, for any
sequence of coin flips the proportion oftruesamples converges topas the number of samples goes to +∞.The equidistribution result is dependent on uniform distribution of
the Boolean values generated by OCaml'sRandom.boolfunction. Seethe paperfor a more detailed
explanation.Similarly, the theoremdie_itree_correctproves semantic correctness of the n-sided die, anddie_samples_equidistributedequidistribution of its samples.Usageseed()initializes the PRNG viaRandom.self_init.Biased coinbuild_coin((num, denom))builds and caches a coin withPr(True) = num/denomfor nonnegative integernumand positive integerdenom.flip()produces a single Boolean sample by flipping the cached coin.flip_n(n)producesnBoolean samples by flipping the cached coin.N-sided diebuild_die(n)builds and caches an n-sided die withPr(m) = 1/nfor
integermwhere0 <= m < n.roll()produces a single sample by rolling the cached die.roll_n(n)producesninteger samples by rolling the cached die.