package ope import ( "encoding/binary" "math" "math/big" "xdx.jelly/xgcl/grand" ) // uniformRand returns a random number in [0, 1] func uniformRand() float64 { b := make([]byte, 4) _, _ = grand.GenerateRandom(b) return float64(binary.LittleEndian.Uint32(b)) / float64(uint64(1)<<32-1) } func minBig(a, b *big.Int) *big.Int { if a.Cmp(b) < 0 { return a } return b } func maxBig(a, b *big.Int) *big.Int { if a.Cmp(b) < 0 { return b } return a } func floatFromBig(a *big.Int) float64 { r, _ := a.Float64() return r } // hypergeometric10 returns a sample in hypergeometric distribution. // Pr[X=x] = choose(y, x) * choose(N-y, M-x) / choose(N, M) func hypergeometricSmall(M *big.Int, N *big.Int, y *big.Int) *big.Int { d1 := new(big.Int).Sub(N, y) nm := new(big.Int).Sub(N, M) d2 := minBig(M, nm) Y := new(big.Int).Set(d2) K := y.Int64() for Y.Sign() > 0 && K != 0 { U := uniformRand() Y.Sub(Y, big.NewInt(int64(U+floatFromBig(Y)/(floatFromBig(d1)+float64(K))))) K-- } Z := new(big.Int).Sub(d2, Y) if nm.Cmp(M) < 0 { Z.Sub(y, Z) } return Z } func loggam(x int64) float64 { return math.Log(math.Gamma(float64(x))) } func hypergeometric(M *big.Int, N *big.Int, y *big.Int) *big.Int { if y.BitLen() < 4 { return hypergeometricSmall(M, N, y) } return hypergeometricBig(M, N, y) } func hypergeometricBig(M *big.Int, N *big.Int, y *big.Int) *big.Int { D1 := 1.7155277699214135 D2 := 0.8989161620588988 // # long mingoodbad, maxgoodbad, popsize, m, d9; // # double d4, d5, d6, d7, d8, d10, d11; // # long Z; // # double T, W, X, Y; good := M bad := new(big.Int).Sub(N, M) sample := y mingoodbad := minBig(good, bad) popsize := N maxgoodbad := maxBig(good, bad) m := minBig(sample, new(big.Int).Sub(popsize, sample)) d4 := floatFromBig(mingoodbad) / floatFromBig(popsize) d5 := 1.0 - d4 d6 := floatFromBig(m)*d4 + 0.5 d7 := math.Sqrt(floatFromBig(new(big.Int).Sub(popsize, m))*floatFromBig(sample)*d4*d5/floatFromBig(new(big.Int).Sub(popsize, one)) + 0.5) d8 := D1*d7 + D2 d9 := floatFromBig(new(big.Int).Add(m, one).Mul(new(big.Int).Add(mingoodbad, one))) / (popsize + 2) d10 := loggam(d9+1) + loggam(mingoodbad-d9+1) + loggam(m-d9+1) + loggam(maxgoodbad-m+d9+1) d11 := min(float64(min(m, mingoodbad)+1), math.Floor(d6+16*d7)) // # 16 for 16-decimal-digit precision in D1 and D2 var Z int64 for { X := uniformRand() Y := uniformRand() W := d6 + d8*(Y-0.5)/X // # fast rejection: if W < 0.0 || W >= d11 { continue } Z = int64(math.Floor(W)) T := d10 - (loggam(Z+1) + loggam(mingoodbad-Z+1) + loggam(m-Z+1) + loggam(maxgoodbad-m+Z+1)) // # fast acceptance: if (X*(4.0-X) - 3.0) <= T { break } // # fast rejection: if X*(X-T) >= 1 { continue } // # acceptance: if 2.0*math.Log(X) <= T { break } } // # this is a correction to HRUA* by Ivan Frohne in rv.py if good > bad { Z = m - Z } // # another fix from rv.py to allow sample to exceed popsize/2 if m < sample { Z = good - Z } return Z }