Bayesian analysis of NPD data#

This notebook continues the NPD refinement tutorial. In that tutorial, you built a two-phase model containing Laβ‚€.β‚…Baβ‚€.β‚…CoO₃ (LBCO) and a small Si impurity, refined the model against simulated time-of-flight neutron data, and saved the project.

A conventional least-squares refinement gives a best-fit point and uncertainty estimates based on the local shape of the objective function. Bayesian analysis instead uses Markov chain Monte Carlo (MCMC) sampling to explore the joint posterior distribution of the free parameters. This allows us to investigate questions such as:

  • Where is each posterior distribution centered, and how does that compare with the deterministic best-fit value?

  • How broad are their credible intervals?

  • Do the local parameter correlations from the deterministic fit persist across the posterior, and are any relationships nonlinear or asymmetric?

  • How does parameter uncertainty propagate into the calculated diffraction pattern?

The deterministic fit already provides a correlation matrix derived from the local covariance estimate near the best-fit point. Bayesian sampling extends this view by exploring how parameters vary together throughout the joint posterior. It can therefore reveal curved, asymmetric, bounded, or multimodal relationships that one local correlation coefficient cannot describe.

This notebook follows the same teaching structure as the refinement tutorial:

  • Introduction: a complete, guided MCMC analysis of the refined LBCO+Si model.

  • Exercise: repeat the analysis after fixing one member of a strongly correlated peak-profile pair, then compare the results.

We will use the DREAM sampler provided by the bumps (dream) minimizer. The short chains used here keep the tutorial practical. A scientific analysis requires longer chains and careful convergence checks.

πŸ› οΈ Import Library#

import easydiffraction as edi

πŸ“˜ Introduction: MCMC Analysis of LBCO+Si#

We will first work through the complete Bayesian workflow without exercises. This reference analysis samples the same seven scientific parameters that remained free at the end of the refinement tutorial:

  • the LBCO lattice parameter,

  • the LBCO and Si scale factors, and

  • four peak-profile parameters.

The resulting project will be called project_1, following the convention used for the introductory Si fit in the previous tutorial.

πŸ“‚ Load the Refined Project#

Rather than rebuilding the experiment and structures, load the project saved at the end of the refinement tutorial. This restores the measured data, both structures, the refined values, and the free-parameter settings.

The following cell reads the refined project from the local projects directory. Complete the NPD refinement tutorial first so that projects/fitting-exercise-si-lbco-main is available.

πŸ“– See documentation for more details about loading a saved project.

refinement_project_dir = 'projects/fitting-exercise-si-lbco-main'
project_1 = edi.Project.load(refinement_project_dir)

Save a copy under a new name before changing the analysis. This keeps the deterministic refinement unchanged and gives the sampler its own location for posterior summaries and chain data.

project_1.metadata.title = 'Reference Bayesian Analysis of LBCO with Si Impurity'
project_1.metadata.description = (
    'Reference MCMC analysis of a two-phase LBCO and Si powder diffraction model.'
)
project_1.save_as(dir_path='projects/exercise-bayesian-si-lbco-reference')
Saving project πŸ“¦ 'main' to 'projects/exercise-bayesian-si-lbco-reference'
β”œβ”€β”€ πŸ“„ project.edi
β”œβ”€β”€ πŸ“ structures/
β”‚   └── πŸ“„ lbco.edi
β”‚   └── πŸ“„ si.edi
β”œβ”€β”€ πŸ“ experiments/
β”‚   └── πŸ“„ sim_lbco.edi
β”œβ”€β”€ πŸ“ analysis/
β”‚   └── πŸ“„ analysis.edi
└── πŸ“ reports/
    └── πŸ“„ main.html

Confirm that the restored project contains the LBCO and Si structures and the sim_lbco experiment.

project_1.structures.show_names()
project_1.experiments.show_names()
Defined structures 🧩
['lbco', 'si']
Defined experiments πŸ”¬
['sim_lbco']
project_1.display.structure(struct_name='lbco')
project_1.display.structure(struct_name='si')
Structure 🧩 'lbco' (Atom view type: 'covalent')
Loading plot…
drag = rotate
wheel = zoom
right-drag = pan
Structure 🧩 'si' (Atom view type: 'covalent')
Loading plot…
drag = rotate
wheel = zoom
right-drag = pan
project_1.display.pattern(expt_name='sim_lbco')

🎯 Choose the Sampled Parameters#

MCMC varies every free parameter simultaneously. Before sampling, it is worth asking which parameters are needed to answer the scientific question.

First, display all free parameters in the refined project.

project_1.display.parameters.free()
Free parameters for both structures (🧩 data blocks) and experiments (πŸ”¬ data blocks)
datablockcategoryentryparametervalueuncertaintyminmaxunits
1lbcocelllength_a3.891910.00028-infinfΓ…
2sim_lbcolinked_structurelbcoscale43.650000.39000-infinf
3sim_lbcolinked_structuresiscale0.321000.02200-infinf
4sim_lbcopeakrise_alpha_10.012320.00017-infinfΞΌs/Γ…
5sim_lbcopeakdecay_beta_10.011470.00047-infinfΞΌs/Γ…
6sim_lbcopeakbroad_gauss_sigma_1-78400.000002900.00000-infinfΞΌs/Γ…
7sim_lbcopeakbroad_gauss_sigma_224500.000001700.00000-infinfΞΌsΒ²/Γ…Β²
8sim_lbcobackground1intensity0.286000.03000-infinf
9sim_lbcobackground2intensity0.256500.00650-infinf
10sim_lbcobackground3intensity0.255500.00570-infinf
11sim_lbcobackground4intensity0.233000.00530-infinf
12sim_lbcobackground5intensity0.239000.00390-infinf
13sim_lbcobackground6intensity0.220400.00470-infinf
14sim_lbcobackground7intensity0.235000.01600-infinf

