fermi

A minimalist calculator for estimating with distributions
Log | Files | Refs | README

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 */