sample.go (5332B)
1 package sample 2 3 import "math" 4 import "sync" 5 import rand "math/rand/v2" 6 7 // https://pkg.go.dev/math/rand/v2 8 9 type Src = *rand.Rand 10 type func64 = func(Src) float64 11 12 var global_r = rand.New(rand.NewPCG(uint64(1), uint64(2))) 13 14 func Sample_unit_uniform(r Src) float64 { 15 return r.Float64() 16 } 17 18 func Sample_unit_normal(r Src) float64 { 19 return r.NormFloat64() 20 } 21 22 func Sample_uniform(start float64, end float64, r Src) float64 { 23 return Sample_unit_uniform(r)*(end-start) + start 24 } 25 26 func Sample_normal(mean float64, sigma float64, r Src) float64 { 27 return mean + Sample_unit_normal(r)*sigma 28 } 29 30 func Sample_lognormal(logmean float64, logstd float64, r Src) float64 { 31 return (math.Exp(Sample_normal(logmean, logstd, r))) 32 } 33 34 func Sample_normal_from_90_ci(low float64, high float64, r Src) float64 { 35 var normal90 float64 = 1.6448536269514727 36 var mean float64 = (high + low) / 2.0 37 var std float64 = (high - low) / (2.0 * normal90) 38 return Sample_normal(mean, std, r) 39 } 40 41 func Sample_to(low float64, high float64, r Src) float64 { 42 // Given a (positive) 90% confidence interval, 43 // returns a sample from a lognorma with a matching 90% c.i. 44 // Key idea: If we want a lognormal with 90% confidence interval [a, b] 45 // we need but get a normal with 90% confidence interval [log(a), log(b)]. 46 // Then see code for Sample_normal_from_90_ci 47 var loglow float64 = math.Log(low) 48 var loghigh float64 = math.Log(high) 49 return math.Exp(Sample_normal_from_90_ci(loglow, loghigh, r)) 50 } 51 52 func Sample_gamma(alpha float64, r Src) float64 { 53 54 // a simple method for generating gamma variables, marsaglia and wan tsang, 2001 55 // https://dl.acm.org/doi/pdf/10.1145/358407.358414 56 // see also the references/ folder 57 // note that the wikipedia page for the gamma distribution includes a scaling parameter 58 // k or beta 59 // https://en.wikipedia.org/wiki/gamma_distribution 60 // such that gamma_k(alpha, k) = k * gamma(alpha) 61 // or gamma_beta(alpha, beta) = gamma(alpha) / beta 62 // so far i have not needed to use this, and thus the second parameter is by default 1. 63 64 if alpha >= 1 { 65 var d, c, x, v, u float64 66 d = alpha - 1.0/3.0 67 c = 1.0 / math.Sqrt(9.0*d) 68 69 for { 70 71 InnerLoop: 72 for { 73 x = Sample_unit_normal(r) 74 v = 1.0 + c*x 75 if v > 0.0 { 76 break InnerLoop 77 } 78 } 79 80 v = v * v * v 81 u = Sample_unit_uniform(r) 82 83 if u < 1.0-0.0331*(x*x*x*x) { // Condition 1 84 // the 0.0331 doesn't inspire much confidence 85 // however, this isn't the whole story 86 // by knowing that Condition 1 implies condition 2 87 // we realize that this is just a way of making the algorithm faster 88 // i.e., of not using the logarithms 89 return d * v 90 } 91 if math.Log(u) < 0.5*(x*x)+d*(1.0-v+math.Log(v)) { // Condition 2 92 return d * v 93 } 94 95 } 96 97 } else { 98 return Sample_gamma(1.0+alpha, r) * math.Pow(Sample_unit_uniform(r), 1.0/alpha) 99 } 100 } 101 102 func Sample_beta(a float64, b float64, r Src) float64 { 103 gamma_a := Sample_gamma(a, r) 104 gamma_b := Sample_gamma(b, r) 105 return gamma_a / (gamma_a + gamma_b) 106 } 107 108 func Sample_mixture(fs []func64, weights []float64, r Src) float64 { 109 110 // fmt.Println("weights initially: ", weights) 111 var sum_weights float64 = 0 112 for _, weight := range weights { 113 sum_weights += weight 114 } 115 116 var total float64 = 0 117 var cumsummed_normalized_weights = append([]float64(nil), weights...) 118 for i, weight := range weights { 119 total += weight / sum_weights 120 cumsummed_normalized_weights[i] = total 121 } 122 123 var result float64 124 var flag int = 0 125 var p float64 = r.Float64() 126 127 for i, cnw := range cumsummed_normalized_weights { 128 if p < cnw { 129 result = fs[i](r) 130 flag = 1 131 break 132 } 133 } 134 135 if flag == 0 { 136 result = fs[len(fs)-1](r) 137 } 138 return result 139 140 } 141 142 func Sample_serially(f func64, n_samples int) []float64 { 143 xs := make([]float64, n_samples) 144 // var global_r = rand.New(rand.NewPCG(uint64(1), uint64(2))) 145 for i := 0; i < n_samples; i++ { 146 xs[i] = f(global_r) 147 } 148 return xs 149 } 150 151 func Sample_parallel(f func64, n_samples int) []float64 { 152 var num_threads = 16 153 var xs = make([]float64, n_samples) 154 var wg sync.WaitGroup 155 var h = n_samples / num_threads 156 wg.Add(num_threads) 157 for i := range num_threads { 158 var xs_i = xs[i*h : (i+1)*h] 159 go func(f func64) { 160 defer wg.Done() 161 var r = rand.New(rand.NewPCG(uint64(i), uint64(i+1))) 162 for i := range xs_i { 163 xs_i[i] = f(r) 164 } 165 }(f) 166 } 167 168 wg.Wait() 169 return xs 170 171 } 172 173 /* 174 func main() { 175 176 var p_a float64 = 0.8 177 var p_b float64 = 0.5 178 var p_c float64 = p_a * p_b 179 ws := [4](float64){1 - p_c, p_c / 2, p_c / 4, p_c / 4} 180 181 Sample_0 := func(r Src) float64 { return 0 } 182 Sample_1 := func(r Src) float64 { return 1 } 183 Sample_few := func(r Src) float64 { return Sample_to(1, 3, r) } 184 Sample_many := func(r Src) float64 { return Sample_to(2, 10, r) } 185 fs := [4](func64){Sample_0, Sample_1, Sample_few, Sample_many} 186 187 model := func(r Src) float64 { return Sample_mixture(fs[0:], ws[0:], r) } 188 n_samples := 1_000_000 189 xs := Sample_parallel(model, n_samples) 190 var avg float64 = 0 191 for _, x := range xs { 192 avg += x 193 } 194 avg = avg / float64(n_samples) 195 fmt.Printf("Average: %v\n", avg) 196 /* 197 // Without concurrency: 198 n_samples := 1_000_000 199 var r = rand.New(rand.NewPCG(uint64(1), uint64(2))) 200 var avg float64 = 0 201 for i := 0; i < n_samples; i++ { 202 avg += Sample_mixture(fs[0:], ws[0:], r) 203 } 204 avg = avg / float64(n_samples) 205 fmt.Printf("Average: %v\n", avg) 206 } 207 */