|
| 1 | + |
| 2 | +import numpy as np |
| 3 | +import matplotlib.pyplot as plt |
| 4 | +from scipy.interpolate import interp1d |
| 5 | +from scipy import linalg |
| 6 | +from capacity_soils import rock_profile |
| 7 | +from capacity_pycurves import py_Lovera |
| 8 | +from capacity_plots import plot_pile |
| 9 | + |
| 10 | +def getCapacityDandG(profile, soil_type, L, D, zlug, H, V, plot=True): |
| 11 | + '''Models a laterally loaded pile using the p-y method. The solution for |
| 12 | + lateral displacements is obtained by solving the 4th order ODE, |
| 13 | + EI*d4y/dz4 - V*d2y/dz2 + ky = 0 using the finite difference method. |
| 14 | + EI*d4y/dz4 - V*d2y/dz2 + K*z*dy/dz + ky = 0 using the finite difference method. |
| 15 | +
|
| 16 | + Assumes that EI remains constant with respect to curvature i.e. pile |
| 17 | + material remains in the elastic region. |
| 18 | +
|
| 19 | + Parameters |
| 20 | + ---------- |
| 21 | + profile : array |
| 22 | + Rock profile as a 2D array: (z (m), UCS (MPa), Em (MPa)) |
| 23 | + soil_type : string |
| 24 | + Select soil condition, 'rock' |
| 25 | + L : float |
| 26 | + Pile length (m) |
| 27 | + D : float |
| 28 | + Pile diameter (m) |
| 29 | + zlug : float |
| 30 | + Load eccentricity above the mudline or depth to mudline relative to the pile head (m) |
| 31 | + H : float |
| 32 | + Horizontal load at pile lug elevation (N) |
| 33 | + V : float |
| 34 | + Vertical load at pile lug elevation (N) |
| 35 | + plot : bool |
| 36 | + Plot the p-y curve and the deflection pile condition if True |
| 37 | +
|
| 38 | + Returns |
| 39 | + ------- |
| 40 | + y : array |
| 41 | + Lateral displacement at each node (n+1 real + 4 imaginary) |
| 42 | + z : array |
| 43 | + Node location along pile (m) |
| 44 | + resultsDandG : dict |
| 45 | + Dictionary with lateral, rotational, vertical and pile weight results |
| 46 | + ''' |
| 47 | + |
| 48 | + n = 50; iterations = 10; loc = 2 |
| 49 | + nhuc = 1; nhu = 0.3 # Resistance factor (-) |
| 50 | + delta_r = 0.08 # Mean roughness height (m) |
| 51 | + |
| 52 | + t = (6.35 + D*20)/1e3 # Pile wall thickness (m), API RP2A-WSD |
| 53 | + E = 200e9 # Elastic modulus of pile material (Pa) |
| 54 | + rhows = 66.90e3 # Submerged steel specific weight (N/m3) |
| 55 | + rhow = 10e3 # Water specific weight (N/m3) |
| 56 | + |
| 57 | + # Pile geometry |
| 58 | + I = (np.pi/64.0)*(D**4 - (D - 2*t)**4) |
| 59 | + EI = E*I |
| 60 | + h = L/n # Element size |
| 61 | + N = (n + 1) + 4 # (n+1) Real + 4 Imaginary nodes |
| 62 | + |
| 63 | + # Dry and wet mass of the pile |
| 64 | + def PileWeight(Len, Dia, tw, rho): |
| 65 | + Wp = ((np.pi/4)*(Dia**2 - (Dia - 2*tw)**2)*Len)*rho |
| 66 | + return Wp |
| 67 | + |
| 68 | + # Array for displacements at nodes, including imaginary nodes. |
| 69 | + y = np.ones(N)*(0.01*D) # An initial value of 0.01D was arbitrarily chosen |
| 70 | + |
| 71 | + # Initialize and assemble array/list of p-y curves at each real node |
| 72 | + z = np.zeros(N) |
| 73 | + k_secant = np.zeros(N) |
| 74 | + py_funs = [] |
| 75 | + DQ = [] |
| 76 | + z0, f_UCS, f_Em = rock_profile(profile) |
| 77 | + |
| 78 | + for i in [0, 1]: # Top two imaginary nodes |
| 79 | + z[i] = (i - 2)*h |
| 80 | + py_funs.append(0) |
| 81 | + k_secant[i] = 0.0 |
| 82 | + |
| 83 | + for i in range(2, n+3): # Real nodes |
| 84 | + z[i] = (i - 2)*h |
| 85 | + if z[i] < z0: |
| 86 | + # No p-y curve above mudline |
| 87 | + py_funs.append(lambda y_val: np.zeros_like(y_val)) |
| 88 | + k_secant[i] = 0.0 |
| 89 | + DQ.append(0.0) |
| 90 | + else: |
| 91 | + py_funs.append(py_Lovera(z[i], D, f_UCS, f_Em, zlug, z0, plot=True)) |
| 92 | + UCS = f_UCS(z[i]) |
| 93 | + Em = f_Em(z[i]) |
| 94 | + SCR = nhuc*Em/(UCS*(1 + nhu))*delta_r/D |
| 95 | + alpha = 0.36*SCR - 0.0005 |
| 96 | + fs = alpha*UCS |
| 97 | + Dq = np.pi*D*fs*z[i] |
| 98 | + DQ.append(Dq) |
| 99 | + Vmax = PileWeight(L, D, t, rhows) + DQ[-1] |
| 100 | + |
| 101 | + k_secant[i] = py_funs[i](y[i])/y[i] |
| 102 | + |
| 103 | + for i in [n+3, n+4]: # Bottom two imaginary nodes |
| 104 | + z[i] = (i - 2)*h |
| 105 | + py_funs.append(0) |
| 106 | + k_secant[i] = 0.0 |
| 107 | + |
| 108 | + for j in range(iterations): |
| 109 | + # if j == 0: print 'FD Solver started!' |
| 110 | + y = fd_solver(n, N, h, EI, V, H, zlug, z0, k_secant) |
| 111 | + if plot: |
| 112 | + plt.plot(y[loc], k_secant[loc]*y[loc]) |
| 113 | + |
| 114 | + for i in range(2, n+3): |
| 115 | + k_secant[i] = py_funs[i](y[i])/y[i] |
| 116 | + |
| 117 | + if plot: |
| 118 | + y1 = np.linspace(-2.*D, 2.*D, 500) |
| 119 | + plt.plot(y1, py_funs[loc](y1)) |
| 120 | + plt.xlabel('y (m)'), plt.ylabel('p (N/m)') |
| 121 | + plt.grid(True) |
| 122 | + |
| 123 | + fig, ax = plt.subplots(figsize=(3, 5)) |
| 124 | + y0 = np.zeros_like(z[2:-2]) |
| 125 | + # Plot original vertical pile |
| 126 | + ax.plot(y0, z[2:-2], 'k', label='Original pile axis') |
| 127 | + # Plot deflected shape |
| 128 | + ax.plot(y[2:-2], z[2:-2], 'r', label='Deflected shape') |
| 129 | + # Padeye marker |
| 130 | + ax.plot(0, zlug, 'ko', label=f'Padeye (zlug = {zlug:.2f} m)') |
| 131 | + # Mudline marker |
| 132 | + ax.axhline(z0, color='blue', linestyle='--', label=f'Mudline (z0 = {z0:.2f} m)') |
| 133 | + |
| 134 | + ax.set_xlabel('Lateral displacement (m)') |
| 135 | + ax.set_ylabel('Depth (m)') |
| 136 | + ax.set_xlim([-0.1*D, 0.1*D]) |
| 137 | + ax.set_ylim([L + 5, -2]) # Downward is positive z |
| 138 | + ax.grid(ls='--') |
| 139 | + ax.legend() |
| 140 | + |
| 141 | + resultsDandG = { |
| 142 | + 'Lateral displacement' : y[2], |
| 143 | + 'Rotational displacement' : np.rad2deg((y[2] - y[3])/h), |
| 144 | + 'Vertical max.' : Vmax, |
| 145 | + 'Weight pile' : PileWeight(L, D, t, (rhows + rhow)) |
| 146 | + } |
| 147 | + |
| 148 | + return y[2:-2], z[2:-2], resultsDandG |
| 149 | + |
| 150 | +def fd_solver(n, N, h, EI, H, V, zlug, z0, k_secant): |
| 151 | + '''Solves the finite difference equations from 'py_analysis_1'. This function should be run iteratively for |
| 152 | + non-linear p-y curves by updating 'k_secant' using 'y'. A single iteration is sufficient if the p-y curves |
| 153 | + are linear. |
| 154 | +
|
| 155 | + Parameters |
| 156 | + ---------- |
| 157 | + n : int |
| 158 | + Number of elements (-) |
| 159 | + N : int |
| 160 | + Total number of nodes (-) |
| 161 | + h : float |
| 162 | + Element size (m) |
| 163 | + EI : float |
| 164 | + Flexural rigidity of the pile (Nm²) |
| 165 | + H : float |
| 166 | + Horizontal load at padeye (N) |
| 167 | + V : float |
| 168 | + Vertical load at padeye (N) |
| 169 | + zlug : float |
| 170 | + Padeye depth from pile head (m) |
| 171 | + z0 : float |
| 172 | + Mudline elevation from pile head (m) |
| 173 | + k_secant : array |
| 174 | + Secant stiffness at each node |
| 175 | +
|
| 176 | + Returns |
| 177 | + ------- |
| 178 | + y : array |
| 179 | + Lateral displacement at each node (n+1 real + 4 imaginary) |
| 180 | + ''' |
| 181 | + |
| 182 | + # Initialize and assemble matrix |
| 183 | + X = np.zeros((N, N)) |
| 184 | + |
| 185 | + # (n+1) finite difference equations for (n+1) real nodes |
| 186 | + for i in range(0,n+1): |
| 187 | + X[i, i] = 1.0 |
| 188 | + X[i, i+1] = -4.0 + V*h**2/EI |
| 189 | + X[i, i+2] = 6.0 - 2*V*h**2/EI + k_secant[i+2]*h**4/EI |
| 190 | + X[i, i+3] = -4.0 + V*h**2/EI |
| 191 | + X[i, i+4] = 1.0 |
| 192 | + |
| 193 | + # Curvature at pile head |
| 194 | + X[n+1, 1] = 1.0 |
| 195 | + X[n+1, 2] = -2.0 |
| 196 | + X[n+1, 3] = 1.0 |
| 197 | + |
| 198 | + # Shear at pile head |
| 199 | + X[n+2, 0] = -1.0 |
| 200 | + X[n+2, 1] = 2.0 - V*h**2/EI |
| 201 | + X[n+2, 2] = 0.0 |
| 202 | + X[n+2, 3] = -2.0 + V*h**2/EI |
| 203 | + X[n+2, 4] = 1.0 |
| 204 | + |
| 205 | + # Curvature at pile tip |
| 206 | + X[n+3, -2] = 1.0 |
| 207 | + X[n+3, -3] = -2.0 |
| 208 | + X[n+3, -4] = 1.0 |
| 209 | + |
| 210 | + # Shear at pile tip |
| 211 | + X[n+4, -1] = 1.0 |
| 212 | + X[n+4, -2] = -2.0 + V*h**2/EI |
| 213 | + X[n+4, -3] = 0.0 |
| 214 | + X[n+4, -4] = 2.0 - V*h**2/EI |
| 215 | + X[n+4, -5] = -1.0 |
| 216 | + |
| 217 | + # Initialize vector q |
| 218 | + q = np.zeros(N) |
| 219 | + |
| 220 | + # Always apply shear |
| 221 | + # Index of the node where the horizontal load is applied (padeye) |
| 222 | + zlug_index = int(zlug/h) |
| 223 | + q[zlug_index] = 2*H*h**3 |
| 224 | + |
| 225 | + # Solve for displacement |
| 226 | + y = linalg.solve(EI*X, q) |
| 227 | + |
| 228 | + return y |
| 229 | + |
| 230 | +if __name__ == '__main__': |
| 231 | + |
| 232 | + profile_rock = np.array([ |
| 233 | + [ 2.0, 1, 1e3], |
| 234 | + [ 5.0, 2, 2e4], |
| 235 | + [ 9.0, 4, 2e4], |
| 236 | + [30.0, 6, 5e4] |
| 237 | + ]) |
| 238 | + |
| 239 | + D = 3.0 # Diameter (m) |
| 240 | + L = 10.0 # Length (m) |
| 241 | + zlug = 1 # Padeye elevation (m) |
| 242 | + |
| 243 | + y, z, results = getCapacityDandG(profile_rock, 'rock', L, D, zlug, H=9.5e12, V=3.0e6, plot=True) |
| 244 | + |
| 245 | + plot_pile(profile_rock, 'rock', y, z, D, L, profile_rock[0, 0], zlug) |
0 commit comments