time-to-botec/wip/nim/samples.nim

129 lines
3.0 KiB
Nim
Raw Normal View History

2023-05-21 01:13:51 +00:00
import std/math
2023-05-21 04:46:10 +00:00
import std/sugar
2023-05-21 02:24:30 +00:00
import std/random
2023-05-21 04:46:10 +00:00
import std/sequtils
2023-05-21 02:24:30 +00:00
randomize()
2023-05-21 01:13:51 +00:00
2023-05-21 02:24:30 +00:00
## Basic math functions
proc factorial(n: int): int =
if n == 0 or n < 0:
return 1
else:
return n * factorial(n - 1)
2023-05-21 01:13:51 +00:00
proc sine(x: float): float =
2023-05-21 02:24:30 +00:00
let n = 8
# ^ Taylor will converge really quickly
# notice that the factorial of 17 is
# already pretty gigantic
2023-05-21 01:45:01 +00:00
var acc = 0.0
2023-05-21 01:13:51 +00:00
for i in 0..n:
2023-05-21 02:24:30 +00:00
var k = 2*i + 1
var taylor = pow(-1, i.float) * pow(x, k.float) / factorial(k).float
2023-05-21 01:45:01 +00:00
acc = acc + taylor
2023-05-21 02:24:30 +00:00
return acc
2023-05-21 01:13:51 +00:00
## Log function
## <https://en.wikipedia.org/wiki/Natural_logarithm#High_precision>
2023-05-21 02:24:30 +00:00
## Arithmetic-geomtric mean
proc ag(x: float, y: float): float =
let n = 128 # just some high number
2023-05-21 02:24:30 +00:00
var a = (x + y)/2.0
var b = sqrt(x * y)
for i in 0..n:
let temp = a
a = (a+b)/2.0
b = sqrt(b*temp)
return a
## Find m such that x * 2^m > 2^precision/2
proc find_m(x:float): float =
var m = 0.0;
let precision = 64 # bits
let c = pow(2.0, precision.float / 2.0)
while x * pow(2.0, m) < c:
m = m + 1
return m
2023-05-21 02:24:30 +00:00
proc log(x: float): float =
let m = find_m(x)
let s = x * pow(2.0, m)
let ln2 = 0.6931471805599453
return ( PI / (2.0 * ag(1, 4.0/s)) ) - m * ln2
2023-05-21 02:24:30 +00:00
## Test these functions
## echo factorial(5)
## echo sine(1.0)
## echo log(0.1)
## echo log(2.0)
## echo log(3.0)
## echo pow(2.0, 32.float)
2023-05-21 02:24:30 +00:00
## Distribution functions
## Normal
## <https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform#Basic_form>
proc ur_normal(): float =
2023-05-21 01:13:51 +00:00
let u1 = rand(1.0)
let u2 = rand(1.0)
let z = sqrt(-2.0 * log(u1)) * sine(2 * PI * u2)
return z
proc normal(mean: float, sigma: float): float =
return (mean + sigma * ur_normal())
proc lognormal(logmean: float, logsigma: float): float =
let answer = pow(E, normal(logmean, logsigma))
return answer
proc to(low: float, high: float): float =
let normal95confidencePoint = 1.6448536269514722
let loglow = log(low)
let loghigh = log(high)
let logmean = (loglow + loghigh)/2
let logsigma = (loghigh - loglow) / (2.0 * normal95confidencePoint);
return lognormal(logmean, logsigma)
2023-05-21 04:46:10 +00:00
## echo ur_normal()
## echo normal(10, 20)
## echo lognormal(2, 4)
## echo to(10, 90)
## Manipulate samples
proc make_samples(f: () -> float, n: int): seq[float] =
result = toSeq(1..n).map(_ => f())
return result
proc mixture(sxs: seq[seq[float]], ps: seq[float], n: int): seq[float] =
assert sxs.len == ps.len
var ws: seq[float]
var sum = 0.0
for i, p in ps:
sum = sum + p
ws.add(sum)
ws = ws.map(w => w/sum)
proc get_mixture_sample(): float =
let r = rand(1.0)
var i = ws.len - 1
for j, w in ws:
if r < w:
i = j
break
## only occasion when ^ doesn't assign i
## is when r is exactly 1
## which would correspond to choosing the last item in ws
## which is why i is initialized to ws.len
let xs = sxs[i]
let l = xs.len-1
let k = rand(0..l)
return xs[k]
return toSeq(1..n).map(_ => get_mixture_sample())