The seven line-segment background intensities were refined in the previous tutorial. If we left them free, this analysis would sample 14 parameters instead of 7. The larger space would require more model evaluations, take longer to explore, and generally need a longer chain to mix well. Background parameters can also correlate with phase scales and broad peaks, making the posterior harder to interpret.

We therefore fix the background at its refined values. This is a practical simplification for a short teaching example, not a universal rule. Fixing the background means that its uncertainty is not propagated into the final credible intervals. In a complete scientific analysis, you should sample relevant background parameters or otherwise account for their uncertainty when it can affect the result.

experiment_1 = project_1.experiments['sim_lbco']

for line_segment in experiment_1.background:
    line_segment.intensity.free = False

Display the remaining free parameters to verify the sampling problem.

project_1.display.parameters.free()
Free parameters for both structures (🧩 data blocks) and experiments (πŸ”¬ data blocks)
datablockcategoryentryparametervalueuncertaintyminmaxunits
1lbcocelllength_a3.891910.00028-infinfΓ…
2sim_lbcolinked_structurelbcoscale43.650000.39000-infinf
3sim_lbcolinked_structuresiscale0.321000.02200-infinf
4sim_lbcopeakrise_alpha_10.012320.00017-infinfΞΌs/Γ…
5sim_lbcopeakdecay_beta_10.011470.00047-infinfΞΌs/Γ…
6sim_lbcopeakbroad_gauss_sigma_1-78400.000002900.00000-infinfΞΌs/Γ…
7sim_lbcopeakbroad_gauss_sigma_224500.000001700.00000-infinfΞΌsΒ²/Γ…Β²

πŸš€ Run a Local Refinement#

Fixing the background changes the optimization problem slightly. We first run a fast local least-squares refinement to update the best-fit values and their uncertainty estimates. These values will provide the starting point and finite bounds for MCMC.

We use the BUMPS Levenberg-Marquardt minimizer, bumps (lm), so the local and Bayesian stages use the same minimizer library.

πŸ“– See documentation for more details about the available minimizers.

project_1.analysis.minimizer.show_supported()
Minimizer types
TypeDescription
1bumpsBUMPS library using the default Levenberg-Marquardt method
2bumps (amoeba)BUMPS library with Nelder-Mead simplex method
3bumps (de)BUMPS library with differential evolution method
4bumps (dream)BUMPS library with DREAM Bayesian sampling
5bumps (lm)BUMPS library with Levenberg-Marquardt method
6dfolsDFO-LS library for derivative-free least-squares optimization
7emceeemcee affine-invariant ensemble Bayesian sampling
8lmfitLMFIT library using the default Levenberg-Marquardt method
9lmfit (least_squares)LMFIT library with SciPy's trust region reflective algorithm
10*lmfit (leastsq)LMFIT library with Levenberg-Marquardt least squares method
project_1.analysis.minimizer.type = 'bumps (lm)'
Current minimizer changed to
bumps (lm)
project_1.analysis.fit()
project_1.display.fit.results()
Standard fitting
πŸ“‹ Using experiment πŸ”¬ 'sim_lbco' for 'single' fitting
πŸš€ Starting fit process with 'bumps (lm)'...
πŸ“ˆ Goodness-of-fit progress:
iterationtime (s)χ²change / status
110.021.24
2270.761.24
πŸ† Best goodness-of-fit (reduced χ²) is 1.24 at iteration 27
βœ… Fitting complete.
βš™οΈ Settings used:
NameValueDescription
1max_iterations1000Maximum solver iterations.
πŸ“‹ Least-squares fit results:
MetricValue
1πŸ§ͺ Minimizerbumps (lm)
2βœ… Overall statussuccess
3⏱️ Fitting time (seconds)0.76
4πŸ“ Goodness-of-fit (reduced χ²)1.24
5πŸ“ R-factor (Rf, %)6.89
6πŸ“ R-factor squared (RfΒ², %)5.58
7πŸ“ Weighted R-factor (wR, %)4.76
πŸ“ˆ Refined parameters:
datablockcategoryentryparameterunitsstartvalues.u.change
1lbcocelllength_aΓ…3.89193.89190.00030.00 % ↓
2sim_lbcolinked_structurelbcoscale43.650043.64970.37700.00 % ↓
3sim_lbcolinked_structuresiscale0.32100.32110.02090.04 % ↑
4sim_lbcopeakrise_alpha_1ΞΌs/Γ…0.01230.01230.00020.05 % ↓
5sim_lbcopeakdecay_beta_1ΞΌs/Γ…0.01150.01150.00050.04 % ↑
6sim_lbcopeakbroad_gauss_sigma_1ΞΌs/Γ…-78400.0000-78430.28122835.83700.04 % ↑
7sim_lbcopeakbroad_gauss_sigma_2ΞΌsΒ²/Γ…Β²24500.000024485.38211722.25690.06 % ↓
β€’ start = parameter value before refinement
β€’ value = refined value from least-squares minimization
β€’ s.u. = standard uncertainty (one sigma), from the covariance matrix
β€’ change = relative change from start, in %; ↑ = increase, ↓ = decrease

πŸ”— Understand the Local Correlation Chart#

The correlation chart summarizes how pairs of refined parameters can change together near the least-squares optimum. Each off-diagonal value is a correlation coefficient between -1 and +1:

  • a value near +1 means that the two parameters tend to increase together;

  • a value near -1 means that one tends to decrease when the other increases; and

  • a value near 0 means that there is little linear relationship.

