-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Andrey Latyshev
committed
Jan 19, 2025
1 parent
1256421
commit e71a022
Showing
5 changed files
with
1,029 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
from mpi4py import MPI | ||
from petsc4py import PETSc | ||
|
||
import jax | ||
jax.config.update("jax_enable_x64", True) | ||
import jax.numpy as jnp | ||
import numpy as np | ||
|
||
E = 6778 # [MPa] Young modulus | ||
nu = 0.25 # [-] Poisson ratio | ||
c = 3.45 # [MPa] cohesion | ||
phi = 30 * np.pi / 180 # [rad] friction angle | ||
psi = 30 * np.pi / 180 # [rad] dilatancy angle | ||
theta_T = 26 * np.pi / 180 # [rad] transition angle as defined by Abbo and Sloan | ||
a = 0.26 * c / np.tan(phi) # [MPa] tension cuff-off parameter | ||
stress_dim = 4 | ||
|
||
def J3(s): | ||
return s[2] * (s[0] * s[1] - s[3] * s[3] / 2.0) | ||
|
||
|
||
def J2(s): | ||
return 0.5 * jnp.vdot(s, s) | ||
|
||
|
||
def theta(s): | ||
J2_ = J2(s) | ||
arg = -(3.0 * np.sqrt(3.0) * J3(s)) / (2.0 * jnp.sqrt(J2_ * J2_ * J2_)) | ||
arg = jnp.clip(arg, -1.0, 1.0) | ||
theta = 1.0 / 3.0 * jnp.arcsin(arg) | ||
return theta | ||
|
||
|
||
def sign(x): | ||
return jax.lax.cond(x < 0.0, lambda x: -1, lambda x: 1, x) | ||
|
||
|
||
def coeff1(theta, angle): | ||
return np.cos(theta_T) - (1.0 / np.sqrt(3.0)) * np.sin(angle) * np.sin(theta_T) | ||
|
||
|
||
def coeff2(theta, angle): | ||
return sign(theta) * np.sin(theta_T) + (1.0 / np.sqrt(3.0)) * np.sin(angle) * np.cos(theta_T) | ||
|
||
|
||
coeff3 = 18.0 * np.cos(3.0 * theta_T) * np.cos(3.0 * theta_T) * np.cos(3.0 * theta_T) | ||
|
||
|
||
def C(theta, angle): | ||
return ( | ||
-np.cos(3.0 * theta_T) * coeff1(theta, angle) - 3.0 * sign(theta) * np.sin(3.0 * theta_T) * coeff2(theta, angle) | ||
) / coeff3 | ||
|
||
def B(theta, angle): | ||
return ( | ||
sign(theta) * np.sin(6.0 * theta_T) * coeff1(theta, angle) - 6.0 * np.cos(6.0 * theta_T) * coeff2(theta, angle) | ||
) / coeff3 | ||
|
||
|
||
def A(theta, angle): | ||
return ( | ||
-(1.0 / np.sqrt(3.0)) * np.sin(angle) * sign(theta) * np.sin(theta_T) | ||
- B(theta, angle) * sign(theta) * np.sin(3 * theta_T) | ||
- C(theta, angle) * np.sin(3.0 * theta_T) * np.sin(3.0 * theta_T) | ||
+ np.cos(theta_T) | ||
) | ||
|
||
|
||
def K(theta, angle): | ||
def K_false(theta): | ||
return jnp.cos(theta) - (1.0 / np.sqrt(3.0)) * np.sin(angle) * jnp.sin(theta) | ||
|
||
def K_true(theta): | ||
return ( | ||
A(theta, angle) | ||
+ B(theta, angle) * jnp.sin(3.0 * theta) | ||
+ C(theta, angle) * jnp.sin(3.0 * theta) * jnp.sin(3.0 * theta) | ||
) | ||
|
||
return jax.lax.cond(jnp.abs(theta) > theta_T, K_true, K_false, theta) | ||
|
||
def a_g(angle): | ||
return a * np.tan(phi) / np.tan(angle) | ||
|
||
dev = np.array( | ||
[ | ||
[2.0 / 3.0, -1.0 / 3.0, -1.0 / 3.0, 0.0], | ||
[-1.0 / 3.0, 2.0 / 3.0, -1.0 / 3.0, 0.0], | ||
[-1.0 / 3.0, -1.0 / 3.0, 2.0 / 3.0, 0.0], | ||
[0.0, 0.0, 0.0, 1.0], | ||
], | ||
dtype=PETSc.ScalarType, | ||
) | ||
tr = np.array([1.0, 1.0, 1.0, 0.0], dtype=PETSc.ScalarType) | ||
|
||
|
||
def surface(sigma_local, angle): | ||
s = dev @ sigma_local | ||
I1 = tr @ sigma_local | ||
theta_ = theta(s) | ||
return ( | ||
(I1 / 3.0 * np.sin(angle)) | ||
+ jnp.sqrt( | ||
J2(s) * K(theta_, angle) * K(theta_, angle) + a_g(angle) * a_g(angle) * np.sin(angle) * np.sin(angle) | ||
) | ||
- c * np.cos(angle) | ||
) | ||
|
||
def f(sigma_local): | ||
return surface(sigma_local, phi) | ||
|
||
def g(sigma_local): | ||
return surface(sigma_local, psi) | ||
|
||
dgdsigma = jax.jacfwd(g) | ||
|
||
lmbda = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)) | ||
mu = E / (2.0 * (1.0 + nu)) | ||
C_elas = np.array( | ||
[ | ||
[lmbda + 2 * mu, lmbda, lmbda, 0], | ||
[lmbda, lmbda + 2 * mu, lmbda, 0], | ||
[lmbda, lmbda, lmbda + 2 * mu, 0], | ||
[0, 0, 0, 2 * mu], | ||
], | ||
dtype=PETSc.ScalarType, | ||
) | ||
S_elas = np.linalg.inv(C_elas) | ||
ZERO_VECTOR = np.zeros(stress_dim, dtype=PETSc.ScalarType) | ||
|
||
def deps_p(sigma_local, dlambda, deps_local, sigma_n_local): | ||
sigma_elas_local = sigma_n_local + C_elas @ deps_local | ||
yielding = f(sigma_elas_local) | ||
|
||
def deps_p_elastic(sigma_local, dlambda): | ||
return ZERO_VECTOR | ||
|
||
def deps_p_plastic(sigma_local, dlambda): | ||
return dlambda * dgdsigma(sigma_local) | ||
|
||
return jax.lax.cond(yielding <= 0.0, deps_p_elastic, deps_p_plastic, sigma_local, dlambda) | ||
|
||
|
||
def r_g(sigma_local, dlambda, deps_local, sigma_n_local): | ||
deps_p_local = deps_p(sigma_local, dlambda, deps_local, sigma_n_local) | ||
return sigma_local - sigma_n_local - C_elas @ (deps_local - deps_p_local) | ||
|
||
|
||
def r_f(sigma_local, dlambda, deps_local, sigma_n_local): | ||
sigma_elas_local = sigma_n_local + C_elas @ deps_local | ||
yielding = f(sigma_elas_local) | ||
|
||
def r_f_elastic(sigma_local, dlambda): | ||
return dlambda | ||
|
||
def r_f_plastic(sigma_local, dlambda): | ||
return f(sigma_local) | ||
|
||
return jax.lax.cond(yielding <= 0.0, r_f_elastic, r_f_plastic, sigma_local, dlambda) | ||
|
||
|
||
def r(y_local, deps_local, sigma_n_local): | ||
sigma_local = y_local[:stress_dim] | ||
dlambda_local = y_local[-1] | ||
|
||
res_g = r_g(sigma_local, dlambda_local, deps_local, sigma_n_local) | ||
res_f = r_f(sigma_local, dlambda_local, deps_local, sigma_n_local) | ||
|
||
res = jnp.c_["0,1,-1", res_g, res_f] # concatenates an array and a scalar | ||
return res | ||
|
||
drdy = jax.jacfwd(r) | ||
|
||
Nitermax, tol = 200, 1e-10 | ||
|
||
ZERO_SCALAR = np.array([0.0]) | ||
|
||
|
||
def return_mapping(deps_local, sigma_n_local): | ||
"""Performs the return-mapping procedure. | ||
It solves elastoplastic constitutive equations numerically by applying the | ||
Newton method in a single Gauss point. The Newton loop is implement via | ||
`jax.lax.while_loop`. | ||
The function returns `sigma_local` two times to reuse its values after | ||
differentiation, i.e. as once we apply | ||
`jax.jacfwd(return_mapping, has_aux=True)` the ouput function will | ||
have an output of | ||
`(C_tang_local, (sigma_local, niter_total, yielding, norm_res, dlambda))`. | ||
Returns: | ||
sigma_local: The stress at the current Gauss point. | ||
niter_total: The total number of iterations. | ||
yielding: The value of the yield function. | ||
norm_res: The norm of the residuals. | ||
dlambda: The value of the plastic multiplier. | ||
""" | ||
niter = 0 | ||
|
||
dlambda = ZERO_SCALAR | ||
sigma_local = sigma_n_local | ||
y_local = jnp.concatenate([sigma_local, dlambda]) | ||
|
||
res = r(y_local, deps_local, sigma_n_local) | ||
norm_res0 = jnp.linalg.norm(res) | ||
|
||
def cond_fun(state): | ||
norm_res, niter, _ = state | ||
return jnp.logical_and(norm_res / norm_res0 > tol, niter < Nitermax) | ||
|
||
def body_fun(state): | ||
norm_res, niter, history = state | ||
|
||
y_local, deps_local, sigma_n_local, res = history | ||
|
||
j = drdy(y_local, deps_local, sigma_n_local) | ||
j_inv_vp = jnp.linalg.solve(j, -res) | ||
y_local = y_local + j_inv_vp | ||
|
||
res = r(y_local, deps_local, sigma_n_local) | ||
norm_res = jnp.linalg.norm(res) | ||
history = y_local, deps_local, sigma_n_local, res | ||
|
||
niter += 1 | ||
|
||
return (norm_res, niter, history) | ||
|
||
history = (y_local, deps_local, sigma_n_local, res) | ||
|
||
norm_res, niter_total, y_local = jax.lax.while_loop(cond_fun, body_fun, (norm_res0, niter, history)) | ||
|
||
sigma_local = y_local[0][:stress_dim] | ||
dlambda = y_local[0][-1] | ||
sigma_elas_local = C_elas @ deps_local | ||
yielding = f(sigma_n_local + sigma_elas_local) | ||
|
||
return sigma_local, (sigma_local, niter_total, yielding, norm_res, dlambda) | ||
|
||
def constitutive_response(sigma_local, sigma_n_local): | ||
deps_elas = S_elas @ sigma_local | ||
sigma_corrected, state = return_mapping(deps_elas, sigma_n_local) | ||
yielding = state[2] | ||
return sigma_corrected, yielding |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.