208 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package sample
 | 
						|
 | 
						|
import "math"
 | 
						|
import "sync"
 | 
						|
import rand "math/rand/v2"
 | 
						|
 | 
						|
// https://pkg.go.dev/math/rand/v2
 | 
						|
 | 
						|
type Src = *rand.Rand
 | 
						|
type func64 = func(Src) float64
 | 
						|
 | 
						|
var global_r = rand.New(rand.NewPCG(uint64(1), uint64(2)))
 | 
						|
 | 
						|
func Sample_unit_uniform(r Src) float64 {
 | 
						|
	return r.Float64()
 | 
						|
}
 | 
						|
 | 
						|
func Sample_unit_normal(r Src) float64 {
 | 
						|
	return r.NormFloat64()
 | 
						|
}
 | 
						|
 | 
						|
func Sample_uniform(start float64, end float64, r Src) float64 {
 | 
						|
	return Sample_unit_uniform(r)*(end-start) + start
 | 
						|
}
 | 
						|
 | 
						|
func Sample_normal(mean float64, sigma float64, r Src) float64 {
 | 
						|
	return mean + Sample_unit_normal(r)*sigma
 | 
						|
}
 | 
						|
 | 
						|
func Sample_lognormal(logmean float64, logstd float64, r Src) float64 {
 | 
						|
	return (math.Exp(Sample_normal(logmean, logstd, r)))
 | 
						|
}
 | 
						|
 | 
						|
func Sample_normal_from_90_ci(low float64, high float64, r Src) float64 {
 | 
						|
	var normal90 float64 = 1.6448536269514727
 | 
						|
	var mean float64 = (high + low) / 2.0
 | 
						|
	var std float64 = (high - low) / (2.0 * normal90)
 | 
						|
	return Sample_normal(mean, std, r)
 | 
						|
}
 | 
						|
 | 
						|
func Sample_to(low float64, high float64, r Src) float64 {
 | 
						|
	// Given a (positive) 90% confidence interval,
 | 
						|
	// returns a sample from a lognorma with a matching 90% c.i.
 | 
						|
	// Key idea: If we want a lognormal with 90% confidence interval [a, b]
 | 
						|
	// we need but get a normal with 90% confidence interval [log(a), log(b)].
 | 
						|
	// Then see code for Sample_normal_from_90_ci
 | 
						|
	var loglow float64 = math.Log(low)
 | 
						|
	var loghigh float64 = math.Log(high)
 | 
						|
	return math.Exp(Sample_normal_from_90_ci(loglow, loghigh, r))
 | 
						|
}
 | 
						|
 | 
						|