Correlation does not mean that one parameter physically causes the other. It means that the measured pattern cannot distinguish their effects independently within this model.

project_1.display.fit.correlations(max_parameters=5)

The matrix shows only one triangular half because the other half would contain the same values in reverse order. With max_parameters=5, EasyDiffraction filters the chart to keep the strongest relationships readable. In an interactive Jupyter display, hover over a colored cell to see the two full parameter names and the numerical correlation coefficient.

The strongest relationship is typically the negative correlation between broad_gauss_sigma_1 and broad_gauss_sigma_2, close to -0.94. Both parameters contribute to the d-spacing dependence of the Gaussian TOF peak width. An increase in one can be partly compensated by a decrease in the other while producing a similar calculated peak shape.

This chart comes from the local covariance estimate, so it describes only the neighborhood around the best-fit point. MCMC will show whether the relationship remains linear and approximately elliptical across a wider region of parameter space.

🎲 Define the Sampling Region#

DREAM requires finite lower and upper bounds for every sampled parameter. Here, we derive them from the uncertainty estimated by the local fit. set_fit_bounds_from_uncertainty() places each bound four estimated standard uncertainties from the current value by default, while respecting any physical parameter limits.

These bounds act as bounded prior support in this example. They must be inspected rather than accepted blindly: narrow bounds can truncate the posterior, while unnecessarily wide bounds can make sampling less efficient.

πŸ“– See documentation for more details about parameter values, uncertainties, and fit bounds.

for param in project_1.free_parameters:
    param.set_fit_bounds_from_uncertainty()
project_1.display.parameters.free()
Free parameters for both structures (🧩 data blocks) and experiments (πŸ”¬ data blocks)
datablockcategoryentryparametervalueuncertaintyminmaxunits
1lbcocelllength_a3.891910.000283.890793.89303Γ…
2sim_lbcolinked_structurelbcoscale43.649720.3769842.1417845.15766
3sim_lbcolinked_structuresiscale0.321130.020850.237720.40454
4sim_lbcopeakrise_alpha_10.012310.000170.011650.01298ΞΌs/Γ…
5sim_lbcopeakdecay_beta_10.011480.000460.009650.01330ΞΌs/Γ…
6sim_lbcopeakbroad_gauss_sigma_1-78430.281232835.83698-89773.62915-67086.93331ΞΌs/Γ…
7sim_lbcopeakbroad_gauss_sigma_224485.382131722.2568517596.3547231374.40955ΞΌsΒ²/Γ…Β²

🎲 Run DREAM Sampling#

In Bayesian analysis, the posterior combines the likelihood of the measured data with prior assumptions. MCMC constructs chains of correlated samples whose long-run distribution approximates that posterior.

DREAM uses multiple chains and differential-evolution proposals to explore correlated parameter spaces. This makes it useful for the strong peak-profile correlation observed above.

πŸ“– See documentation for more details about Bayesian minimizers and posterior displays.

project_1.analysis.minimizer.type = 'bumps (dream)'
project_1.analysis.minimizer.sampling_steps = 300  # lower than the default 3000
project_1.analysis.minimizer.burn_in_steps = 60  # lower than the default 600
project_1.analysis.minimizer.random_seed = 42
⚠️ Switching minimizer type removes these settings:                                                                               
   β€’ max_iterations                                                                                                               
⚠️ Switching minimizer type adds these settings with defaults:                                                                    
   β€’ burn_in_steps=600                                                                                                            
   β€’ initialization_method='latin_hypercube'                                                                                      
   β€’ parallel_workers=0                                                                                                           
   β€’ population_size=4                                                                                                            
   β€’ random_seed=None                                                                                                             
   β€’ sampling_steps=3000                                                                                                          
   β€’ thinning_interval=1                                                                                                          
Current minimizer changed to
bumps (dream)

Burn-in samples allow the chains to move away from their initial positions before the retained posterior is summarized. The fixed seed makes the tutorial output reproducible.

project_1.analysis.fit()
Standard fitting
πŸ“‹ Using experiment πŸ”¬ 'sim_lbco' for 'single' fitting
πŸš€ Starting fit process with 'bumps (dream)'...
πŸ“ˆ Bayesian sampling progress:
stepprogresstime (s)log posteriorphase
10.60-435.88pre-processing
221/3605.8%5.67-275.53burn-in
340/36011.1%10.57-268.83burn-in
460/36016.7%15.72-267.41burn-in
561/36016.9%16.03-267.29sampling
676/36021.1%19.93-267.10sampling
791/36025.3%24.20-266.65sampling
8106/36029.4%28.30-266.10sampling
9121/36033.6%32.37-266.35sampling
10136/36037.8%36.35-266.12sampling
11151/36041.9%40.40-267.18sampling
12166/36046.1%44.43-266.73sampling
13181/36050.3%48.78-266.95sampling
14196/36054.4%52.76-266.56sampling
15211/36058.6%57.02-266.54sampling
16226/36062.8%61.04-266.85sampling
17241/36066.9%65.12-266.57sampling
18256/36071.1%68.77-266.49sampling
19271/36075.3%73.22-266.52sampling
20286/36079.4%77.25-266.56sampling
21301/36083.6%81.37-266.17sampling
22316/36087.8%85.18-266.81sampling
23331/36091.9%89.30-266.49sampling
24346/36096.1%93.24-266.30sampling
25360/360100.0%97.17-267.24sampling
26124.07post-processing
βœ… Bayesian sampling complete.
⚠️ Convergence diagnostics indicate the posterior may be poorly mixed.                                                            

πŸ“‹ Understand the Bayesian Fit Summary#

