From 2ee7f5b33688ff254ff98af260b99ddde5abbc3d Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Fri, 6 Jan 2023 19:29:49 -0600 Subject: [PATCH 1/3] Dynamically generate logprob RandomVariable list in docs --- .pre-commit-config.yaml | 3 + docs/source/api/distributions.rst | 502 +----------------------------- docs/source/conf.py | 44 +++ 3 files changed, 49 insertions(+), 500 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4e30e0f1..b51341e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,6 +29,9 @@ repos: hooks: - id: mypy args: [--ignore-missing-imports] + additional_dependencies: + - numpy>=1.20 + - types-docutils - repo: https://github.com/humitos/mirrors-autoflake.git rev: v1.1 hooks: diff --git a/docs/source/api/distributions.rst b/docs/source/api/distributions.rst index c1f224fc..048fe18a 100644 --- a/docs/source/api/distributions.rst +++ b/docs/source/api/distributions.rst @@ -2,504 +2,6 @@ Random variables ================ -Most random variables are implemented in Aesara, and AePPL only derives their log-densities. In the following we provide examples of the random variables supported by AePPL and how their log-densities can be obtained. +Most random variables are implemented in Aesara, and AePPL only derives their log-densities. In the following we provide examples of the random variables supported by AePPL (i.e. have log-probabilities that can be obtained using :py:func:`aeppl.logprob.logprob`): -The :py:func:`aeppl.logprob.logprob` function can be called on any random variable instance to create a graph that represents its log-density computed at a given value: - -.. code:: - - import aesara.tensor as at - from aeppl.logprob import _logprob - - srng = at.random.RandomStream() - - mu = at.scalar("mu") - sigma = at.scalar("sigma") - x_rv = snrg.bernoulli(mu, sigma) - - x_val = at.scalar('x_val') - x_logprob = logprob(x_rv, x_val) - -Bernoulli ---------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.BernoulliRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - p = at.scalar("p") - x_rv = snrg.bernoulli(p) - -Beta ----- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.BetaRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - a = at.scalar("a") - b = at.scalar("b") - x_rv = snrg.beta(a, b) - - -Beta-binomial --------------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.BetaBinomialRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - n = at.iscalar("n") - a = at.scalar("a") - b = at.scalar("b") - x_rv = snrg.betabinom(n, a, b) - - -Binomial --------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.BinomialRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - n = at.iscalar("n") - p = at.scalar("p") - x_rv = snrg.binomal(n, p) - - -Cauchy ------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.CauchyRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - loc = at.scalar("loc") - scale = at.scalar("scale") - x_rv = snrg.cauchy(loc, scale) - -Categorical ------------ - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.CategoricalRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - p = at.vector("p") - x_rv = snrg.categorical(p) - -Chi-squared ------------ - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.ChiSquareRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - df = at.scalar("df") - x_rv = snrg.chisquare(df) - -Dirac ------ - -The Dirac measure is defined in AePPL. - -.. code:: - - import aeppl.dists as ad - - loc = at.scalar("loc") - x_rv = ad.dirac_delta(loc) - - -Dirichlet ---------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.DirichletRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - alpha = at.vector("alpha") - x_rv = snrg.dirichlet(alpha) - -Discrete Markov Chain ---------------------- - -.. autofunction:: aeppl.dists.discrete_markov_chain - -Exponential ------------ - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.ExponentialRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - beta = at.scalar("beta") - x_rv = snrg.exponential(beta) - -Gamma ------ - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.GammaRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - alpha = at.scalar('alpha') - beta = at.scalar('beta') - x_rv = srng.gamma(alpha, beta) - -Geometric ---------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.GeometricRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - p = at.scalar("p") - x_rv = snrg.geometric(p) - -Gumbel ------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.GumbelRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - mu = at.scalar('mu') - beta = at.scalar('beta') - x_rv = srng.gumbel(mu, beta) - -Half-Cauchy ------------ - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.HalfCauchyRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - x0 = at.scalar('x0') - gamma = at.scalar('gamma') - x_rv = srng.halfcauchy(x0, gamma) - -Half-Normal ------------ - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.HalfNormalRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - mu = at.scalar('mu') - sigma = at.scalar('sigma') - x_rv = srng.halfnormal(mu, sigma) - -Hypergeometric --------------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.HyperGeometricRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - ngood = at.scalar("ngood") - nbad = at.scalar("nbad") - nsample = at.scalar("nsample") - x_rv = snrg.hypergeometric(ngood, nbad, nsample) - -Inverse-Gamma -------------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.InvGammaRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - alpha = at.scalar('alpha') - beta = at.scalar('beta') - x_rv = srng.invgamma(alpha, beta) - -Laplace -------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.LaplaceRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - mu = at.scalar("mu") - lmbda = at.scalar("lambda") - x_rv = snrg.laplace(mu, lmbda) - -Logistic --------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.LogisticRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - mu = at.scalar("mu") - s = at.scalar("s") - x_rv = snrg.logistic(mu, s) - -Lognormal ---------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.LogNormalRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - mu = at.scalar("mu") - sigma = at.scalar("sigma") - x_rv = snrg.lognormal(mu, sigma) - -Multinomial ------------ - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.MultinomialRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - n = at.iscalar("n") - p = at.vector("p") - x_rv = snrg.multinomial(n, p) - -Multivariate-Normal -------------------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.MvNormalRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - mu = at.vector('mu') - Sigma = at.matrix('sigma') - x_rv = srng.multivariate_normal(mu, Sigma) - - -Negative-Binomial ------------------ - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.NegBinomialRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - n = at.iscalar("n") - p = at.scalar("p") - x_rv = snrg.negative_binomial(n, p) - -Normal ------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.NormalRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - mu = at.scalar('mu') - sigma = at.scalar('sigma') - x_rv = srng.normal(mu, sigma) - -Pareto ------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.ParetoRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - b = at.scalar("b") - scale = at.scalar("scale") - x_rv = snrg.pareto(b, scale) - -Poisson -------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.PoissonRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - lmbda = at.scalar("lambda") - x_rv = snrg.poisson(lmbda) - -Student T ---------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.StudentTRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - df = at.scalar('df') - loc = at.scalar('loc') - scale = at.scalar('scale') - x_rv = srng.t(df, loc, scale) - -Triangular ----------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.TriangularRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - left = at.scalar('left') - mode = at.scalar('mode') - right = at.scalar('right') - x_rv = srng.triangular(left, mode, right) - -Uniform -------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.UniformRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - low = at.scalar('low') - high = at.scalar('high') - x_rv = srng.uniform(low, high) - -Von Mises ---------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.VonMisesRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - mu = at.scalar('mu') - kappa = at.scalar('kappa') - x_rv = srng.vonmises(mu, kappa) - -Wald ----- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.WaldRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - mu = at.scalar('mu') - lmbda = at.scalar('lambda') - x_rv = srng.wald(mu, lmbda) - - -Weibull -------- - -Documentation for the Aesara implementation can be found here: :external:py:class:`aesara.tensor.random.basic.WeibullRV` - -.. code:: - - import aesara.tensor as at - - srng = at.random.RandomStream() - - k = at.scalar('k') - x_rv = srng.weibull(k) +.. print-supported-dists:: diff --git a/docs/source/conf.py b/docs/source/conf.py index 2df6692d..96ebf178 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,5 +1,10 @@ import os +import sphinx.addnodes +import sphinx.directives +from docutils import nodes +from sphinx.util.docutils import SphinxDirective + import aeppl # -- Project information @@ -67,3 +72,42 @@ "numpy": ("https://numpy.org/doc/stable/", None), "python": ("https://docs.python.org/3/", None), } + + +class SupportedDistributionsDirective(SphinxDirective): + def run(self): + from aesara.tensor.random.op import RandomVariable + + from aeppl.logprob import _logprob + + supported_dists = tuple( + mtype.__name__ + for mtype in _logprob.registry.keys() + if issubclass(mtype, RandomVariable) + and not mtype.__module__.startswith(r"aeppl.") + ) + + res = nodes.bullet_list() + for dist_name in supported_dists: + attributes = {} + reftarget = f"aesara.tensor.random.basic.{dist_name}" + attributes["reftarget"] = reftarget + attributes["reftype"] = "class" + attributes["refdomain"] = "py" + + ref = nodes.paragraph() + + rawsource = rf":external:py:class:`{reftarget}`" + xref = sphinx.addnodes.pending_xref(rawsource, **attributes) + xref += nodes.literal(reftarget, reftarget, classes=["xref"]) + # ref += nodes.inline(reftarget, reftarget) + + ref += xref + item = nodes.list_item("", ref) + res += item + + return [res] + + +def setup(app): + app.add_directive("print-supported-dists", SupportedDistributionsDirective) From b39b74442226a901591fcbceabcd4039e926ba0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Louf?= Date: Tue, 24 Jan 2023 18:18:38 +0100 Subject: [PATCH 2/3] Exclude the RandomVariables created by `create_default_transformed_rv_op` --- docs/source/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 96ebf178..630a770f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -82,9 +82,10 @@ def run(self): supported_dists = tuple( mtype.__name__ - for mtype in _logprob.registry.keys() + for mtype, mfunc in _logprob.registry.items() if issubclass(mtype, RandomVariable) and not mtype.__module__.startswith(r"aeppl.") + and not mfunc.__name__ == "transformed_logprob" ) res = nodes.bullet_list() From 1a1d422fe70cfe7bdca9435d75259f19c888778c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Louf?= Date: Wed, 25 Jan 2023 13:43:16 +0100 Subject: [PATCH 3/3] Dynamically generate RVTransform list in docs --- docs/source/api/transforms.rst | 27 ++--------------------- docs/source/conf.py | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/docs/source/api/transforms.rst b/docs/source/api/transforms.rst index 5dc9dad2..e0a4ff1a 100644 --- a/docs/source/api/transforms.rst +++ b/docs/source/api/transforms.rst @@ -56,37 +56,14 @@ associated variables and their values: .. autoclass:: aeppl.transforms.TransformValuesRewrite + --------------------- Invertible transforms --------------------- AePPL currently supports transforms using the following (invertible) Aesara operators. This means that AePPL can compute the log-probability of a random variable that is the result of one of the following transformations applied to another random variable: -- `aesara.tensor.add` -- `aesara.tensor.sub` -- `aesara.tensor.mul` -- `aesara.tensor.true_div` -- `aesara.tensor.exponential` -- `aesara.tensor.exp` -- `aesara.tensor.log` - -One can also apply the following transforms directly: - -.. autoclass:: aeppl.transforms.LocTransform -.. autoclass:: aeppl.transforms.ScaleTransform -.. autoclass:: aeppl.transforms.LogTransform -.. autoclass:: aeppl.transforms.ExpTransform -.. autoclass:: aeppl.transforms.ReciprocalTransform -.. autoclass:: aeppl.transforms.IntervalTransform -.. autoclass:: aeppl.transforms.LogOddsTransform -.. autoclass:: aeppl.transforms.SimplexTransform -.. autoclass:: aeppl.transforms.CircularTransform - -These transformations can be chained using: - - -.. autoclass:: aeppl.transforms.ChainedTransform - +.. print-invertible-transforms:: --------- Censoring diff --git a/docs/source/conf.py b/docs/source/conf.py index 630a770f..4973222c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -3,6 +3,9 @@ import sphinx.addnodes import sphinx.directives from docutils import nodes +from docutils.frontend import OptionParser +from docutils.utils import new_document +from sphinx.parsers import RSTParser from sphinx.util.docutils import SphinxDirective import aeppl @@ -110,5 +113,41 @@ def run(self): return [res] +class InvertibleTransformationsDirective(SphinxDirective): + def run(self): + import inspect + import sys + + import aeppl.transforms as transforms + + invertible_transforms = ( + mname + for mname, mtype in inspect.getmembers(sys.modules["aeppl.transforms"]) + if inspect.isclass(mtype) + and issubclass(mtype, transforms.RVTransform) + and not mtype == transforms.RVTransform + ) + + rst = ".. autosummary::\n" + rst += " :toctree: _generated\n\n" + for transform_name in invertible_transforms: + rst += f" aeppl.transforms.{transform_name}\n" + + return self.parse_rst(rst) + + def parse_rst(self, text: str): + parser = RSTParser() + parser.set_application(self.env.app) + + settings = OptionParser( + defaults=self.env.settings, + components=(RSTParser,), + read_config_files=True, + ).get_default_values() + document = new_document("", settings=settings) + parser.parse(text, document) + return [document.children[1]] + def setup(app): app.add_directive("print-supported-dists", SupportedDistributionsDirective) + app.add_directive("print-invertible-transforms", InvertibleTransformationsDirective)