{"id":13735970,"url":"https://github.com/andreaferretti/alea","last_synced_at":"2025-06-16T15:12:06.244Z","repository":{"id":38086360,"uuid":"75389244","full_name":"andreaferretti/alea","owner":"andreaferretti","description":"Define and compose random variables","archived":false,"fork":false,"pushed_at":"2023-04-24T07:26:27.000Z","size":165,"stargazers_count":45,"open_issues_count":3,"forks_count":3,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-04-09T16:17:48.430Z","etag":null,"topics":["nim","random-number-distributions"],"latest_commit_sha":null,"homepage":"https://github.com/andreaferretti/alea","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andreaferretti.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-12-02T11:18:06.000Z","updated_at":"2025-02-20T01:18:09.000Z","dependencies_parsed_at":"2024-01-12T03:36:38.429Z","dependency_job_id":"1cc8c80d-63dd-4bb3-91c2-b2f6ac3c44ae","html_url":"https://github.com/andreaferretti/alea","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/andreaferretti/alea","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreaferretti%2Falea","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreaferretti%2Falea/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreaferretti%2Falea/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreaferretti%2Falea/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andreaferretti","download_url":"https://codeload.github.com/andreaferretti/alea/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreaferretti%2Falea/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260183300,"owners_count":22971201,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nim","random-number-distributions"],"created_at":"2024-08-03T03:01:13.581Z","updated_at":"2025-06-16T15:12:06.219Z","avatar_url":"https://github.com/andreaferretti.png","language":"Nim","funding_links":[],"categories":["Operating System"],"sub_categories":["Randomization"],"readme":"# Alea\n\n[![nimble](https://raw.githubusercontent.com/yglukhov/nimble-tag/master/nimble.png)](https://github.com/yglukhov/nimble-tag)\n\nDefine and compose random variables.\n\n## Random numbers\n\nFirst, we need a way to generate random numbers. Here, a random number generator\nis defined dynamically as an object having a method that returns a uniform\nnumber in [0,1]:\n\n```nim\ntype Random = object\n  random: proc(): float\n```\n\nOne can obtain instances of `Random` by wrapping the RNG defined in\n[nim-random](https://github.com/BlaXpirit/nim-random), such as in\n\n```nim\nimport random/urandom, random/mersenne\nimport alea\n\nvar rng = wrap(initMersenneTwister(urandom(16)))\n```\n\nThe reason why we need to wrap them is that random number generators in\nnim-random are defined as a [concept](http://nim-lang.org/docs/manual.html#generics-concepts),\nwhile it will be simpler in the sequel to represent them as a single type.\n\n## Random variables\n\nA random variable of type `A` is just something that can take a random number\ngenerator and provide an instance of type `A`:\n\n```nim\ntype RandomVar[A] = concept x\n  var rng: Random\n  rng.sample(x) is A\n```\n\nIn other word, the only operation that we need to define on a type `T` to\nmake it an instance of `RandomVar[A]` is\n\n```nim\nproc sample(rng: var Random, t: T): A = ...\n```\n\nHere we require the first parameter to be of type `var Random` because drawing\na random number mutates the internal state of the RNG. It may be more clear to\nreturn a new state together with the value of type `A`, much like in the\n[state monad](https://en.wikibooks.org/wiki/Haskell/Understanding_monads/State)\nbut we avoid doing so for performance reason.\n\nIf we think of the internal state space of the random number generator as the\nprobability space `Ω`, the similarity between our definition and the mathematical\ndefinition of random variable is apparent.\n\nA few core random variables are defined:\n\n* `ConstantVar[A]` is a just a trivial random variable that always samples\n  the same value\n* `Uniform` is a uniform variable over a real interval\n* `Choice[A]` is a discrete random variable that can take a finite number of\n  values with equal probability\n* `ClosureVar[A]` is a wrapper over a `proc(rng: var Random): A`\n\nMost random variables that arise by manipulating other variables are of\n`ClosureVar` type.\n\nHere is an example showing how to costruct instances of these variables. Types\nare inferred, and are there just for explanatory purposes:\n\n```nim\nimport alea\n\nproc f(rng: var Random): float = 2 * rng.random()\n\nlet\n  c: ConstantVar[string] = constant(\"hello\")\n  u: Uniform = uniform(2, 14)\n  d: Choice[int] = choice(@[1, 2, 3, 4, 5])\n  x: ClosureVar[float] = closure(f)\n```\n\n## Operations on random variables\n\nA few common operations on random variables are supported - in particular\nmapping and filtering:\n\n```nim\nimport alea, future\n\nlet\n  a = uniform(3, 12)\n  b = a.map((x: float) =\u003e 3 - x)\n  c = a.filter((x: float) =\u003e x \u003e 5)\n```\n\nMapping, in particular, is a common operation, and there is a macro `lift`\nthat takes a function `A =\u003e B` and declares a function of the same name of\ntype `RandomVar[A] =\u003e ClosureVar[B]` that is obtained by mapping. It can be\nused with a type hint in case the function is overloaded, as in\n\n```nim\nimport math\n\nproc sq(x: float): float = x * x\n\nlift(abs, float)\nlift(sq) # No ambiguity here\n\nlet\n  a = uniform(3, 12)\n  b = sq(abs(a))\n```\n\nThere is also a version of two arguments `map2`, that takes two random variables\nand a binary function. Generalization for more than two arguments can be done\nusing the fact that random variables form\n[a monad](https://slawekk.wordpress.com/2009/05/31/probability-monad/)\nbut the relevant functions are still to be implemented.\n\nMany mathematical functions, as well as the arithmetic operations, are already\nlifted, so the following is valid:\n\n```nim\nlet\n  a = uniform(3, 12)\n  b = choice(@[1.0, 2.5, 3.7])\n  c = abs(a - b) * sqrt(a)\n```\n\n## Conditioning random variables\n\nRandom variables can also be conditioned with respect to each other. For\ninstance, if `a` and `b` are real random variables and we want to condition\n`a` to the occurrence that `b` is positive, we can do:\n\n```nim\nlet c = a.where(b, (x: float) =\u003e x \u003e 0)\n```\n\nwhere the last parameter to `where` is a predicate that should be satisfied\nby samples from `b`.\n\nHow to make this work? Drawing from `b` will change the status of the random\nnumber generator, which in theory prevents us from sampling `a` at the same\npoint.\n\nTo avoid this issue, we use a fake random number generator that wraps another\ninstance of `Random`, but repeats its result twice. That is, internally we\nuse an auxiliary (fake) RNG defined like\n\n```nim\nvar repeated = rng.repeat(2)\n```\n\nYou can use the same trick whenever there is the need to draw more than a single\nsample from the same point of the probability space.\n\n## Statistics on random variables\n\nA few common statistics are implemented on `RandomVar[float]`, such as the mean,\nvariance and so on. There is a generic implementation that will work for any\nrandom variable, but particular types of random variables can use more specialized\nmethods.\n\nAn example of their usage is:\n\n```nim\nlet x = uniform(2, 5) + choice(@[1.2, 3.3, 4.5])\nvar rng = ...\n\necho rng.sample(x)\necho rng.mean(x)\necho rng.variance(x)\necho rng.stddev(x)\n```\n\nThe covariance is also implemented, again by using the trick of a repeating\nrandom number generator to draw from the two distributions at the same time.\n\nAll there operations admit an optional parameter which is the number of samples\nto compute the statistics with more or less accuracy:\n\n```nim\necho rng.mean(x, samples = 1000000)\n```\n\nFinally, complex random variables, that are represented by chains of closures,\ncan be approximated by sampling enough times. There is a function `discretize`\nthat will take any `RandomVar[A]` and produce an instance of `Choice[A]` that\nwill wrap a certain number of samples:\n\n```nim\nlet f = ... # Some complex random variable\nlet d = f.discretize(samples = 20000)\n```\n\n## More distributions\n\nA few common real random variables are implemented:\n\n```nim\nlet\n  g = gaussian(mu = 0, sigma = 1)\n  p = poisson(3.5)\n  b = bernoulli(0.7)\n```\n\nUsually, the statistics for these notable random variables are known, so we have\noverloads, in such a way that, for instance, the mean of a Gaussian variable\nwill always be exact.\n\n## Defining custom distributions\n\nTo define your own random variables, you can take inspiration, say, from\n`bernoulli.nim`. The only mandatory operation for a type `T` to be an instance\nof `RandomVar[A]` is\n\n```nim\nproc sample(rng: var Random, t: T): A\n```\n\nIf other statistics are known (such as mean, variance and so on), one can\nalso define overloads such as\n\n```nim\nproc mean(rng: var Random, t: T, samples = 100000)\n```\n\n## A complete example\n\nHere is a small example that combines all of the above:\n\n```nim\nimport future\nimport random/urandom, random/mersenne\nimport alea\n\nvar rng = wrap(initMersenneTwister(urandom(16)))\nlet\n  a = uniform(0, 9)\n  b = choice([1, 2, 3, 4, 5]).map((x: int) =\u003e x.float)\n  c = poisson(13)\n  d = gaussian(mu = 3, sigma = 5).filter((x: float) =\u003e x \u003e 3)\n  s = ln(abs((sqrt(a) * b) - (a.floor / log10(c)))) + d\n  t = c.where(s, (x: float) =\u003e x \u003e 5)\n  u = rng.discretize(t)\n\necho rng.mean(s)\necho rng.stddev(u)\n```\n\n## TODO\n\n* improve the DSL for conditioning\n* higher moments and other statistics\n* monad composition\n* histograms\n* add more standard distributions (beta, gamma, geometric...)\n* entropy etc.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreaferretti%2Falea","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandreaferretti%2Falea","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreaferretti%2Falea/lists"}