The result table now reports posterior medians and 95% credible intervals in addition to fit-quality metrics. A 95% credible interval is the interval containing 95% of the retained marginal posterior samples under this model and bounded sampling setup.

The table also reports convergence diagnostics:

  • r-hat compares variation within and between chains. Values close to 1 are desirable; EasyDiffraction uses r-hat <= 1.01 as its displayed convergence criterion.

  • ess bulk is the effective sample size after accounting for autocorrelation. A larger value means that the chain contains more independent information; the display recommends at least 400.

project_1.display.fit.results()
βš™οΈ Settings used:
NameValueDescription
1sampling_steps300Total sampler iterations per chain.
2burn_in_steps60Sampler iterations discarded as warm-up.
3thinning_interval1Sampler thinning interval.
4population_size4Number of chains or walkers.
5parallel_workers0Worker count; 0 uses all available CPUs.
6initialization_methodlatin_hypercubeSampler initialization method.
7random_seed42Random seed; None uses a system-derived seed.
πŸ“‹ Bayesian fit results:
MetricValue
1πŸ§ͺ Samplerbumps (dream)
2❌ Overall statusfailed
3πŸ’¬ Engine messageDREAM sampling completed
4⏱️ Fitting time (seconds)124.07
5πŸ“ Goodness-of-fit (reduced χ²)1.24
6πŸ“ R-factor (Rf, %)6.89
7πŸ“ R-factor squared (RfΒ², %)5.58
8πŸ“ Weighted R-factor (wR, %)4.76
9πŸ“‰ Best log-posterior-263.03
10πŸ“Š Convergence statusfailed
11πŸ“Š Max r-hat1.180
12πŸ“Š Min ess bulk296.603
13πŸ“Š Draws per chain300
14πŸ“Š Chains28
πŸ“ˆ Committed parameters:
datablockcategoryentryparameterunitsstartvalues.u.change
1lbcocelllength_aΓ…3.89193.89190.00030.00 % ↓
2sim_lbcolinked_structurelbcoscale43.649743.64970.33860.00 % ↓
3sim_lbcolinked_structuresiscale0.32110.32110.01870.00 % ↓
4sim_lbcopeakrise_alpha_1ΞΌs/Γ…0.01230.01230.00020.00 % ↓
5sim_lbcopeakdecay_beta_1ΞΌs/Γ…0.01150.01150.00040.00 % ↓
6sim_lbcopeakbroad_gauss_sigma_1ΞΌs/Γ…-78430.2812-78430.28122557.91020.00 % ↓
7sim_lbcopeakbroad_gauss_sigma_2ΞΌsΒ²/Γ…Β²24485.382124485.38211585.80850.00 % ↓
β€’ start = parameter value before sampling
β€’ value = estimate written back to the project (best posterior sample)
β€’ s.u. = standard uncertainty (one sigma), posterior standard deviation
β€’ change = relative change from start, in %; ↑ = increase, ↓ = decrease
πŸ“Š Posterior distribution:
datablockcategoryentryparameterunitsmedian95% CIr-hatess bulk
1lbcocelllength_aΓ…3.8919[3.8914, 3.8924]1.100296.6
2sim_lbcolinked_structurelbcoscale43.6910[43.0364, 44.3511]1.113355.7
3sim_lbcolinked_structuresiscale0.3221[0.2826, 0.3578]1.128347.1
4sim_lbcopeakrise_alpha_1ΞΌs/Γ…0.0123[0.0120, 0.0127]1.180312.4
5sim_lbcopeakdecay_beta_1ΞΌs/Γ…0.0115[0.0107, 0.0124]1.138322.1
6sim_lbcopeakbroad_gauss_sigma_1ΞΌs/Γ…-78701.7593[-84008.4869, -73831.7126]1.105312.5
7sim_lbcopeakbroad_gauss_sigma_2ΞΌsΒ²/Γ…Β²24677.0434[21778.4041, 27996.7606]1.110318.5
β€’ median = 50th percentile of the marginal posterior
β€’ 95% CI = 95% credible interval (2.5%-97.5%, asymmetric)
β€’ r-hat = Gelman-Rubin diagnostic (good convergence: r-hat <= 1.01)
β€’ ess bulk = bulk effective sample size (typically >= 400)
⚠️ r-hat > 1.01: Consider longer sampling, better initialization, or reparameterization.
⚠️ ess bulk < 400: Consider longer sampling or reparameterization.

A short teaching run may fail these convergence criteria even when it finishes normally. That means the numerical posterior summaries are provisional. Increase the number of sampling steps and inspect the diagnostics again before drawing scientific conclusions.

πŸ”— Understand Posterior Correlations#

Calling the same method after MCMC now builds the matrix from posterior samples rather than from the local least-squares covariance estimate. It answers: across the sampled posterior, which parameter pairs vary together, and how strongly?

project_1.display.fit.correlations(max_parameters=5)

Compare this chart with the local chart above. Similar coefficients suggest that the local approximation captured the main linear relationship. A substantial difference can indicate curvature, asymmetry, bounds, or another feature that a local covariance matrix cannot represent.

πŸ—ΊοΈ Understand the Posterior Pair Plot#

The pair plot shows more information than a matrix of single coefficients:

  • Diagonal panels show each parameter’s one-dimensional marginal posterior density. A narrow peak indicates greater precision; a wide, skewed, truncated, or multimodal shape indicates more uncertainty or a more complicated posterior.

  • Lower-triangle panels show joint posterior samples for pairs of parameters. The contours summarize where the sampled density is concentrated.

  • A compact, nearly round cloud suggests weak correlation. An elongated upward cloud suggests positive correlation, and an elongated downward cloud suggests negative correlation. Curved or split contours cannot be summarized well by one correlation coefficient.

  • The upper triangle is intentionally blank because it would duplicate the lower triangle.

