Note
Go to the end to download the full example code.
Use the Ratio of Uniforms algorithm to sample a distributionΒΆ
In this example, we illustrate in dimension the ratio of uniforms algorithm to sample a distribution which
probability density function is known up to a normalization factor.
Consider a scalar distribution whose
probability density function is known up to a normalization factor:
let
be a function such that
for all
with
.
Let . Let
be the domain defined by:
The Lebesgue measure of is equal to
.
Let be a random variable uniformly distributed on
.
Then,
is a random variable on
distributed according to
.
Under the condition that and
are bounded, then the domain
is bounded and can be included in a rectangular bounding box
defined by:
Note that ,
and
.
This allows one to sample uniformly the domain by rejection sampling inside
.
Study of the
-space.ΒΆ
The boundary of is defined by:
where the boundaries are defined by:
The boundary of is defined by:
The following function draws the domain and the bounding box
in the
-space.
import openturns as ot
import openturns.viewer as otv
import openturns.experimental as otexp
from math import pi, exp
def draw_RatioOfUniforms_UV(ratio_algo):
r = ratio_algo.getR()
range_logF = ratio_algo.getRange()
xInf = range_logF.getLowerBound()[0]
xSup = range_logF.getUpperBound()[0]
uSup = ratio_algo.getSupU()
vInf = ratio_algo.getInfV()[0]
vSup = ratio_algo.getSupV()[0]
logF = ratio_algo.getLogUnscaledPDF()
# \partial A_{f,r} domain
x = xInf
u = 0.0
v = 0.0
xPrec = None
uPrec = None
vPrec = None
first = True
step = 0.01 * (uSup + vSup - vInf)
delta = step
data_UV = ot.Sample(0, 2)
while x < xSup:
val_F = exp(logF([x])[0])
u = val_F ** (1.0 / (1.0 + r))
v = x * val_F ** (r / (1.0 + r))
# Step adaptation after the first point
if not first:
# If the step is too large
while abs(u - uPrec) + abs(v - vPrec) > 2 * delta:
step *= 0.5
x = xPrec + step
if x < xSup:
val_F = exp(logF([x])[0])
u = val_F ** (1.0 / (1.0 + r))
v = x * val_F ** (r / (1.0 + r))
# If the step is too small
while abs(u - uPrec) + abs(v - vPrec) < 0.5 * delta and x < xSup:
step *= 2.0
x = xPrec + step
if x < xSup:
val_F = exp(logF([x])[0])
u = val_F ** (1.0 / (1.0 + r))
v = x * val_F ** (r / (1.0 + r))
xPrec = x
uPrec = u
vPrec = v
x += step
first = False
data_UV.add([u, v])
# to get a closed boundary
data_UV.add(data_UV[0])
g = ot.Graph("", "u", "v", True, "bottomright")
data_A = ot.Curve(data_UV)
data_A.setColor("purple")
g.add(data_A)
# \partial \tilde{A}_{f,r} bounding box
boundary_1 = ot.Curve([[0, vSup], [uSup, vSup]])
boundary_1.setColor("blue")
boundary_2 = ot.Curve([[0, vInf], [uSup, vInf]])
boundary_2.setColor("red")
boundary_3 = ot.Curve([[uSup, vInf], [uSup, vSup]])
boundary_3.setColor("green")
boundary_4 = ot.Curve([[0, vInf], [0, vSup]])
boundary_4.setColor("black")
g.add(boundary_1)
g.add(boundary_2)
g.add(boundary_3)
g.add(boundary_4)
leg = g.getLegends()
leg[0] = r"boundary of $A_{f,r}$"
leg[1] = r"$\mathcal{F}_1$"
leg[2] = r"$\mathcal{F}_2$"
leg[3] = r"$\mathcal{F}_3$"
leg[4] = r"$\mathcal{F}_4$"
g.setLegends(leg)
return g
Study of the
-space.ΒΆ
We use the mapping function defined by:
Then we have:
and:
where the boundaries are defined by:
The following function draws the boundary and the bounding box
in the
-space.
def draw_RatioOfUniforms_XY(ratio_algo, nPoints):
r = ratio_algo.getR()
range_logF = ratio_algo.getRange()
xInf = range_logF.getLowerBound()[0]
xSup = range_logF.getUpperBound()[0]
uSup = ratio_algo.getSupU()
vInf = ratio_algo.getInfV()[0]
vSup = ratio_algo.getSupV()[0]
logF = ratio_algo.getLogUnscaledPDF()
# \partial varpΔ₯i(A_{f,r}) domain
eps = 1e-10
step = (xSup - xInf - 2.0 * eps) / (nPoints - 1)
data_XY = ot.Sample(nPoints, 2)
for i in range(nPoints):
x = xInf + eps + i * step
val_F = exp(logF([x])[0])
data_XY[i, 0] = x
data_XY[i, 1] = val_F ** (1.0 / (1.0 + r))
g = ot.Graph("", "x", "y", True, "lower center")
data_A = ot.Curve(data_XY)
data_A.setColor("purple")
g.add(data_A)
# \partial varphi(\tilde{A}_{f,r}) bounding box
leg = ot.Description()
leg.add(r"boundary of $\varphi(A_{f,r})$")
boundary_1_fct = ot.SymbolicFunction(
"x", "(" + str(vSup) + "/x)^(1/" + str(r) + ")"
)
if vSup / (uSup**r) < xSup:
boundary_1 = boundary_1_fct.draw(vSup / (uSup**r), xSup, 101)
boundary_1.setColors(["blue"])
leg.add(r"$\varphi(\mathcal{F}_1)$")
g.add(boundary_1)
if vInf / (uSup**r) < uSup:
boundary_2_fct = ot.SymbolicFunction(
"x", "(" + str(vInf) + "/x)^(1/" + str(r) + ")"
)
boundary_2 = boundary_2_fct.draw(xInf, vInf / (uSup**r), 101)
boundary_2.setColors(["red"])
leg.add(r"$\varphi(\mathcal{F}_2)$")
g.add(boundary_2)
boundary_3 = ot.Curve([[vInf / (uSup**r), uSup], [vSup / (uSup**r), uSup]])
boundary_3.setColor("green")
g.add(boundary_3)
leg.add(r"$\varphi(\mathcal{F}_3)$")
g.setLegends(leg)
return g
Illustration.ΒΆ
We create the function defined by:
(1)ΒΆ
and the RatioOfUniforms
.
log_UnscaledPDF = ot.SymbolicFunction("x", "log(cos(x)) + x")
eps = 1e-5
range_PDF = ot.Interval(-pi / 2.0 + eps, pi / 2.0 - eps)
ratio_Algo = otexp.RatioOfUniforms(log_UnscaledPDF, range_PDF)
We draw the boundary and the bounding box
in the
-space.
g_UV = draw_RatioOfUniforms_UV(ratio_Algo)
g_UV.setTitle(
rf"$r = ${ratio_Algo.getR():.3g}, $\tau = ${ratio_Algo.getAcceptanceRatio():.3g}"
)
We draw the boundary and the bounding box
in the
-space.
sphinx_gallery_thumbnail_number = 1
g_XY = draw_RatioOfUniforms_XY(ratio_Algo, 1001)
g_XY.setTitle(
rf"$r = ${ratio_Algo.getR():.3g}, $\tau = ${ratio_Algo.getAcceptanceRatio():.3g}"
)
grid_default = ot.GridLayout(1, 2)
grid_default.setGraph(0, 0, g_UV)
grid_default.setGraph(0, 1, g_XY)
grid_default.setTitle(r"$\log f(x) = \log(\cos(x)) + x, x \in ]-\pi/2, \pi/2[$")
view = otv.View(grid_default)
We change the value and we draw the same plots.
grid_full = ot.GridLayout(4, 2)
for i, r in enumerate([0.5, 0.8, 1.2, 2.0]):
ratio_Algo.setR(r)
g_UV = draw_RatioOfUniforms_UV(ratio_Algo)
g_UV.setLegendPosition("")
g_UV.setTitle(
rf"$r = ${ratio_Algo.getR():.3g}, $\tau = ${ratio_Algo.getAcceptanceRatio():.3g}"
)
g_XY = draw_RatioOfUniforms_XY(ratio_Algo, 1001)
g_XY.setLegendPosition("")
g_XY.setTitle(
rf"$r = ${ratio_Algo.getR():.3g}, $\tau = ${ratio_Algo.getAcceptanceRatio():.3g}"
)
grid_full.setGraph(i, 0, g_UV)
grid_full.setGraph(i, 1, g_XY)
grid_full.setTitle(r"$\log f(x) = \log(\cos(x)) + x, \quad x \in ]-\pi/2, \pi/2[$")
view = otv.View(grid_full)
Study of the acceptance ratio.ΒΆ
We study the acceptance ratio as a function of the parameter
for some distributions.
In these cases, the value is exact and computed from the volume of
and the one of
.
We draw the function
. We conclude that the default value
seems to be correct
in all these cases.
mixt_gaus = ot.Mixture([ot.Normal(-2.0, 1.0), ot.Normal(4.0, 1.0)], [0.2, 0.8])
logPDF_list = [
ot.Normal().getLogPDF(),
ot.Normal(2).getLogPDF(),
ot.Normal(3).getLogPDF(),
ot.Exponential().getLogPDF(),
mixt_gaus.getLogPDF(),
]
range_list = [
ot.Normal().getRange(),
ot.Normal(2).getRange(),
ot.Normal(3).getRange(),
ot.Exponential().getRange(),
mixt_gaus.getRange(),
]
name_list = ["Normal(1)", "Normal(2)", "Normal(3)", "Exponential()", "Mixture Normal"]
g = ot.Graph(
r"Acceptance ratio $\tau$ as a function of $r$", r"$r$", r"$\tau$", True, "topright"
)
g.setLogScale(1)
numberR = 50
a = (1e2) ** (2.0 / numberR)
data_r_ratio = ot.Sample(numberR + 1, 2)
isScaled = True
for k in range(len(logPDF_list)):
ratio_Algo = otexp.RatioOfUniforms(logPDF_list[k], range_list[k], isScaled)
for i in range(numberR + 1):
r = a ** (i - 0.5 * numberR)
ratio_Algo.setR(r)
accept_Ratio = ratio_Algo.getAcceptanceRatio()
data_r_ratio[i] = [r, accept_Ratio]
draw = ot.Curve(data_r_ratio)
draw.setLegend(name_list[k])
g.add(draw)
isScaled = False
ratio_Algo = otexp.RatioOfUniforms(log_UnscaledPDF, range_PDF, isScaled)
for i in range(numberR + 1):
r = a ** (i - 0.5 * numberR)
ratio_Algo.setR(r)
accept_Ratio = ratio_Algo.getAcceptanceRatio()
data_r_ratio[i] = [r, accept_Ratio]
draw = ot.Curve(data_r_ratio)
draw.setLegend(
r"$f(x) = \cos(x)e^x\mathbf{1}_{\left[-\frac{\pi}{2}, \frac{\pi}{2}\right]}(x)$"
)
g.add(draw)
g.setLegendPosition("upper left")
g.setLegendCorner([1.0, 1.0])
view = otv.View(g)
Exploration of the optimization problems.ΒΆ
We illustrate how to explore the results of the optimization problems that have been solved to define
.
We consider the example defined by the function:
The method initialize returns all the starting points that have been used to compute respectively ,
and
.
The maximum number of starting points is defined in
ResourceMap
, entry
RatioOfUniformas-MaximumMultiStart. These
points are selected scrolling through the Sobol low discrepance sequence in dimension
and which maximum size is defined in
ResourceMap
, entry RatioOfUniforms-CandidateNumber.
f = ot.SymbolicFunction(
"x", "0.5 * (2 + sin(x)^2) * exp( -( 2 + cos(3*x)^3 + sin(2*x)^3) * x )"
)
log_UnscaledPDF = ot.ComposedFunction(ot.SymbolicFunction("x", "log(x)"), f)
lower_bound = 0.0
upper_bound = 2.0 * pi
range_PDF = ot.Interval(lower_bound, upper_bound)
graph = f.draw(lower_bound, upper_bound, 512)
graph.setTitle(
r"Christian Robert function: $f(x) = 0.5(2 + \sin^2 x) e^{ -x( 2 + \cos^3 3x + \sin^3 2x)}, x \in [0, 2\pi]$"
)
graph.setXTitle(r"$x$")
graph.setYTitle(r"$f(x)$")
view = otv.View(graph)
ratio_Algo = otexp.RatioOfUniforms(log_UnscaledPDF, range_PDF)
In order to get all the intermediate results, we change the ResourceMap
,
entry MultiStart-KeepResults.
Furthermore, as , then
implies that
.
There are only the bounds
and
to find: the collection of optimization problems
only contains 2 items.
ot.ResourceMap.SetAsBool("MultiStart-KeepResults", True)
coll_Multi_Start = ratio_Algo.initialize()
start_Sample_SupU = coll_Multi_Start[0].getStartingSample()
start_Sample_SupV = coll_Multi_Start[1].getStartingSample()
We plot on the previous graph all the starting points (red) used to find and the associated maxima found by the algorithm. We
also link each starting point to its associated optimal value (green).
Cloud_start_Usup = ot.Cloud(
start_Sample_SupU, ot.Sample(start_Sample_SupU.getSize(), [0.0])
)
Cloud_start_Usup.setColor("red")
Cloud_start_Usup.setPointStyle("fsquare")
graph.add(Cloud_start_Usup)
coll_results = coll_Multi_Start[0].getResultCollection()
sample_optima = ot.Sample(0, 2)
for i in range(coll_results.getSize()):
opt_x = coll_results[i].getOptimalPoint()[0]
opt_value = f([opt_x])[0]
sample_optima.add([opt_x, opt_value])
line_convergence = ot.Curve([[start_Sample_SupU[i, 0], 0.0], [opt_x, opt_value]])
line_convergence.setLineStyle("dashed")
line_convergence.setColor("black")
graph.add(line_convergence)
cloud_optima = ot.Cloud(sample_optima)
cloud_optima.setColor("green")
cloud_optima.setPointStyle("star")
graph.add(cloud_optima)
graph.setYTitle("")
view = otv.View(graph)
view.ShowAll()