Skip to content

Commit 662e879

Browse files
committed
added bayes benchmark script
1 parent 4d08b5d commit 662e879

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
"""
2+
This example compares three Bayesian posteriors for a low-dimensional
3+
example: a posterior generated by DREAM, one generated by NS, and
4+
one calculated directly.
5+
6+
The likelihood of the parameters being equal to a certain value is proportional
7+
to exp(-chi^2 / 2) [1], so for a low-dimensional example we can calculate this directly
8+
for a sample of parameter values.
9+
10+
Citation:
11+
[1] D. S. Sivia, J. R. P. Webster,
12+
"The Bayesian approach to reflectivity data",
13+
Physica B: Condensed Matter,
14+
Volume 248, June 1998, pages 327-337
15+
DOI: 10.1016/S0921-4526(98)00259-2
16+
URL: https://bayes.wustl.edu/sivia/98_20feb03.pdf
17+
18+
"""
19+
20+
from dataclasses import dataclass
21+
from pathlib import Path
22+
23+
import matplotlib.pyplot as plt
24+
import numpy as np
25+
26+
import RATapi as RAT
27+
import RATapi.utils.plotting as RATplot
28+
29+
PWD = Path(__file__).parents[0]
30+
31+
32+
@dataclass
33+
class CalculationResults:
34+
"""Data class for results from a direct calculation."""
35+
36+
x_data: list[np.array]
37+
distribution: np.array
38+
39+
40+
def bayes_benchmark_2d(grid_size: int) -> (RAT.outputs.BayesResults, CalculationResults):
41+
"""Bayes benchmark for a 2-dimensional example.
42+
43+
Parameters
44+
----------
45+
grid_size : int
46+
The number of points to sample for each fit parameter.
47+
48+
Here we estimate the substrate roughness and background using two different methods:
49+
nested sampling (the 'ns' procedure in RAT) and through a direct calculation of chi-squared
50+
over a range of parameter values.
51+
52+
Returns
53+
-------
54+
RAT.BayesResults
55+
The BayesResults object from a nested sampler calculation.
56+
CalculationResults
57+
Results from the direct calculation.
58+
59+
"""
60+
problem = RAT.utils.convert.r1_to_project_class(str(PWD / "defaultR1ProjectTemplate.mat"))
61+
62+
ns_controls = RAT.Controls(procedure="ns", calcSldDuringFit=True, nsTolerance=1, nLive=500, display="final")
63+
64+
_, ns_results = RAT.run(problem, ns_controls)
65+
66+
# now we get the parameters and use them to do a direct calculation
67+
rough_param = problem.parameters[0]
68+
roughness = np.linspace(rough_param.min, rough_param.max, grid_size)
69+
70+
back_param = problem.background_parameters[0]
71+
background = np.linspace(back_param.min, back_param.max, grid_size)
72+
73+
controls = RAT.Controls(procedure="calculate", calcSldDuringFit=True, display="off")
74+
75+
def calculate_posterior(roughness_index: int, background_index: int) -> float:
76+
"""Calculate the posterior for an item in the roughness and background vectors.
77+
78+
Parameters
79+
----------
80+
roughness_index : int
81+
The index of the roughness vector to use as the roughness parameter value.
82+
background_index : int
83+
The index of the background vector to use as the background parameter value.
84+
85+
Returns
86+
-------
87+
float
88+
The value of exp(-chi^2 / 2) for the given roughness and background values.
89+
"""
90+
problem.parameters[0].value = roughness[int(roughness_index)]
91+
problem.background_parameters[0].value = background[background_index]
92+
93+
_, results = RAT.run(problem, controls)
94+
chi_squared = results.calculationResults.sumChi
95+
96+
return np.exp(-chi_squared / 2)
97+
98+
vectorized_calc_posterior = np.vectorize(calculate_posterior)
99+
100+
print("Calculating posterior directly...")
101+
probability_array = vectorized_calc_posterior(*np.indices((grid_size, grid_size), dtype=int))
102+
103+
return ns_results, CalculationResults(x_data=[roughness, background], distribution=probability_array)
104+
105+
106+
def bayes_benchmark_3d(grid_size: int) -> (RAT.outputs.BayesResults, CalculationResults):
107+
"""Bayes benchmark for a 3-dimensional example.
108+
109+
Here we estimate the substrate roughness and background using two different methods:
110+
nested sampling (the 'ns' procedure in RAT) and through a direct calculation of chi-squared
111+
over a range of parameter values.
112+
113+
Parameters
114+
----------
115+
grid_size : int
116+
The number of points to sample for each fit parameter.
117+
118+
Returns
119+
-------
120+
RAT.BayesResults
121+
The BayesResults object from a nested sampler calculation.
122+
CalculationResults
123+
Results from the direct calculation.
124+
125+
"""
126+
problem = RAT.utils.convert.r1_to_project_class(str(PWD / "defaultR1ProjectTemplate.mat"))
127+
128+
ns_controls = RAT.Controls(procedure="ns", calcSldDuringFit=True, nsTolerance=1, nLive=500, display="final")
129+
130+
_, ns_results = RAT.run(problem, ns_controls)
131+
132+
# now we get the parameters and use them to do a direct calculation
133+
rough_param = problem.parameters[0]
134+
roughness = np.linspace(rough_param.min, rough_param.max, grid_size)
135+
136+
back_param = problem.background_parameters[0]
137+
background = np.linspace(back_param.min, back_param.max, grid_size)
138+
139+
scale_param = problem.scalefactors[0]
140+
scalefactor = np.linspace(scale_param.min, scale_param.max, grid_size)
141+
142+
controls = RAT.Controls(procedure="calculate", calcSldDuringFit=True, display="off")
143+
144+
def calculate_posterior(roughness_index: int, background_index: int, scalefactor_index: int) -> float:
145+
"""Calculate the posterior for an item in the roughness, background, and scalefactor vectors.
146+
147+
Parameters
148+
----------
149+
roughness_index : int
150+
The index of the roughness vector to use as the roughness parameter value.
151+
background_index : int
152+
The index of the background vector to use as the background parameter value.
153+
scalefactor_index : int
154+
The index of the scalefactor vector to use as the scalefactor parameter.
155+
156+
Returns
157+
-------
158+
float
159+
The value of exp(-chi^2 / 2) for the given roughness and background values.
160+
"""
161+
problem.parameters[0].value = roughness[roughness_index]
162+
problem.background_parameters[0].value = background[background_index]
163+
problem.scalefactors[0].value = scalefactor[scalefactor_index]
164+
165+
_, results = RAT.run(problem, controls)
166+
chi_squared = results.calculationResults.sumChi
167+
168+
return np.exp(-chi_squared / 2)
169+
170+
vectorized_calc_posterior = np.vectorize(calculate_posterior)
171+
172+
print("Calculating posterior directly...")
173+
probability_array = vectorized_calc_posterior(*np.indices((grid_size, grid_size, grid_size), dtype=int))
174+
175+
return ns_results, CalculationResults(x_data=[roughness, background, scalefactor], distribution=probability_array)
176+
177+
178+
def plot_posterior_comparison(ns_results: RAT.outputs.BayesResults, calc_results: CalculationResults):
179+
"""Create a grid of marginalised posteriors comparing different calculation methods.
180+
181+
Parameters
182+
----------
183+
ns_results : RAT.BayesResults
184+
The BayesResults object from a nested sampler calculation.
185+
calc_results : CalculationResults
186+
The results from a direct calculation.
187+
"""
188+
num_params = calc_results.distribution.ndim
189+
fig, axes = plt.subplots(2, num_params)
190+
191+
def plot_marginalised_result(dimension: int, axes: plt.Axes):
192+
"""Plot a histogram of a marginalised posterior from the calculation results.
193+
194+
Parameters
195+
----------
196+
dimension : int
197+
The dimension of the array to marginalise over.
198+
axes : plt.Axes
199+
The Axes object to plot the histogram onto.
200+
201+
"""
202+
# marginalise to the dimension
203+
# note we don't need to normalise here as np.histogram normalises for us
204+
sum_axes = tuple(i for i in range(0, num_params) if i != dimension)
205+
distribution = np.sum(calc_results.distribution, axis=sum_axes)
206+
207+
# create histogram
208+
axes.hist(
209+
calc_results.x_data[i],
210+
weights=distribution,
211+
density=True,
212+
bins=25,
213+
edgecolor="black",
214+
linewidth=1.2,
215+
color="white",
216+
)
217+
218+
# row 0 contains NS histograms for each parameter
219+
# row 1 contains direct calculation histograms for each parameter
220+
for i in range(0, num_params):
221+
RATplot.plot_one_hist(ns_results, i, smooth=False, axes=axes[0][i])
222+
plot_marginalised_result(i, axes[1][i])
223+
axes[1][i].set_xlim(*axes[0][i].get_xlim())
224+
225+
axes[0][0].set_ylabel("nested sampler")
226+
axes[1][0].set_ylabel("direct calculation")
227+
228+
fig.tight_layout()
229+
fig.show()
230+
231+
232+
if __name__ == "__main__":
233+
ns_2d, calc_2d = bayes_benchmark_2d(30)
234+
ns_3d, calc_3d = bayes_benchmark_3d(40)
235+
236+
plot_posterior_comparison(ns_2d, calc_2d)
237+
238+
plot_posterior_comparison(ns_3d, calc_3d)
7.48 KB
Binary file not shown.

0 commit comments

Comments
 (0)