project_1.display.posterior.pairs(max_parameters=5)

In Jupyter, the default plotting engine resolves to interactive Plotly. Hover over a diagonal density curve to see the full parameter name, parameter value, and probability density. Hover over a visible sample point in a lower-triangle panel to see the exact values of both parameters for that posterior draw. The shaded contour itself has no hover tooltip; it is a smoothed two-dimensional density guide. Use the Plotly toolbar to zoom, pan, and reset the view, and click legend items to hide or show samples, contours, or marginal densities.

The broad_gauss_sigma_1–broad_gauss_sigma_2 panel should form a long downward band. This reveals the range of compensating parameter combinations, whereas the correlation chart reduces the relationship to one number.

πŸ“ˆ Understand Marginal Posterior Distributions#

A separate distribution plot gives a more detailed view of each diagonal panel. It includes a histogram, a smoothed marginal density, the median, the best posterior sample, and the 95% credible interval. Hover over the traces and interval markers to read their values.

project_1.display.posterior.distribution()

A density pressed against a fit bound warns that the allowed region may be too narrow or that the parameter is poorly identified. A best sample far from the median can occur for a skewed or irregular posterior and is another reason not to summarize MCMC with only one point estimate.

πŸ“Š Understand the Posterior-Predictive Plot#

Posterior prediction propagates an evenly spaced subset of retained parameter combinations through the diffraction calculation. The best-posterior-sample curve shows one calculated pattern, while the 95% band shows parameter uncertainty propagated into the pattern.

project_1.display.posterior.predictive(expt_name='sim_lbco')
project_1.display.posterior.predictive(
    expt_name='sim_lbco',
    x_min=80000,
    x_max=81500,
)

Compare the width of the band with the experimental uncertainty and remaining residuals. A narrow band does not prove that the model is correct: systematic deviations can indicate model inadequacy, underestimated experimental uncertainty, or effects not included in the refinement. In the interactive view, hover over the measured and best-sample curves to inspect individual x and intensity values. The shaded 95% band is read from its upper and lower boundaries and does not itself show a hover tooltip.

Save the completed reference Bayesian project. Its MCMC chain and posterior data are stored with the project and remain available for comparison with the exercise below.

project_1.save()
Saving project πŸ“¦ 'main' to 'projects/exercise-bayesian-si-lbco-reference'
β”œβ”€β”€ πŸ“„ project.edi
β”œβ”€β”€ πŸ“ structures/
β”‚   └── πŸ“„ lbco.edi
β”‚   └── πŸ“„ si.edi
β”œβ”€β”€ πŸ“ experiments/
β”‚   └── πŸ“„ sim_lbco.edi
β”œβ”€β”€ πŸ“ analysis/
β”‚   β”œβ”€β”€ πŸ“„ analysis.edi
β”‚   └── πŸ“„ mcmc.h5
└── πŸ“ reports/
    └── πŸ“„ main.html

πŸ’ͺ Exercise: Fix One Correlated Parameter#

The reference analysis showed that broad_gauss_sigma_1 and broad_gauss_sigma_2 are strongly negatively correlated. In this exercise, you will fix broad_gauss_sigma_2 at its refined value and repeat the Bayesian workflow.

Removing one member of the pair reduces the sampling dimension and removes that pair from the correlation and pair plots. This can improve sampling efficiency, but it changes the scientific question: the new posterior is conditional on the chosen fixed value of broad_gauss_sigma_2. Its uncertainty is no longer propagated. The exercise demonstrates the computational and visual effect of fixing a parameter; it does not establish that fixing it is always the correct scientific choice.

πŸ“‚ Exercise 1: Create a Fresh Project#

Load the original deterministic refinement again as project_2 and save it under a new name. Starting from the deterministic project ensures that the second MCMC run does not reuse the first posterior state or its uncertainty estimates.

Hint:

Use edi.Project.load() with the local refinement_project_dir defined in the introduction.

Solution:

Hide code cell source

project_2 = edi.Project.load(refinement_project_dir)
project_2.metadata.title = 'Bayesian Analysis with Fixed broad_gauss_sigma_2'
project_2.metadata.description = (
    'MCMC analysis of LBCO and Si with one peak-profile parameter fixed.'
)
project_2.save_as(dir_path='projects/exercise-bayesian-si-lbco-main')
Saving project πŸ“¦ 'main' to 'projects/exercise-bayesian-si-lbco-main'
β”œβ”€β”€ πŸ“„ project.edi
β”œβ”€β”€ πŸ“ structures/
β”‚   └── πŸ“„ lbco.edi
β”‚   └── πŸ“„ si.edi
β”œβ”€β”€ πŸ“ experiments/
β”‚   └── πŸ“„ sim_lbco.edi
β”œβ”€β”€ πŸ“ analysis/
β”‚   └── πŸ“„ analysis.edi
└── πŸ“ reports/
    └── πŸ“„ main.html

🎯 Exercise 2: Reduce the Free-Parameter Set#

Exercise 2.1: Fix the Background#

Fix every background intensity for the same computational reason as in the reference analysis.

Hint:

Iterate over project_2.experiments['sim_lbco'].background and set each point’s intensity.free attribute to False.

Solution:

Hide code cell source

experiment_2 = project_2.experiments['sim_lbco']

for line_segment in experiment_2.background:
    line_segment.intensity.free = False

Exercise 2.2: Fix broad_gauss_sigma_2#

