Source code for cracking

"""
**Summary**

Thick-walled expansive cylinder model to calculate internal 
stress strain through the concrete cover and the location of the crack tip

+ **Resistance**: 	cover depth

+ **Load**: 		crack length

+ **limit-state**: 	crack length = cover depth

+ **Field data**: 	concrete mechanical properties
		(compressive, tensile strength Young’s modulus) 
		delamination, visible crack ratio
"""


import matplotlib.pyplot as plt
from copy import deepcopy
import numpy as np

# model function
[docs]def bilinear_stress_strain(epsilon_theta, f_t, E_0): """Return the stress in concrete from strain using the bilinear stress-strain curve. Parameters ---------- epsilon_theta : numpy array strain [-] f_t : numpy array cracking tensile strength [MPa] E_0 : numpy array modulus of elasticity [MPa] Returns ------- numpy array stress [MPa] """ # parameters defining the bilinear curve # critical cracking strain epsilon_cr = f_t / E_0 # strain corresponding to stress = 0.15*f_t epsilon_1 = 0.0003 # strain corresponding to zero residual tensile strength epsilon_u = 0.002 # vectorization with numpy matrix operation for speed, replacing the use of np.where or "if-condition" sigma_theta = np.empty_like(epsilon_theta) sigma_theta[:] = np.nan mask_0 = epsilon_theta <= epsilon_cr sigma_theta[mask_0] = (E_0 * epsilon_theta)[mask_0] mask_1 = (epsilon_theta > epsilon_cr) & (epsilon_theta <= epsilon_1) sigma_theta[mask_1] = ( f_t * (1.0 - 0.85 * (epsilon_theta - epsilon_cr) / (epsilon_1 - epsilon_cr)) )[mask_1] mask_2 = (epsilon_theta > epsilon_1) & (epsilon_theta <= epsilon_u) sigma_theta[mask_2] = ( 0.15 * f_t * (epsilon_u - epsilon_theta) / (epsilon_u - epsilon_1) )[mask_2] # sigma_theta[idx_rest] were set as np.nan return sigma_theta
[docs]def crack_width_open(a, b, u_st, f_t, E_0): """ Calculate crack opening size on the concrete cover surface. Parameters ---------- a: numpy array inner radius boundary of the rust (center of rebar to rust-concrete interface) [m] b : numpy array outer radius boundary of the concrete (center of rebar to cover surface) [m] u_st : numpy array rust expansion(to original rebar surface) beyond the porous zone [m] f_t : numpy array ultimate tensile strength [MPa] E_0 : numpy array modulus of elasticity [MPa] Returns ------- numpy array samples of crack opening size on the concrete cover surface """ epsilon_cr = f_t / E_0 w = 2 * np.pi * b * (2.0 / ((b / a) ** 2.0 + 1) * u_st / a - epsilon_cr) return w
[docs]def strain_f(r, a, b, u_st, f_t, E_0, crack_condition): """Calculate the strain along the polar axis r, a <= r <= b, fully vectorized for matrix representing samples. Parameters ---------- r : 2D numpy array coordinate along the polar axis, a matrix with rows representing each r grid, column number is repeated values [m] a : numpy array inner radius boundary of the rust (center of rebar to rust-concrete interface) [m] b : numpy array outer radius boundary of the concrete (center of rebar to cover surface) [m] u_st : numpy array rust expansion (to original rebar surface) beyond the porous zone [m] f_t : array ultimate tensile strength [MPa] E_0 : array modulus of elasticity [MPa] crack_condition : array crack_condition array [int]. Each element corresponds to the condition of each row of the matrix - 0: 'sound cover' - 1: 'partially cracked' - 2: 'fully cracked' Returns ------- 2D numpy array strain, epsilon_theta matrix. Row is the strain along the polar axis. """ # numpy matrix operation for speed epsilon_theta = np.empty_like(r) * np.nan # initialize # sound cover row_mask_0 = crack_condition == 0 # 'sound cover': elem_mask = (r >= a) & (r <= b) epsilon_theta[row_mask_0 & elem_mask] = ( u_st / a * ((b / r) ** 2 + 1.0) / ((b / a) ** 2 + 1) )[row_mask_0 & elem_mask] # partially cracked cover row_mask_1 = crack_condition == 1 # 'partially cracked cover': R_c = b / (f_t * a / (E_0 * u_st) * ((b / a) ** 2 + 1) - 1) ** 0.5 epsilon_theta[row_mask_1 & elem_mask] = ( ((b / r) ** 2 + 1) / ((b / R_c) ** 2 + 1) * f_t / E_0 )[row_mask_1 & elem_mask] # fully cracked cover row_mask_2 = crack_condition == 2 # 'fully cracked cover': epsilon_theta[row_mask_2 & elem_mask] = ( u_st / a * ((b / r) ** 2 + 1.0) / ((b / a) ** 2 + 1) )[row_mask_2 & elem_mask] return epsilon_theta
[docs]def strain_stress_crack_f( r, r0_bar, x_loss, cover, f_t, E_0, w_c, r_v, plot=False, ax=None ): """calculate the stress, strain, crack_condition for the whole concrete cover (fully vectorized with numpy matrix functions). Parameters ---------- r : 2D numpy array coordinate along the polar axis, a matrix with rows representing each r grid, column number is repeated values [m] r0_bar : numpy array original rebar radius [m] x_loss : numpy array section loss of the steel due to corrosion cover : numpy array concrete cover depth [m] f_t : array ultimate tensile strength [MPa] E_0 : array modulus of elasticity [MPa] w_c : float, array water cement ratio r_v : numpy array expansion rate r_v ranges from 2 to 6.5 times plot : bool, optional if true, plot the stress and strain along r, by default False ax : axis instance subplot axis, by default None Returns ------- tuple (epsilon_theta, sigma_theta, rust_thickness, crack_condition, R_c, w_open) (strain, stress, rust thickness, crack condition code, crack front coordinate, open crack width) Note ---- Vectorization: r is a matrix. Other material property parameters(such as E) are 1-D arrays (to be converted to column vector in the calculation) """ epsilon_cr = f_t / E_0 # r_v = Beta_custom(2.96, 2.96*0.05, 3.3, 2.6) # volumetric expansion rate 2.96 lower 2.6 upper: 3.3 u_r = (r_v - 1) * x_loss a = r0_bar + u_r # Rust meets cover, rust-concrete interface b = cover + r0_bar rust_thickness = u_r - x_loss u_p = 12.5e-6 * w_c / 0.5 # m u_st = u_r - u_p u_st[u_st < 0] = 0 a = r0_bar + u_r # Rust meets cover, rust-concrete interface b = cover + r0_bar a = a[:, None] # to column vector b = b[:, None] u_st = u_st[:, None] E_0 = E_0[:, None] f_t = f_t[:, None] epsilon_cr = epsilon_cr[:, None] # sound crack crack_condition = np.zeros_like( E_0 ) # 0: 'sound cover' initial crack condition guess R_c = np.empty_like(E_0) * np.nan w_open = np.empty_like(E_0) * np.nan # crack width on cover epsilon_theta = strain_f(r, a, b, u_st, f_t, E_0, crack_condition) # partially cracked row_mask_1 = ( (epsilon_theta >= epsilon_cr).any(axis=1) & (epsilon_theta <= epsilon_cr).any(axis=1) )[:, None] crack_condition[row_mask_1] = 1 # 1 'partially cracked' elem_mask = np.full(r.shape, True, dtype=bool) # all True epsilon_theta[row_mask_1 & elem_mask] = strain_f( r, a, b, u_st, f_t, E_0, crack_condition )[row_mask_1 & elem_mask] R_c[row_mask_1] = (b / (f_t * a / (E_0 * u_st) * ((b / a) ** 2 + 1) - 1) ** 0.5)[ row_mask_1 ] # crack tip # fully cracked # there could be Nans in epsilon_theta; compare with Nan is always False row_mask_2_inverse = ((epsilon_theta) < epsilon_cr).any(axis=1)[:, None] row_mask_2 = ~row_mask_2_inverse crack_condition[row_mask_2] = 2 # 2 'fully cracked' epsilon_theta[row_mask_2 & elem_mask] = strain_f( r, a, b, u_st, f_t, E_0, crack_condition )[row_mask_2 & elem_mask] R_c[row_mask_2] = b[row_mask_2] w_open = crack_width_open(a, b, u_st, f_t, E_0) # calculate stress sigma_theta = bilinear_stress_strain(epsilon_theta, f_t, E_0) if plot: if ax is None: ax = plt.gca() ax.plot( r[0, :], epsilon_theta[0, :], color="C0", label=r"strain $\epsilon_{\theta}$", ) # epsilon_1 = 0.0003 epsilon_u = 0.002 # ax.hlines(epsilon_cr, r.min(), r.max(),'r', label=r'critical strain $\epsilon_{cr}$') # ax.hlines(epsilon_1, r.min(), r.max(),'C1', label=r'$\epsilon_1$') # ax.hlines(epsilon_u, r.min(), r.max(), 'b', label=r'zero residual stress strain $\epsilon_u$') ax.vlines(a[0], 0, epsilon_u, linestyle="-", label="rust-concrete boundary") ax.vlines(R_c[0], 0, epsilon_u, linestyle=":", label="crack tip") ax.set_title( "crack condition: {}, 0-sound, 1-partial, 2-fully cracked".format( crack_condition[0] ) ) ax.set_xlabel("Distance from the center of the rebar,[m]") ax.set_ylabel("Strain perpendicular to polar axis") ax.legend() ax1 = ax.twinx() ax1.plot( r[0, :], sigma_theta[0, :], color="C1", label=r"stress $\sigma_{\theta}$" ) ax1.set_ylabel("Stress perpendicular to polar axis,[MPa]") ax1.legend(loc="center right") plt.tight_layout() plt.show() return epsilon_theta, sigma_theta, rust_thickness, crack_condition, R_c, w_open
[docs]def solve_stress_strain_crack_deterministic(pars, number_of_points=100, plot=True): """Solve the stress and strain along the polar axis using strain_stress_crack_f(). One deterministic solution is returned by the mean values of all input variables. Parameters ---------- pars : Param object instance an object instance containing material properties number_of_points : int, optional number of points where the stress and strain is reported along the polar axis, by default 100 plot : bool, optional If True, plot the stress and strain diagram, by default True Returns ------- tuple (epsilon_theta, sigma_theta, rust_thickness, crack_condition, R_c, w_open) (strain, stress, rust thickness, crack condition code, crack front coordinate, open crack width) """ # deterministic with the mean values r0_bar_mean = np.array([pars.r0_bar.mean()]) x_loss_mean = np.array([pars.x_loss.mean()]) cover_mean = np.array([pars.cover.mean()]) f_t_mean = np.array([pars.f_t.mean()]) E_0_mean = np.array([pars.E_0.mean()]) w_c = np.array([pars.w_c.mean()]) r_v = np.array([pars.r_v.mean()]) r = np.full( (1, number_of_points), np.linspace(r0_bar_mean[0], cover_mean[0], number_of_points), ) # solution point ( epsilon_theta, sigma_theta, rust_thickness, crack_condition, R_c, w_open, ) = strain_stress_crack_f( r, r0_bar_mean, x_loss_mean, cover_mean, f_t_mean, E_0_mean, w_c, r_v, plot=plot ) return epsilon_theta, sigma_theta, rust_thickness, crack_condition, R_c, w_open
[docs]def solve_stress_strain_crack_stochastic(pars, number_of_points=100): """Solve the stress and strain along the polar axis using strain_stress_crack_f(). the stochastic solution matrix is returned, where each row represents a deterministic solution Parameters ---------- pars : Param object instance an object instance containing material properties number_of_points : int, optional number of points where the stress and strain is reported along the polar axis, by default 100 Returns ------- tuple (epsilon_theta, sigma_theta, rust_thickness, crack_condition, R_c, w_open) (strain, stress, rust thickness, crack condition code, crack front coordinate, open crack width) """ # Stochastic with random variables r0_bar = pars.r0_bar x_loss = pars.x_loss cover = pars.cover f_t = pars.f_t E_0 = pars.E_0 w_c = pars.w_c r_v = pars.r_v # every row is a sample of r r_mat = np.array( [ np.linspace(this_r0_bar, this_cover, number_of_points) for (this_r0_bar, this_cover) in zip(r0_bar, cover) ] ) # solution point ( epsilon_theta, sigma_theta, rust_thickness, crack_condition, R_c, w_open, ) = strain_stress_crack_f(r_mat, r0_bar, x_loss, cover, f_t, E_0, w_c, r_v) return ( epsilon_theta, sigma_theta, rust_thickness, crack_condition.transpose()[0], R_c.transpose()[0], w_open.transpose()[0], )
[docs]class Cracking_Model: def __init__(self, pars): self.pars = pars
[docs] def run(self, stochastic=True, plot_deterministic_result = True): """ Solve stress, strain, and crack tip location in concrete cover. Parameters ---------- stochastic : bool, optional If True, run the model in stochastic mode, by default True plot_deterministic_result : bool, optional If True, plot the deterministic result, by default True """ if stochastic: self.stochastic = stochastic sol = solve_stress_strain_crack_stochastic(self.pars) # no plot else: self.stochastic = stochastic print("deterministic") sol = solve_stress_strain_crack_deterministic( self.pars, plot=plot_deterministic_result ) # plot to plt.gca() ( self.epsilon_theta, self.sigma_theta, self.rust_thickness, self.crack_condition, self.R_c, self.w_open, ) = sol
[docs] def postproc(self): """calculate the crack length and surface crack rate. """ if self.stochastic: crack_length_over_cover = (self.R_c - self.pars.r0_bar) / self.pars.cover crack_length_over_cover[ np.isnan(crack_length_over_cover) ] = 0.0 # crack length=0 for no crack self.crack_length_over_cover = crack_length_over_cover self.crack_visible_rate_count = len( self.crack_condition[self.crack_condition == 2] ) / len(self.crack_condition) else: print("Warning! Postprocessing for stochastic solution only")
[docs] def copy(self): """Create a deepcopy of the Cracking_Model instance.""" return deepcopy(self)