Recently, we have presented a paper titled: Bayesian analysis of fracture of polyamide 12 U-notched specimens, in collaboration with the group of Professor Jesús Rodríguez-Pérez (DIMME, Grupo de Durabilidad e Integridad Mecánica de Materiales Estructurales, Escuela Superior de Ciencias Experimentales y Tecnología, Universidad Rey Juan Carlos) in the European Conference of Fracture 2024
We share the Python code in this blog using synthetic notched fracture data as an example of Bayesian non-linear regression.
We describe step by step the code used
import numpy as np import matplotlib.pyplot as plt import pymc as pm import arviz as az
def KN_func(KIC, ft, R): pi = np.pi L = (KIC / ft) ** 2 * 1000 KN = KIC * (1 + pi * R / L) ** 1.5 / (1 + 2 * pi * R / L) return KN # Fixed parameters for synthetic data KIC = 3.25 ft = 82.0 # Generate synthetic data N = 100 # Number of data points radio_vect = np.linspace(0, 1.2, N) mean_KN = KN_func(KIC,ft, radio_vect) # Adding noise to simulate real measurements observed_KN_values = mean_KN + np.random.normal(0, 0.3,N) # Plot the synthetic data plt.scatter(radio_vect, observed_KN_values, color='blue', label='Synthetic KN data') plt.plot(radio_vect,mean_KN, color='red', linestyle='--', label='True KN curve') plt.xlabel('Sample') plt.ylabel(r'K$_N$ (MPa m$^{0.5}$)') plt.ylim([0,None]) plt.legend() plt.show()
- KIC: Represents the critical stress intensity factor (fracture toughness).
- ft: Critical stress.
- ϵ: The error parameter.
We’ll specify priors for the two material parameters, and, the standard deviation.
with pm.Model() as model: # Priors for KIC, ft, and R based on domain knowledge KIC = pm.Uniform('KIC', 1, 5) ft = pm.Uniform('ft', 10, 150) ϵ = pm.InverseGamma('ϵ', alpha=1.0,beta=1) # Likelihood: observed data is compared against the model function KN_observed = pm.Normal('KN_observed', mu=KN_func(KIC, ft, radio_vect), sigma=ϵ, observed=observed_KN_values) # Inference trace = pm.sample(2000, tune=1000, return_inferencedata=True,cores=3)
Using arviz, a powerful library for Bayesian analysis, we plot the trace of the parameters and their posterior distributions:
trace_fig = az.plot_trace(trace, var_names=[ 'KIC', 'ft','ϵ'], figsize=(12, 15)); # get and change x labels for i in range(trace_fig.shape[0]): for j in range(trace_fig.shape[1]): if j==0: x_labels = [str(np.round(x,3)) for x in trace_fig[i][j].get_xticks()]; trace_fig[i][j].set_xticklabels(x_labels, fontsize=14); if j==1: y_labels = [str(np.round(y,3)) for y in trace_fig[i][j].get_yticks()]; trace_fig[i][j].set_yticklabels(y_labels, fontsize=14); x_labels = [str(int(x)) for x in trace_fig[i][j].get_xticks()]; trace_fig[i][j].set_xticklabels(x_labels, fontsize=14); title=trace_fig[i][j].get_title() trace_fig[i][j].set_title(title, fontsize=20); trace_fig[0][0].set_title(r'K$_{IC}$', fontsize=20); trace_fig[0][1].set_title(r'K$_{IC}$', fontsize=20); trace_fig[1][0].set_title(r'$f_{t}$', fontsize=20); trace_fig[1][1].set_title(r'$f_{t}$', fontsize=20);
The posterior distribution can be plotted in better quality
# Plot the posterior distribution for KIC az.plot_density(trace, var_names=['KIC'], shade=0.3, hdi_prob=0.99,colors='lightpink') plt.xlabel(r'K$_{IC}$ (MPa m$^{0.5}$)') plt.title('Posterior Distribution for K$_{IC}$') plt.show() az.plot_density(trace, var_names=['ft'], shade=0.3, hdi_prob=0.99) plt.xlabel(r'f$_{t}$ (MPa)') plt.title('Posterior Distribution for f$_{t}$') plt.show() az.plot_density(trace, var_names=['ϵ'], shade=0.3, hdi_prob=0.99,colors='lightgreen') plt.xlabel(r'ϵ (MPa m$^{0.5}$)') plt.title('Posterior Distribution for ϵ') plt.show() # Summarize the results summary = az.summary(trace) print(summary)
The parameter region that fits the experimental results can be studied with the next code
az.plot_pair(trace,kind='kde', var_names=[ 'KIC', 'ft']) plt.ylabel(r'$\sigma_{C}$ (MPam)') plt.xlabel(r'K$_{IC}$ (MPam$^{0.5}$)')
# Posterior samples posterior_KIC = trace.posterior['KIC'].values.flatten() posterior_ft = trace.posterior['ft'].values.flatten() KICmean = trace.posterior['KIC'].mean().values ftmean = trace.posterior['ft'].mean().values fit_KN = KN_func(KICmean,ftmean, radio_vect) # Select some posterior samples for plotting num_samples = 500 # Number of samples to plot posterior_samples_idx = np.random.choice(len(posterior_KIC), num_samples, replace=False) # Plot original data vs. posterior curves plt.scatter(radio_vect, observed_KN_values, color='blue', label='Observed KN data') for i in posterior_samples_idx: plt.plot(radio_vect, KN_func(posterior_KIC[i], posterior_ft[i], radio_vect), color='gray', alpha=0.1) # Plot the true KN curve plt.plot(radio_vect, fit_KN, 'r--', label='Fitted curve', linewidth=2) plt.xlabel('Sample') plt.ylabel(r'K$_N$ (MPa m$^{0.5}$)') plt.ylim([0,None]) plt.legend() plt.show()