Remove broad_gauss_sigma_2 from the refined and sampled parameter set, then display the remaining free parameters. How many remain?

Hint:

Set the parameter’s free attribute to False, then call project_2.display.parameters.free().

Solution:

Hide code cell source

experiment_2.peak.broad_gauss_sigma_2.free = False

project_2.display.parameters.free()
Free parameters for both structures (🧩 data blocks) and experiments (πŸ”¬ data blocks)
datablockcategoryentryparametervalueuncertaintyminmaxunits
1lbcocelllength_a3.891910.00028-infinfΓ…
2sim_lbcolinked_structurelbcoscale43.650000.39000-infinf
3sim_lbcolinked_structuresiscale0.321000.02200-infinf
4sim_lbcopeakrise_alpha_10.012320.00017-infinfΞΌs/Γ…
5sim_lbcopeakdecay_beta_10.011470.00047-infinfΞΌs/Γ…
6sim_lbcopeakbroad_gauss_sigma_1-78400.000002900.00000-infinfΞΌs/Γ…

Six parameters remain. The background intensities and broad_gauss_sigma_2 will stay at their refined values throughout the following local fit and MCMC run.

πŸš€ Exercise 3: Repeat the Local Refinement#

Select bumps (lm), run the fit, display the result table, and inspect the local correlation chart. Is the original strongly correlated pair still present?

Hint:

Repeat the local-refinement sequence from the introduction with project_2.

Solution:

Hide code cell source

project_2.analysis.minimizer.type = 'bumps (lm)'
project_2.analysis.fit()
project_2.display.fit.results()

project_2.display.fit.correlations(max_parameters=5)
Current minimizer changed to
bumps (lm)
Standard fitting
πŸ“‹ Using experiment πŸ”¬ 'sim_lbco' for 'single' fitting
πŸš€ Starting fit process with 'bumps (lm)'...
πŸ“ˆ Goodness-of-fit progress:
iterationtime (s)χ²change / status
110.021.24
2240.891.24
πŸ† Best goodness-of-fit (reduced χ²) is 1.24 at iteration 24
βœ… Fitting complete.
βš™οΈ Settings used:
NameValueDescription
1max_iterations1000Maximum solver iterations.
πŸ“‹ Least-squares fit results:
MetricValue
1πŸ§ͺ Minimizerbumps (lm)
2βœ… Overall statussuccess
3⏱️ Fitting time (seconds)0.89
4πŸ“ Goodness-of-fit (reduced χ²)1.24
5πŸ“ R-factor (Rf, %)6.89
6πŸ“ R-factor squared (RfΒ², %)5.58
7πŸ“ Weighted R-factor (wR, %)4.76
πŸ“ˆ Refined parameters:
datablockcategoryentryparameterunitsstartvalues.u.change
1lbcocelllength_aΓ…3.89193.89190.00030.00 % ↓
2sim_lbcolinked_structurelbcoscale43.650043.64980.37630.00 % ↓
3sim_lbcolinked_structuresiscale0.32100.32110.02080.05 % ↑
4sim_lbcopeakrise_alpha_1ΞΌs/Γ…0.01230.01230.00020.04 % ↓
5sim_lbcopeakdecay_beta_1ΞΌs/Γ…0.01150.01150.00040.05 % ↑
6sim_lbcopeakbroad_gauss_sigma_1ΞΌs/Γ…-78400.0000-78452.49951074.76140.07 % ↑
β€’ start = parameter value before refinement
β€’ value = refined value from least-squares minimization
β€’ s.u. = standard uncertainty (one sigma), from the covariance matrix
β€’ change = relative change from start, in %; ↑ = increase, ↓ = decrease

The broad_gauss_sigma_1–broad_gauss_sigma_2 pair is absent because broad_gauss_sigma_2 was not varied. This does not show that the physical ambiguity disappeared; it shows that the fixed parameter is no longer part of the estimated covariance or posterior.

🎲 Exercise 4: Set New Sampling Bounds#

Derive finite bounds from the new local-fit uncertainties and verify them. Why is it better to recalculate the bounds than to copy them from project_1?

Hint:

Iterate over project_2.free_parameters and call set_fit_bounds_from_uncertainty().

Solution:

Hide code cell source

for param in project_2.free_parameters:
    param.set_fit_bounds_from_uncertainty()

project_2.display.parameters.free()
Free parameters for both structures (🧩 data blocks) and experiments (πŸ”¬ data blocks)
datablockcategoryentryparametervalueuncertaintyminmaxunits
1lbcocelllength_a3.891910.000283.890793.89303Γ…
2sim_lbcolinked_structurelbcoscale43.649810.3763442.1444645.15517
3sim_lbcolinked_structuresiscale0.321150.020750.238150.40415
4sim_lbcopeakrise_alpha_10.012310.000160.011670.01296ΞΌs/Γ…
5sim_lbcopeakdecay_beta_10.011480.000450.009690.01327ΞΌs/Γ…
6sim_lbcopeakbroad_gauss_sigma_1-78452.499471074.76142-82751.54514-74153.45380ΞΌs/Γ…

Fixing one correlated parameter changes the local covariance matrix and therefore the uncertainty estimates of the remaining parameters. The new bounds should be based on this new local problem.

🎲 Exercise 5: Repeat DREAM Sampling#

Configure DREAM with the same short-chain settings used for project_1, then sample the six-parameter posterior.

Hint:

Use 300 sampling steps, 60 burn-in steps, and random seed 42 so the two runs use comparable settings.

Solution:

Hide code cell source

project_2.analysis.minimizer.type = 'bumps (dream)'
project_2.analysis.minimizer.sampling_steps = 300
project_2.analysis.minimizer.burn_in_steps = 60
project_2.analysis.minimizer.random_seed = 42

