|
| 1 | +""" |
| 2 | +
|
| 3 | +Physics-Informed PCE: Uncertainty Quantification of Euler Beam |
| 4 | +====================================================================== |
| 5 | +
|
| 6 | +In this example, we use Physics-informed Polynomial Chaos Expansion for approximation and UQ of Euler beam equation. |
| 7 | +""" |
| 8 | + |
| 9 | +# %% md |
| 10 | +# |
| 11 | +# Import necessary libraries. |
| 12 | + |
| 13 | +# %% |
| 14 | + |
| 15 | +from UQpy.distributions import Uniform, JointIndependent |
| 16 | +from UQpy.surrogates import * |
| 17 | + |
| 18 | +# load PC^2 |
| 19 | +from UQpy.surrogates.polynomial_chaos.polynomials.TotalDegreeBasis import TotalDegreeBasis |
| 20 | +from UQpy.surrogates.polynomial_chaos.physics_informed.ConstrainedPCE import ConstrainedPCE |
| 21 | +from UQpy.surrogates.polynomial_chaos.physics_informed.PdeData import PdeData |
| 22 | +from UQpy.surrogates.polynomial_chaos.physics_informed.PdePCE import PdePCE |
| 23 | +from UQpy.surrogates.polynomial_chaos.physics_informed.Utilities import * |
| 24 | +from UQpy.surrogates.polynomial_chaos.physics_informed.ReducedPCE import ReducedPCE |
| 25 | + |
| 26 | + |
| 27 | +# %% md |
| 28 | +# |
| 29 | +# We then define functions used for construction of Physically Constrained PCE (PC :math:`^2`) |
| 30 | + |
| 31 | +# %% |
| 32 | + |
| 33 | +# Definition of PDE/ODE |
| 34 | +def pde_func(standardized_sample, pce): |
| 35 | + der_order = 4 |
| 36 | + deriv_pce = derivative_basis(standardized_sample, pce, derivative_order=der_order, leading_variable=0) * ((2 / 1) ** der_order) |
| 37 | + |
| 38 | + pde_basis = deriv_pce |
| 39 | + |
| 40 | + return pde_basis |
| 41 | + |
| 42 | + |
| 43 | +# Definition of the source term |
| 44 | +def pde_res(standardized_sample): |
| 45 | + load_s = load(standardized_sample) |
| 46 | + |
| 47 | + return -load_s[:, 0] |
| 48 | + |
| 49 | + |
| 50 | +def load(standardized_sample): |
| 51 | + return const_load(standardized_sample) |
| 52 | + |
| 53 | + |
| 54 | +def const_load(standardized_sample): |
| 55 | + l = (1 + (1 + standardized_sample[:, 1]) / 2).reshape(-1, 1) |
| 56 | + return l |
| 57 | + |
| 58 | + |
| 59 | +# Definition of the function for sampling of boundary conditions |
| 60 | +def bc_sampling(nsim=1000): |
| 61 | + # BC sampling |
| 62 | + |
| 63 | + nsim_half = round(nsim / 2) |
| 64 | + sample = np.zeros((nsim, 2)) |
| 65 | + real_ogrid_1d = ortho_grid(nsim_half, 1, 0.0, 1.0)[:, 0] |
| 66 | + |
| 67 | + sample[:nsim_half, 0] = np.zeros(nsim_half) |
| 68 | + sample[:nsim_half, 1] = real_ogrid_1d |
| 69 | + |
| 70 | + sample[nsim_half:, 0] = np.ones(nsim_half) |
| 71 | + sample[nsim_half:, 1] = real_ogrid_1d |
| 72 | + |
| 73 | + return sample |
| 74 | + |
| 75 | + |
| 76 | +# define sampling and evaluation of BC for estimation of error |
| 77 | +def bc_res(nsim, pce): |
| 78 | + physical_sample= np.zeros((2, 2)) |
| 79 | + physical_sample[1, 0] = 1 |
| 80 | + standardized_sample = polynomial_chaos.Polynomials.standardize_sample(physical_sample, pce.polynomial_basis.distributions) |
| 81 | + |
| 82 | + der_order = 2 |
| 83 | + deriv_pce = np.sum( |
| 84 | + derivative_basis(standardized_sample, pce, derivative_order=der_order, leading_variable=0) * |
| 85 | + ((2 / 1) ** der_order) * np.array(pce.coefficients).T, axis=1) |
| 86 | + |
| 87 | + return deriv_pce |
| 88 | + |
| 89 | + |
| 90 | +# Definition of the reference solution for an error estimation |
| 91 | +def ref_sol(physical_coordinate, q): |
| 92 | + return (q + 1) * (-(physical_coordinate ** 4) / 24 + physical_coordinate ** 3 / 12 - physical_coordinate / 24) |
| 93 | + |
| 94 | + |
| 95 | +# %% md |
| 96 | +# |
| 97 | +# Beam equation is parametrized by one deterministic input variable (coordinate :math:`x`) and |
| 98 | +# random load intensity :math:`q`, both modeled as Uniform random variables. |
| 99 | + |
| 100 | +# number of input variables |
| 101 | + |
| 102 | +nrand = 1 |
| 103 | +nvar = 1 + nrand |
| 104 | + |
| 105 | +# definition of a joint probability distribution |
| 106 | +dist1 = Uniform(loc=0, scale=1) |
| 107 | +dist2 = Uniform(loc=0, scale=1) |
| 108 | +marg = [dist1, dist2] |
| 109 | +joint = JointIndependent(marginals=marg) |
| 110 | + |
| 111 | +# %% md |
| 112 | +# |
| 113 | +# The physical domain is defined by :math:`x \in [0,1]` |
| 114 | + |
| 115 | + |
| 116 | +# define geometry of physical space |
| 117 | +geometry_xmin = [0] |
| 118 | +geometry_xmax = [1] |
| 119 | + |
| 120 | +# %% md |
| 121 | +# |
| 122 | +# Boundary conditions are prescribed as |
| 123 | +# |
| 124 | +# .. math:: \frac{d^2 u(0,q)}{d x^2}=0, \qquad \frac{d^2 u(1,q)}{d x^2}=0, \qquad u(0,q)=0, \qquad u(1,q)=0 |
| 125 | + |
| 126 | +# number of BC samples |
| 127 | +nbc = 2 * 10 |
| 128 | + |
| 129 | +# derivation orders of prescribed BCs |
| 130 | +der_orders = [0, 2] |
| 131 | +# normals associated to precribed BCs |
| 132 | +bc_normals = [0, 0] |
| 133 | +# sampling of BC points |
| 134 | + |
| 135 | +bc_xtotal = bc_sampling(20) |
| 136 | +bc_ytotal = np.zeros(len(bc_xtotal)) |
| 137 | + |
| 138 | +bc_x = [bc_xtotal, bc_xtotal] |
| 139 | +bc_y = [bc_ytotal, bc_ytotal] |
| 140 | + |
| 141 | +# %% md |
| 142 | +# |
| 143 | +# Here we construct an object containing general physical information (geometry, boundary conditions) |
| 144 | +pde_data = PdeData(geometry_xmax, geometry_xmin, der_orders, bc_normals, bc_x, bc_y) |
| 145 | + |
| 146 | +# %% md |
| 147 | +# |
| 148 | +# Further we construct an object containing PDE physical data and PC :math:`^2` definitions of PDE |
| 149 | + |
| 150 | +pde_pce = PdePCE(pde_data, pde_func, pde_source=pde_res, boundary_conditions_evaluate=bc_res) |
| 151 | + |
| 152 | +# %% md |
| 153 | +# |
| 154 | +# Get dirichlet boundary conditions from PDE data object that are further used for construction of initial PCE object. |
| 155 | + |
| 156 | +dirichlet_bc = pde_data.dirichlet |
| 157 | +x_train = dirichlet_bc[:, :-1] |
| 158 | +y_train = dirichlet_bc[:, -1] |
| 159 | + |
| 160 | +# %% md |
| 161 | +# |
| 162 | +# Finally, PC :math:`^2` is constructed using Karush-Kuhn-Tucker normal equations solved by ordinary least squares and |
| 163 | +# Least Angle Regression. |
| 164 | + |
| 165 | +least_squares = LeastSquareRegression() |
| 166 | +p = 9 |
| 167 | + |
| 168 | +PCEorder = p |
| 169 | +polynomial_basis = TotalDegreeBasis(joint, PCEorder, hyperbolic=1) |
| 170 | + |
| 171 | +# create initial PCE object containing basis, regression method and dirichlet BC |
| 172 | +initpce = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=least_squares) |
| 173 | +initpce.set_data(x_train, y_train) |
| 174 | + |
| 175 | +# construct a PC^2 object combining pde_data, pde_pce and initial PCE objects |
| 176 | +pcpc = ConstrainedPCE(pde_data, pde_pce, initpce) |
| 177 | +# get coefficients of PC^2 by least angle regression |
| 178 | +pcpc.lar() |
| 179 | + |
| 180 | +# get coefficients of PC^2 by ordinary least squares |
| 181 | +pcpc.ols() |
| 182 | + |
| 183 | +# evaluate errors of approximations |
| 184 | +real_ogrid = ortho_grid(100, nvar, 0.0, 1.0) |
| 185 | +yy_val_pce = pcpc.lar_pce.predict(real_ogrid).flatten() |
| 186 | +yy_val_pce_ols = pcpc.initial_pce.predict(real_ogrid).flatten() |
| 187 | +yy_val_true = ref_sol(real_ogrid[:, 0], real_ogrid[:, 1]).flatten() |
| 188 | + |
| 189 | +err = np.abs(yy_val_pce - yy_val_true) |
| 190 | +tot_err = np.sum(err) |
| 191 | +print('\nTotal approximation error by PC^2-LAR: ', tot_err) |
| 192 | + |
| 193 | +err_ols = np.abs(yy_val_pce_ols - yy_val_true) |
| 194 | +tot_err_ols = np.sum(err_ols) |
| 195 | +print('Total approximation error by PC^2-OLS: ', tot_err_ols) |
| 196 | + |
| 197 | +# %% md |
| 198 | +# |
| 199 | +# Once the PC :math:`^2` is constructed, we use ReducedPce class to filter out influence of deterministic variable in UQ |
| 200 | + |
| 201 | +reduced_pce = ReducedPCE(pcpc.lar_pce, n_deterministic=1) |
| 202 | + |
| 203 | +# %% md |
| 204 | +# |
| 205 | +# ReducedPce is used for estimation of local statistical moments and quantiles :math:`\pm2\sigma` |
| 206 | +coeff_res = [] |
| 207 | +var_res = [] |
| 208 | +mean_res = [] |
| 209 | +vartot_res = [] |
| 210 | +lower_quantiles_modes = [] |
| 211 | +upper_quantiles_modes = [] |
| 212 | + |
| 213 | +n_derivations = 4 |
| 214 | +sigma_mult = 2 |
| 215 | +beam_x = np.arange(0, 101) / 100 |
| 216 | + |
| 217 | +for x in beam_x: |
| 218 | + mean = np.zeros(n_derivations + 1) |
| 219 | + var = np.zeros(n_derivations + 1) |
| 220 | + variances = np.zeros((n_derivations + 1, nrand)) |
| 221 | + lq = np.zeros((1 + n_derivations, nrand)) |
| 222 | + uq = np.zeros((1 + n_derivations, nrand)) |
| 223 | + for d in range(1 + n_derivations): |
| 224 | + if d == 0: |
| 225 | + coeff = (reduced_pce.evaluate_coordinate(np.array(x), return_coefficients=True)) |
| 226 | + else: |
| 227 | + coeff = reduced_pce.derive_coordinate(np.array(x), derivative_order=d, leading_variable=0, |
| 228 | + return_coefficients=True, |
| 229 | + derivative_multiplier=transformation_multiplier(pde_data, 0, d)) |
| 230 | + mean[d] = coeff[0] |
| 231 | + var[d] = np.sum(coeff[1:] ** 2) |
| 232 | + variances[d, :] = reduced_pce.variance_contributions(coeff) |
| 233 | + |
| 234 | + for e in range(nrand): |
| 235 | + lq[d, e] = mean[d] + sigma_mult * np.sqrt(np.sum(variances[d, :e + 1])) |
| 236 | + uq[d, e] = mean[d] - sigma_mult * np.sqrt(np.sum(variances[d, :e + 1])) |
| 237 | + |
| 238 | + lower_quantiles_modes.append(lq) |
| 239 | + upper_quantiles_modes.append(uq) |
| 240 | + var_res.append(variances) |
| 241 | + mean_res.append(mean) |
| 242 | + vartot_res.append(var) |
| 243 | + |
| 244 | +mean_res = np.array(mean_res) |
| 245 | +vartot_res = np.array(vartot_res) |
| 246 | +var_res = np.array(var_res) |
| 247 | +lower_quantiles_modes = np.array(lower_quantiles_modes) |
| 248 | +upper_quantiles_modes = np.array(upper_quantiles_modes) |
| 249 | + |
| 250 | +# %% md |
| 251 | +# |
| 252 | +# PC :math:`^2` and corresponding Reduced PCE can be further used for local UQ. Obtained results are depicted in figure. |
| 253 | + |
| 254 | +# plot results |
| 255 | +import matplotlib.pyplot as plt |
| 256 | + |
| 257 | +fig, ax = plt.subplots(1, 4, figsize=(14.5, 3)) |
| 258 | +colors = ['black', 'blue', 'green', 'red'] |
| 259 | + |
| 260 | +for d in range(2, 1 + n_derivations): |
| 261 | + ax[d - 1].plot(beam_x, mean_res[:, d], color=colors[d - 1]) |
| 262 | + ax[d - 1].plot(beam_x, mean_res[:, d] + sigma_mult * np.sqrt(vartot_res[:, d]), color=colors[d - 1]) |
| 263 | + ax[d - 1].plot(beam_x, mean_res[:, d] - sigma_mult * np.sqrt(vartot_res[:, d]), color=colors[d - 1]) |
| 264 | + |
| 265 | + ax[d - 1].plot(beam_x, lower_quantiles_modes[:, d, 0], '--', alpha=0.7, color=colors[d - 1]) |
| 266 | + ax[d - 1].plot(beam_x, upper_quantiles_modes[:, d, 0], '--', alpha=0.7, color=colors[d - 1]) |
| 267 | + ax[d - 1].fill_between(beam_x, lower_quantiles_modes[:, d, 0], upper_quantiles_modes[:, d, 0], |
| 268 | + facecolor=colors[d - 1], alpha=0.05) |
| 269 | + |
| 270 | + ax[d - 1].plot(beam_x, np.zeros(len(beam_x)), color='black') |
| 271 | + |
| 272 | +ax[0].plot(beam_x, mean_res[:, 0], color=colors[0]) |
| 273 | +ax[0].plot(beam_x, mean_res[:, 0] + sigma_mult * np.sqrt(vartot_res[:, 0]), color=colors[0]) |
| 274 | +ax[0].plot(beam_x, mean_res[:, 0] - sigma_mult * np.sqrt(vartot_res[:, 0]), color=colors[0]) |
| 275 | + |
| 276 | +ax[0].plot(beam_x, lower_quantiles_modes[:, 0, 0], '--', alpha=0.7, color=colors[0]) |
| 277 | +ax[0].plot(beam_x, upper_quantiles_modes[:, 0, 0], '--', alpha=0.7, color=colors[0]) |
| 278 | +ax[0].fill_between(beam_x, lower_quantiles_modes[:, 0, 0], upper_quantiles_modes[:, 0, 0], |
| 279 | + facecolor=colors[0], alpha=0.05) |
| 280 | + |
| 281 | +ax[0].plot(beam_x, np.zeros(len(beam_x)), color='black') |
| 282 | + |
| 283 | +ax[3].invert_yaxis() |
| 284 | +ax[1].invert_yaxis() |
| 285 | + |
| 286 | +ax[3].set_title(r'Load $\frac{\partial^4 w}{\partial x^4}$', y=1.04) |
| 287 | +ax[2].set_title(r'Shear Force $\frac{\partial^3 w}{\partial x^3}$', y=1.04) |
| 288 | +ax[1].set_title(r'Bending Moment $\frac{\partial^2 w}{\partial x^2}$', y=1.04) |
| 289 | +ax[0].set_title(r'Deflection $w$', y=1.04) |
| 290 | + |
| 291 | +for axi in ax.flatten(): |
| 292 | + axi.set_xlabel(r'$x$') |
| 293 | +plt.show() |
0 commit comments