Skip to content

Commit c711b2b

Browse files
committed
Add the relation between a Half-Cauchy and an Inverse-Gamma scale mixture
1 parent 5b5ae2f commit c711b2b

File tree

3 files changed

+123
-2
lines changed

3 files changed

+123
-2
lines changed

aemcmc/scale_mixtures.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import aesara.tensor as at
2+
from etuples import etuple, etuplize
3+
from kanren import eq, lall, var
4+
5+
6+
def halfcauchy_inverse_gamma(in_expr, out_expr):
7+
r"""Produce a goal that represents the fact that a half-Cauchy distribution can be
8+
expressed as a scale mixture of inverse-Gamma distributions.
9+
10+
.. math::
11+
12+
\begin{equation*}
13+
\frac{
14+
X^2 | a \sim \operatorname{Gamma^{-1}}\left(1/2, a), \quad
15+
a \sim \operatorname{Gamma^{-1}}\left(1/2, 1/A^2\right), \quad
16+
}{
17+
X \sim \operatorname{C^{+}}\left(0, A)
18+
}
19+
\end{equation*}
20+
21+
TODO: This relation is a particular case of a similar relation for the
22+
Half-t distribution [1]_ which does not have an implementation yet in Aesara.
23+
When it becomes available we should replace this relation with the more
24+
general one, and implement the relation between the Half-t and Half-Cauchy
25+
distributions.
26+
27+
Parameters
28+
----------
29+
in_expr
30+
An expression that represents a half-Cauchy distribution.
31+
out_expr
32+
An expression that represents the square root of the inverse-Gamma scale
33+
mixture.
34+
35+
References
36+
----------
37+
.. [1]: Wand, M. P., Ormerod, J. T., Padoan, S. A., & Frühwirth, R. (2011).
38+
Mean field variational Bayes for elaborate distributions. Bayesian
39+
Analysis, 6(4), 847-900.
40+
41+
"""
42+
43+
# Half-Cauchy distribution
44+
rng_lv, size_lv, type_idx_lv = var(), var(), var()
45+
loc_at = at.as_tensor(0)
46+
scale_lv = var()
47+
X_halfcauchy_et = etuple(
48+
etuplize(at.random.halfcauchy), rng_lv, size_lv, type_idx_lv, loc_at, scale_lv
49+
)
50+
51+
# Inverse-Gamma scale mixture
52+
rng_inner_lv, size_inner_lv, type_idx_inner_lv = var(), var(), var()
53+
rng_outer_lv, size_outer_lv, type_idx_outer_lv = var(), var(), var()
54+
X_square_scale_mixture_et = etuple(
55+
etuplize(at.random.invgamma),
56+
at.as_tensor(0.5),
57+
etuple(
58+
etuplize(at.true_div),
59+
at.as_tensor(1),
60+
etuple(
61+
etuplize(at.random.invgamma),
62+
at.as_tensor(0.5),
63+
etuple(
64+
etuplize(at.true_div),
65+
at.as_tensor(1),
66+
etuple(etuplize(at.pow), scale_lv, at.as_tensor(2)),
67+
),
68+
rng=rng_inner_lv,
69+
size=size_inner_lv,
70+
dtype=type_idx_inner_lv,
71+
),
72+
),
73+
rng=rng_outer_lv,
74+
size=size_outer_lv,
75+
dtype=type_idx_outer_lv,
76+
)
77+
X_scale_mixture_et = etuple(etuplize(at.sqrt), X_square_scale_mixture_et)
78+
79+
return lall(
80+
eq(in_expr, X_halfcauchy_et),
81+
eq(out_expr, X_scale_mixture_et),
82+
)

aemcmc/transforms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ def location_scale_transform(in_expr, out_expr):
3434
Parameters
3535
----------
3636
in_expr
37-
An expression that represents a random variable whose distribution belongs
37+
an expression that represents a random variable whose distribution belongs
3838
to the location-scale family.
3939
out_expr
40-
An expression for the non-centered representation of this random variable.
40+
an expression for the non-centered representation of this random variable.
4141
4242
"""
4343

tests/test_scale_mixtures.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import aesara.tensor as at
2+
import pytest
3+
from aesara.graph.fg import FunctionGraph
4+
from aesara.graph.kanren import KanrenRelationSub
5+
6+
from aemcmc.scale_mixtures import halfcauchy_inverse_gamma
7+
8+
9+
def test_halfcauchy_to_inverse_gamma_mixture():
10+
11+
srng = at.random.RandomStream(0)
12+
A = at.scalar("A")
13+
X_rv = srng.halfcauchy(0, A)
14+
15+
fgraph = FunctionGraph(outputs=[X_rv], clone=False)
16+
res = KanrenRelationSub(halfcauchy_inverse_gamma).transform(
17+
fgraph, fgraph.outputs[0].owner
18+
)[0]
19+
20+
assert isinstance(res.owner.op, type(at.sqrt))
21+
assert isinstance(res.owner.inputs[0].owner.op, type(at.random.invgamma))
22+
23+
24+
@pytest.mark.xfail(
25+
reason="Op.__call__ does not dispatch to Op.make_node for some RandomVariable and etuple evaluation returns an error"
26+
)
27+
def test_halfcauchy_from_inverse_gamma_mixture():
28+
29+
srng = at.random.RandomStream(0)
30+
A = at.scalar("A")
31+
a_rv = srng.invgamma(0.5, 1 / A**2)
32+
X_rv = at.sqrt(srng.invgamma(0.5, 1 / a_rv))
33+
34+
fgraph = FunctionGraph(outputs=[X_rv], clone=False)
35+
res = KanrenRelationSub(lambda x, y: halfcauchy_inverse_gamma(y, x)).transform(
36+
fgraph, fgraph.outputs[0].owner
37+
)[0]
38+
39+
assert isinstance(res.owner, type(at.random.halfcauchy))

0 commit comments

Comments
 (0)