project_2.analysis.fit()
⚠️ Switching minimizer type removes these settings:                                                                               
   β€’ max_iterations                                                                                                               
⚠️ Switching minimizer type adds these settings with defaults:                                                                    
   β€’ burn_in_steps=600                                                                                                            
   β€’ initialization_method='latin_hypercube'                                                                                      
   β€’ parallel_workers=0                                                                                                           
   β€’ population_size=4                                                                                                            
   β€’ random_seed=None                                                                                                             
   β€’ sampling_steps=3000                                                                                                          
   β€’ thinning_interval=1                                                                                                          
Current minimizer changed to
bumps (dream)
Standard fitting
πŸ“‹ Using experiment πŸ”¬ 'sim_lbco' for 'single' fitting
πŸš€ Starting fit process with 'bumps (dream)'...
πŸ“ˆ Bayesian sampling progress:
stepprogresstime (s)log posteriorphase
10.21-325.24pre-processing
221/3605.8%4.29-269.94burn-in
340/36011.1%8.09-268.12burn-in
460/36016.7%12.40-266.68burn-in
561/36016.9%12.59-266.39sampling
676/36021.1%15.64-266.48sampling
791/36025.3%18.70-266.67sampling
8106/36029.4%21.78-266.12sampling
9121/36033.6%24.84-266.15sampling
10136/36037.8%28.25-266.81sampling
11151/36041.9%31.31-266.30sampling
12166/36046.1%34.43-265.99sampling
13181/36050.3%37.51-266.23sampling
14196/36054.4%40.61-265.93sampling
15211/36058.6%43.74-265.68sampling
16226/36062.8%46.87-266.31sampling
17241/36066.9%49.98-265.88sampling
18256/36071.1%53.06-266.13sampling
19271/36075.3%56.48-265.67sampling
20286/36079.4%59.59-265.67sampling
21301/36083.6%62.73-265.96sampling
22316/36087.8%65.80-266.38sampling
23331/36091.9%68.89-265.75sampling
24346/36096.1%72.01-266.01sampling
25360/360100.0%75.11-265.37sampling
26106.85post-processing
βœ… Bayesian sampling complete.
⚠️ Convergence diagnostics indicate the posterior may be poorly mixed.                                                            

πŸ“Š Exercise 6: Compare the Posterior Results#

Exercise 6.1: Check Convergence and Credible Intervals#

Display the Bayesian result table. Compare r-hat, effective sample size, and the credible intervals with the reference run. Did fixing one parameter automatically guarantee convergence?

Hint:

Use project_2.display.fit.results(), then compare the diagnostics with those displayed for project_1 in the introduction.

Solution:

Hide code cell source

project_2.display.fit.results()
βš™οΈ Settings used:
NameValueDescription
1sampling_steps300Total sampler iterations per chain.
2burn_in_steps60Sampler iterations discarded as warm-up.
3thinning_interval1Sampler thinning interval.
4population_size4Number of chains or walkers.
5parallel_workers0Worker count; 0 uses all available CPUs.
6initialization_methodlatin_hypercubeSampler initialization method.
7random_seed42Random seed; None uses a system-derived seed.
πŸ“‹ Bayesian fit results:
MetricValue
1πŸ§ͺ Samplerbumps (dream)
2❌ Overall statusfailed
3πŸ’¬ Engine messageDREAM sampling completed
4⏱️ Fitting time (seconds)106.85
5πŸ“ Goodness-of-fit (reduced χ²)1.24
6πŸ“ R-factor (Rf, %)6.89
7πŸ“ R-factor squared (RfΒ², %)5.58
8πŸ“ Weighted R-factor (wR, %)4.76
9πŸ“‰ Best log-posterior-263.03
10πŸ“Š Convergence statusfailed
11πŸ“Š Max r-hat1.113
12πŸ“Š Min ess bulk333.492
13πŸ“Š Draws per chain300
14πŸ“Š Chains24
πŸ“ˆ Committed parameters:
datablockcategoryentryparameterunitsstartvalues.u.change
1lbcocelllength_aΓ…3.89193.89190.00030.00 % ↓
2sim_lbcolinked_structurelbcoscale43.649843.64980.34910.00 % ↓
3sim_lbcolinked_structuresiscale0.32110.32110.01810.00 % ↓
4sim_lbcopeakrise_alpha_1ΞΌs/Γ…0.01230.01230.00020.00 % ↓
5sim_lbcopeakdecay_beta_1ΞΌs/Γ…0.01150.01150.00040.00 % ↓
6sim_lbcopeakbroad_gauss_sigma_1ΞΌs/Γ…-78452.4995-78452.4995954.41000.00 % ↓
β€’ start = parameter value before sampling
β€’ value = estimate written back to the project (best posterior sample)
β€’ s.u. = standard uncertainty (one sigma), posterior standard deviation
β€’ change = relative change from start, in %; ↑ = increase, ↓ = decrease
πŸ“Š Posterior distribution:
datablockcategoryentryparameterunitsmedian95% CIr-hatess bulk
1lbcocelllength_aΓ…3.8919[3.8914, 3.8924]1.090359.2
2sim_lbcolinked_structurelbcoscale43.5626[42.8986, 44.2689]1.091369.4
3sim_lbcolinked_structuresiscale0.3203[0.2838, 0.3554]1.113379.3
4sim_lbcopeakrise_alpha_1ΞΌs/Γ…0.0123[0.0120, 0.0126]1.102333.5
5sim_lbcopeakdecay_beta_1ΞΌs/Γ…0.0115[0.0108, 0.0124]1.107343.1
6sim_lbcopeakbroad_gauss_sigma_1ΞΌs/Γ…-78402.8714[-80143.2173, -76385.3326]1.110352.1
β€’ median = 50th percentile of the marginal posterior
β€’ 95% CI = 95% credible interval (2.5%-97.5%, asymmetric)
β€’ r-hat = Gelman-Rubin diagnostic (good convergence: r-hat <= 1.01)
β€’ ess bulk = bulk effective sample size (typically >= 400)
⚠️ r-hat > 1.01: Consider longer sampling, better initialization, or reparameterization.
⚠️ ess bulk < 400: Consider longer sampling or reparameterization.