func Sample_gamma(alpha float64, r Src) float64 {
 | 
						|
 | 
						|
	// a simple method for generating gamma variables, marsaglia and wan tsang, 2001
 | 
						|
	// https://dl.acm.org/doi/pdf/10.1145/358407.358414
 | 
						|
	// see also the references/ folder
 | 
						|
	// note that the wikipedia page for the gamma distribution includes a scaling parameter
 | 
						|
	// k or beta
 | 
						|
	// https://en.wikipedia.org/wiki/gamma_distribution
 | 
						|
	// such that gamma_k(alpha, k) = k * gamma(alpha)
 | 
						|
	// or gamma_beta(alpha, beta) = gamma(alpha) / beta
 | 
						|
	// so far i have not needed to use this, and thus the second parameter is by default 1.
 | 
						|
 | 
						|
	if alpha >= 1 {
 | 
						|
		var d, c, x, v, u float64
 | 
						|
		d = alpha - 1.0/3.0
 | 
						|
		c = 1.0 / math.Sqrt(9.0*d)
 | 
						|
 | 
						|
		for {
 | 
						|
 | 
						|
		InnerLoop:
 | 
						|
			for {
 | 
						|
				x = Sample_unit_normal(r)
 | 
						|
				v = 1.0 + c*x
 | 
						|
				if v > 0.0 {
 | 
						|
					break InnerLoop
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			v = v * v * v
 | 
						|
			u = Sample_unit_uniform(r)
 | 
						|
 | 
						|
			if u < 1.0-0.0331*(x*x*x*x) { // Condition 1
 | 
						|
				// the 0.0331 doesn't inspire much confidence
 | 
						|
				// however, this isn't the whole story
 | 
						|
				// by knowing that Condition 1 implies condition 2
 | 
						|
				// we realize that this is just a way of making the algorithm faster
 | 
						|
				// i.e., of not using the logarithms
 | 
						|
				return d * v
 | 
						|
			}
 | 
						|
			if math.Log(u) < 0.5*(x*x)+d*(1.0-v+math.Log(v)) { // Condition 2
 | 
						|
				return d * v
 | 
						|
			}
 | 
						|
 | 
						|
		}
 | 
						|
 | 
						|
	} else {
 | 
						|
		return Sample_gamma(1.0+alpha, r) * math.Pow(Sample_unit_uniform(r), 1.0/alpha)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func Sample_beta(a float64, b float64, r Src) float64 {
 | 
						|
	gamma_a := Sample_gamma(a, r)
 | 
						|
	gamma_b := Sample_gamma(b, r)
 | 
						|
	return gamma_a / (gamma_a + gamma_b)
 | 
						|
}
 | 
						|
 | 
						|
func Sample_mixture(fs []func64, weights []float64, r Src) float64 {
 | 
						|
 | 
						|
	// fmt.Println("weights initially: ", weights)
 | 
						|
	var sum_weights float64 = 0
 | 
						|
	for _, weight := range weights {
 | 
						|
		sum_weights += weight
 | 
						|
	}
 | 
						|
 | 
						|
	var total float64 = 0
 | 
						|
	var cumsummed_normalized_weights = append([]float64(nil), weights...)
 | 
						|
	for i, weight := range weights {
 | 
						|
		total += weight / sum_weights
 | 
						|
		cumsummed_normalized_weights[i] = total
 | 
						|
	}
 | 
						|
 | 
						|
	var result float64
 | 
						|
	var flag int = 0
 | 
						|
	var p float64 = r.Float64()
 | 
						|
 | 
						|
	for i, cnw := range cumsummed_normalized_weights {
 | 
						|
		if p < cnw {
 | 
						|
			result = fs[i](r)
 | 
						|
			flag = 1
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if flag == 0 {
 | 
						|
		result = fs[len(fs)-1](r)
 | 
						|
	}
 | 
						|
	return result
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func Sample_serially(f func64, n_samples int) []float64 {
 | 
						|
	xs := make([]float64, n_samples)
 | 
						|
	// var global_r = rand.New(rand.NewPCG(uint64(1), uint64(2)))
 | 
						|
	for i := 0; i < n_samples; i++ {
 | 
						|
		xs[i] = f(global_r)
 | 
						|
	}
 | 
						|
	return xs
 | 
						|
}
 | 
						|
 | 
						|
func Sample_parallel(f func64, n_samples int) []float64 {
 | 
						|
	var num_threads = 16
 | 
						|
	var xs = make([]float64, n_samples)
 | 
						|
	var wg sync.WaitGroup
 | 
						|
	var h = n_samples / num_threads
 | 
						|
	wg.Add(num_threads)
 | 
						|
	for i := range num_threads {
 | 
						|
		var xs_i = xs[i*h : (i+1)*h]
 | 
						|
		go func(f func64) {
 | 
						|
			defer wg.Done()
 | 
						|
			var r = rand.New(rand.NewPCG(uint64(i), uint64(i+1)))
 | 
						|
			for i := range xs_i {
 | 
						|
				xs_i[i] = f(r)
 | 
						|
			}
 | 
						|
		}(f)
 | 
						|
	}
 | 
						|
 | 
						|
	wg.Wait()
 | 
						|
	return xs
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
func main() {
 | 
						|
 | 
						|
	var p_a float64 = 0.8
 | 
						|
	var p_b float64 = 0.5
 | 
						|
	var p_c float64 = p_a * p_b
 | 
						|
	ws := [4](float64){1 - p_c, p_c / 2, p_c / 4, p_c / 4}
 | 
						|
 | 
						|
	Sample_0 := func(r Src) float64 { return 0 }
 | 
						|
	Sample_1 := func(r Src) float64 { return 1 }
 | 
						|
	Sample_few := func(r Src) float64 { return Sample_to(1, 3, r) }
 | 
						|
	Sample_many := func(r Src) float64 { return Sample_to(2, 10, r) }
 | 
						|
	fs := [4](func64){Sample_0, Sample_1, Sample_few, Sample_many}
 | 
						|
 | 
						|
	model := func(r Src) float64 { return Sample_mixture(fs[0:], ws[0:], r) }
 | 
						|
	n_samples := 1_000_000
 | 
						|
	xs := Sample_parallel(model, n_samples)
 | 
						|
	var avg float64 = 0
 | 
						|
	for _, x := range xs {
 | 
						|
		avg += x
 | 
						|
	}
 | 
						|
	avg = avg / float64(n_samples)
 | 
						|
	fmt.Printf("Average: %v\n", avg)
 | 
						|
	/*
 | 
						|
		  // Without concurrency:
 | 
						|
			n_samples := 1_000_000
 | 
						|
			var r = rand.New(rand.NewPCG(uint64(1), uint64(2)))
 | 
						|
			var avg float64 = 0
 | 
						|
			for i := 0; i < n_samples; i++ {
 | 
						|
				avg += Sample_mixture(fs[0:], ws[0:], r)
 | 
						|
			}
 | 
						|
			avg = avg / float64(n_samples)
 | 
						|
			fmt.Printf("Average: %v\n", avg)
 | 
						|
}
 | 
						|
*/
 |