Reducing the dimension can make sampling easier, but a 300-step chain is still short. Convergence must be judged from the diagnostics, not assumed from the number of free parameters.

Exercise 6.2: Inspect Correlations and Pair Relationships#

Display the posterior correlation matrix and pair plot. Confirm that broad_gauss_sigma_2 is absent, then inspect whether broad_gauss_sigma_1 is correlated with any remaining parameter.

Hint:

Use the same display.fit.correlations(max_parameters=5) and display.posterior.pairs(max_parameters=5) calls as in the introduction. Hover over cells and sample points to identify the parameter pairs and values.

Solution:

Hide code cell source

project_2.display.fit.correlations(max_parameters=5)

project_2.display.posterior.pairs(max_parameters=5)

The original pair is gone because only sampled parameters appear in posterior charts. Look for any remaining elongated contours rather than concluding that all correlations have disappeared. Fixing one parameter can expose or strengthen relationships among the parameters that remain free.

Exercise 6.3: Compare broad_gauss_sigma_1#

Plot the marginal posterior for broad_gauss_sigma_1 from both runs. Compare the medians, shapes, and 95% credible intervals. Why might the second interval be narrower?

Hint:

Call display.posterior.distribution() for each project and pass the corresponding broad_gauss_sigma_1 parameter using param.

Solution:

Hide code cell source

project_1.display.posterior.distribution(
    param=experiment_1.peak.broad_gauss_sigma_1,
)

project_2.display.posterior.distribution(
    param=experiment_2.peak.broad_gauss_sigma_1,
)

The conditional posterior can be narrower because broad_gauss_sigma_1 no longer shares uncertainty with broad_gauss_sigma_2. That apparent precision comes from assuming the fixed value is exact. It may underestimate the true uncertainty if broad_gauss_sigma_2 is not independently known.

Exercise 6.4: Compare Posterior Predictions#

Plot the same zoomed posterior-predictive region for both analyses. Does fixing broad_gauss_sigma_2 noticeably change the best curve or uncertainty band?

Hint:

Call display.posterior.predictive() for both projects with the same experiment name and the same x_min and x_max values.

Solution:

Hide code cell source

project_1.display.posterior.predictive(
    expt_name='sim_lbco',
    x_min=80000,
    x_max=81500,
)

project_2.display.posterior.predictive(
    expt_name='sim_lbco',
    x_min=80000,
    x_max=81500,
)

Two parameterizations can produce similarly good calculated patterns while assigning different uncertainties to individual parameters. This is why parameter correlations, marginal posteriors, and posterior predictions should be interpreted together.

πŸ’Ύ Exercise 7: Save the Project#

Save the second posterior and its MCMC chain.

Hint:

The project directory was set in Exercise 1, so use project_2.save() to update the existing saved project.

Solution:

Hide code cell source

project_2.save()
Saving project πŸ“¦ 'main' to 'projects/exercise-bayesian-si-lbco-main'
β”œβ”€β”€ πŸ“„ project.edi
β”œβ”€β”€ πŸ“ structures/
β”‚   └── πŸ“„ lbco.edi
β”‚   └── πŸ“„ si.edi
β”œβ”€β”€ πŸ“ experiments/
β”‚   └── πŸ“„ sim_lbco.edi
β”œβ”€β”€ πŸ“ analysis/
β”‚   β”œβ”€β”€ πŸ“„ analysis.edi
β”‚   └── πŸ“„ mcmc.h5
└── πŸ“ reports/
    └── πŸ“„ main.html

Final Remarks#

In this part of the notebook, you learned how to:

  • prepare a refined EasyDiffraction project for MCMC;

  • reduce runtime by fixing nuisance parameters while recognizing the uncertainty tradeoff;

  • use a local fit to obtain starting values and finite bounds;

  • sample a correlated posterior with DREAM;

  • distinguish a local covariance correlation from a posterior-sample correlation;

  • read correlation matrices, pair plots, marginal distributions, and posterior-predictive plots; and

  • understand how fixing one member of a correlated pair changes the statistical question and the reported uncertainty.

For a scientific analysis, run longer chains, verify convergence, examine sensitivity to bounds and fixed values, and reconsider the diffraction model when systematic residuals remain.

🎁 Bonus#

Congratulations β€” you’ve now completed Part 2 of the diffraction data analysis exercises for the DMSC Summer School!

If you’d like to keep exploring, the EasyDiffraction library offers many additional tutorials and examples on the official documentation site: πŸ‘‰ https://docs.easydiffraction.org/lib/latest/tutorials

Besides the Python package, EasyDiffraction also comes with a graphical user interface (GUI) for deterministic diffraction refinement workflows. Bayesian analysis is not yet available in the GUI, so MCMC workflows currently require the Python library.

If you prefer a point-and-click interface over coding, the GUI provides a user-friendly way to perform deterministic refinements. You can download it as a standalone application here: πŸ‘‰ https://easydiffraction.org

We’d love to hear your feedback on EasyDiffraction β€” both the library and the GUI! πŸ’¬