diff --git a/cmad/.DS_Store b/cmad/.DS_Store new file mode 100644 index 0000000..c620a83 Binary files /dev/null and b/cmad/.DS_Store differ diff --git a/cmad/calibrations/al7079/support.py b/cmad/calibrations/al7079/support.py index e00cc09..a6dde6e 100644 --- a/cmad/calibrations/al7079/support.py +++ b/cmad/calibrations/al7079/support.py @@ -7,7 +7,7 @@ import jax.numpy as jnp -from jax import tree_map +from jax.tree_util import tree_map from cmad.parameters.parameters import Parameters diff --git a/cmad/fem_utils/.DS_Store b/cmad/fem_utils/.DS_Store new file mode 100644 index 0000000..f387f4a Binary files /dev/null and b/cmad/fem_utils/.DS_Store differ diff --git a/cmad/fem_utils/global_residuals/global_residual.py b/cmad/fem_utils/global_residuals/global_residual.py new file mode 100755 index 0000000..36e4aa8 --- /dev/null +++ b/cmad/fem_utils/global_residuals/global_residual.py @@ -0,0 +1,215 @@ +import numpy as np +import jax.numpy as jnp +from jax import jit, vmap, jacfwd + +from cmad.fem_utils.models.global_deriv_types import GlobalDerivType + +from abc import ABC +from cmad.fem_utils.utils.fem_utils import assemble_global_fields, assemble_prescribed + +from scipy.sparse import coo_matrix, csc_matrix + +class Global_residual(ABC): + def __init__( + self, resid_fun, elem_surf_traction, volume_conn, elem_points, + eq_num, params, num_nodes_elem, dof_node, num_free_dof, disp_node, + disp_val, num_pres_dof, is_mixed, pres_surf_traction_points, pres_surf_traction, + surf_traction_vector): + + self._global_resid = jit(vmap(resid_fun, in_axes=[0, None, 0])) + self._global_jac = [jit(vmap(jacfwd(resid_fun), in_axes=[0, None, 0])), + jit(vmap(jacfwd(resid_fun, argnums=1), in_axes=[0, None, 0]))] + self._surf_traction_batched = jit(vmap(elem_surf_traction, in_axes=[0, None])) + + # Halley's method + mapped_halley_axes = [0, None, 0, 0] + self._global_hessian = jit(jacfwd(jacfwd(resid_fun))) + self._batch_halley_correction = jit(vmap(self._compute_halley, + in_axes=mapped_halley_axes)) + + self._deriv_mode = GlobalDerivType.DNONE + + self._num_free_dof = num_free_dof + self._volume_conn = volume_conn + self._elem_points = elem_points + self._params = params + self._eq_num = eq_num + self._UF = np.zeros(num_free_dof) + self._is_mixed = is_mixed + + # displacement and pressure boundary conditions + self._disp_node = disp_node + self._disp_val = disp_val + self._num_pres_dof = num_pres_dof + + # traction boundary conditions + self._pres_surf_traction_points = pres_surf_traction_points + self._surf_traction_vector = surf_traction_vector + self._pres_surf_traction = pres_surf_traction + + # indices for element vector assembly + global_indices = eq_num[volume_conn, :].transpose(0, 2, 1).reshape(volume_conn.shape[0], -1) + global_free_indices = np.where(global_indices > 0, global_indices - 1, -1) + flat_global_free_indices = global_free_indices.ravel() + mask_vector = flat_global_free_indices >= 0 + global_free_indices_vector = flat_global_free_indices[mask_vector] + + self._mask_vector = mask_vector + self._global_free_indices_vector = global_free_indices_vector + + # indices for element matrix assembly + elem_dofs = num_nodes_elem * dof_node + ii, jj = np.meshgrid(np.arange(elem_dofs), np.arange(elem_dofs), + indexing='ij') + row_f = global_free_indices[:, ii] + col_f = global_free_indices[:, jj] + mask_f = (row_f >= 0) & (col_f >= 0) + row_f = row_f[mask_f] + col_f = col_f[mask_f] + + self._ii = ii + self._jj = jj + self._row_f = row_f + self._col_f = col_f + self._mask_f = mask_f + + # index arrays for traction vector assembly + if not pres_surf_traction is None: + if is_mixed: + surf_global_indices_all = eq_num[:, :-1][pres_surf_traction, :]. \ + transpose(0, 2, 1).reshape(pres_surf_traction.shape[0], -1) + else: + surf_global_indices_all = eq_num[pres_surf_traction, :]. \ + transpose(0, 2, 1).reshape(pres_surf_traction.shape[0], -1) + flat_surf_global_free_indices = np.where(surf_global_indices_all > 0, + surf_global_indices_all - 1, -1).ravel() + self._surf_valid_free_mask = flat_surf_global_free_indices >= 0 + self._surf_global_indices = flat_surf_global_free_indices[self._surf_valid_free_mask] + + # data storage + self._point_data = [] + + def initialize_variables(self): + return + + def advance_model(self): + return + + def evaluate(self): + + variables = self._variables() + deriv_mode = self._deriv_mode + + if deriv_mode == GlobalDerivType.DNONE: + self._R = np.asarray(self._global_resid(*variables)) + self._Jac = None + elif deriv_mode == GlobalDerivType.DU: + self._Jac = np.asarray(self._global_jac[0](*variables)) + elif deriv_mode == GlobalDerivType.DParams: + self._Jac = np.asarray(self._global_jac[1](*variables)) + + def R(self): + return self._R + + def Jac(self): + return self._Jac + + def set_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + self._u_elem = UUR[self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + def save_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + if self._is_mixed: + self._point_data.append({'displacement_field': UUR[:, :-1], + 'pressure_field': UUR[:, -1]}) + else: + self._point_data.append({'displacement_field': UUR}) + + def compute_surf_tractions(self, step): + self._FF = np.zeros(self._num_free_dof) + if not self._pres_surf_traction is None: + FEL = self._surf_traction_batched(self._pres_surf_traction_points, + self._surf_traction_vector[step]) + np.add.at(self._FF, self._surf_global_indices, FEL.ravel()[self._surf_valid_free_mask]) + + def get_data(self): + return self._point_data + + def set_prescribed_dofs(self, step): + self._UP = assemble_prescribed(self._num_pres_dof, self._disp_node, + self._disp_val[:, step], self._eq_num) + + def _variables(self): + return self._u_elem, self._params, self._elem_points + + def add_to_UF(self, delta): + self._UF += delta + + def set_UF(self, UF): + self._UF = UF.copy() + + def get_UF(self): + return self._UF.copy() + + def scatter_rhs(self): + RF_global = np.zeros(self._num_free_dof) + np.add.at(RF_global, self._global_free_indices_vector, + self._R.ravel()[self._mask_vector]) + return RF_global - self._FF + + def scatter_lhs(self): + KFF = csc_matrix(coo_matrix((self._Jac[:, self._ii, self._jj][self._mask_f], + (self._row_f, self._col_f)), + shape=(self._num_free_dof, self._num_free_dof))) + return KFF + + def seed_none(self): + self._deriv_mode = GlobalDerivType.DNONE + + def seed_U(self): + self._deriv_mode = GlobalDerivType.DU + + # Functions for Halley's method + ######################################### + def set_newton_increment(self, delta_F): + delta_UR = assemble_global_fields(self._eq_num, delta_F, np.zeros(self._num_pres_dof)) + self._delta_elem = delta_UR[self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + def _compute_halley(self, u, params, elem_points, delta): + variables = u, params, elem_points + d2R_dU2 = self._global_hessian(*variables) + + cont_axes = ([1, 2], [0, 1]) + return jnp.tensordot(d2R_dU2, jnp.outer(delta, delta), axes=cont_axes) + + def evaluate_halley_correction(self): + variables = self._halley_variables() + halley_batch = np.asarray(self._batch_halley_correction(*variables)) + + halley_global = np.zeros(self._num_free_dof) + np.add.at(halley_global, self._global_free_indices_vector, + halley_batch.ravel()[self._mask_vector]) + + return halley_global + + def _halley_variables(self): + return self._u_elem, self._params, self._elem_points, self._delta_elem + + + + + + + + + + + + + + + + diff --git a/cmad/fem_utils/global_residuals/global_residual_plasticity.py b/cmad/fem_utils/global_residuals/global_residual_plasticity.py new file mode 100755 index 0000000..b5d278e --- /dev/null +++ b/cmad/fem_utils/global_residuals/global_residual_plasticity.py @@ -0,0 +1,526 @@ +import numpy as np +import jax.numpy as jnp +from jax import jit, vmap, jacfwd +import pyvista as pv +import time + +from cmad.fem_utils.models.global_deriv_types import GlobalDerivType + +from abc import ABC +from cmad.fem_utils.utils.fem_utils import (assemble_global_fields, + assemble_prescribed) +from cmad.models.deformation_types import DefType + +from scipy.sparse import coo_matrix, csc_matrix +from jax.lax import while_loop + +class Global_residual_plasticity(ABC): + def __init__( + self, global_resid_fun, local_resid_fun, elem_surf_traction, volume_conn, + nodal_coords, eq_num, params, num_nodes_elem, dof_node, num_quad_pts, + num_free_dof, num_pres_dof, num_elem, disp_node, disp_val, init_xi, + pres_surf_traction_points, pres_surf_traction, surf_traction_vector, def_type): + + mapped_axes = [0, 0, None, 0, 0, 0] + + self._global_resid = jit(global_resid_fun) + self._global_jac = [jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU)), + jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU_prev)), + jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DParams)), + jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DXI)), + jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DXI_prev))] + + self._global_resid_batch = jit(vmap(global_resid_fun, in_axes=mapped_axes)) + self._global_jac_batch = [jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU), + in_axes=mapped_axes)), + jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU_prev), + in_axes=mapped_axes)), + jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DParams), + in_axes=mapped_axes)), + jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DXI), + in_axes=mapped_axes)), + jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DXI_prev), + in_axes=mapped_axes))] + + self._local_resid = jit(local_resid_fun) + self._local_jac = [jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU)), + jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU_prev)), + jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DParams)), + jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DXI)), + jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DXI_prev))] + + self._local_resid_batch = jit(vmap(local_resid_fun, in_axes=mapped_axes)) + self._local_jac_batch = [jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU), + in_axes=mapped_axes)), + jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU_prev), + in_axes=mapped_axes)), + jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DParams), + in_axes=mapped_axes)), + jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DXI), + in_axes=mapped_axes)), + jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DXI_prev), + in_axes=mapped_axes))] + + self._local_hessian = [jit(jacfwd(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU), + argnums=GlobalDerivType.DU)), + jit(jacfwd(jacfwd(local_resid_fun, argnums=GlobalDerivType.DXI), + argnums=GlobalDerivType.DXI)), + jit(jacfwd(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU), + argnums=GlobalDerivType.DXI))] + + self._global_hessian = [jit(jacfwd(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU), + argnums=GlobalDerivType.DU)), + jit(jacfwd(jacfwd(global_resid_fun, argnums=GlobalDerivType.DXI), + argnums=GlobalDerivType.DXI)), + jit(jacfwd(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU), + argnums=GlobalDerivType.DXI))] + + self._batch_local_newton = jit(vmap(self._local_resid_newton, in_axes=mapped_axes)) + self._batch_tang = jit(vmap(self._compute_tang, in_axes=mapped_axes)) + + # Halley's method + mapped_halley_axes = [0, 0, None, 0, 0, 0, 0, 0, 0, 0] + self._batch_halley_correction = jit(vmap(self._compute_halley, + in_axes=mapped_halley_axes)) + + mapped_halley_fd_axes = [0, 0, None, 0, 0, 0, 0] + self._batch_halley_correction_fd = jit(vmap(self._compute_halley_fd, + in_axes=mapped_halley_fd_axes)) + + self._surf_traction_batch = jit(vmap(elem_surf_traction, in_axes=[0, None])) + + self._deriv_mode = GlobalDerivType.DNONE + self._def_type = def_type + + self._num_free_dof = num_free_dof + self._volume_conn = volume_conn + self._nodal_coords = nodal_coords + self._elem_points = nodal_coords[volume_conn, :] + self._params = params + self._eq_num = eq_num + self._num_quad_pts = num_quad_pts + self._init_xi = init_xi + self._num_elem = num_elem + + if (def_type == DefType.PLANE_STRESS): + self._elem_points = nodal_coords[:, :-1][volume_conn, :] + + # displacement and pressure boundary conditions + self._disp_node = disp_node + self._disp_val = disp_val + self._num_pres_dof = num_pres_dof + + # traction boundary conditions + self._pres_surf_traction_points = pres_surf_traction_points + self._surf_traction_vector = surf_traction_vector + self._pres_surf_traction = pres_surf_traction + + # indices for element vector assembly + global_indices = eq_num[volume_conn, :].transpose(0, 2, 1).reshape(volume_conn.shape[0], -1) + global_free_indices = np.where(global_indices > 0, global_indices - 1, -1) + flat_global_free_indices = global_free_indices.ravel() + mask_vector = flat_global_free_indices >= 0 + global_free_indices_vector = flat_global_free_indices[mask_vector] + + self._mask_vector = mask_vector + self._global_free_indices_vector = global_free_indices_vector + + # indices for element matrix assembly + elem_dofs = num_nodes_elem * dof_node + ii, jj = np.meshgrid(np.arange(elem_dofs), np.arange(elem_dofs), + indexing='ij') + row_f = global_free_indices[:, ii] + col_f = global_free_indices[:, jj] + mask_f = (row_f >= 0) & (col_f >= 0) + row_f = row_f[mask_f] + col_f = col_f[mask_f] + + self._ii = ii + self._jj = jj + self._row_f = row_f + self._col_f = col_f + self._mask_f = mask_f + + # indices for traction vector assembly + if not pres_surf_traction is None: + surf_global_indices_all = eq_num[:, :-1][pres_surf_traction, :]. \ + transpose(0, 2, 1).reshape(pres_surf_traction.shape[0], -1) + flat_surf_global_free_indices = np.where(surf_global_indices_all > 0, + surf_global_indices_all - 1, -1).ravel() + self._surf_valid_free_mask = flat_surf_global_free_indices >= 0 + self._surf_global_indices = flat_surf_global_free_indices[self._surf_valid_free_mask] + + # data storage + self._point_data = [] + self._cell_data = [] + + def initialize_variables(self): + self._UF = np.zeros(self._num_free_dof) + self._UF_prev = np.zeros(self._num_free_dof) + self._UP_prev = np.zeros(self._num_pres_dof) + self._xi_elem_prev = np.tile(self._init_xi, (self._num_elem, 1)) + self._xi_elem = self._xi_elem_prev.copy() + + def reset_xi(self): + self._xi_elem = self._xi_elem_prev.copy() + + def compute_local_state_variables(self): + variables = self._variables() + self._xi_elem = np.asarray(self._batch_local_newton(*variables)) + + def evaluate_tang(self): + variables = self._variables() + tang, dxi_du, dc_dxi_inv, dR_dxi = self._batch_tang(*variables) + self._tang = np.asarray(tang) + self._dxi_du = np.asarray(dxi_du) + self._dc_dxi_inv = np.asarray(dc_dxi_inv) + self._dR_dxi = np.asarray(dR_dxi) + + def evaluate_global(self): + + variables = self._variables() + deriv_mode = self._deriv_mode + + if deriv_mode == GlobalDerivType.DNONE: + self._R = np.asarray(self._global_resid_batch(*variables)) + self._Jac_R = None + else: + self._Jac_R = np.asarray(self._global_jac_batch[deriv_mode](*variables)) + + def evaluate_local(self): + + variables = self._variables() + deriv_mode = self._deriv_mode + + if deriv_mode == GlobalDerivType.DNONE: + self._C = np.asarray(self._local_resid_batch(*variables)) + self._Jac_C = None + else: + self._Jac_C = np.asarray(self._local_jac_batch[deriv_mode](*variables)) + + def R(self): + return self._R + + def C(self): + return self._C + + def Jac_global(self): + return self._Jac_R + + def Jac_local(self): + return self._Jac_C + + def set_prescribed_dofs(self, step): + self._UP = assemble_prescribed(self._num_pres_dof, self._disp_node, + self._disp_val[:, step], self._eq_num) + + def set_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + UUR_prev = assemble_global_fields(self._eq_num, self._UF_prev, self._UP_prev) + self._u_elem = UUR[self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + self._u_elem_prev = UUR_prev[self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + def save_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + disp_field = UUR[:, :-1] + pressure_field = UUR[:, -1] + + if self._def_type == DefType.PLANE_STRESS: + disp_field = np.append(disp_field, np.zeros((len(self._eq_num), 1)), axis=1) + + self._point_data.append({'displacement_field': disp_field, + 'pressure_field': pressure_field}) + + plastic_strain = self.average_plastic_strain() + self._cell_data.append({'eq_plastic_strain': plastic_strain}) + + def get_num_plastic_elements(self): + plastic_strain = self.average_plastic_strain() + num_plastic = np.sum(np.abs(plastic_strain) > 1e-13) + print('Number of plastic elements: ', num_plastic) + + def compute_surf_tractions(self, step): + self._FF = np.zeros(self._num_free_dof) + if not self._pres_surf_traction is None: + FEL = self._surf_traction_batch(self._pres_surf_traction_points, + self._surf_traction_vector[step]) + np.add.at(self._FF, self._surf_global_indices, FEL.ravel()[self._surf_valid_free_mask]) + + def get_data(self): + return self._point_data, self._cell_data + + def add_to_UF(self, delta): + self._UF += delta + + def get_UF(self): + return self._UF.copy() + + def set_UF(self, UF): + self._UF = UF.copy() + + def scatter_rhs(self): + RF_global = np.zeros(self._num_free_dof) + np.add.at(RF_global, self._global_free_indices_vector, + self._R.ravel()[self._mask_vector]) + return RF_global - self._FF + + def scatter_lhs(self): + KFF = csc_matrix(coo_matrix((self._tang[:, self._ii, self._jj][self._mask_f], + (self._row_f, self._col_f)), + shape=(self._num_free_dof, self._num_free_dof))) + return KFF + + def seed_none(self): + self._deriv_mode = GlobalDerivType.DNONE + + def seed_U(self): + self._deriv_mode = GlobalDerivType.DU + + def seed_xi(self): + self._deriv_mode = GlobalDerivType.DXI + + def seed_xi_prev(self): + self._deriv_mode = GlobalDerivType.DXI_prev + + def seed_params(self): + self._deriv_mode = GlobalDerivType.DParams + + def advance_model(self): + self._xi_elem_prev = self._xi_elem.copy() + self._UF_prev = self._UF.copy() + self._UP_prev = self._UP.copy() + + def average_plastic_strain(self): + return np.mean(self._xi_elem[:, -self._num_quad_pts:], axis=1).reshape(-1, 1) + + def _compute_tang(self, u, u_prev, params, xi, xi_prev, elem_points): + variables = u, u_prev, params, xi, xi_prev, elem_points + dc_du = self._local_jac[GlobalDerivType.DU](*variables) + dc_dxi = self._local_jac[GlobalDerivType.DXI](*variables) + dc_dxi_inv = jnp.linalg.inv(dc_dxi) + dxi_du = dc_dxi_inv @ -dc_du + + dR_du = self._global_jac[GlobalDerivType.DU](*variables) + dR_dxi = self._global_jac[GlobalDerivType.DXI](*variables) + return dR_du + dR_dxi @ dxi_du, dxi_du, dc_dxi_inv, dR_dxi + + def initialize_plot(self): + num_elem = self._volume_conn.shape[0] + + if self._def_type == DefType.PLANE_STRESS: + cell_types = np.full(len(self._volume_conn), pv.CellType.TRIANGLE, dtype=np.uint8) + cells = np.hstack((3 * np.ones((num_elem, 1)), self._volume_conn)) + else: + cell_types = np.full(len(self._volume_conn), pv.CellType.TETRA, dtype=np.uint8) + cells = np.hstack((4 * np.ones((num_elem, 1)), self._volume_conn)) + + cells = cells.flatten().astype(int) + self._grid = pv.UnstructuredGrid(cells, cell_types, self._nodal_coords.copy()) + + self._grid.cell_data["plastic_strain"] = np.zeros(num_elem) + + self._plotter = pv.Plotter() + self._mesh_actor = self._plotter.add_mesh( + self._grid, + scalars="plastic_strain", + cmap="coolwarm", + show_edges=True + ) + self._plotter.show(interactive_update=True) + + def update_plot(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + disp_field = UUR[:, :-1] + if self._def_type == DefType.PLANE_STRESS: + disp_field = np.append(disp_field, np.zeros((len(self._eq_num), 1)), axis=1) + pressure_field = UUR[:, -1] + + scale = 5.0 + deformed_coords = self._nodal_coords + scale * disp_field + self._grid.points = deformed_coords + + equiv_plastic_strain = self.average_plastic_strain().flatten() + self._grid.cell_data["plastic_strain"] = equiv_plastic_strain + + # updating the colorbar scaling + max = np.max(equiv_plastic_strain) + min = np.min(equiv_plastic_strain) + self._mesh_actor.mapper.scalar_range = (min, max) + + # update plot + self._plotter.update() + self._plotter.render() + + # delay + time.sleep(0.05) + + # newton solve for local state variables + def _local_resid_newton(self, u, u_prev, params, xi_0, xi_prev, elem_points): + max_iters = 20 + tol = 1e-10 + init_state = (xi_0, xi_prev, params, u, u_prev, elem_points, 0, max_iters, tol) + final_state = while_loop(self._newton_cond_fun, self._newton_body_fun, init_state) + return final_state[0] + + def _newton_cond_fun(self, state): + xi, xi_prev, params, u, u_prev, elem_points, iter_count, max_iters, tol = state + C = self._local_resid(u, u_prev, params, xi, xi_prev, elem_points) + return jnp.logical_and(jnp.linalg.norm(C) > tol, iter_count < max_iters) + + def _newton_body_fun(self, state): + xi, xi_prev, params, u, u_prev, elem_points, iter_count, max_iters, tol = state + C = self._local_resid(u, u_prev, params, xi, xi_prev, elem_points) + DC = self._local_jac[GlobalDerivType.DXI](u, u_prev, params, xi, xi_prev, elem_points) + xi_new = xi - jnp.linalg.solve(DC, C) + return xi_new, xi_prev, params, u, u_prev, elem_points, iter_count + 1, max_iters, tol + + def _variables(self): + return self._u_elem, self._u_elem_prev, self._params.values, \ + self._xi_elem, self._xi_elem_prev, self._elem_points + + # functions for Halley's method + ######################################### + + def set_newton_increment(self, delta_F): + delta_UR = assemble_global_fields(self._eq_num, delta_F, np.zeros(self._num_pres_dof)) + self._delta_elem = delta_UR[self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + def _compute_halley( + self, u, u_prev, params, xi, xi_prev, elem_points, + dc_dxi_inv, dxi_du, dR_dxi, delta): + + variables = u, u_prev, params, xi, xi_prev, elem_points + d2C_du2 = self._local_hessian[GlobalDerivType.DU_DU](*variables) + d2C_dxi2 = self._local_hessian[GlobalDerivType.DXI_DXI](*variables) + d2C_du_dxi = self._local_hessian[GlobalDerivType.DU_DXI](*variables) + + d2R_du2 = self._global_hessian[GlobalDerivType.DU_DU](*variables) + d2R_dxi2 = self._global_hessian[GlobalDerivType.DXI_DXI](*variables) + d2R_du_dxi = self._global_hessian[GlobalDerivType.DU_DXI](*variables) + + v_n = dxi_du @ delta + a_j_v_n = jnp.outer(delta, v_n) + a_j_a_k = jnp.outer(delta, delta) + v_n_v_m = jnp.outer(v_n, v_n) + + # compute d2xi_du2-vector contraction + cont_axes = ([1, 2], [0, 1]) + rhs = 2 * jnp.tensordot(d2C_du_dxi, a_j_v_n, axes=cont_axes) \ + + jnp.tensordot(d2C_dxi2, v_n_v_m, axes=cont_axes) \ + + jnp.tensordot(d2C_du2, a_j_a_k, axes=cont_axes) + d2xi_du2_a_a = -dc_dxi_inv @ rhs + + # compute hessian-vector contraction + d2R_dU2_a_a = 2 * jnp.tensordot(d2R_du_dxi, a_j_v_n, axes=cont_axes) \ + + jnp.tensordot(d2R_dxi2, v_n_v_m, axes=cont_axes) \ + + jnp.tensordot(d2R_du2, a_j_a_k, axes=cont_axes) \ + + dR_dxi @ d2xi_du2_a_a + + return d2R_dU2_a_a + + def _compute_halley_fd(self, u, u_prev, params, xi, xi_prev, elem_points, delta): + h = 1.e-3 + R_ref = self._global_resid(u, u_prev, params, xi, xi_prev, elem_points) + + u_plus = u + h * delta + xi_plus = self._local_resid_newton(u_plus, u_prev, params, xi_prev, xi_prev, elem_points) + R_plus = self._global_resid(u_plus, u_prev, params, xi_plus, xi_prev, elem_points) + + u_minus = u - h * delta + xi_minus = self._local_resid_newton(u_minus, u_prev, params, xi_prev, xi_prev, elem_points) + R_minus = self._global_resid(u_minus, u_prev, params, xi_minus, xi_prev, elem_points) + + return (R_plus - 2 * R_ref + R_minus) / h ** 2 + + def evaluate_halley_correction(self): + variables = self._halley_variables() + halley_batch = np.asarray(self._batch_halley_correction(*variables)) + + halley_global = np.zeros(self._num_free_dof) + np.add.at(halley_global, self._global_free_indices_vector, + halley_batch.ravel()[self._mask_vector]) + + return halley_global + + def evaluate_halley_correction_multi(self, num_batches): + variables = self._halley_variables_multi(num_batches) + + halley_batch_all = np.empty_like(self._R) + pos = 0 + for variable in variables: + batch = np.asarray(self._batch_halley_correction(*variable)) + batch_size = batch.shape[0] + halley_batch_all[pos:pos + batch_size] = batch + pos += batch_size + + halley_global = np.zeros(self._num_free_dof) + np.add.at(halley_global, self._global_free_indices_vector, + halley_batch_all.ravel()[self._mask_vector]) + + return halley_global + + def evaluate_halley_correction_fd(self): + variables = self._halley_variables_fd() + halley_batch = np.asarray(self._batch_halley_correction_fd(*variables)) + halley_global = np.zeros(self._num_free_dof) + np.add.at(halley_global, self._global_free_indices_vector, + halley_batch.ravel()[self._mask_vector]) + + return halley_global + + def _halley_variables(self): + return self._u_elem, self._u_elem_prev, self._params.values, \ + self._xi_elem, self._xi_elem_prev, self._elem_points, \ + self._dc_dxi_inv, self._dxi_du, self._dR_dxi, self._delta_elem + + def _halley_variables_multi(self, num_batches): + vars = ( + self._u_elem, + self._u_elem_prev, + self._params.values, + self._xi_elem, + self._xi_elem_prev, + self._elem_points, + self._dc_dxi_inv, + self._dxi_du, + self._dR_dxi, + self._delta_elem + ) + + vars_split = [] + for i, var in enumerate(vars): + if i == 2: # index of self._params.values + vars_split.append([var] * num_batches) + else: + vars_split.append(np.array_split(var, num_batches)) + + result = list(zip(*vars_split)) + return result + + def _halley_variables_fd(self): + return self._u_elem, self._u_elem_prev, self._params.values, \ + self._xi_elem, self._xi_elem_prev, self._elem_points, \ + self._delta_elem + + + + + + + + + + + + + + + + + + + + diff --git a/cmad/fem_utils/global_residuals/global_residual_thermo.py b/cmad/fem_utils/global_residuals/global_residual_thermo.py new file mode 100755 index 0000000..3640a2e --- /dev/null +++ b/cmad/fem_utils/global_residuals/global_residual_thermo.py @@ -0,0 +1,188 @@ +import numpy as np +from jax import jit, vmap, jacfwd + +from cmad.fem_utils.models.global_deriv_types import GlobalDerivType + +from abc import ABC +from cmad.fem_utils.utils.fem_utils import assemble_global_fields +from scipy.sparse import coo_matrix, csr_matrix + +class Global_residual_thermo(ABC): + def __init__( + self, resid_fun, elem_surf_heat_flux, volume_conn, elem_points, + eq_num, params, num_nodes_elem, num_nodes_surf, num_free_dof, disp_node, + disp_val, num_pres_dof, pres_surf_flux_points, pres_surf_flux, + init_temp, dt): + + mapped_indicies = [0, 0, None, 0] + self._global_resid = jit(vmap(resid_fun, in_axes=mapped_indicies)) + self._global_jac = jit(vmap(jacfwd(resid_fun), in_axes=mapped_indicies)) + + self._surf_flux_batch = jit(vmap(elem_surf_heat_flux, in_axes=[0, 0, None])) + self._surf_flux_jac = jit(vmap(jacfwd(elem_surf_heat_flux), in_axes=[0, 0, None])) + + self._deriv_mode = GlobalDerivType.DNONE + + self._num_free_dof = num_free_dof + self._volume_conn = volume_conn + self._elem_points = elem_points + self._params = params + self._eq_num = eq_num + self._init_temp = init_temp + self._dt = dt + + # free dofs + self._UF = np.zeros(num_free_dof) + self._UF_prev = np.zeros(num_free_dof) + + # prescribed dofs + self._UP = np.zeros(num_pres_dof) + self._UP_prev = np.zeros(num_pres_dof) + + # dirichlet temperature boundary conditions + self._disp_node = disp_node + self._disp_val = disp_val + self._num_pres_dof = num_pres_dof + + # surf heat flux boundary conditions + self._pres_surf_flux_points = pres_surf_flux_points + self._pres_surf_flux = pres_surf_flux + + # indices for element vector assembly + global_indices = eq_num[volume_conn] + global_free_indices = np.where(global_indices > 0, global_indices - 1, -1) + flat_global_free_indices = global_free_indices.ravel() + mask_vector = flat_global_free_indices >= 0 + global_free_indices_vector = flat_global_free_indices[mask_vector] + + self._mask_vector = mask_vector + self._global_free_indices_vector = global_free_indices_vector + + # indices for element matrix assembly + elem_dofs = num_nodes_elem + ii, jj = np.meshgrid(np.arange(elem_dofs), + np.arange(elem_dofs), + indexing='ij') + row_f = global_free_indices[:, ii] + col_f = global_free_indices[:, jj] + mask_f = (row_f >= 0) & (col_f >= 0) + row_f = row_f[mask_f] + col_f = col_f[mask_f] + + self._ii = ii + self._jj = jj + self._row_f = row_f + self._col_f = col_f + self._mask_f = mask_f + + # index arrays for heat flux bc vector and jacobian assembly + if not pres_surf_flux is None: + # indices for vector assembly + surf_flux_global_indices_all = eq_num[pres_surf_flux] + surf_flux_global_free_indices = np.where(surf_flux_global_indices_all > 0, + surf_flux_global_indices_all - 1, -1) + flat_surf_flux_global_free_indices = surf_flux_global_free_indices.ravel() + self._surf_flux_valid_free_mask = flat_surf_flux_global_free_indices >= 0 + self._surf_flux_global_indices = flat_surf_flux_global_free_indices[self._surf_flux_valid_free_mask] + + # indices for jacobian assembly + ii_flux, jj_flux = np.meshgrid(np.arange(num_nodes_surf), + np.arange(num_nodes_surf), + indexing='ij') + row_f_flux = surf_flux_global_free_indices[:, ii_flux] + col_f_flux = surf_flux_global_free_indices[:, jj_flux] + mask_f_flux = (row_f_flux >= 0) & (col_f_flux >= 0) + row_f_flux = row_f_flux[mask_f_flux] + col_f_flux = col_f_flux[mask_f_flux] + + self._ii_flux = ii_flux + self._jj_flux = jj_flux + self._row_f_flux = row_f_flux + self._col_f_flux = col_f_flux + self._mask_f_flux = mask_f_flux + + # data storage + self._point_data = [] + + def initialize_variables(self): + for i, temp in enumerate(self._init_temp): + eqn_number = self._eq_num[i] + if eqn_number > 0: + self._UF_prev[eqn_number - 1] = temp + self._UF = self._UF_prev.copy() + + for i, node in enumerate(self._disp_node): + eqn_number = -self._eq_num[node] + pres_val = self._disp_val[i, 0] + self._UP_prev[eqn_number - 1] = pres_val + + def set_prescribed_dofs(self, step): + for i, node in enumerate(self._disp_node): + eqn_number = -self._eq_num[node] + pres_val = self._disp_val[i, step] + self._UP[eqn_number - 1] = pres_val + + def set_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + UUR_prev = assemble_global_fields(self._eq_num, self._UF_prev, self._UP_prev) + self._u_elem = UUR[self._volume_conn] + self._u_elem_prev = UUR_prev[self._volume_conn] + self._surf_theta = UUR[self._pres_surf_flux] + + def _variables(self): + return self._u_elem, self._u_elem_prev, self._params, self._elem_points + + def evaluate(self): + variables = self._variables() + if self._deriv_mode == GlobalDerivType.DNONE: + self._R = np.asarray(self._global_resid(*variables)) + if not self._pres_surf_flux is None: + surf_vars = self._surf_theta, self._pres_surf_flux_points, self._params + self._Q = np.asarray(self._surf_flux_batch(*surf_vars)) + + elif self._deriv_mode == GlobalDerivType.DU: + self._Jac_R = np.asarray(self._global_jac(*variables)) + if not self._pres_surf_flux is None: + surf_vars = self._surf_theta, self._pres_surf_flux_points, self._params + self._Jac_Q = np.asarray(self._surf_flux_jac(*surf_vars)) + + def scatter_rhs(self): + RF_global = np.zeros(self._num_free_dof) + np.add.at(RF_global, self._global_free_indices_vector, + self._R.ravel()[self._mask_vector]) + if not self._pres_surf_flux is None: + np.add.at(RF_global, self._surf_flux_global_indices, + self._Q.ravel()[self._surf_flux_valid_free_mask]) + return RF_global + + def scatter_lhs(self): + KFF = coo_matrix((self._Jac_R[:, self._ii, self._jj][self._mask_f], + (self._row_f, self._col_f)), + shape=(self._num_free_dof, self._num_free_dof)) + + if not self._pres_surf_flux is None: + KFF = KFF + coo_matrix((self._Jac_Q[:, self._ii_flux, self._jj_flux][self._mask_f_flux], + (self._row_f_flux, self._col_f_flux)), + shape=(self._num_free_dof, self._num_free_dof)) + + return csr_matrix(KFF) + + def add_to_UF(self, delta): + self._UF += delta + + def advance_model(self): + self._UF_prev = self._UF.copy() + self._UP_prev = self._UP.copy() + + def save_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + self._point_data.append({'temperature_field': UUR}) + + def get_data(self): + return self._point_data + + def seed_none(self): + self._deriv_mode = GlobalDerivType.DNONE + + def seed_U(self): + self._deriv_mode = GlobalDerivType.DU \ No newline at end of file diff --git a/cmad/fem_utils/global_residuals/global_residual_thermomech.py b/cmad/fem_utils/global_residuals/global_residual_thermomech.py new file mode 100755 index 0000000..746b5f9 --- /dev/null +++ b/cmad/fem_utils/global_residuals/global_residual_thermomech.py @@ -0,0 +1,257 @@ +import numpy as np +from jax import jit, vmap, jacfwd + +from cmad.fem_utils.models.global_deriv_types import GlobalDerivType + +from abc import ABC +from cmad.fem_utils.utils.fem_utils import assemble_global_fields, assemble_prescribed + +from scipy.sparse import coo_matrix, csc_matrix + +class Global_residual_thermomech(ABC): + def __init__( + self, resid_fun, elem_surf_traction, elem_surf_heat_flux, volume_conn, elem_points, + eq_num, params, num_nodes_elem, num_nodes_surf, dof_node, num_free_dof, disp_node, + disp_val, num_pres_dof, pres_surf_traction_points, pres_surf_traction, + surf_traction_vector, pres_surf_flux_points, pres_surf_flux, init_temp, dt): + + mapped_indicies = [0, 0, 0, 0, None, 0, 0] + self._global_resid = jit(vmap(resid_fun, in_axes=mapped_indicies)) + self._global_jac = jit(vmap(jacfwd(resid_fun), in_axes=mapped_indicies)) + + self._surf_traction_batch = jit(vmap(elem_surf_traction, in_axes=[0, None])) + + self._surf_flux_batch = jit(vmap(elem_surf_heat_flux, in_axes=[0, 0, None])) + self._surf_flux_jacobian = jit(vmap(jacfwd(elem_surf_heat_flux), in_axes=[0, 0, None])) + + self._deriv_mode = GlobalDerivType.DNONE + + self._num_free_dof = num_free_dof + self._volume_conn = volume_conn + self._elem_points = elem_points + self._params = params + self._eq_num = eq_num + self._init_temp = init_temp + self._dt = dt + + # free dofs + self._UF = np.zeros(num_free_dof) + self._UF_prev = np.zeros(num_free_dof) + + # intermediate variables + self._aF_prev = np.zeros(num_free_dof) + self._vF_prev = np.zeros(num_free_dof) + + # prescribed dofs + self._UP = np.zeros(num_pres_dof) + self._UP_prev = np.zeros(num_pres_dof) + self._aP_prev = np.zeros(num_pres_dof) + self._vP_prev = np.zeros(num_pres_dof) + + # dirichlet displacement and temperature boundary conditions + self._disp_node = disp_node + self._disp_val = disp_val + self._num_pres_dof = num_pres_dof + + # traction boundary conditions + self._pres_surf_traction_points = pres_surf_traction_points + self._surf_traction_vector = surf_traction_vector + self._pres_surf_traction = pres_surf_traction + + # surf heat flux boundary conditions + self._pres_surf_flux_points = pres_surf_flux_points + self._pres_surf_flux = pres_surf_flux + + # indices for element vector assembly + global_indices = eq_num[volume_conn, :].transpose(0, 2, 1).reshape(volume_conn.shape[0], -1) + global_free_indices = np.where(global_indices > 0, global_indices - 1, -1) + flat_global_free_indices = global_free_indices.ravel() + mask_vector = flat_global_free_indices >= 0 + global_free_indices_vector = flat_global_free_indices[mask_vector] + + self._mask_vector = mask_vector + self._global_free_indices_vector = global_free_indices_vector + + # indices for element matrix assembly + elem_dofs = num_nodes_elem * dof_node + ii, jj = np.meshgrid(np.arange(elem_dofs), np.arange(elem_dofs), + indexing='ij') + row_f = global_free_indices[:, ii] + col_f = global_free_indices[:, jj] + mask_f = (row_f >= 0) & (col_f >= 0) + row_f = row_f[mask_f] + col_f = col_f[mask_f] + + self._ii = ii + self._jj = jj + self._row_f = row_f + self._col_f = col_f + self._mask_f = mask_f + + # index arrays for traction vector assembly + if not pres_surf_traction is None: + surf_global_indices_all = eq_num[:, :-1][pres_surf_traction, :]. \ + transpose(0, 2, 1).reshape(pres_surf_traction.shape[0], -1) + flat_surf_global_free_indices = np.where(surf_global_indices_all > 0, + surf_global_indices_all - 1, -1).ravel() + self._surf_valid_free_mask = flat_surf_global_free_indices >= 0 + self._surf_global_indices = flat_surf_global_free_indices[self._surf_valid_free_mask] + + # index arrays for heat flux bc vector and jacobian assembly + if not pres_surf_flux is None: + # indices for vector assembly + surf_flux_global_indices_all = eq_num[:, -1][pres_surf_flux] + surf_flux_global_free_indices = np.where(surf_flux_global_indices_all > 0, + surf_flux_global_indices_all - 1, -1) + flat_surf_flux_global_free_indices = surf_flux_global_free_indices.ravel() + self._surf_flux_valid_free_mask = flat_surf_flux_global_free_indices >= 0 + self._surf_flux_global_indices = flat_surf_flux_global_free_indices[self._surf_flux_valid_free_mask] + + # indices for jacobian assembly + ii_flux, jj_flux = np.meshgrid(np.arange(num_nodes_surf), + np.arange(num_nodes_surf), + indexing='ij') + row_f_flux = surf_flux_global_free_indices[:, ii_flux] + col_f_flux = surf_flux_global_free_indices[:, jj_flux] + mask_f_flux = (row_f_flux >= 0) & (col_f_flux >= 0) + row_f_flux = row_f_flux[mask_f_flux] + col_f_flux = col_f_flux[mask_f_flux] + + self._ii_flux = ii_flux + self._jj_flux = jj_flux + self._row_f_flux = row_f_flux + self._col_f_flux = col_f_flux + self._mask_f_flux = mask_f_flux + + # data storage + self._point_data = [] + + def initialize_variables(self): + for i, temp in enumerate(self._init_temp): + eqn_number = self._eq_num[i][-1] + if eqn_number > 0: + self._UF_prev[eqn_number - 1] = temp + self._UF = self._UF_prev.copy() + self._UP_prev = assemble_prescribed(self._num_pres_dof, self._disp_node, + self._disp_val[:, 0], self._eq_num) + self._elem_init_temp = self._init_temp[self._volume_conn] + + def set_prescribed_dofs(self, step): + self._UP = assemble_prescribed(self._num_pres_dof, self._disp_node, + self._disp_val[:, step], self._eq_num) + + def set_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + UUR_prev = assemble_global_fields(self._eq_num, self._UF_prev, self._UP_prev) + self._u_elem = UUR[self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + self._u_elem_prev = UUR_prev[self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + aUR_prev = assemble_global_fields(self._eq_num, self._aF_prev, self._aP_prev) + self._a_elem_prev = aUR_prev[:, :-1][self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + vUR_prev = assemble_global_fields(self._eq_num, self._vF_prev, self._vP_prev) + self._v_elem_prev = vUR_prev[:, :-1][self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + self._surf_theta = UUR[:, -1][self._pres_surf_flux] + + def _variables(self): + return self._u_elem, self._u_elem_prev, self._v_elem_prev, \ + self._a_elem_prev, self._params, self._elem_points, self._elem_init_temp + + def evaluate(self): + variables = self._variables() + if self._deriv_mode == GlobalDerivType.DNONE: + self._R = np.asarray(self._global_resid(*variables)) + if not self._pres_surf_flux is None: + surf_vars = self._surf_theta, self._pres_surf_flux_points, self._params + self._Q = np.asarray(self._surf_flux_batch(*surf_vars)) + + elif self._deriv_mode == GlobalDerivType.DU: + self._Jac_R = np.asarray(self._global_jac(*variables)) + if not self._pres_surf_flux is None: + surf_vars = self._surf_theta, self._pres_surf_flux_points, self._params + self._Jac_Q = np.asarray(self._surf_flux_jacobian(*surf_vars)) + + def scatter_rhs(self): + RF_global = np.zeros(self._num_free_dof) + np.add.at(RF_global, self._global_free_indices_vector, + self._R.ravel()[self._mask_vector]) + if not self._pres_surf_flux is None: + np.add.at(RF_global, self._surf_flux_global_indices, + self._Q.ravel()[self._surf_flux_valid_free_mask]) + return RF_global - self._FF + + def scatter_lhs(self): + KFF = coo_matrix((self._Jac_R[:, self._ii, self._jj][self._mask_f], + (self._row_f, self._col_f)), + shape=(self._num_free_dof, self._num_free_dof)) + + if not self._pres_surf_flux is None: + KFF = KFF + coo_matrix((self._Jac_Q[:, self._ii_flux, self._jj_flux][self._mask_f_flux], + (self._row_f_flux, self._col_f_flux)), + shape=(self._num_free_dof, self._num_free_dof)) + + return csc_matrix(KFF) + + def compute_surf_tractions(self, step): + self._FF = np.zeros(self._num_free_dof) + if not self._pres_surf_traction is None: + FEL = self._surf_traction_batch(self._pres_surf_traction_points, + self._surf_traction_vector[step]) + np.add.at(self._FF, self._surf_global_indices, + FEL.ravel()[self._surf_valid_free_mask]) + + def add_to_UF(self, delta): + self._UF += delta + + def advance_model(self): + dt = self._dt + + aF_new = 4 / dt ** 2 * (self._UF - self._UF_prev - self._vF_prev * dt) - self._aF_prev + vF_new = self._vF_prev + 1 / 2 * (self._aF_prev + aF_new) * dt + + self._UF_prev = self._UF.copy() + self._aF_prev = aF_new.copy() + self._vF_prev = vF_new.copy() + + aP_new = 4 / dt ** 2 * (self._UP - self._UP_prev - self._vP_prev * dt) - self._aP_prev + vP_new = self._vP_prev + 1 / 2 * (self._aP_prev + aP_new) * dt + + self._UP_prev = self._UP.copy() + self._aP_prev = aP_new.copy() + self._vP_prev = vP_new.copy() + + def save_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + disp_field = UUR[:, :-1] + temperature_field = UUR[:, -1] + + self._point_data.append({'displacement_field': disp_field, + 'temperature_field': temperature_field}) + def get_data(self): + return self._point_data + + def seed_none(self): + self._deriv_mode = GlobalDerivType.DNONE + + def seed_U(self): + self._deriv_mode = GlobalDerivType.DU + + + + + + + + + + + + + + diff --git a/cmad/fem_utils/global_residuals/global_residual_thermoplastic.py b/cmad/fem_utils/global_residuals/global_residual_thermoplastic.py new file mode 100644 index 0000000..aeb2bbb --- /dev/null +++ b/cmad/fem_utils/global_residuals/global_residual_thermoplastic.py @@ -0,0 +1,363 @@ +import numpy as np +from jax import jit, vmap, jacfwd +import jax.numpy as jnp + +from cmad.fem_utils.models.global_deriv_types import GlobalDerivType + +from abc import ABC +from cmad.fem_utils.utils.fem_utils import assemble_global_fields, assemble_prescribed + +from scipy.sparse import coo_matrix, csc_matrix +from jax.lax import while_loop + +class Global_residual_thermoplastic(ABC): + def __init__( + self, global_resid_fun, local_resid_fun, elem_surf_traction, elem_surf_heat_flux, + volume_conn, elem_points, eq_num, params, num_nodes_elem, num_nodes_surf, dof_node, + num_quad_pts, num_free_dof, disp_node, disp_val, init_xi ,num_pres_dof, + num_elem, pres_surf_traction_points, pres_surf_traction, surf_traction_vector, + pres_surf_flux_points, pres_surf_flux, init_temp, dt): + + mapped_axes = [0, 0, None, 0, 0, 0] + self._global_resid = jit(global_resid_fun) + self._global_jac = [jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU)), + jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU_prev)), + jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DParams)), + jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DXI)), + jit(jacfwd(global_resid_fun, argnums=GlobalDerivType.DXI_prev))] + + self._global_resid_batch = jit(vmap(global_resid_fun, in_axes=mapped_axes)) + self._global_jac_batch = [jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU), + in_axes=mapped_axes)), + jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DU_prev), + in_axes=mapped_axes)), + jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DParams), + in_axes=mapped_axes)), + jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DXI), + in_axes=mapped_axes)), + jit(vmap(jacfwd(global_resid_fun, argnums=GlobalDerivType.DXI_prev), + in_axes=mapped_axes))] + + self._local_resid = jit(local_resid_fun) + self._local_jac = [jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU)), + jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU_prev)), + jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DParams)), + jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DXI)), + jit(jacfwd(local_resid_fun, argnums=GlobalDerivType.DXI_prev))] + + self._local_resid_batch = jit(vmap(local_resid_fun, in_axes=mapped_axes)) + self._local_jac_batch = [jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU), + in_axes=mapped_axes)), + jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DU_prev), + in_axes=mapped_axes)), + jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DParams), + in_axes=mapped_axes)), + jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DXI), + in_axes=mapped_axes)), + jit(vmap(jacfwd(local_resid_fun, argnums=GlobalDerivType.DXI_prev), + in_axes=mapped_axes))] + + self._batch_local_newton = jit(vmap(self._local_resid_newton, in_axes=mapped_axes)) + self._batch_tang = jit(vmap(self._compute_tang, in_axes=mapped_axes)) + + self._surf_traction_batch = jit(vmap(elem_surf_traction, in_axes=[0, None])) + + self._surf_flux_batch = jit(vmap(elem_surf_heat_flux, in_axes=[0, 0, None])) + self._surf_flux_jacobian = jit(vmap(jacfwd(elem_surf_heat_flux), in_axes=[0, 0, None])) + + self._deriv_mode = GlobalDerivType.DNONE + + self._num_free_dof = num_free_dof + self._volume_conn = volume_conn + self._elem_points = elem_points + self._params = params + self._eq_num = eq_num + self._num_quad_pts = num_quad_pts + self._init_temp = init_temp + self._dt = dt + self._init_xi = init_xi + self._num_elem = num_elem + + # dirichlet displacement and temperature boundary conditions + self._disp_node = disp_node + self._disp_val = disp_val + self._num_pres_dof = num_pres_dof + + # traction boundary conditions + self._pres_surf_traction_points = pres_surf_traction_points + self._surf_traction_vector = surf_traction_vector + self._pres_surf_traction = pres_surf_traction + + # surf heat flux boundary conditions + self._pres_surf_flux_points = pres_surf_flux_points + self._pres_surf_flux = pres_surf_flux + + # indices for element vector assembly + global_indices = eq_num[volume_conn, :].transpose(0, 2, 1).reshape(volume_conn.shape[0], -1) + global_free_indices = np.where(global_indices > 0, global_indices - 1, -1) + flat_global_free_indices = global_free_indices.ravel() + mask_vector = flat_global_free_indices >= 0 + global_free_indices_vector = flat_global_free_indices[mask_vector] + + self._mask_vector = mask_vector + self._global_free_indices_vector = global_free_indices_vector + + # indices for element matrix assembly + elem_dofs = num_nodes_elem * dof_node + ii, jj = np.meshgrid(np.arange(elem_dofs), np.arange(elem_dofs), + indexing='ij') + row_f = global_free_indices[:, ii] + col_f = global_free_indices[:, jj] + mask_f = (row_f >= 0) & (col_f >= 0) + row_f = row_f[mask_f] + col_f = col_f[mask_f] + + self._ii = ii + self._jj = jj + self._row_f = row_f + self._col_f = col_f + self._mask_f = mask_f + + # index arrays for traction vector assembly + if not pres_surf_traction is None: + surf_global_indices_all = eq_num[:, :-2][pres_surf_traction, :]. \ + transpose(0, 2, 1).reshape(pres_surf_traction.shape[0], -1) + flat_surf_global_free_indices = np.where(surf_global_indices_all > 0, + surf_global_indices_all - 1, -1).ravel() + self._surf_valid_free_mask = flat_surf_global_free_indices >= 0 + self._surf_global_indices = flat_surf_global_free_indices[self._surf_valid_free_mask] + + # index arrays for heat flux bc vector and jacobian assembly + if not pres_surf_flux is None: + # indices for vector assembly + surf_flux_global_indices_all = eq_num[:, -2][pres_surf_flux] + surf_flux_global_free_indices = np.where(surf_flux_global_indices_all > 0, + surf_flux_global_indices_all - 1, -1) + flat_surf_flux_global_free_indices = surf_flux_global_free_indices.ravel() + self._surf_flux_valid_free_mask = flat_surf_flux_global_free_indices >= 0 + self._surf_flux_global_indices = flat_surf_flux_global_free_indices[self._surf_flux_valid_free_mask] + + # indices for jacobian assembly + ii_flux, jj_flux = np.meshgrid(np.arange(num_nodes_surf), + np.arange(num_nodes_surf), + indexing='ij') + row_f_flux = surf_flux_global_free_indices[:, ii_flux] + col_f_flux = surf_flux_global_free_indices[:, jj_flux] + mask_f_flux = (row_f_flux >= 0) & (col_f_flux >= 0) + row_f_flux = row_f_flux[mask_f_flux] + col_f_flux = col_f_flux[mask_f_flux] + + self._ii_flux = ii_flux + self._jj_flux = jj_flux + self._row_f_flux = row_f_flux + self._col_f_flux = col_f_flux + self._mask_f_flux = mask_f_flux + + # data storage + self._point_data = [] + self._cell_data = [] + + def reset_xi(self): + self._xi_elem = self._xi_elem_prev.copy() + + def compute_local_state_variables(self): + variables = self._variables() + self._xi_elem = np.asarray(self._batch_local_newton(*variables)) + + def evaluate_tang(self): + variables = self._variables() + tang = self._batch_tang(*variables) + self._tang = np.asarray(tang) + if not self._pres_surf_flux is None: + surf_vars = self._surf_theta, self._pres_surf_flux_points, self._params + self._Jac_Q = np.asarray(self._surf_flux_jacobian(*surf_vars)) + + def evaluate_global(self): + + variables = self._variables() + deriv_mode = self._deriv_mode + + if deriv_mode == GlobalDerivType.DNONE: + self._R = np.asarray(self._global_resid_batch(*variables)) + self._Jac_R = None + if not self._pres_surf_flux is None: + surf_vars = self._surf_theta, self._pres_surf_flux_points, self._params.values + self._Q = np.asarray(self._surf_flux_batch(*surf_vars)) + else: + self._Jac_R = np.asarray(self._global_jac_batch[deriv_mode](*variables)) + + def evaluate_local(self): + + variables = self._variables() + deriv_mode = self._deriv_mode + + if deriv_mode == GlobalDerivType.DNONE: + self._C = np.asarray(self._local_resid_batch(*variables)) + self._Jac_C = None + else: + self._Jac_C = np.asarray(self._local_jac_batch[deriv_mode](*variables)) + + def initialize_variables(self): + num_free_dof = self._num_free_dof + num_pres_dof = self._num_pres_dof + + # free dofs + self._UF = np.zeros(num_free_dof) + self._UF_prev = np.zeros(num_free_dof) + + # prescribed dofs + self._UP = np.zeros(num_pres_dof) + self._UP_prev = np.zeros(num_pres_dof) + + # local state variables + self._xi_elem_prev = np.tile(self._init_xi, (self._num_elem, 1)) + self._xi_elem = self._xi_elem_prev.copy() + + # initialize temperatures + for i, temp in enumerate(self._init_temp): + eqn_number = self._eq_num[i][-2] + if eqn_number > 0: + self._UF_prev[eqn_number - 1] = temp + else : + self._UP_prev[-eqn_number - 1] = temp + self._UF = self._UF_prev.copy() + self._elem_init_temp = self._init_temp[self._volume_conn] + + def set_prescribed_dofs(self, step): + self._UP = assemble_prescribed(self._num_pres_dof, self._disp_node, + self._disp_val[:, step], self._eq_num) + + def set_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + UUR_prev = assemble_global_fields(self._eq_num, self._UF_prev, self._UP_prev) + self._u_elem = UUR[self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + self._u_elem_prev = UUR_prev[self._volume_conn, :].transpose(0, 2, 1) \ + .reshape(self._volume_conn.shape[0], -1) + + self._surf_theta = UUR[:, -2][self._pres_surf_flux] + + def scatter_rhs(self): + RF_global = np.zeros(self._num_free_dof) + np.add.at(RF_global, self._global_free_indices_vector, + self._R.ravel()[self._mask_vector]) + if not self._pres_surf_flux is None: + np.add.at(RF_global, self._surf_flux_global_indices, + self._Q.ravel()[self._surf_flux_valid_free_mask]) + return RF_global - self._FF + + def scatter_lhs(self): + KFF = coo_matrix((self._tang[:, self._ii, self._jj][self._mask_f], + (self._row_f, self._col_f)), + shape=(self._num_free_dof, self._num_free_dof)) + + if not self._pres_surf_flux is None: + KFF = KFF + coo_matrix((self._Jac_Q[:, self._ii_flux, self._jj_flux][self._mask_f_flux], + (self._row_f_flux, self._col_f_flux)), + shape=(self._num_free_dof, self._num_free_dof)) + + return csc_matrix(KFF) + + def compute_surf_tractions(self, step): + self._FF = np.zeros(self._num_free_dof) + if not self._pres_surf_traction is None: + FEL = self._surf_traction_batch(self._pres_surf_traction_points, + self._surf_traction_vector[step]) + np.add.at(self._FF, self._surf_global_indices, + FEL.ravel()[self._surf_valid_free_mask]) + + def add_to_UF(self, delta): + self._UF += delta + + def set_UF(self, UF): + self._UF = UF.copy() + + def get_UF(self): + return self._UF.copy() + + def advance_model(self): + self._UF_prev = self._UF.copy() + self._UP_prev = self._UP.copy() + self._xi_elem_prev = self._xi_elem.copy() + + def save_global_fields(self): + UUR = assemble_global_fields(self._eq_num, self._UF, self._UP) + disp_field = UUR[:, :-2] + pressure_field = UUR[:, -1] + temperature_field = UUR[:, -2] + + self._point_data.append({'displacement_field': disp_field, + 'temperature_field': temperature_field, + 'pressure_field': pressure_field }) + + plastic_strain = self.average_plastic_strain() + self._cell_data.append({'eq_plastic_strain': plastic_strain}) + + def get_data(self): + return self._point_data, self._cell_data + + def seed_none(self): + self._deriv_mode = GlobalDerivType.DNONE + + def seed_U(self): + self._deriv_mode = GlobalDerivType.DU + + def average_plastic_strain(self): + return np.mean(self._xi_elem[:, -self._num_quad_pts:], axis=1).reshape(-1, 1) + + def _compute_tang(self, u, u_prev, params, xi, xi_prev, elem_points): + variables = u, u_prev, params, xi, xi_prev, elem_points + dc_du = self._local_jac[GlobalDerivType.DU](*variables) + dc_dxi = self._local_jac[GlobalDerivType.DXI](*variables) + dc_dxi_inv = jnp.linalg.inv(dc_dxi) + dxi_du = dc_dxi_inv @ -dc_du + + dR_du = self._global_jac[GlobalDerivType.DU](*variables) + dR_dxi = self._global_jac[GlobalDerivType.DXI](*variables) + return dR_du + dR_dxi @ dxi_du + + def _variables(self): + return self._u_elem, self._u_elem_prev, self._params.values, self._xi_elem, \ + self._xi_elem_prev, self._elem_points + + def C(self): + return self._C + + def R(self): + return self._R + + # newton solve for local state variables + def _local_resid_newton( + self, u, u_prev, params, xi_0, xi_prev, elem_points): + max_iters = 20 + tol = 1e-10 + init_state = (xi_0, xi_prev, params, u, u_prev, elem_points, 0, max_iters, tol) + final_state = while_loop(self._newton_cond_fun, self._newton_body_fun, init_state) + return final_state[0] + + def _newton_cond_fun(self, state): + xi, xi_prev, params, u, u_prev, elem_points, iter_count, max_iters, tol = state + C = self._local_resid(u, u_prev, params, xi, xi_prev, elem_points) + return jnp.logical_and(jnp.linalg.norm(C) > tol, iter_count < max_iters) + + def _newton_body_fun(self, state): + xi, xi_prev, params, u, u_prev, elem_points, iter_count, max_iters, tol = state + C = self._local_resid(u, u_prev, params, xi, xi_prev, elem_points) + DC = self._local_jac[GlobalDerivType.DXI](u, u_prev, params, xi, xi_prev, elem_points) + xi_new = xi - jnp.linalg.solve(DC, C) + return xi_new, xi_prev, params, u, u_prev, elem_points, iter_count + 1, max_iters, tol + + + + + + + + + + + + + + diff --git a/cmad/fem_utils/interpolants/interpolants.py b/cmad/fem_utils/interpolants/interpolants.py new file mode 100755 index 0000000..43b709b --- /dev/null +++ b/cmad/fem_utils/interpolants/interpolants.py @@ -0,0 +1,194 @@ +import numpy as np + +class ShapeFunctions(): + """ + + Shape functions and shape function gradients (in the parametric space). + + Attributes: + values: Values of the shape functions at a discrete set of points. + Shape is ``(num_eval_points, num_nodes_elem)``, where ``num_eval_points`` is the number of + points at which the shape functions are evaluated, and ``num_nodes_elem`` + is the number of nodes in the element (which is equal to the + number of shape functions). + gradients: Values of the parametric gradients of the shape functions. + Shape is ``(num_eval_points, dof_node, num_nodes_elem)``, where ``dof_node`` is the number + of spatial dimensions. Line elements are an exception, which + have shape ``(num_eval_points, num_nodes_elem)``. + + """ + def __init__(self, values, gradients): + self.values = values + self.gradients = gradients + +def shape1d(evaluationPoints): + """ + + Evaluate 1D shape functions and derivatives at points in the master element. + + Args: + evaluationPoints: Array of points in the master element domain at + which to evaluate the shape functions and derivatives. + + Returns: + Shape function values and shape function derivatives at ``evaluationPoints``, + in a tuple (``shape``, ``dshape``). + shapes: [num_eval_points, num_nodes_elem] + dshapes: [num_eval_points, num_nodes_elem] + + """ + + shape = np.vstack(((1 - evaluationPoints[:, 0]) / 2.0, (1 + evaluationPoints[:, 0]) / 2.0)).T + dshape = np.vstack((-0.5 * np.ones(len(evaluationPoints)), 0.5 * np.ones(len(evaluationPoints)))).T + + return ShapeFunctions(shape, dshape) + +def shape_triangle(evaluationPoints): + """ + + Evaluate triangle shape functions and derivatives at points in the master element. + + Args: + evaluationPoints: Array of points in the master element domain at + which to evaluate the shape functions and derivatives. + + Returns: + Shape function values and shape function derivatives at ``evaluationPoints``, + in a tuple (``shape``, ``dshape``). + shapes: [num_eval_points, num_nodes_elem] + dshapes: [num_eval_points, dof_node, num_nodes_elem] + + """ + num_eval_points = len(evaluationPoints) + dof_node = 2 + num_nodes_elem = 3 + + shape = np.vstack((1 - evaluationPoints[:, 0] - evaluationPoints[:, 1], + evaluationPoints[:, 0], evaluationPoints[:, 1])).T + + dshape = np.zeros((num_eval_points, dof_node, num_nodes_elem)) + + for i in range(num_eval_points): + dshape[i, :, :] = np.array([[-1, 1, 0],[-1, 0, 1]]) + + return ShapeFunctions(shape, dshape) + +def shape_quad(evaluationPoints): + """ + + Evaluate quad shape functions and derivatives at points in the master element. + + Args: + evaluationPoints: Array of points in the master element domain at + which to evaluate the shape functions and derivatives. + + Returns: + Shape function values and shape function derivatives at ``evaluationPoints``, + in a tuple (``shape``, ``dshape``). + shapes: [num_eval_points, num_nodes_elem] + dshapes: [num_eval_points, dof_node, num_nodes_elem] + + """ + + num_eval_points = len(evaluationPoints) + dof_node = 2 + num_nodes_elem = 4 + + l0x = 1 - evaluationPoints[:, 0] + l1x = 1 + evaluationPoints[:, 0] + l0y = 1 - evaluationPoints[:, 1] + l1y = 1 + evaluationPoints[:, 1] + + shape = np.vstack((l0x * l0y / 4, l1x * l0y / 4, l1x * l1y / 4, l0x * l1y / 4)).T + dshape = np.zeros((num_eval_points, dof_node, num_nodes_elem)) + + for i in range(num_eval_points): + point = evaluationPoints[i] + l0x = 1 - point[0] + l1x = 1 + point[0] + l0y = 1 - point[1] + l1y = 1 + point[1] + dshape[i, :, :] = np.array([[-l0y, l0y, l1y, -l1y],[-l0x, -l1x, l1x, l0x]]) / 4 + + return ShapeFunctions(shape, dshape) + +def shape_tetrahedron(evaluationPoints): + """ + + Evaluate tetrahedron shape functions and derivatives at points in the master element. + + Args: + evaluationPoints: Array of points in the master element domain at + which to evaluate the shape functions and derivatives. + + Returns: + Shape function values and shape function derivatives at ``evaluationPoints``, + in a tuple (``shape``, ``dshape``). + shapes: [num_eval_points, num_nodes_elem] + dshapes: [num_eval_points, dof_node, num_nodes_elem] + + """ + + num_eval_points = len(evaluationPoints) + dof_node = 3 + num_nodes_elem = 4 + + shape = np.vstack((1 - evaluationPoints[:, 0] - evaluationPoints[:, 1] - evaluationPoints[:, 2], + evaluationPoints[:, 0], evaluationPoints[:, 1], evaluationPoints[:, 2])).T + + dshape = np.zeros((num_eval_points, dof_node, num_nodes_elem)) + + for i in range(num_eval_points): + dshape[i, :, :] = np.array([[-1, 1, 0, 0], [-1, 0, 1, 0], [-1, 0, 0, 1]]) + + return ShapeFunctions(shape, dshape) + + +def shape_brick(evaluationPoints): + """ + + Evaluate brick shape functions and derivatives at points in the master element. + + Args: + evaluationPoints: Array of points in the master element domain at + which to evaluate the shape functions and derivatives. + + Returns: + Shape function values and shape function derivatives at ``evaluationPoints``, + in a tuple (``shape``, ``dshape``). + shapes: [num_eval_points, num_nodes_elem] + dshapes: [num_eval_points, dof_node, num_nodes_elem] + + """ + + num_eval_points = len(evaluationPoints) + dof_node = 3 + num_nodes_elem = 8 + + m1 = 1 - evaluationPoints[:, 0] + p1 = 1 + evaluationPoints[:, 0] + m2 = 1 - evaluationPoints[:, 1] + p2 = 1 + evaluationPoints[:, 1] + m3 = 1 - evaluationPoints[:, 2] + p3 = 1 + evaluationPoints[:, 2] + + shape = np.vstack((m1 * m2 * m3 / 8, p1 * m2 * m3 / 8, p1 * p2 * m3 / 8, m1 * p2 * m3 / 8, + m1 * m2 * p3 / 8, p1 * m2 * p3 / 8, p1 * p2 * p3 / 8, m1 * p2 * p3 / 8)).T + + dshape = np.zeros((num_eval_points, dof_node, num_nodes_elem)) + + for i in range(num_eval_points): + point = evaluationPoints[i] + m1 = 1 - point[0] + p1 = 1 + point[0] + m2 = 1 - point[1] + p2 = 1 + point[1] + m3 = 1 - point[2] + p3 = 1 + point[2] + dshape[i, :, :] = np.array([[-m2 * m3 / 8, m2 * m3 / 8, p2 * m3 / 8, -p2 * m3 / 8, -m2 * p3 / 8, m2 * p3 / 8, p2 * p3 / 8, -p2 * p3 / 8], + [-m1 * m3 / 8, -p1 * m3 / 8, p1 * m3 / 8, m1 * m3 / 8, -m1 * p3 / 8, -p1 * p3 / 8, p1 * p3 / 8, m1 * p3 / 8], + [-m1 * m2 / 8, -p1 * m2 / 8, -p1 * p2 / 8, -m1 * p2 / 8, m1 * m2 / 8, p1 * m2 / 8, p1 * p2 / 8, m1 * p2 / 8]]) + + return ShapeFunctions(shape, dshape) + + diff --git a/cmad/fem_utils/mesh/mesh.py b/cmad/fem_utils/mesh/mesh.py new file mode 100755 index 0000000..8cc3931 --- /dev/null +++ b/cmad/fem_utils/mesh/mesh.py @@ -0,0 +1,429 @@ +import meshio +import pygmsh + +class Mesh(): + + def __init__(self, mesh_type): + + self._mesh_type = mesh_type + with pygmsh.occ.Geometry() as geom: + + # square prism with hole through center + if self._mesh_type == "hole_block": + + geom.characteristic_length_min = 0.03 + geom.characteristic_length_max = 0.03 + + rectangle = geom.add_rectangle([0.0, 0.0, 0.0], 1.0, 1.0) + + disk = geom.add_disk([0.5, 0.5, 0.0], 0.25) + + flat = geom.boolean_difference( + rectangle, + disk + ) + geom.extrude(flat, [0.0, 0.0, 0.0625], num_layers=5) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "hole_block_half": + + geom.characteristic_length_min = 0.03 + geom.characteristic_length_max = 0.03 + + rectangle = geom.add_rectangle([0.0, 0.0, 0.0], 1.0, 0.5) + + disk = geom.add_disk([0.5, 0.0, 0.0], 0.25) + + flat = geom.boolean_difference( + rectangle, + disk + ) + geom.extrude(flat, [0.0, 0.0, 0.03125], num_layers=3) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "hole_block_quarter": + + geom.characteristic_length_min = 0.03 + geom.characteristic_length_max = 0.03 + + rectangle = geom.add_rectangle([0.0, 0.0, 0.0], 0.5, 0.5) + + disk = geom.add_disk([0.0, 0.0, 0.0], 0.25) + + flat = geom.boolean_difference( + rectangle, + disk + ) + geom.extrude(flat, [0.0, 0.0, 0.03125], num_layers=2) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "notched_bar": + geom.characteristic_length_min = 0.035 + geom.characteristic_length_max = 0.035 + + width = 1.0 + rectangle = geom.add_rectangle([0.0, 0.0, 0.0], 4.0, width) + + disk1 = geom.add_disk([1.0, 0.0, 0.0], 0.5) + disk2 = geom.add_disk([3.0, 0.0, 0.0], 0.5) + disk3 = geom.add_disk([2.0, width, 0.0], 0.5) + + flat = geom.boolean_difference( + rectangle, + [disk1, disk2, disk3] + ) + geom.extrude(flat, [0.0, 0.0, 0.0625], num_layers=5) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "hole_block_2D": + + geom.characteristic_length_min = 0.02 + geom.characteristic_length_max = 0.02 + + rectangle = geom.add_rectangle([0.0, 0.0, 0.0], 1.0, 0.5) + + disk = geom.add_disk([0.5, 0.0, 0.0], 0.25) + + flat = geom.boolean_difference( + rectangle, + disk + ) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[1].data + self._surface_conn = self._cells[0].data + self._mesh.cells = [self._cells[1]] + + # thin beam + if self._mesh_type == "bar": + + geom.characteristic_length_min = 0.1 + geom.characteristic_length_max = 0.1 + + rectangle = geom.add_rectangle([0.0, 0.0, 0.0], 2, 0.5) + + geom.extrude(rectangle, [0.0, 0.0, 0.5], num_layers=5) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "bar_brick": + # Define corner points of the base rectangle + p0 = geom.add_point([0.0, 0.0, 0.0]) + p1 = geom.add_point([2.0, 0.0, 0.0]) + p2 = geom.add_point([2.0, 0.5, 0.0]) + p3 = geom.add_point([0.0, 0.5, 0.0]) + + # Create lines and loop + l0 = geom.add_line(p0, p1) + l1 = geom.add_line(p1, p2) + l2 = geom.add_line(p2, p3) + l3 = geom.add_line(p3, p0) + + loop = geom.add_curve_loop([l0, l1, l2, l3]) + surface = geom.add_plane_surface(loop) + + mesh_size = 0.06 + num_nodes_length = int(2.0 / mesh_size) + 1 + num_nodes_width = int(0.5 / mesh_size) + 1 + + # Transfinite meshing for structured quad elements on base + geom.set_transfinite_curve(l0, num_nodes_length, mesh_type="Progression", coeff=1.0) + geom.set_transfinite_curve(l1, num_nodes_width, mesh_type="Progression", coeff=1.0) + geom.set_transfinite_curve(l2, num_nodes_length, mesh_type="Progression", coeff=1.0) + geom.set_transfinite_curve(l3, num_nodes_width, mesh_type="Progression", coeff=1.0) + geom.set_transfinite_surface(surface, arrangement="left", corner_pts=[p0, p1, p2, p3]) + geom.set_recombined_surfaces([surface]) # Make it quad + + # Extrude to get 3D hex mesh + geom.extrude( + surface, + [0, 0, 0.5], + num_layers=int(0.5 / mesh_size), + recombine=True, # Make it hexahedral + ) + + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "boat_fender": + + geom.characteristic_length_min = 0.03 + geom.characteristic_length_max = 0.03 + + rectangle = geom.add_rectangle([0.0, 0.0, 0.0], 0.7, 0.5) + + disk = geom.add_disk([0.35, 0.23, 0.0], 0.25, 0.125) + rect = geom.add_rectangle([0.20, 0, 0], 0.3, 0.23) + + flat = geom.boolean_difference( + rectangle, + [disk, rect] + ) + geom.extrude(flat, [0.0, 0.0, 0.5], num_layers=20) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + # 2 x 2 x 2 cube + if self._mesh_type == "cube": + + geom.characteristic_length_min = 0.2 + geom.characteristic_length_max = 0.2 + + rectangle = geom.add_rectangle([0.0, 0.0, 0.0], 2, 2) + + geom.extrude(rectangle, [0.0, 0.0, 2], num_layers=10) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "cook": + + geom.characteristic_length_min = 0.6 + geom.characteristic_length_max = 0.6 + + beam = geom.add_polygon([[0.0, 0.0], + [48., 44.], + [48., 60.], + [0.0, 44.]]) + + geom.extrude(beam, [0.0, 0.0, 1.0], num_layers=1) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "vert_beam": + + geom.characteristic_length_min = 0.15 + geom.characteristic_length_max = 0.15 + + beam = geom.add_polygon([[0.0, 0.0], + [1.0, 0.0], + [1.0, 1.0], + [0.0, 1.0]]) + + geom.extrude(beam, [0.0, 0.0, 5.0], num_layers=30) + + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "rect_prism": + + geom.characteristic_length_min = 0.05 + geom.characteristic_length_max = 0.05 + + beam = geom.add_polygon([[0.0, 0.0], + [2.0, 0.0], + [2.0, 1.0], + [0.0, 1.0]]) + + geom.extrude(beam, [0.0, 0.0, 0.1], num_layers=3) + + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "rect_prism_brick": + # Define corner points of the base rectangle + p0 = geom.add_point([0.0, 0.0, 0.0]) + p1 = geom.add_point([2.0, 0.0, 0.0]) + p2 = geom.add_point([2.0, 1.0, 0.0]) + p3 = geom.add_point([0.0, 1.0, 0.0]) + + # Create lines and loop + l0 = geom.add_line(p0, p1) + l1 = geom.add_line(p1, p2) + l2 = geom.add_line(p2, p3) + l3 = geom.add_line(p3, p0) + + loop = geom.add_curve_loop([l0, l1, l2, l3]) + surface = geom.add_plane_surface(loop) + + mesh_size = 0.08 + num_nodes_length = int(2.0 / mesh_size) + 1 + num_nodes_width = int(1.0 / mesh_size) + 1 + + # Transfinite meshing for structured quad elements on base + geom.set_transfinite_curve(l0, num_nodes_length, mesh_type="Progression", coeff=1.0) + geom.set_transfinite_curve(l1, num_nodes_width, mesh_type="Progression", coeff=1.0) + geom.set_transfinite_curve(l2, num_nodes_length, mesh_type="Progression", coeff=1.0) + geom.set_transfinite_curve(l3, num_nodes_width, mesh_type="Progression", coeff=1.0) + geom.set_transfinite_surface(surface, arrangement="left", corner_pts=[p0, p1, p2, p3]) + geom.set_recombined_surfaces([surface]) # Make it quad + + # Extrude to get 3D hex mesh + geom.extrude( + surface, + [0, 0, 0.5], + num_layers=7, + recombine=True, # Make it hexahedral + ) + + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "dogbone": + geom.characteristic_length_min = 0.02 + geom.characteristic_length_max = 0.02 + + dogbone = geom.add_polygon([[0.0, 0.0], + [0.285, 0.0], + [0.285, 0.331], + [0.1875, 0.331], + [0.1875, 0.668], + [0.285, 0.668], + [0.285, 1.0], + [0.0, 1.0], + [0.0, 0.668], + [0.0975, 0.668], + [0.0975, 0.331], + [0.0, 0.331]]) + + disk_1 = geom.add_disk([0.285, 0.331, 0.0], 0.0975) + disk_2 = geom.add_disk([0.0, 0.331, 0.0], 0.0975) + disk_3 = geom.add_disk([0.285, 0.668, 0.0], 0.0975) + disk_4 = geom.add_disk([0.0, 0.668, 0.0], 0.0975) + + dogbone = geom.boolean_difference(dogbone, [disk_1, disk_2, disk_3, disk_4]) + + geom.extrude(dogbone, [0.0, 0.0, 0.04], num_layers=2) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "dogbone_quarter": + geom.characteristic_length_min = 0.015 + geom.characteristic_length_max = 0.015 + + dogbone = geom.add_polygon([[0.0, 0.0], + [0.1425, 0.0], + [0.1425, 1.0], + [0.0, 1.0], + [0.0, 0.668], + [0.0975, 0.668], + [0.0975, 0.331], + [0.0, 0.331]]) + + disk_1 = geom.add_disk([0.0, 0.331, 0.0], 0.0975) + disk_2 = geom.add_disk([0.0, 0.668, 0.0], 0.0975) + + dogbone = geom.boolean_difference(dogbone, [disk_1, disk_2]) + + geom.extrude(dogbone, [0.0, 0.0, 0.04], num_layers=2) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + if self._mesh_type == "rect_sheet_defect": + + L = 0.015 + geom.characteristic_length_min = L + geom.characteristic_length_max = L + + R = 0.119 + sheet = geom.add_polygon([[-0.5, 0.0], + [0.5, 0.0], + [0.5, 1.01 * R], + [0.0, R], + [-0.5, 1.01 * R]]) + + geom.extrude(sheet, [0.0, 0.0, L], num_layers=1) + self._mesh = geom.generate_mesh() + + self._points = self._mesh.points + self._cells = self._mesh.cells + self._volume_conn = self._cells[2].data + self._surface_conn = self._cells[1].data + self._mesh.cells = [self._cells[2]] + + + def get_nodal_coordinates(self): + return self._points + + def get_cells(self): + return self._mesh.cells + + def get_volume_connectivity(self): + return self._volume_conn + + def get_surface_connectivity(self): + return self._surface_conn + + def add_point_data(self, data): + self._mesh.point_data = data + + def add_cell_data(self, data): + self._mesh.cell_data = data + + def save_mesh(self, filename): + self._mesh.write(filename + ".vtk") diff --git a/cmad/fem_utils/models/elastic_plastic_finite.py b/cmad/fem_utils/models/elastic_plastic_finite.py new file mode 100644 index 0000000..782a3ef --- /dev/null +++ b/cmad/fem_utils/models/elastic_plastic_finite.py @@ -0,0 +1,377 @@ +import numpy as np +import jax.numpy as jnp +from functools import partial +from jax.tree_util import tree_map + +from cmad.fem_utils.global_residuals.global_residual_plasticity import Global_residual_plasticity +from cmad.fem_utils.utils.fem_utils import (initialize_equation, + compute_shape_jacobian, + interpolate_vector_3D, + interpolate_scalar, + calc_element_traction_vector_3D) +from cmad.models.var_types import (get_sym_tensor_from_vector, + get_vector_from_sym_tensor) +from cmad.models.elastic_stress import two_mu_scale_factor +from cmad.parameters.parameters import Parameters +from cmad.models.deformation_types import DefType +from jax.lax import cond +from cmad.fem_utils.interpolants import interpolants +from cmad.fem_utils.quadrature import quadrature_rule + + +def create_J2_parameters(): + # E = 200.e3 + # nu = 0.249 + # Y = 349. + # K = 1.e5 + # S = 1.23e3 + # D = 0.55 + + E = 69.e3 + nu = 0.31 + Y = 128. + K = 1.e5 + S = 230.42 + D = 11.37 + + elastic_params = {"E": E, "nu": nu} + J2_effective_stress_params = {"J2": 0.} + initial_yield_params = {"Y": Y} + voce_params = {"S": S, "D": D} + linear_params = {"K": K} + hardening_params = {"voce": voce_params} + + Y_log_scale = np.array([48.]) + K_log_scale = np.array([100.]) + S_log_scale = np.array([106.]) + D_log_scale = np.array([25.]) + + J2_values = { + "rotation matrix": np.eye(3), + "elastic": elastic_params, + "plastic": { + "effective stress": J2_effective_stress_params, + "flow stress": { + "initial yield": initial_yield_params, + "hardening": hardening_params}}} + + J2_active_flags = J2_values.copy() + J2_active_flags = tree_map(lambda a: False, J2_active_flags) + J2_active_flags["plastic"]["flow stress"] = tree_map( + lambda x: True, J2_active_flags["plastic"]["flow stress"]) + + J2_transforms = J2_values.copy() + J2_transforms = tree_map(lambda a: None, J2_transforms) + J2_flow_stress_transforms = J2_transforms["plastic"]["flow stress"] + J2_flow_stress_transforms["initial yield"]["Y"] = Y_log_scale + # J2_flow_stress_transforms["hardening"]["linear"]["K"] = K_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["S"] = S_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["D"] = D_log_scale + + J2_parameters = \ + Parameters(J2_values, J2_active_flags, J2_transforms) + + return J2_parameters + +def evaluate_yield_function(s, alpha, params): + plastic_params = params["plastic"] + Y = plastic_params["flow stress"]["initial yield"]["Y"] + hardening_params = plastic_params["flow stress"]["hardening"] + voce_params = hardening_params["voce"] + S = voce_params["S"] + D = voce_params["D"] + s_norm = jnp.sqrt(jnp.sum(s * s)) + flow_stress = jnp.sqrt(2 / 3) * (Y + S * (1 - jnp.exp(-D * alpha))) + return (s_norm - flow_stress) / two_mu_scale_factor(params) + +def compute_yield_normal(s): + s_norm = jnp.sqrt(jnp.sum(s * s)) + return s / s_norm + +class Elastic_plastic_finite(Global_residual_plasticity): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + quad_rule_3D, shape_func_3D = problem.get_volume_basis_functions() + quad_rule_2D, shape_func_2D = problem.get_surface_basis_functions() + num_quad_pts = len(quad_rule_3D.wgauss) + + eq_num, num_free_dof, num_pres_dof = initialize_equation(num_nodes, dof_node, disp_node) + + pres_surf_traction_points = nodal_coords[pres_surf_traction] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + params = create_J2_parameters() + + def_type = DefType.FULL_3D + + num_local_resid_dofs = 8 + init_xi = np.zeros(num_local_resid_dofs * num_quad_pts) + init_xi[-2 * num_quad_pts:-num_quad_pts] = np.ones(num_quad_pts) + + # 4-point quadrature rule for pressure term (hard-coded for now) + quad_rule_tetra \ + = quadrature_rule.create_quadrature_rule_on_tetrahedron(2) + gauss_pts_tetra = quad_rule_tetra.xigauss + shape_func_tetra = interpolants.shape_tetrahedron(gauss_pts_tetra) + + + elem_local_resid = partial(self._elem_local_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + num_local_resid_dofs=num_local_resid_dofs) + + elem_global_resid = partial(self._elem_global_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + num_local_resid_dofs=num_local_resid_dofs, + gauss_weights_3D_2=quad_rule_tetra.wgauss, + shape_3D_2=shape_func_tetra.values, + dshape_3D_2=shape_func_tetra.gradients) + + elem_surf_traction = partial(calc_element_traction_vector_3D, + num_nodes_surf=num_nodes_surf, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + super().__init__(elem_global_resid, elem_local_resid, elem_surf_traction, volume_conn, + nodal_coords, eq_num, params, num_nodes_elem, dof_node, num_quad_pts, + num_free_dof, num_pres_dof, num_elem, disp_node, disp_val, init_xi, + pres_surf_traction_points, pres_surf_traction, surf_traction_vector, + def_type) + + def _elem_local_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, num_local_resid_dofs): + + elem_disp = u[0:num_nodes_elem * ndim] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + + num_quad_pts = len(gauss_weights_3D) + + elem_local_residual = jnp.zeros((num_quad_pts, num_local_resid_dofs)) + + zeta_dofs = 6 + elem_Gamma_bar = xi[:num_quad_pts * zeta_dofs].reshape(num_quad_pts, zeta_dofs) + elem_Gamma_bar_prev = xi_prev[:num_quad_pts * zeta_dofs].reshape(num_quad_pts, zeta_dofs) + elem_I_bar = xi[-2 * num_quad_pts:-num_quad_pts] + elem_I_bar_prev = xi_prev[-2 * num_quad_pts:-num_quad_pts] + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + + for gaussPt3D in range(num_quad_pts): + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_3D(elem_disp_prev, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + F_q = grad_u_q + jnp.eye(ndim) + F_prev_q = grad_u_prev_q + jnp.eye(ndim) + + Gamma_bar_q = elem_Gamma_bar[gaussPt3D] + Gamma_bar_prev_q = elem_Gamma_bar_prev[gaussPt3D] + I_bar_q = elem_I_bar[gaussPt3D] + I_bar_prev_q = elem_I_bar_prev[gaussPt3D] + alpha_q = elem_alpha[gaussPt3D] + alpha_prev_q = elem_alpha_prev[gaussPt3D] + + xi_recast = [Gamma_bar_q, I_bar_q, alpha_q] + xi_prev_recast = [Gamma_bar_prev_q, I_bar_prev_q, alpha_prev_q] + + elem_residual_q = self._local_resid_material_pt(F_q, F_prev_q, params, xi_recast, xi_prev_recast) + + elem_local_residual = elem_local_residual.at[gaussPt3D, :].set(elem_residual_q) + + return elem_local_residual.reshape(-1) + + def _elem_global_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, num_local_resid_dofs, + gauss_weights_3D_2, shape_3D_2, dshape_3D_2): + + # extract element displacement and pressure + elem_disp = u[0:num_nodes_elem * ndim] + p = u[num_nodes_elem * ndim:] + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + mu = E / (2 * (1 + nu)) + K_param = E / (3 * (1 - 2 * nu)) + G_param = E / (2 * (1 + nu)) + alpha = 1000.0 + + H = 0 + G = jnp.zeros(num_nodes_elem) + incomp_residual = jnp.zeros(num_nodes_elem) + + # stress divergence residual + S_D_vec = jnp.zeros((num_nodes_elem, ndim)) + + num_quad_pts = len(gauss_weights_3D) + + zeta_dofs = 6 + elem_Gamma_bar = xi[:num_quad_pts * zeta_dofs].reshape(num_quad_pts, zeta_dofs) + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + p_q = interpolate_scalar(p, shape_3D_q) + grad_p_q = gradphiXYZ_q @ p + + F_q = grad_u_q + jnp.eye(ndim) + F_inv_q = jnp.linalg.inv(F_q) + J_q = jnp.linalg.det(F_q) + + elem_Gamma_bar_q = get_sym_tensor_from_vector(elem_Gamma_bar[gaussPt3D], 3) + + P = mu * elem_Gamma_bar_q @ F_inv_q.T + J_q * p_q * F_inv_q.T + S_D_vec += w_q * gradphiXYZ_q.T @ P.T * dv_q + + incomp_residual += w_q * shape_3D_q * (1 / 2 * (J_q ** 2 - 1) / J_q) * dv_q + + # DB contibution (projection onto constant polynomial space) + H += w_q * 1.0 * dv_q + G += w_q * shape_3D_q * dv_q + + # alpha / G * (G.T)(H^-1)(G)(p) + incomp_residual += alpha / G_param * G * (1 / H) * jnp.dot(G, p) + + # 4-point quadrature + num_quad_pts_2 = len(gauss_weights_3D_2) + for gaussPt3D in range(num_quad_pts_2): + w_q = gauss_weights_3D_2[gaussPt3D] + + dshape_3D_q = dshape_3D_2[gaussPt3D, :, :] + shape_3D_q = shape_3D_2[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + p_q = interpolate_scalar(p, shape_3D_q) + + # (N.T)(alpha / G)(N)(p) + incomp_residual -= (alpha / G_param + 1 / K_param) * w_q \ + * shape_3D_q * p_q * dv_q + + return jnp.concatenate((S_D_vec.reshape(-1, order='F'), incomp_residual)) + + + def _local_resid_material_pt(self, F, F_prev, params, xi, xi_prev): + # material parameters + E = params['elastic']['E'] + nu = params['elastic']['nu'] + mu = E / (2 * (1 + nu)) + + # extract local state variables + Gamma_bar = get_sym_tensor_from_vector(xi[0], 3) + I_bar = xi[1] + alpha = xi[2] + + Gamma_bar_prev = get_sym_tensor_from_vector(xi_prev[0], 3) + I_bar_prev = xi_prev[1] + alpha_prev = xi_prev[2] + + # isochoric Deformation Gradients + F_bar = 1 / jnp.cbrt(jnp.linalg.det(F)) * F + F_bar_prev = 1 / jnp.cbrt(jnp.linalg.det(F_prev)) * F_prev + F_bar_prev_inv = jnp.linalg.inv(F_bar_prev) + + b_bar_prev = Gamma_bar_prev + I_bar_prev * jnp.eye(3) + + # define trial variables + b_bar_trial = F_bar @ F_bar_prev_inv @ b_bar_prev @ F_bar_prev_inv.T @ F_bar.T + Gamma_bar_trial = b_bar_trial - 1 / 3 * jnp.trace(b_bar_trial) * jnp.eye(3) + dev_tau_trial = mu * Gamma_bar_trial + phi_trial = evaluate_yield_function(dev_tau_trial, alpha_prev, params) + + return self._cond_residual(phi_trial, Gamma_bar, I_bar, alpha, b_bar_trial, + dev_tau_trial, alpha_prev, params, 1.e-14) + + @staticmethod + def _elastic_path(Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, params): + I_bar_trial = 1 / 3 * jnp.trace(b_bar_trial) + Gamma_bar_trial = b_bar_trial - I_bar_trial * jnp.eye(3) + C_Gamma_bar_elastic = get_vector_from_sym_tensor(Gamma_bar - Gamma_bar_trial, 3) + C_I_bar_elastic = I_bar - I_bar_trial + C_alpha_elastic = alpha - alpha_prev + return jnp.r_[C_Gamma_bar_elastic, C_I_bar_elastic, C_alpha_elastic] + + @staticmethod + def _plastic_path(Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, params): + E = params['elastic']['E'] + nu = params['elastic']['nu'] + mu = E / (2 * (1 + nu)) + + I_bar_trial = 1 / 3 * jnp.trace(b_bar_trial) + Gamma_bar_trial = b_bar_trial - I_bar_trial * jnp.eye(3) + + n = compute_yield_normal(dev_tau_trial) + C_Gamma_bar_plastic = get_vector_from_sym_tensor(Gamma_bar - Gamma_bar_trial + 2 * jnp.sqrt(3 / 2) \ + * (alpha - alpha_prev) * I_bar_trial * n, 3) + + dev_tau = mu * (Gamma_bar_trial - 2 * jnp.sqrt(3 / 2) * (alpha - alpha_prev) * I_bar_trial * n) + C_alpha_plastic = evaluate_yield_function(dev_tau, alpha, params) + + C_I_bar_plastic = jnp.linalg.det(Gamma_bar + I_bar * jnp.eye(3)) - 1 + + return jnp.r_[C_Gamma_bar_plastic, C_I_bar_plastic, C_alpha_plastic] + + def _cond_residual( + self, phi, Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, params, tol): + + def inner_cond_residual( + Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, params): + return cond(jnp.abs(phi) < tol, self._plastic_path, self._elastic_path, + Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, params) + + def outer_cond_residual( + Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, params): + return cond(phi > tol, self._plastic_path, inner_cond_residual, + Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, params) + + return outer_cond_residual(Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, params) + + + + + + + + + + + + + + + + + + + + + diff --git a/cmad/fem_utils/models/elastic_plastic_finite_Gp.py b/cmad/fem_utils/models/elastic_plastic_finite_Gp.py new file mode 100644 index 0000000..94f5d04 --- /dev/null +++ b/cmad/fem_utils/models/elastic_plastic_finite_Gp.py @@ -0,0 +1,322 @@ +import numpy as np +import jax.numpy as jnp +from functools import partial +from jax.tree_util import tree_map + +from cmad.fem_utils.global_residuals.global_residual_plasticity import Global_residual_plasticity +from cmad.fem_utils.utils.fem_utils import (initialize_equation, + compute_shape_jacobian, + interpolate_vector_3D, + interpolate_scalar, + calc_element_traction_vector_3D) +from cmad.models.var_types import (get_sym_tensor_from_vector, + get_vector_from_sym_tensor) +from cmad.models.elastic_stress import two_mu_scale_factor +from cmad.parameters.parameters import Parameters +from cmad.models.deformation_types import DefType +from jax.lax import cond + +def create_J2_parameters(): + # E = 200.e3 + # nu = 0.249 + # Y = 349. + # K = 1.e5 + # S = 1.23e3 + # D = 0.55 + + E = 69.e3 + nu = 0.31 + Y = 128. + K = 1.e5 + S = 230.42 + D = 11.37 + + elastic_params = {"E": E, "nu": nu} + J2_effective_stress_params = {"J2": 0.} + initial_yield_params = {"Y": Y} + voce_params = {"S": S, "D": D} + linear_params = {"K": K} + hardening_params = {"voce": voce_params} + + Y_log_scale = np.array([48.]) + K_log_scale = np.array([100.]) + S_log_scale = np.array([106.]) + D_log_scale = np.array([25.]) + + J2_values = { + "rotation matrix": np.eye(3), + "elastic": elastic_params, + "plastic": { + "effective stress": J2_effective_stress_params, + "flow stress": { + "initial yield": initial_yield_params, + "hardening": hardening_params}}} + + J2_active_flags = J2_values.copy() + J2_active_flags = tree_map(lambda a: False, J2_active_flags) + J2_active_flags["plastic"]["flow stress"] = tree_map( + lambda x: True, J2_active_flags["plastic"]["flow stress"]) + + J2_transforms = J2_values.copy() + J2_transforms = tree_map(lambda a: None, J2_transforms) + J2_flow_stress_transforms = J2_transforms["plastic"]["flow stress"] + J2_flow_stress_transforms["initial yield"]["Y"] = Y_log_scale + # J2_flow_stress_transforms["hardening"]["linear"]["K"] = K_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["S"] = S_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["D"] = D_log_scale + + J2_parameters = \ + Parameters(J2_values, J2_active_flags, J2_transforms) + + return J2_parameters + +def evaluate_yield_function(s, alpha, params): + plastic_params = params["plastic"] + Y = plastic_params["flow stress"]["initial yield"]["Y"] + hardening_params = plastic_params["flow stress"]["hardening"] + voce_params = hardening_params["voce"] + S = voce_params["S"] + D = voce_params["D"] + s_norm = jnp.sqrt(jnp.sum(s * s)) + flow_stress = jnp.sqrt(2 / 3) * (Y + S * (1 - jnp.exp(-D * alpha))) + return (s_norm - flow_stress) / two_mu_scale_factor(params) + +def compute_yield_normal(s): + s_norm = jnp.sqrt(jnp.sum(s * s)) + return s / s_norm + +class Elastic_plastic_finite_Gp(Global_residual_plasticity): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + quad_rule_3D, shape_func_3D = problem.get_volume_basis_functions() + quad_rule_2D, shape_func_2D = problem.get_surface_basis_functions() + num_quad_pts = len(quad_rule_3D.wgauss) + + eq_num, num_free_dof, num_pres_dof = initialize_equation(num_nodes, dof_node, disp_node) + + pres_surf_traction_points = nodal_coords[pres_surf_traction] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + params = create_J2_parameters() + + def_type = DefType.FULL_3D + + num_local_resid_dofs = 7 + init_xi = np.zeros(num_local_resid_dofs * num_quad_pts) + init_G_p = np.array(get_vector_from_sym_tensor(jnp.eye(3), 3)) + init_xi[:-num_quad_pts] = np.tile(init_G_p, num_quad_pts) + + + elem_local_resid = partial(self._elem_local_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + num_local_resid_dofs=num_local_resid_dofs) + + elem_global_resid = partial(self._elem_global_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + num_local_resid_dofs=num_local_resid_dofs) + + elem_surf_traction = partial(calc_element_traction_vector_3D, + num_nodes_surf=num_nodes_surf, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + super().__init__(elem_global_resid, elem_local_resid, elem_surf_traction, volume_conn, + nodal_coords, eq_num, params, num_nodes_elem, dof_node, num_quad_pts, + num_free_dof, num_pres_dof, num_elem, disp_node, disp_val, init_xi, + pres_surf_traction_points, pres_surf_traction, surf_traction_vector, + def_type) + + def _elem_local_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, num_local_resid_dofs): + + elem_disp = u[0:num_nodes_elem * ndim] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + + num_quad_pts = len(gauss_weights_3D) + + elem_local_residual = jnp.zeros((num_quad_pts, num_local_resid_dofs)) + + G_p_dofs = 6 + elem_G_p = xi[:num_quad_pts * G_p_dofs].reshape(num_quad_pts, G_p_dofs) + elem_G_p_prev = xi_prev[:num_quad_pts * G_p_dofs].reshape(num_quad_pts, G_p_dofs) + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + + for gaussPt3D in range(num_quad_pts): + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_3D(elem_disp_prev, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + F_q = grad_u_q + jnp.eye(ndim) + F_prev_q = grad_u_prev_q + jnp.eye(ndim) + + G_p_q = elem_G_p[gaussPt3D] + G_p_prev_q = elem_G_p_prev[gaussPt3D] + alpha_q = elem_alpha[gaussPt3D] + alpha_prev_q = elem_alpha_prev[gaussPt3D] + + xi_recast = [G_p_q, alpha_q] + xi_prev_recast = [G_p_prev_q, alpha_prev_q] + + elem_residual_q = self._local_resid_material_pt(F_q, F_prev_q, params, xi_recast, xi_prev_recast) + + elem_local_residual = elem_local_residual.at[gaussPt3D, :].set(elem_residual_q) + + return elem_local_residual.reshape(-1) + + def _elem_global_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, num_local_resid_dofs): + + # extract element displacement and pressure + elem_disp = u[0:num_nodes_elem * ndim] + p = u[num_nodes_elem * ndim:] + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + mu = E / (2 * (1 + nu)) + K_param = E / (3 * (1 - 2 * nu)) + G_param = E / (2 * (1 + nu)) + alpha = 1000.0 + + H = 0 + G = jnp.zeros(num_nodes_elem) + incomp_residual = jnp.zeros(num_nodes_elem) + + # stress divergence residual + S_D_vec = jnp.zeros((num_nodes_elem, ndim)) + + num_quad_pts = len(gauss_weights_3D) + + G_p_dofs = 6 + elem_G_p = xi[:num_quad_pts * G_p_dofs].reshape(num_quad_pts, G_p_dofs) + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + p_q = interpolate_scalar(p, shape_3D_q) + grad_p_q = gradphiXYZ_q @ p + + F_q = grad_u_q + jnp.eye(ndim) + F_inv_q = jnp.linalg.inv(F_q) + J_q = jnp.linalg.det(F_q) + + elem_G_p_q = get_sym_tensor_from_vector(elem_G_p[gaussPt3D], 3) + F_bar_q = jnp.linalg.det(F_q) ** (-1 / 3) * F_q + B_e_bar_q = F_bar_q @ elem_G_p_q @ F_bar_q.T + I_e_bar_q = 1 / 3 * jnp.trace(B_e_bar_q) + dev_t_q = mu * (B_e_bar_q - I_e_bar_q * jnp.eye(3)) + + P = dev_t_q @ F_inv_q.T + J_q * p_q * F_inv_q.T + S_D_vec += w_q * gradphiXYZ_q.T @ P.T * dv_q + + incomp_residual += w_q * shape_3D_q * (1 / 2 * (J_q ** 2 - 1) / J_q) * dv_q + + # DB contibution (projection onto constant polynomial space) + H += w_q * 1.0 * dv_q + G += w_q * shape_3D_q * dv_q + + # (N.T)(alpha / G)(N)(p) + incomp_residual -= (alpha / G_param + 1 / K_param) * w_q \ + * shape_3D_q * jnp.dot(shape_3D_q, p) * dv_q + + # alpha / G * (G.T)(H^-1)(G)(p) + incomp_residual += alpha / G_param * G * (1 / H) * jnp.dot(G, p) + + return jnp.concatenate((S_D_vec.reshape(-1, order='F'), incomp_residual)) + + + def _local_resid_material_pt(self, F, F_prev, params, xi, xi_prev): + # material parameters + E = params['elastic']['E'] + nu = params['elastic']['nu'] + mu = E / (2 * (1 + nu)) + + # extract local state variables + G_p = get_sym_tensor_from_vector(xi[0], 3) + alpha = xi[1] + + G_p_prev = get_sym_tensor_from_vector(xi_prev[0], 3) + alpha_prev = xi_prev[1] + + # deformation gradient + F_bar = jnp.linalg.det(F) ** (-1 / 3) * F + + # define trial variables + b_bar_trial = F_bar @ G_p_prev @ F_bar.T + dev_t_trial = mu * (b_bar_trial - 1 / 3 * jnp.trace(b_bar_trial) * jnp.eye(3)) + f_trial = evaluate_yield_function(dev_t_trial, alpha_prev, params) + + return self._cond_residual(f_trial, F_bar, G_p, alpha, G_p_prev, alpha_prev, + b_bar_trial, dev_t_trial, params, 1.e-14) + + @staticmethod + def _elastic_path(F_bar, G_p, alpha, G_p_prev, alpha_prev, b_bar_trial, dev_t_trial, params): + C_zeta_bar_elastic = get_vector_from_sym_tensor(G_p - G_p_prev, 3) + C_alpha_elastic = alpha - alpha_prev + return jnp.r_[C_zeta_bar_elastic, C_alpha_elastic] + + @staticmethod + def _plastic_path(F_bar, G_p, alpha, G_p_prev, alpha_prev, b_bar_trial, dev_t_trial, params): + E = params['elastic']['E'] + nu = params['elastic']['nu'] + mu = E / (2 * (1 + nu)) + + n = compute_yield_normal(dev_t_trial) + mu_bar = mu * 1 / 3 * jnp.trace(b_bar_trial) + b_bar = F_bar @ G_p @ F_bar.T + I_bar = 1 / 3 * jnp.trace(b_bar) + dev_t = mu * (b_bar - I_bar * jnp.eye(3)) + C_zeta_bar_plastic = get_vector_from_sym_tensor(dev_t - dev_t_trial + 2 * mu_bar * jnp.sqrt(3 / 2) \ + * (alpha - alpha_prev) * n, 3) + + dev_t_temp = dev_t_trial - 2 * mu_bar * jnp.sqrt(3 / 2) * (alpha - alpha_prev) * n + C_alpha_plastic = evaluate_yield_function(dev_t_temp, alpha, params) + + C_I_bar_plastic = jnp.linalg.det(b_bar) - 1 + + return jnp.r_[C_zeta_bar_plastic[:-1], C_I_bar_plastic, C_alpha_plastic] + + def _cond_residual( + self, f, F_bar, G_p, alpha, G_p_prev, alpha_prev, b_bar_trial, dev_t_trial, params, tol): + + def inner_cond_residual( + F_bar, G_p, alpha, G_p_prev, alpha_prev, b_bar_trial, dev_t_trial, params): + return cond(jnp.abs(f) < tol, self._plastic_path, self._elastic_path, + F_bar, G_p, alpha, G_p_prev, alpha_prev, b_bar_trial, dev_t_trial, params) + + def outer_cond_residual( + F_bar, G_p, alpha, G_p_prev, alpha_prev, b_bar_trial, dev_t_trial, params): + return cond(f > tol, self._plastic_path, inner_cond_residual, + F_bar, G_p, alpha, G_p_prev, alpha_prev, b_bar_trial, dev_t_trial, params) + + return outer_cond_residual(F_bar, G_p, alpha, G_p_prev, alpha_prev, b_bar_trial, dev_t_trial, params) \ No newline at end of file diff --git a/cmad/fem_utils/models/elastic_plastic_small.py b/cmad/fem_utils/models/elastic_plastic_small.py new file mode 100755 index 0000000..a7690e2 --- /dev/null +++ b/cmad/fem_utils/models/elastic_plastic_small.py @@ -0,0 +1,250 @@ +import numpy as np +import jax.numpy as jnp +from functools import partial +from jax.tree_util import tree_map + +from cmad.fem_utils.global_residuals.global_residual_plasticity import Global_residual_plasticity +from cmad.fem_utils.utils.fem_utils import (initialize_equation, + compute_shape_jacobian, + interpolate_vector_3D, + interpolate_scalar, + calc_element_traction_vector_3D) +from cmad.parameters.parameters import Parameters +from cmad.models.deformation_types import DefType +from cmad.models.small_elastic_plastic import SmallElasticPlastic + +def create_J2_parameters(): + E = 200.e3 + nu = 0.249 + Y = 349. + K = 1.e5 + S = 1.23e3 + D = 0.55 + + elastic_params = {"E": E, "nu": nu} + J2_effective_stress_params = {"J2": 0.} + initial_yield_params = {"Y": Y} + voce_params = {"S": S, "D": D} + linear_params = {"K": K} + hardening_params = {"voce": voce_params} + + Y_log_scale = np.array([48.]) + K_log_scale = np.array([100.]) + S_log_scale = np.array([106.]) + D_log_scale = np.array([25.]) + + J2_values = { + "rotation matrix": np.eye(3), + "elastic": elastic_params, + "plastic": { + "effective stress": J2_effective_stress_params, + "flow stress": { + "initial yield": initial_yield_params, + "hardening": hardening_params}}} + + J2_active_flags = J2_values.copy() + J2_active_flags = tree_map(lambda a: False, J2_active_flags) + J2_active_flags["plastic"]["flow stress"] = tree_map( + lambda x: True, J2_active_flags["plastic"]["flow stress"]) + + J2_transforms = J2_values.copy() + J2_transforms = tree_map(lambda a: None, J2_transforms) + J2_flow_stress_transforms = J2_transforms["plastic"]["flow stress"] + J2_flow_stress_transforms["initial yield"]["Y"] = Y_log_scale + # J2_flow_stress_transforms["hardening"]["linear"]["K"] = K_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["S"] = S_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["D"] = D_log_scale + + J2_parameters = \ + Parameters(J2_values, J2_active_flags, J2_transforms) + + return J2_parameters + +class Elastic_plastic_small(Global_residual_plasticity): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + quad_rule_3D, shape_func_3D = problem.get_volume_basis_functions() + quad_rule_2D, shape_func_2D = problem.get_surface_basis_functions() + num_quad_pts = len(quad_rule_3D.wgauss) + + eq_num, num_free_dof, num_pres_dof = initialize_equation(num_nodes, dof_node, disp_node) + + pres_surf_traction_points = nodal_coords[pres_surf_traction] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + params = create_J2_parameters() + + def_type = DefType.FULL_3D + + # material point model + mat_point_model = SmallElasticPlastic(params, def_type=def_type) + self._local_residual_material_point = mat_point_model.get_local_residual() + self._cauchy_fun = mat_point_model.get_cauchy() + num_local_resid_dofs = mat_point_model.num_dofs + + init_xi = np.zeros(num_local_resid_dofs * num_quad_pts) + + elem_local_resid = partial(self._elem_local_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + num_local_resid_dofs=num_local_resid_dofs) + + elem_global_resid = partial(self._elem_global_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + num_local_resid_dofs=num_local_resid_dofs) + + elem_surf_traction = partial(calc_element_traction_vector_3D, + num_nodes_surf=num_nodes_surf, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + super().__init__(elem_global_resid, elem_local_resid, elem_surf_traction, volume_conn, + nodal_coords, eq_num, params, num_nodes_elem, dof_node, num_quad_pts, + num_free_dof, num_pres_dof, num_elem, disp_node, disp_val, init_xi, + pres_surf_traction_points, pres_surf_traction, surf_traction_vector, + def_type) + + def _elem_local_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, num_local_resid_dofs): + + elem_disp = u[0:num_nodes_elem * ndim] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + + num_quad_pts = len(gauss_weights_3D) + + elem_local_residual = jnp.zeros((num_quad_pts, num_local_resid_dofs)) + + ep_dofs = 6 + elem_ep = xi[:num_quad_pts * ep_dofs] + elem_ep_prev = xi_prev[:num_quad_pts * ep_dofs] + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + + for gaussPt3D in range(num_quad_pts): + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_3D(elem_disp_prev, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + F_q = [grad_u_q + jnp.eye(ndim)] + F_prev_q = [grad_u_prev_q + jnp.eye(ndim)] + + ep_q = elem_ep[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + ep_prev_q = elem_ep_prev[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + alpha_q = jnp.array([elem_alpha[gaussPt3D - num_quad_pts]]) + alpha_prev_q = jnp.array([elem_alpha_prev[gaussPt3D - num_quad_pts]]) + + xi_recast = [ep_q, alpha_q] + xi_prev_recast = [ep_prev_q, alpha_prev_q] + + elem_residual_q = self._local_residual_material_point(xi_recast, xi_prev_recast, params, F_q, F_prev_q) + + elem_local_residual = elem_local_residual.at[gaussPt3D, :].set(elem_residual_q) + + return elem_local_residual.reshape(-1) + + def _elem_global_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, num_local_resid_dofs): + + # extract element displacement and pressure + elem_disp = u[0:num_nodes_elem * ndim] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + p = u[num_nodes_elem * ndim:] + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + G_param = E / (2 * (1 + nu)) + K_param = E / (3 * (1 - 2 * nu)) + alpha = 0.01 + + # incompressibility residual + H = 0 + G = jnp.zeros(num_nodes_elem) + incomp_residual = jnp.zeros(num_nodes_elem) + + # stress divergence residual + S_D_vec = jnp.zeros((num_nodes_elem, ndim)) + + num_quad_pts = len(gauss_weights_3D) + + ep_dofs = 6 + elem_ep = xi[:num_quad_pts * ep_dofs] + elem_ep_prev = xi_prev[:num_quad_pts * ep_dofs] + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_3D(elem_disp_prev, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + p_q = interpolate_scalar(p, shape_3D_q) + + F_q = [grad_u_q + jnp.eye(ndim)] + F_prev_q = [grad_u_prev_q + jnp.eye(ndim)] + + ep_q = elem_ep[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + ep_prev_q = elem_ep_prev[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + alpha_q = jnp.array([elem_alpha[gaussPt3D - num_quad_pts]]) + alpha_prev_q = jnp.array([elem_alpha_prev[gaussPt3D - num_quad_pts]]) + + xi_recast = [ep_q, alpha_q] + xi_prev_recast = [ep_prev_q, alpha_prev_q] + + stress = self._cauchy_fun(xi_recast, xi_prev_recast, params, F_q, F_prev_q) + stress = stress - 1 / ndim * jnp.trace(stress) * jnp.eye(ndim) + p_q * jnp.eye(ndim) + + S_D_vec += w_q * gradphiXYZ_q.T @ stress.T * dv_q + + incomp_residual += w_q * shape_3D_q * (jnp.trace(grad_u_q)) * dv_q + + # DB contibution (projection onto constant polynomial space) + H += w_q * 1.0 * dv_q + G += w_q * shape_3D_q * dv_q + + # (N.T)(alpha / G)(N)(p) + incomp_residual -= (alpha / G_param + 1 / K_param) * w_q \ + * shape_3D_q * jnp.dot(shape_3D_q, p) * dv_q + + # alpha / G * (G.T)(H^-1)(G)(p) + incomp_residual += alpha / G_param * G * (1 / H) * jnp.dot(G, p) + + return jnp.concatenate((S_D_vec.reshape(-1, order='F'), incomp_residual)) + + + + + + + + + + + + diff --git a/cmad/fem_utils/models/elastic_plastic_small_plane_stress.py b/cmad/fem_utils/models/elastic_plastic_small_plane_stress.py new file mode 100755 index 0000000..898f557 --- /dev/null +++ b/cmad/fem_utils/models/elastic_plastic_small_plane_stress.py @@ -0,0 +1,246 @@ +import numpy as np +import jax.numpy as jnp +from functools import partial +from jax.tree_util import tree_map + +from cmad.fem_utils.global_residuals.global_residual_plasticity import Global_residual_plasticity +from cmad.fem_utils.utils.fem_utils import (initialize_equation, + compute_shape_jacobian, + interpolate_vector_2D, + interpolate_scalar, + calc_element_traction_vector_2D) +from cmad.parameters.parameters import Parameters +from cmad.models.deformation_types import DefType +from cmad.models.small_elastic_plastic import SmallElasticPlastic + +def create_J2_parameters(): + E = 69.e3 + nu = 0.33 + Y = 48. + K = 0e3 + S = 106. + D = 25. + + elastic_params = {"E": E, "nu": nu} + J2_effective_stress_params = {"J2": 0.} + initial_yield_params = {"Y": Y} + voce_params = {"S": S, "D": D} + hardening_params = {"voce": voce_params} + + Y_log_scale = np.array([48.]) + S_log_scale = np.array([106.]) + D_log_scale = np.array([25.]) + + J2_values = { + "rotation matrix": np.eye(3), + "elastic": elastic_params, + "plastic": { + "effective stress": J2_effective_stress_params, + "flow stress": { + "initial yield": initial_yield_params, + "hardening": hardening_params}}} + + J2_active_flags = J2_values.copy() + J2_active_flags = tree_map(lambda a: False, J2_active_flags) + J2_active_flags["plastic"]["flow stress"] = tree_map( + lambda x: True, J2_active_flags["plastic"]["flow stress"]) + + J2_transforms = J2_values.copy() + J2_transforms = tree_map(lambda a: None, J2_transforms) + J2_flow_stress_transforms = J2_transforms["plastic"]["flow stress"] + J2_flow_stress_transforms["initial yield"]["Y"] = Y_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["S"] = S_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["D"] = D_log_scale + + J2_parameters = \ + Parameters(J2_values, J2_active_flags, J2_transforms) + + return J2_parameters + +class Elastic_plastic_small_plane_stress(Global_residual_plasticity): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + quad_rule_2D, shape_func_2D = problem.get_volume_basis_functions() + quad_rule_1D, shape_func_1D = problem.get_surface_basis_functions() + num_quad_pts = len(quad_rule_2D.wgauss) + + eq_num, num_free_dof, num_pres_dof = initialize_equation(num_nodes, dof_node, disp_node) + + pres_surf_traction_points = nodal_coords[:, :-1][pres_surf_traction] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + params = create_J2_parameters() + + def_type = DefType.PLANE_STRESS + + # material point model + material_point_model = SmallElasticPlastic(params, def_type=def_type) + self._local_residual_material_point = material_point_model.get_local_residual() + self._cauchy_fun = material_point_model.get_cauchy() + num_local_resid_dofs = material_point_model.num_dofs + + init_xi = np.zeros(num_local_resid_dofs * num_quad_pts) + init_xi[-2 * num_quad_pts:-num_quad_pts] = np.ones(num_quad_pts) + + elem_local_resid = partial(self._elem_local_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients, + num_local_resid_dofs=num_local_resid_dofs) + + elem_global_resid = partial(self._elem_global_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + elem_surf_traction = partial(calc_element_traction_vector_2D, + num_nodes_surf=num_nodes_surf, + ndim=ndim, + gauss_weights_1D=quad_rule_1D.wgauss, + shape_1D=shape_func_1D.values, + dshape_1D=shape_func_1D.gradients) + + super().__init__(elem_global_resid, elem_local_resid, elem_surf_traction, volume_conn, + nodal_coords, eq_num, params, num_nodes_elem, dof_node, num_quad_pts, + num_free_dof, num_pres_dof, num_elem, disp_node, disp_val, init_xi, + pres_surf_traction_points, pres_surf_traction, surf_traction_vector, + def_type) + + def _elem_local_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_2D, shape_2D, dshape_2D, num_local_resid_dofs): + + elem_disp = u[0:num_nodes_elem * ndim] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + + num_quad_pts = len(gauss_weights_2D) + + elem_local_residual = jnp.zeros((num_quad_pts, num_local_resid_dofs)) + + ep_dofs = 6 + elem_ep = xi[:num_quad_pts * ep_dofs] + elem_ep_prev = xi_prev[:num_quad_pts * ep_dofs] + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + elem_F33 = xi[-2 * num_quad_pts: -num_quad_pts] + elem_F33_prev = xi_prev[-2 * num_quad_pts: -num_quad_pts] + + for gaussPt3D in range(num_quad_pts): + + dshape_2D_q = dshape_2D[gaussPt3D, :, :] + shape_2D_q = shape_2D[gaussPt3D, :] + + dv_q, gradphiXY_q = compute_shape_jacobian(elem_points, dshape_2D_q) + + u_q, grad_u_q = interpolate_vector_2D(elem_disp, shape_2D_q, gradphiXY_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_2D(elem_disp_prev, shape_2D_q, gradphiXY_q, num_nodes_elem) + + F_q = [grad_u_q + jnp.eye(ndim)] + F_prev_q = [grad_u_prev_q + jnp.eye(ndim)] + + ep_q = elem_ep[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + ep_prev_q = elem_ep_prev[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + + alpha_q = jnp.array([elem_alpha[gaussPt3D - num_quad_pts]]) + alpha_prev_q = jnp.array([elem_alpha_prev[gaussPt3D - num_quad_pts]]) + + F33_q = jnp.array([elem_F33[gaussPt3D - num_quad_pts]]) + F33_prev_q = jnp.array([elem_F33_prev[gaussPt3D - num_quad_pts]]) + + xi_recast = [ep_q, alpha_q, F33_q] + xi_prev_recast = [ep_prev_q, alpha_prev_q, F33_prev_q] + + elem_residual_q = self._local_residual_material_point(xi_recast, xi_prev_recast, params, F_q, F_prev_q) + + elem_local_residual = elem_local_residual.at[gaussPt3D, :].set(elem_residual_q) + + return elem_local_residual.reshape(-1) + + def _elem_global_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, + num_nodes_elem,ndim, gauss_weights_2D, shape_2D, + dshape_2D): + + # extract element displacement and pressure + elem_disp = u[0:num_nodes_elem * ndim] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + p = u[num_nodes_elem * ndim:] + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + G_param = E / (2 * (1 + nu)) + alpha = 1.0 + + # incompressibility residual + H = 0 + G = jnp.zeros(num_nodes_elem) + incomp_residual = jnp.zeros(num_nodes_elem) + + # stress divergence residual + S_D_vec = jnp.zeros((num_nodes_elem, ndim)) + + num_quad_pts = len(gauss_weights_2D) + + ep_dofs = 6 + elem_ep = xi[:num_quad_pts * ep_dofs] + elem_ep_prev = xi_prev[:num_quad_pts * ep_dofs] + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + elem_F33 = xi[-2 * num_quad_pts: -num_quad_pts] + elem_F33_prev = xi_prev[-2 * num_quad_pts: -num_quad_pts] + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_2D[gaussPt3D] + + dshape_2D_q = dshape_2D[gaussPt3D, :, :] + shape_2D_q = shape_2D[gaussPt3D, :] + + dv_q, gradphiXY_q = compute_shape_jacobian(elem_points, dshape_2D_q) + u_q, grad_u_q = interpolate_vector_2D(elem_disp, shape_2D_q, gradphiXY_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_2D(elem_disp_prev, shape_2D_q, gradphiXY_q, num_nodes_elem) + p_q = interpolate_scalar(p, shape_2D_q) + + F_q = [grad_u_q + jnp.eye(ndim)] + F_prev_q = [grad_u_prev_q + jnp.eye(ndim)] + + ep_q = elem_ep[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + ep_prev_q = elem_ep_prev[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + + alpha_q = jnp.array([elem_alpha[gaussPt3D - num_quad_pts]]) + alpha_prev_q = jnp.array([elem_alpha_prev[gaussPt3D - num_quad_pts]]) + + F33_q = jnp.array([elem_F33[gaussPt3D - num_quad_pts]]) + F33_prev_q = jnp.array([elem_F33_prev[gaussPt3D - num_quad_pts]]) + + xi_recast = [ep_q, alpha_q, F33_q] + xi_prev_recast = [ep_prev_q, alpha_prev_q, F33_prev_q] + + stress = self._cauchy_fun(xi_recast, xi_prev_recast, params, F_q, F_prev_q)[:ndim, :ndim] + stress = stress - 1 / ndim * jnp.trace(stress) * jnp.eye(ndim) + p_q * jnp.eye(ndim) + + S_D_vec += w_q * gradphiXY_q.T @ stress.T * dv_q + + incomp_residual += w_q * shape_2D_q * (jnp.trace(grad_u_q) + (F33_q[0] - 1)) * dv_q + + # DB contibution (projection onto constant polynomial space) + H += w_q * 1.0 * dv_q + G += w_q * shape_2D_q * dv_q + + # (N.T)(alpha / G)(N)(p) + incomp_residual -= alpha / G_param * w_q * shape_2D_q * jnp.dot(shape_2D_q, p) * dv_q + + # alpha / G * (G.T)(H^-1)(G)(p) + incomp_residual += alpha / G_param * G * (1 / H) * jnp.dot(G, p) + + return jnp.concatenate((S_D_vec.reshape(-1, order='F'), incomp_residual)) \ No newline at end of file diff --git a/cmad/fem_utils/models/global_deriv_types.py b/cmad/fem_utils/models/global_deriv_types.py new file mode 100755 index 0000000..7141a57 --- /dev/null +++ b/cmad/fem_utils/models/global_deriv_types.py @@ -0,0 +1,14 @@ +from enum import IntEnum + + +class GlobalDerivType(IntEnum): + DU = 0 + DU_prev = 1 + DParams = 2 + DXI = 3 + DXI_prev = 4 + DNONE = 5 + + DU_DU = 0 + DXI_DXI = 1 + DU_DXI = 2 \ No newline at end of file diff --git a/cmad/fem_utils/models/mooney_rivlin.py b/cmad/fem_utils/models/mooney_rivlin.py new file mode 100644 index 0000000..db56b3f --- /dev/null +++ b/cmad/fem_utils/models/mooney_rivlin.py @@ -0,0 +1,178 @@ +import numpy as np +import jax.numpy as jnp + +from cmad.fem_utils.global_residuals.global_residual import Global_residual +from cmad.fem_utils.utils.fem_utils import (initialize_equation, + compute_shape_jacobian, + interpolate_vector_3D, + interpolate_scalar, + calc_element_traction_vector_3D) + +from functools import partial + +class Mooney_rivlin(Global_residual): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + quad_rule_3D, shape_func_3D = problem.get_volume_basis_functions() + quad_rule_2D, shape_func_2D = problem.get_surface_basis_functions() + + eq_num, num_free_dof, num_pres_dof = initialize_equation(num_nodes, dof_node, disp_node) + + elem_points = nodal_coords[volume_conn, :] + pres_surf_traction_points = nodal_coords[pres_surf_traction] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + # C01 (MPa), C10 (MPa), D1 (MPa^-1) + params = np.array([0.0035972, 0.186438, 0.004]) + + is_mixed = problem.is_mixed() + + if (is_mixed): + residual = partial(self._compute_residual_u_p, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients) + else: + residual = partial(self._compute_residual_u, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients) + + elem_surf_traction = partial(calc_element_traction_vector_3D, + num_nodes_surf=num_nodes_surf, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + super().__init__(residual, elem_surf_traction, volume_conn, elem_points, eq_num, + params, num_nodes_elem, dof_node, num_free_dof, disp_node, disp_val, + num_pres_dof, is_mixed, pres_surf_traction_points, pres_surf_traction, + surf_traction_vector) + + def _compute_residual_u_p( + self, u, params, elem_points, num_nodes_elem, ndim, + gauss_weights_3D, shape_3D, dshape_3D): + + # extract element displacement and pressure + elem_disp = u[0:num_nodes_elem * ndim] + p = u[num_nodes_elem * ndim:] + + E = params[0] + nu = params[1] + G_param = E / (2 * (1 + nu)) + alpha = 1.0 + + # incompressibility residual + H = 0 + G = jnp.zeros(num_nodes_elem) + incomp_residual = jnp.zeros(num_nodes_elem) + + # stress divergence residual + S_D_vec = jnp.zeros((num_nodes_elem, ndim)) + + for gaussPt3D in range(len(gauss_weights_3D)): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + p_q = interpolate_scalar(p, shape_3D_q) + + # compute stress divergence residual + stress = self._compute_mooney_rivlin_stress_p(grad_u_q, p_q, params) + + S_D_vec += w_q * gradphiXYZ_q.T @ stress.T * dv_q + + # compute incompressibility residual + F = jnp.eye(3) + grad_u_q + J = jnp.linalg.det(F) + incomp_residual += w_q * shape_3D_q * (J - 1) * dv_q + + # DB contibution (projection onto constant polynomial space) + H += w_q * 1.0 * dv_q + G += w_q * shape_3D_q * dv_q + + # (N.T)(alpha / G)(N)(p) + incomp_residual -= (alpha / G_param) * w_q \ + * shape_3D_q * jnp.dot(shape_3D_q, p) * dv_q + + # alpha / G * (G.T)(H^-1)(G)(p) + incomp_residual += alpha / G_param * G * (1 / H) * jnp.dot(G, p) + + return jnp.concatenate((S_D_vec.reshape(-1, order='F'), incomp_residual)) + + def _compute_residual_u( + self, u, params, elem_points, num_nodes_elem, ndim, + gauss_weights_3D, shape_3D, dshape_3D): + + SD_vec = jnp.zeros((num_nodes_elem, ndim)) + + for gaussPt3D in range(len(gauss_weights_3D)): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(u, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + stress = self._compute_mooney_rivlin_stress(grad_u_q, params) + + SD_vec += w_q * gradphiXYZ_q.T @ stress.T * dv_q + + return SD_vec.reshape(-1, order='F') + + @staticmethod + def _compute_mooney_rivlin_stress_p(grad_u, p, params): + + # computes the first Piola-Kirchoff stress tensor (set J = 1) + C01, C10, D1 = params + + F = jnp.eye(3) + grad_u + F_inv_T = jnp.linalg.inv(F).T + b = F @ F.T + I1 = jnp.trace(b) + I2 = 1 / 2 * (jnp.trace(b) ** 2 - jnp.trace(b @ b)) + + T = 2 * (C10 + I1 * C01) * b - 2 * C01 * b @ b \ + - (2 / 3 * (C10 * I1 + 2 * C01 * I2) - p) * jnp.eye(3) + P = T @ F_inv_T + + return P + + @staticmethod + def _compute_mooney_rivlin_stress(grad_u, params): + + # computes the first Piola-Kirchoff stress tensor + C01, C10, D1 = params + + F = jnp.eye(3) + grad_u + F_inv_T = jnp.linalg.inv(F).T + J = jnp.linalg.det(F) + b = F @ F.T + b_bar = J ** (-2 / 3) * b + I1_bar = J ** (-2 / 3) * jnp.trace(b) + I2_bar = J ** (-4 / 3) * 1 / 2 * (jnp.trace(b) ** 2 - jnp.trace(b @ b)) + + T = 2 / J * (C10 + C01 * I1_bar) * b_bar - 2 * C01 / J * b_bar @ b_bar \ + + (2 / D1 * (J - 1) - 2 * I1_bar * C10 / (3 * J) \ + - 4 * I2_bar * C01 / (3 * J)) * jnp.eye(3) + + P = J * T @ F_inv_T + + return P diff --git a/cmad/fem_utils/models/neo_hookean.py b/cmad/fem_utils/models/neo_hookean.py new file mode 100755 index 0000000..157950c --- /dev/null +++ b/cmad/fem_utils/models/neo_hookean.py @@ -0,0 +1,172 @@ +import numpy as np +import jax.numpy as jnp + +from cmad.fem_utils.global_residuals.global_residual import Global_residual +from cmad.fem_utils.utils.fem_utils import (initialize_equation, + compute_shape_jacobian, + interpolate_vector_3D, + interpolate_scalar, + calc_element_traction_vector_3D) + +from functools import partial + +class Neo_hookean(Global_residual): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + quad_rule_3D, shape_func_3D = problem.get_volume_basis_functions() + quad_rule_2D, shape_func_2D = problem.get_surface_basis_functions() + + eq_num, num_free_dof, num_pres_dof = initialize_equation(num_nodes, dof_node, disp_node) + + elem_points = nodal_coords[volume_conn, :] + pres_surf_traction_points = nodal_coords[pres_surf_traction] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + # E, nu + params = np.array([200e3, 0.3]) + + is_mixed = problem.is_mixed() + + if (is_mixed): + residual = partial(self._compute_residual_u_p, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients) + else: + residual = partial(self._compute_residual_u, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients) + + elem_surf_traction = partial(calc_element_traction_vector_3D, + num_nodes_surf=num_nodes_surf, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + super().__init__(residual, elem_surf_traction, volume_conn, elem_points, eq_num, + params, num_nodes_elem, dof_node, num_free_dof, disp_node, disp_val, + num_pres_dof, is_mixed, pres_surf_traction_points, pres_surf_traction, + surf_traction_vector) + + def _compute_residual_u_p( + self, u, params, elem_points, num_nodes_elem, ndim, + gauss_weights_3D, shape_3D, dshape_3D): + + # extract element displacement and pressure + elem_disp = u[0:num_nodes_elem * ndim] + p = u[num_nodes_elem * ndim:] + + E = params[0] + nu = params[1] + G_param = E / (2 * (1 + nu)) + alpha = 1.0 + + # incompressibility residual + H = 0 + G = jnp.zeros(num_nodes_elem) + incomp_residual = jnp.zeros(num_nodes_elem) + + # stress divergence residual + S_D_vec = jnp.zeros((num_nodes_elem, ndim)) + + for gaussPt3D in range(len(gauss_weights_3D)): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + p_q = interpolate_scalar(p, shape_3D_q) + + # compute stress divergence residual + stress = self._compute_neo_hookean_stress_p(grad_u_q, p_q, params) + + S_D_vec += w_q * gradphiXYZ_q.T @ stress.T * dv_q + + # compute incompressibility residual + F = jnp.eye(3) + grad_u_q + J = jnp.linalg.det(F) + incomp_residual += w_q * shape_3D_q * (J - 1) * dv_q + + # DB contibution (projection onto constant polynomial space) + H += w_q * 1.0 * dv_q + G += w_q * shape_3D_q * dv_q + + # (N.T)(alpha / G)(N)(p) + incomp_residual -= (alpha / G_param) * w_q \ + * shape_3D_q * jnp.dot(shape_3D_q, p) * dv_q + + # alpha / G * (G.T)(H^-1)(G)(p) + incomp_residual += alpha / G_param * G * (1 / H) * jnp.dot(G, p) + + return jnp.concatenate((S_D_vec.reshape(-1, order='F'), incomp_residual)) + + def _compute_residual_u( + self, u, params, elem_points, num_nodes_elem, ndim, + gauss_weights_3D, shape_3D, dshape_3D): + + SD_vec = jnp.zeros((num_nodes_elem, ndim)) + + for gaussPt3D in range(len(gauss_weights_3D)): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(u, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + stress = self._compute_neo_hookean_stress(grad_u_q, params) + + SD_vec += w_q * gradphiXYZ_q.T @ stress.T * dv_q + + return SD_vec.reshape(-1, order='F') + + @staticmethod + def _compute_neo_hookean_stress_p(grad_u, p, params): + + # computes the first Piola-Kirchoff stress tensor (set J = 1) + E = params[0] + nu = params[1] + + mu = E / (2 * (1 + nu)) + lam = E * nu / ((1 + nu) * (1 - 2 * nu)) + + F = jnp.eye(3) + grad_u + F_inv_T = jnp.linalg.inv(F).T + T = mu * F @ F.T - 1 / 3 * jnp.trace(mu * F @ F.T) * jnp.eye(3) + p * jnp.eye(3) + P = T @ F_inv_T + + return P + + @staticmethod + def _compute_neo_hookean_stress(grad_u, params): + + # computes the first Piola-Kirchoff stress tensor + E = params[0] + nu = params[1] + + mu = E / (2 * (1 + nu)) + lam = E * nu / ((1 + nu) * (1 - 2 * nu)) + + F = jnp.eye(3) + grad_u + F_inv_T = jnp.linalg.inv(F).T + J = jnp.linalg.det(F) + P = mu * (F - F_inv_T) + lam * J * (J - 1) * F_inv_T + + return P diff --git a/cmad/fem_utils/models/thermo.py b/cmad/fem_utils/models/thermo.py new file mode 100755 index 0000000..a911074 --- /dev/null +++ b/cmad/fem_utils/models/thermo.py @@ -0,0 +1,116 @@ +import numpy as np +import jax.numpy as jnp + +from cmad.fem_utils.global_residuals.global_residual_thermo import Global_residual_thermo +from cmad.fem_utils.utils.fem_utils import (initialize_equation_thermo, + compute_shape_jacobian, + interpolate_vector_3D, + interpolate_scalar, + calc_element_traction_vector_3D) + +from functools import partial +import jax + + +class Thermo(Global_residual_thermo): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + pres_surf_flux = problem.get_convection_boundary_conditions() + + init_temp = problem.get_initial_temp() + + quad_rule_3D, shape_func_3D = problem.get_volume_basis_functions() + quad_rule_2D, shape_func_2D = problem.get_surface_basis_functions() + + eq_num, num_free_dof, num_pres_dof = initialize_equation_thermo(num_nodes, disp_node) + + num_steps, dt = problem.num_steps() + + elem_points = nodal_coords[volume_conn, :] + pres_surf_flux_points = nodal_coords[pres_surf_flux] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + # k, h, c_0, rho_0 + params = np.array([16.2, 30., 490., 7930.]) + + global_residual = partial(self._global_residual_thermo, + num_nodes_elem=num_nodes_elem, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + dt=dt) + + elem_surf_heat_flux = partial(self._calc_element_heat_flux_3D, + num_nodes_surf=num_nodes_surf, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + super().__init__(global_residual, elem_surf_heat_flux, + volume_conn, elem_points, eq_num, params, num_nodes_elem, + num_nodes_surf, num_free_dof, disp_node, disp_val, + num_pres_dof, pres_surf_flux_points, pres_surf_flux, + init_temp, dt) + + def _global_residual_thermo( + self, theta, theta_prev, params, elem_points, + num_nodes_elem, gauss_weights_3D, shape_3D, + dshape_3D, dt): + + k, h, c_0, rho_0 = params + residual = jnp.zeros(num_nodes_elem) + + for gaussPt3D in range(len(gauss_weights_3D)): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + theta_q = interpolate_scalar(theta, shape_3D_q) + theta_prev_q = interpolate_scalar(theta_prev, shape_3D_q) + + # theta-dot term + theta_dot_q = 1 / dt * (theta_q - theta_prev_q) + residual += w_q * shape_3D_q * rho_0 * c_0 * theta_dot_q * dv_q + + # heat flux term + grad_theta_q = gradphiXYZ_q @ theta + q0 = -k * grad_theta_q + residual -= w_q * gradphiXYZ_q.T @ q0 * dv_q + + return residual + + @staticmethod + def _calc_element_heat_flux_3D( + surf_theta, surf_points, params, num_nodes_surf, + gauss_weights_2D, shape_2D, dshape_2D): + + QEL = jnp.zeros(num_nodes_surf) + h = params[1] + theta_inf = 300. + + for gaussPt2D in range(len(gauss_weights_2D)): + w_q = gauss_weights_2D[gaussPt2D] + shape_tri_q = shape_2D[gaussPt2D, :] + dshape_tri_q = dshape_2D[gaussPt2D, :, :] + + J_q = dshape_tri_q @ surf_points + + da_q = jnp.linalg.norm(jnp.cross(J_q[0, :], J_q[1, :])) + + theta_q = interpolate_scalar(surf_theta, shape_tri_q) + + q_bar = h * (theta_q - theta_inf) + + QEL += w_q * shape_tri_q * q_bar * da_q + + return QEL \ No newline at end of file diff --git a/cmad/fem_utils/models/thermoelastic.py b/cmad/fem_utils/models/thermoelastic.py new file mode 100755 index 0000000..41959a8 --- /dev/null +++ b/cmad/fem_utils/models/thermoelastic.py @@ -0,0 +1,281 @@ +import numpy as np +import jax.numpy as jnp + +from cmad.fem_utils.global_residuals.global_residual_thermomech import Global_residual_thermomech +from cmad.fem_utils.utils.fem_utils import (initialize_equation, + compute_shape_jacobian, + interpolate_vector_3D, + interpolate_scalar, + calc_element_traction_vector_3D) + +from functools import partial +import jax + + +class Thermoelastic(Global_residual_thermomech): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + pres_surf_flux = problem.get_convection_boundary_conditions() + + init_temp = problem.get_initial_temp() + + quad_rule_3D, shape_func_3D = problem.get_volume_basis_functions() + quad_rule_2D, shape_func_2D = problem.get_surface_basis_functions() + + eq_num, num_free_dof, num_pres_dof = initialize_equation(num_nodes, dof_node, disp_node) + + num_steps, dt = problem.num_steps() + + elem_points = nodal_coords[volume_conn, :] + pres_surf_traction_points = nodal_coords[pres_surf_traction] + pres_surf_flux_points = nodal_coords[pres_surf_flux] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + # E_0, nu, k, h, c_0, rho_0, alpha_0 + params = np.array([200e9, 0.265, 16.2, 30., 490., 7930., 16.e-6]) + + global_residual = partial(self._global_residual_full, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + dt=dt) + + elem_surf_traction = partial(calc_element_traction_vector_3D, + num_nodes_surf=num_nodes_surf, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + elem_surf_heat_flux = partial(self._calc_element_heat_flux_3D, + num_nodes_surf=num_nodes_surf, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + super().__init__(global_residual, elem_surf_traction, elem_surf_heat_flux, + volume_conn, elem_points, eq_num, params, num_nodes_elem, + num_nodes_surf, dof_node, num_free_dof, disp_node, disp_val, + num_pres_dof, pres_surf_traction_points, pres_surf_traction, + surf_traction_vector, pres_surf_flux_points, pres_surf_flux, + init_temp, dt) + + + def _global_residual_full( + self, u, u_prev, v_prev, a_prev, params, elem_points, elem_init_temp, + num_nodes_elem, ndim, gauss_weights_3D, shape_3D, + dshape_3D, dt): + + momentum_residual = self._compute_momentum_residual(u, u_prev, v_prev, a_prev, params, + elem_points, elem_init_temp, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, + dshape_3D, dt) + + thermal_residual = self._compute_thermal_residual(u, u_prev, v_prev, a_prev, params, + elem_points, num_nodes_elem, ndim, + gauss_weights_3D, shape_3D, + dshape_3D, dt) + + return jnp.concatenate((momentum_residual, thermal_residual)) + + def _compute_momentum_residual( + self, u, u_prev, v_prev, a_prev, params, elem_points, elem_init_temp, + num_nodes_elem, ndim, gauss_weights_3D, shape_3D, dshape_3D, dt): + + SD_vec = jnp.zeros((num_nodes_elem, ndim)) + inertial_vec = jnp.zeros((num_nodes_elem, ndim)) + + rho_0 = params[5] + + elem_disp = u[0:num_nodes_elem * ndim] + elem_theta = u[num_nodes_elem * ndim:] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + elem_theta_prev = u_prev[num_nodes_elem * ndim:] + + # compute a_{n+1} + a = (elem_disp - elem_disp_prev - v_prev * dt) * 4 / dt ** 2 - a_prev + + for gaussPt3D in range(len(gauss_weights_3D)): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + + a_q, grad_a_q = interpolate_vector_3D(a, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + + theta_q = interpolate_scalar(elem_theta, shape_3D_q) + + # compute initial stress + theta_init_q = interpolate_scalar(elem_init_temp, shape_3D_q) + init_grad_u_q = jnp.zeros((ndim, ndim)) + init_stress_q = self._compute_stress(init_grad_u_q, theta_init_q, params) + + # Inertial term + inertial_vec += w_q * rho_0 * \ + jnp.column_stack([shape_3D_q, shape_3D_q, shape_3D_q]) * \ + a_q * dv_q + + # stress-divergence term + stress = self._compute_stress(grad_u_q, theta_q, params) + SD_vec += w_q * gradphiXYZ_q.T @ (stress - init_stress_q).T * dv_q + + residual_full = SD_vec + inertial_vec + return residual_full.reshape(-1, order='F') + + def _compute_thermal_residual( + self, u, u_prev, v_prev, a_prev, params, elem_points, + num_nodes_elem, ndim, gauss_weights_3D, shape_3D, + dshape_3D, dt): + + residual = jnp.zeros(num_nodes_elem) + + elem_disp = u[0:num_nodes_elem * ndim] + elem_theta = u[num_nodes_elem * ndim:] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + elem_theta_prev = u_prev[num_nodes_elem * ndim:] + + k = params[2] + + # compute a_{n+1} and v_{n+1} + a = (elem_disp - elem_disp_prev - v_prev * dt) * 4 / dt ** 2 - a_prev + v = v_prev + 1 / 2 * (a_prev + a) * dt + + for gaussPt3D in range(len(gauss_weights_3D)): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + + v_q, grad_v_q = interpolate_vector_3D(v, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + + theta_q = interpolate_scalar(elem_theta, shape_3D_q) + theta_prev_q = interpolate_scalar(elem_theta_prev, shape_3D_q) + + # theta-dot term + theta_dot_q = 1 / dt * (theta_q - theta_prev_q) + residual += w_q * shape_3D_q * \ + self.compute_heat_capacity(grad_u_q, theta_q, params) * \ + theta_dot_q * dv_q + + # heat flux term + grad_theta_q = gradphiXYZ_q @ elem_theta + q0 = -k * grad_theta_q + residual -= w_q * gradphiXYZ_q.T @ q0 * dv_q + + # stress power term + M_q = self._compute_stress_temp_modulus(grad_u_q, theta_q, params) + residual -= w_q * shape_3D_q * theta_q * jnp.sum(M_q * grad_v_q) * dv_q + + return residual + + @staticmethod + def _calc_element_heat_flux_3D( + surf_theta, surf_points, params, num_nodes_surf, + gauss_weights_2D, shape_2D, dshape_2D): + + QEL = jnp.zeros(num_nodes_surf) + E_0, nu, k, h, c_0, rho_0, alpha = params + theta_inf = 300. + + for gaussPt2D in range(len(gauss_weights_2D)): + w_q = gauss_weights_2D[gaussPt2D] + shape_tri_q = shape_2D[gaussPt2D, :] + dshape_tri_q = dshape_2D[gaussPt2D, :, :] + + J_q = dshape_tri_q @ surf_points + + da_q = jnp.linalg.norm(jnp.cross(J_q[0, :], J_q[1, :])) + + theta_q = interpolate_scalar(surf_theta, shape_tri_q) + + q_bar = h * (theta_q - theta_inf) + + QEL += w_q * shape_tri_q * q_bar * da_q + + return QEL + + @staticmethod + def _f(theta): + a = -5.5 + b = 1.0 + theta_0_m = 475. + + f = b * (theta / theta_0_m) ** a + b * (a - 1) + \ + (1 - a * b) * theta / theta_0_m + + return 1. + + def _compute_stress_temp_modulus(self, grad_u, theta, params): + df = jax.jacfwd(self._f) + + E_0, nu, k, h, c_0, rho_0, alpha = params + theta_0 = 300. + + K_0 = E_0 / (3 * (1 - 2 * nu)) + mu_0 = E_0 / (2 * (1 + nu)) + lam_0 = E_0 * nu / ((1 + nu) * (1 - 2 * nu)) + + eps = 1 / 2 * (grad_u + grad_u.T) + + sigma_0 = 2 * mu_0 * eps + lam_0 * jnp.trace(eps) * jnp.eye(3) + M = df(theta) * sigma_0 - (df(theta) * (theta - theta_0) + self._f(theta)) * \ + K_0 * alpha * 1 / (1 + jnp.trace(eps)) * jnp.eye(3) + + return M + + def compute_heat_capacity(self, grad_u, theta, params): + df = jax.jacfwd(self._f) + d2f = jax.jacfwd(jax.jacfwd(self._f)) + E_0, nu, k, h, c_0, rho_0, alpha = params + theta_0 = 300. + + K_0 = E_0 / (3 * (1 - 2 * nu)) + mu_0 = E_0 / (2 * (1 + nu)) + lam_0 = E_0 * nu / ((1 + nu) * (1 - 2 * nu)) + + eps = 1 / 2 * (grad_u + grad_u.T) + + sigma_0 = 2 * mu_0 * eps + lam_0 * jnp.trace(eps) * jnp.eye(3) + + c = -theta * d2f(theta) * 1 / 2 * jnp.sum(eps * sigma_0) + \ + (theta * d2f(theta) * (theta - theta_0) + 2 * theta * df(theta)) * \ + K_0 * alpha * jnp.log(1 + jnp.trace(eps)) + c_0 * rho_0 + + return c + + def _compute_stress(self, grad_u, theta, params): + # computes the stress tensor + E_0, nu, k, h, c_0, rho_0, alpha = params + theta_0 = 300. + + K_0 = E_0 / (3 * (1 - 2 * nu)) + mu_0 = E_0 / (2 * (1 + nu)) + lam_0 = E_0 * nu / ((1 + nu) * (1 - 2 * nu)) + + eps = 1 / 2 * (grad_u + grad_u.T) + sigma_0 = 2 * mu_0 * eps + lam_0 * jnp.trace(eps) * jnp.eye(3) + sigma_theta = K_0 * alpha * (theta - theta_0) / (1 + jnp.trace(eps)) * jnp.eye(3) + sigma = self._f(theta) * (sigma_0 - sigma_theta) + + return sigma diff --git a/cmad/fem_utils/models/thermoplastic_finite.py b/cmad/fem_utils/models/thermoplastic_finite.py new file mode 100644 index 0000000..95a5912 --- /dev/null +++ b/cmad/fem_utils/models/thermoplastic_finite.py @@ -0,0 +1,539 @@ +import numpy as np +import jax.numpy as jnp +from functools import partial +from jax.tree_util import tree_map + +from cmad.fem_utils.global_residuals.global_residual_thermoplastic import Global_residual_thermoplastic +from cmad.fem_utils.utils.fem_utils import (initialize_equation, + compute_shape_jacobian, + interpolate_vector_3D, + interpolate_scalar, + calc_element_traction_vector_3D) +from cmad.models.var_types import (get_sym_tensor_from_vector, + get_vector_from_sym_tensor) +from cmad.models.elastic_stress import two_mu_scale_factor +from cmad.parameters.parameters import Parameters +from cmad.models.deformation_types import DefType +from jax.lax import cond +from cmad.fem_utils.interpolants import interpolants +from cmad.fem_utils.quadrature import quadrature_rule + + +def create_J2_parameters(): + + E = 206.9e3 # MPa + nu = 0.29 + Y_0 = 450.0 # MPa + K_0 = 129.24 # MPa + S_0 = 265.0 # MPa + D = 16.92 + + k = 45.0 # W/(m K) + h = 30. # W/(m^2 K) + c_0 = 1328.9 # J/(kg K) + rho_0 = 2700.0 # kg/m^3 + alpha_0 = 1e-05 # 1/K + theta_0 = 300. # K + + w = 0.002 # 1/K + + elastic_params = {"E": E, "nu": nu} + J2_effective_stress_params = {"J2": 0.} + initial_yield_params = {"Y_0": Y_0} + voce_params = {"S_0": S_0, "D": D} + linear_params = {"K_0": K_0} + hardening_params = {"voce": voce_params, "linear": linear_params} + thermal_params = {"k": k, "h": h, "c_0": c_0, "rho_0": rho_0, "alpha_0": alpha_0, "theta_0": theta_0} + softening_params = {"w": w} + + J2_values = { + "rotation matrix": np.eye(3), + "elastic": elastic_params, + "thermal": thermal_params, + "plastic": { + "effective stress": J2_effective_stress_params, + "flow stress": { + "initial yield": initial_yield_params, + "softening": softening_params, + "hardening": hardening_params}}} + + J2_parameters = \ + Parameters(J2_values) + + return J2_parameters + +def evaluate_yield_function(s, alpha, theta, params): + plastic_params = params["plastic"] + thermal_params = params["thermal"] + Y_0 = plastic_params["flow stress"]["initial yield"]["Y_0"] + hardening_params = plastic_params["flow stress"]["hardening"] + softening_params = plastic_params["flow stress"]["softening"] + voce_params = hardening_params["voce"] + linear_params = hardening_params["linear"] + S_0 = voce_params["S_0"] + D = voce_params["D"] + K_0 = linear_params["K_0"] + w = softening_params["w"] + theta_0 = thermal_params["theta_0"] + + # temperature dependent parameters + Y = Y_0 * (1 - w * (theta - theta_0)) + K = K_0 * (1 - w * (theta - theta_0)) + S = S_0 * (1 - w * (theta - theta_0)) + + s_norm = jnp.sqrt(jnp.sum(s * s)) + flow_stress = jnp.sqrt(2 / 3) * (Y + K * alpha + S * (1 - jnp.exp(-D * alpha))) + return (s_norm - flow_stress) / two_mu_scale_factor(params) + +def evaluate_current_yield_stress(alpha, theta, params): + plastic_params = params["plastic"] + thermal_params = params["thermal"] + Y_0 = plastic_params["flow stress"]["initial yield"]["Y_0"] + hardening_params = plastic_params["flow stress"]["hardening"] + softening_params = plastic_params["flow stress"]["softening"] + voce_params = hardening_params["voce"] + linear_params = hardening_params["linear"] + S_0 = voce_params["S_0"] + D = voce_params["D"] + K_0 = linear_params["K_0"] + w = softening_params["w"] + theta_0 = thermal_params["theta_0"] + + # temperature dependent parameters + Y = Y_0 * (1 - w * (theta - theta_0)) + K = K_0 * (1 - w * (theta - theta_0)) + S = S_0 * (1 - w * (theta - theta_0)) + + return Y + K * alpha + S * (1 - jnp.exp(-D * alpha)) + +def compute_yield_normal(s): + s_norm = jnp.sqrt(jnp.sum(s * s)) + return s / s_norm + +class Thermoplastic_finite(Global_residual_thermoplastic): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + pres_surf_flux = problem.get_convection_boundary_conditions() + + init_temp = problem.get_initial_temp() + + quad_rule_3D, shape_func_3D = problem.get_volume_basis_functions() + quad_rule_2D, shape_func_2D = problem.get_surface_basis_functions() + num_quad_pts = len(quad_rule_3D.wgauss) + + eq_num, num_free_dof, num_pres_dof = initialize_equation(num_nodes, dof_node, disp_node) + + num_steps, dt = problem.num_steps() + + elem_points = nodal_coords[volume_conn, :] + pres_surf_traction_points = nodal_coords[pres_surf_traction] + pres_surf_flux_points = nodal_coords[pres_surf_flux] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + params = create_J2_parameters() + + def_type = DefType.FULL_3D + + num_local_resid_dofs = 8 + init_xi = np.zeros(num_local_resid_dofs * num_quad_pts) + init_xi[-2 * num_quad_pts:-num_quad_pts] = np.ones(num_quad_pts) + + + elem_local_resid = partial(self._elem_local_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + num_local_resid_dofs=num_local_resid_dofs) + + elem_global_resid = partial(self._elem_global_resid_full, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + dt=dt) + + elem_surf_traction = partial(calc_element_traction_vector_3D, + num_nodes_surf=num_nodes_surf, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + elem_surf_heat_flux = partial(self._calc_element_heat_flux_3D, + num_nodes_surf=num_nodes_surf, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + super().__init__(elem_global_resid, elem_local_resid, elem_surf_traction, elem_surf_heat_flux, + volume_conn, elem_points, eq_num, params, num_nodes_elem, num_nodes_surf, dof_node, + num_quad_pts, num_free_dof, disp_node, disp_val, init_xi, num_pres_dof, + num_elem, pres_surf_traction_points, pres_surf_traction, surf_traction_vector, + pres_surf_flux_points, pres_surf_flux, init_temp, dt) + + def _elem_global_resid_full( + self, u, u_prev, params, xi, xi_prev, elem_points, + num_nodes_elem, ndim, gauss_weights_3D, shape_3D, dshape_3D, dt): + + momentum_residual = self._elem_momentum_resid(u, u_prev, params, xi, xi_prev, elem_points, + num_nodes_elem, ndim, gauss_weights_3D, shape_3D, dshape_3D) + + thermal_residual = self._elem_thermal_residual(u, u_prev, params, xi, xi_prev, elem_points, + num_nodes_elem, ndim, gauss_weights_3D, shape_3D, dshape_3D, dt) + + DB_residual = self._elem_DB_residual(u, params, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D) + + return jnp.concatenate((momentum_residual, thermal_residual, DB_residual)) + + def _elem_local_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, num_local_resid_dofs): + + elem_disp = u[0:num_nodes_elem * ndim] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + elem_theta = u[num_nodes_elem * ndim:num_nodes_elem * (ndim + 1)] + + num_quad_pts = len(gauss_weights_3D) + + elem_local_residual = jnp.zeros((num_quad_pts, num_local_resid_dofs)) + + zeta_dofs = 6 + elem_Gamma_bar = xi[:num_quad_pts * zeta_dofs].reshape(num_quad_pts, zeta_dofs) + elem_Gamma_bar_prev = xi_prev[:num_quad_pts * zeta_dofs].reshape(num_quad_pts, zeta_dofs) + elem_I_bar = xi[-2 * num_quad_pts:-num_quad_pts] + elem_I_bar_prev = xi_prev[-2 * num_quad_pts:-num_quad_pts] + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + + for gaussPt3D in range(num_quad_pts): + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_3D(elem_disp_prev, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + theta_q = interpolate_scalar(elem_theta, shape_3D_q) + + F_q = grad_u_q + jnp.eye(ndim) + F_prev_q = grad_u_prev_q + jnp.eye(ndim) + + Gamma_bar_q = elem_Gamma_bar[gaussPt3D] + Gamma_bar_prev_q = elem_Gamma_bar_prev[gaussPt3D] + I_bar_q = elem_I_bar[gaussPt3D] + I_bar_prev_q = elem_I_bar_prev[gaussPt3D] + alpha_q = elem_alpha[gaussPt3D] + alpha_prev_q = elem_alpha_prev[gaussPt3D] + + xi_recast = [Gamma_bar_q, I_bar_q, alpha_q] + xi_prev_recast = [Gamma_bar_prev_q, I_bar_prev_q, alpha_prev_q] + + elem_residual_q = self._local_resid_material_pt(theta_q, F_q, F_prev_q, params, xi_recast, xi_prev_recast) + + elem_local_residual = elem_local_residual.at[gaussPt3D, :].set(elem_residual_q) + + return elem_local_residual.reshape(-1) + + def _elem_momentum_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D): + + # extract element displacement and pressure + elem_disp = u[0:num_nodes_elem * ndim] + p = u[-num_nodes_elem:] + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + mu = E / (2 * (1 + nu)) + + # stress divergence residual + S_D_vec = jnp.zeros((num_nodes_elem, ndim)) + + num_quad_pts = len(gauss_weights_3D) + + zeta_dofs = 6 + elem_Gamma_bar = xi[:num_quad_pts * zeta_dofs].reshape(num_quad_pts, zeta_dofs) + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + p_q = interpolate_scalar(p, shape_3D_q) + grad_p_q = gradphiXYZ_q @ p + + F_q = grad_u_q + jnp.eye(ndim) + F_inv_q = jnp.linalg.inv(F_q) + J_q = jnp.linalg.det(F_q) + + elem_Gamma_bar_q = get_sym_tensor_from_vector(elem_Gamma_bar[gaussPt3D], 3) + + P = mu * elem_Gamma_bar_q @ F_inv_q.T + J_q * p_q * F_inv_q.T + S_D_vec += w_q * gradphiXYZ_q.T @ P.T * dv_q + + return S_D_vec.reshape(-1, order='F') + + def _elem_thermal_residual( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, dt): + + thermal_resid = jnp.zeros(num_nodes_elem) + + elem_disp = u[0:num_nodes_elem * ndim] + elem_theta = u[num_nodes_elem * ndim:num_nodes_elem * (ndim + 1)] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + elem_theta_prev = u_prev[num_nodes_elem * ndim:num_nodes_elem * (ndim + 1)] + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + K_param = E / (3 * (1 - 2 * nu)) + + rho_0 = params['thermal']['rho_0'] + alpha_0 = params['thermal']['alpha_0'] + k = params['thermal']['k'] + c_0 = params['thermal']['c_0'] + + # Taylor Quinney + beta = 0.9 + + num_quad_pts = len(gauss_weights_3D) + + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_3D (elem_disp_prev, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + + theta_q = interpolate_scalar(elem_theta, shape_3D_q) + theta_prev_q = interpolate_scalar(elem_theta_prev, shape_3D_q) + + F_q = grad_u_q + jnp.eye(ndim) + F_inv_q = jnp.linalg.inv(F_q) + J_q = jnp.linalg.det(F_q) + + F_prev_q = grad_u_prev_q + jnp.eye(ndim) + J_prev_q = jnp.linalg.det(F_prev_q) + + # theta-dot term + theta_dot_q = 1 / dt * (theta_q - theta_prev_q) + thermal_resid += w_q * shape_3D_q * c_0 * rho_0 * theta_dot_q * dv_q + + # heat flux term + grad_theta_q = gradphiXYZ_q @ elem_theta + q0 = -J_q * k * F_inv_q @ F_inv_q.T @ grad_theta_q + thermal_resid -= w_q * gradphiXYZ_q.T @ q0 * dv_q + + # mechanical dissipation + alpha_q = elem_alpha[gaussPt3D] + alpha_prev_q = elem_alpha_prev[gaussPt3D] + y_q = evaluate_current_yield_stress(alpha_q, theta_q, params) + D_mech = beta * (alpha_q - alpha_prev_q) / dt * y_q * 1.e6 + thermal_resid -= w_q * shape_3D_q * D_mech * dv_q + + # structural heating + eta_q = - K_param / 2 * alpha_0 * ((J_q ** 2 - 1) / J_q) + eta_prev_q = - K_param / 2 * alpha_0 * ((J_prev_q ** 2 - 1) / J_prev_q) + H_q = - theta_q * (eta_q - eta_prev_q) / dt * 1.e6 + thermal_resid += w_q * shape_3D_q * H_q * dv_q + + return thermal_resid * 1.e-4 + + def _elem_DB_residual( + self, u, params, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D): + + elem_disp = u[0:num_nodes_elem * ndim] + elem_theta = u[num_nodes_elem * ndim:num_nodes_elem * (ndim + 1)] + p = u[-num_nodes_elem:] + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + G_param = E / (2 * (1 + nu)) + K_param = E / (3 * (1 - 2 * nu)) + alpha = 1000. + + alpha_0 = params['thermal']['alpha_0'] + theta_0 = params['thermal']['theta_0'] + + # incompressibility residual + H = 0 + G = jnp.zeros(num_nodes_elem) + DB_residual = jnp.zeros(num_nodes_elem) + + num_quad_pts = len(gauss_weights_3D) + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + p_q = interpolate_scalar(p, shape_3D_q) + theta_q = interpolate_scalar(elem_theta, shape_3D_q) + + F_q = grad_u_q + jnp.eye(ndim) + J_q = jnp.linalg.det(F_q) + + DB_residual += w_q * shape_3D_q * (1 / 2 * (J_q ** 2 - 1) / J_q \ + - 1 / 2 * alpha_0 * (theta_q - theta_0) * (J_q ** 2 + 1) / J_q ** 2) * dv_q + + # DB contibution (projection onto constant polynomial space) + H += w_q * 1.0 * dv_q + G += w_q * shape_3D_q * dv_q + + # (N.T)(alpha / G)(N)(p) + DB_residual -= (alpha / G_param + 1 / K_param) * w_q \ + * shape_3D_q * p_q * dv_q + + # alpha / G * (G.T)(H^-1)(G)(p) + DB_residual += alpha / G_param * G * (1 / H) * jnp.dot(G, p) + + return DB_residual + + def _local_resid_material_pt(self, theta, F, F_prev, params, xi, xi_prev): + # material parameters + E = params['elastic']['E'] + nu = params['elastic']['nu'] + mu = E / (2 * (1 + nu)) + + # extract local state variables + Gamma_bar = get_sym_tensor_from_vector(xi[0], 3) + I_bar = xi[1] + alpha = xi[2] + + Gamma_bar_prev = get_sym_tensor_from_vector(xi_prev[0], 3) + I_bar_prev = xi_prev[1] + alpha_prev = xi_prev[2] + + # isochoric Deformation Gradients + F_bar = 1 / jnp.cbrt(jnp.linalg.det(F)) * F + F_bar_prev = 1 / jnp.cbrt(jnp.linalg.det(F_prev)) * F_prev + F_bar_prev_inv = jnp.linalg.inv(F_bar_prev) + + b_bar_prev = Gamma_bar_prev + I_bar_prev * jnp.eye(3) + + # define trial variables + b_bar_trial = F_bar @ F_bar_prev_inv @ b_bar_prev @ F_bar_prev_inv.T @ F_bar.T + Gamma_bar_trial = b_bar_trial - 1 / 3 * jnp.trace(b_bar_trial) * jnp.eye(3) + dev_tau_trial = mu * Gamma_bar_trial + phi_trial = evaluate_yield_function(dev_tau_trial, alpha_prev, theta, params) + + return self._cond_residual(phi_trial, Gamma_bar, I_bar, alpha, b_bar_trial, + dev_tau_trial, alpha_prev, theta, params, 1.e-14) + + @staticmethod + def _elastic_path(Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, theta, params): + I_bar_trial = 1 / 3 * jnp.trace(b_bar_trial) + Gamma_bar_trial = b_bar_trial - I_bar_trial * jnp.eye(3) + C_Gamma_bar_elastic = get_vector_from_sym_tensor(Gamma_bar - Gamma_bar_trial, 3) + C_I_bar_elastic = I_bar - I_bar_trial + C_alpha_elastic = alpha - alpha_prev + return jnp.r_[C_Gamma_bar_elastic, C_I_bar_elastic, C_alpha_elastic] + + @staticmethod + def _plastic_path(Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, theta, params): + E = params['elastic']['E'] + nu = params['elastic']['nu'] + mu = E / (2 * (1 + nu)) + + I_bar_trial = 1 / 3 * jnp.trace(b_bar_trial) + Gamma_bar_trial = b_bar_trial - I_bar_trial * jnp.eye(3) + + n = compute_yield_normal(dev_tau_trial) + C_Gamma_bar_plastic = get_vector_from_sym_tensor(Gamma_bar - Gamma_bar_trial + 2 * jnp.sqrt(3 / 2) \ + * (alpha - alpha_prev) * I_bar_trial * n, 3) + + dev_tau = mu * (Gamma_bar_trial - 2 * jnp.sqrt(3 / 2) * (alpha - alpha_prev) * I_bar_trial * n) + C_alpha_plastic = evaluate_yield_function(dev_tau, alpha, theta, params) + + C_I_bar_plastic = jnp.linalg.det(Gamma_bar + I_bar * jnp.eye(3)) - 1 + + return jnp.r_[C_Gamma_bar_plastic, C_I_bar_plastic, C_alpha_plastic] + + def _cond_residual( + self, phi, Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, theta, params, tol): + + def inner_cond_residual( + Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, theta, params): + return cond(jnp.abs(phi) < tol, self._plastic_path, self._elastic_path, + Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, theta, params) + + def outer_cond_residual( + Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, theta, params): + return cond(phi > tol, self._plastic_path, inner_cond_residual, + Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, theta, params) + + return outer_cond_residual(Gamma_bar, I_bar, alpha, b_bar_trial, dev_tau_trial, alpha_prev, theta, params) + + + @staticmethod + def _calc_element_heat_flux_3D( + surf_theta, surf_points, params, num_nodes_surf, + gauss_weights_2D, shape_2D, dshape_2D): + + QEL = jnp.zeros(num_nodes_surf) + h = params['thermal']['h'] + theta_inf = 300. + + for gaussPt2D in range(len(gauss_weights_2D)): + w_q = gauss_weights_2D[gaussPt2D] + shape_tri_q = shape_2D[gaussPt2D, :] + dshape_tri_q = dshape_2D[gaussPt2D, :, :] + + J_q = dshape_tri_q @ surf_points + + da_q = jnp.linalg.norm(jnp.cross(J_q[0, :], J_q[1, :])) + + theta_q = interpolate_scalar(surf_theta, shape_tri_q) + + q_bar = h * (theta_q - theta_inf) + + QEL += w_q * shape_tri_q * q_bar * da_q + + return QEL + + + + + + + + + + + + + + + + + + diff --git a/cmad/fem_utils/models/thermoplastic_small.py b/cmad/fem_utils/models/thermoplastic_small.py new file mode 100644 index 0000000..a1381f6 --- /dev/null +++ b/cmad/fem_utils/models/thermoplastic_small.py @@ -0,0 +1,431 @@ +import numpy as np +import jax +import jax.numpy as jnp +from functools import partial +from jax.tree_util import tree_map + +from cmad.fem_utils.global_residuals.global_residual_thermoplastic import Global_residual_thermoplastic +from cmad.fem_utils.utils.fem_utils import (initialize_equation, + compute_shape_jacobian, + interpolate_vector_3D, + interpolate_scalar, + calc_element_traction_vector_3D) +from cmad.parameters.parameters import Parameters +from cmad.models.deformation_types import DefType +from cmad.models.small_elastic_plastic import SmallElasticPlastic +from cmad.models.var_types import get_sym_tensor_from_vector + +def create_J2_parameters(): + # E = 200.e3 + # nu = 0.249 + # Y = 349. + # K = 1.e5 + # S = 1.23e3 + # D = 0.55 + + E = 69.e3 + nu = 0.31 + Y = 128. + K = 1.e5 + S = 230.42 + D = 11.37 + + # thermal parameters + k = 16.2 + h = 30. + c_0 = 490. + rho_0 = 7930. + alpha_0 = 16.e-6 + + elastic_params = {"E": E, "nu": nu} + J2_effective_stress_params = {"J2": 0.} + initial_yield_params = {"Y": Y} + voce_params = {"S": S, "D": D} + linear_params = {"K": K} + hardening_params = {"voce": voce_params} + thermal_params = {"k": k, "h": h, "c_0": c_0, "rho_0": rho_0, "alpha_0": alpha_0} + + Y_log_scale = np.array([48.]) + K_log_scale = np.array([100.]) + S_log_scale = np.array([106.]) + D_log_scale = np.array([25.]) + + J2_values = { + "rotation matrix": np.eye(3), + "elastic": elastic_params, + "thermal": thermal_params, + "plastic": { + "effective stress": J2_effective_stress_params, + "flow stress": { + "initial yield": initial_yield_params, + "hardening": hardening_params}}} + + J2_active_flags = J2_values.copy() + J2_active_flags = tree_map(lambda a: False, J2_active_flags) + J2_active_flags["plastic"]["flow stress"] = tree_map( + lambda x: True, J2_active_flags["plastic"]["flow stress"]) + + J2_transforms = J2_values.copy() + J2_transforms = tree_map(lambda a: None, J2_transforms) + J2_flow_stress_transforms = J2_transforms["plastic"]["flow stress"] + J2_flow_stress_transforms["initial yield"]["Y"] = Y_log_scale + # J2_flow_stress_transforms["hardening"]["linear"]["K"] = K_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["S"] = S_log_scale + J2_flow_stress_transforms["hardening"]["voce"]["D"] = D_log_scale + + J2_parameters = \ + Parameters(J2_values, J2_active_flags, J2_transforms) + return J2_parameters + +class Thermoplastic_small(Global_residual_thermoplastic): + def __init__(self, problem): + dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + nodal_coords, volume_conn, ndim = problem.get_mesh_properties() + + disp_node, disp_val, pres_surf_traction, surf_traction_vector \ + = problem.get_boundary_conditions() + + pres_surf_flux = problem.get_convection_boundary_conditions() + + init_temp = problem.get_initial_temp() + + quad_rule_3D, shape_func_3D = problem.get_volume_basis_functions() + quad_rule_2D, shape_func_2D = problem.get_surface_basis_functions() + num_quad_pts = len(quad_rule_3D.wgauss) + + eq_num, num_free_dof, num_pres_dof = initialize_equation(num_nodes, dof_node, disp_node) + + num_steps, dt = problem.num_steps() + + elem_points = nodal_coords[volume_conn, :] + pres_surf_traction_points = nodal_coords[pres_surf_traction] + pres_surf_flux_points = nodal_coords[pres_surf_flux] + + print('Number of elements: ', num_elem) + print('Number of free DOFS: ', num_free_dof) + + params = create_J2_parameters() + + def_type = DefType.FULL_3D + + # material point model + mat_point_model = SmallElasticPlastic(params, def_type=def_type) + self._local_residual_material_point = mat_point_model.get_local_residual() + self._cauchy_fun = mat_point_model.get_cauchy() + num_local_resid_dofs = mat_point_model.num_dofs + + init_xi = np.zeros(num_local_resid_dofs * num_quad_pts) + + elem_local_resid = partial(self._elem_local_resid, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + num_local_resid_dofs=num_local_resid_dofs) + + elem_global_resid = partial(self._elem_global_resid_full, + num_nodes_elem=num_nodes_elem, + ndim=ndim, + gauss_weights_3D=quad_rule_3D.wgauss, + shape_3D=shape_func_3D.values, + dshape_3D=shape_func_3D.gradients, + dt=dt) + + elem_surf_traction = partial(calc_element_traction_vector_3D, + num_nodes_surf=num_nodes_surf, + ndim=ndim, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + elem_surf_heat_flux = partial(self._calc_element_heat_flux_3D, + num_nodes_surf=num_nodes_surf, + gauss_weights_2D=quad_rule_2D.wgauss, + shape_2D=shape_func_2D.values, + dshape_2D=shape_func_2D.gradients) + + super().__init__(elem_global_resid, elem_local_resid, elem_surf_traction, elem_surf_heat_flux, + volume_conn, elem_points, eq_num, params, num_nodes_elem, num_nodes_surf, dof_node, + num_quad_pts, num_free_dof, disp_node, disp_val, init_xi, num_pres_dof, + num_elem, pres_surf_traction_points, pres_surf_traction, surf_traction_vector, + pres_surf_flux_points, pres_surf_flux, init_temp, dt) + + def _elem_local_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, num_local_resid_dofs): + + elem_disp = u[0:num_nodes_elem * ndim] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + + num_quad_pts = len(gauss_weights_3D) + + elem_local_residual = jnp.zeros((num_quad_pts, num_local_resid_dofs)) + + ep_dofs = 6 + elem_ep = xi[:num_quad_pts * ep_dofs] + elem_ep_prev = xi_prev[:num_quad_pts * ep_dofs] + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + + for gaussPt3D in range(num_quad_pts): + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_3D(elem_disp_prev, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + + F_q = [grad_u_q + jnp.eye(ndim)] + F_prev_q = [grad_u_prev_q + jnp.eye(ndim)] + + ep_q = elem_ep[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + ep_prev_q = elem_ep_prev[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + alpha_q = jnp.array([elem_alpha[gaussPt3D - num_quad_pts]]) + alpha_prev_q = jnp.array([elem_alpha_prev[gaussPt3D - num_quad_pts]]) + + xi_recast = [ep_q, alpha_q] + xi_prev_recast = [ep_prev_q, alpha_prev_q] + + elem_residual_q = self._local_residual_material_point(xi_recast, xi_prev_recast, params, F_q, F_prev_q) + + elem_local_residual = elem_local_residual.at[gaussPt3D, :].set(elem_residual_q) + + return elem_local_residual.reshape(-1) + + def _elem_global_resid_full( + self, u, u_prev, params, xi, xi_prev, elem_points, + num_nodes_elem, ndim, gauss_weights_3D, shape_3D, dshape_3D, dt): + + momentum_residual = self._elem_momentum_resid(u, u_prev, params, xi, xi_prev, elem_points, + num_nodes_elem, ndim, gauss_weights_3D, shape_3D, dshape_3D) + + thermal_residual = self._elem_thermal_residual(u, u_prev, params, xi, xi_prev, elem_points, + num_nodes_elem, ndim, gauss_weights_3D, shape_3D, dshape_3D, dt) + + DB_residual = self._elem_DB_residual(u, params, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D) + + return jnp.concatenate((momentum_residual, thermal_residual, DB_residual)) + + def _elem_momentum_resid( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D): + + # reference temperature + theta_0 = 300. + + # extract element displacement and pressure + elem_disp = u[0:num_nodes_elem * ndim] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + elem_theta = u[num_nodes_elem * ndim:num_nodes_elem * (ndim + 1)] + p = u[-num_nodes_elem:] + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + K_param = E / (3 * (1 - 2 * nu)) + + rho_0 = params['thermal']['rho_0'] + alpha_0 = params['thermal']['alpha_0'] + + # stress divergence residual + S_D_vec = jnp.zeros((num_nodes_elem, ndim)) + + num_quad_pts = len(gauss_weights_3D) + + ep_dofs = 6 + elem_ep = xi[:num_quad_pts * ep_dofs] + elem_ep_prev = xi_prev[:num_quad_pts * ep_dofs] + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_3D(elem_disp_prev, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + theta_q = interpolate_scalar(elem_theta, shape_3D_q) + p_q = interpolate_scalar(p, shape_3D_q) + + F_q = [grad_u_q + jnp.eye(ndim)] + F_prev_q = [grad_u_prev_q + jnp.eye(ndim)] + + ep_q = elem_ep[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + ep_prev_q = elem_ep_prev[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + alpha_q = jnp.array([elem_alpha[gaussPt3D - num_quad_pts]]) + alpha_prev_q = jnp.array([elem_alpha_prev[gaussPt3D - num_quad_pts]]) + + xi_recast = [ep_q, alpha_q] + xi_prev_recast = [ep_prev_q, alpha_prev_q] + + elastic_stress = self._cauchy_fun(xi_recast, xi_prev_recast, params, F_q, F_prev_q) + dev_elastic_stress = elastic_stress - 1 / ndim * jnp.trace(elastic_stress) * jnp.eye(ndim) + stress = dev_elastic_stress + p_q * jnp.eye(ndim) + + #stress divergence term + S_D_vec += w_q * gradphiXYZ_q.T @ stress.T * dv_q + + return (S_D_vec).reshape(-1, order='F') + + def _elem_DB_residual( + self, u, params, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D): + + elem_disp = u[0:num_nodes_elem * ndim] + elem_theta = u[num_nodes_elem * ndim:num_nodes_elem * (ndim + 1)] + p = u[-num_nodes_elem:] + + alpha_0 = params['thermal']['alpha_0'] + theta_0 = 300. + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + G_param = E / (2 * (1 + nu)) + K_param = E / (3 * (1 - 2 * nu)) + alpha = 100. + + # incompressibility residual + H = 0 + G = jnp.zeros(num_nodes_elem) + DB_residual = jnp.zeros(num_nodes_elem) + + num_quad_pts = len(gauss_weights_3D) + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, gradphiXYZ_q, num_nodes_elem) + p_q = interpolate_scalar(p, shape_3D_q) + theta_q = interpolate_scalar(elem_theta, shape_3D_q) + + DB_residual += w_q * shape_3D_q * (jnp.trace(grad_u_q) \ + - alpha_0 * (theta_q - theta_0) / (1 + jnp.trace(grad_u_q))) * dv_q + + # DB contibution (projection onto constant polynomial space) + H += w_q * 1.0 * dv_q + G += w_q * shape_3D_q * dv_q + + # (N.T)(alpha / G)(N)(p) + DB_residual -= (alpha / G_param + 1 / K_param) * w_q \ + * shape_3D_q * p_q * dv_q + + # alpha / G * (G.T)(H^-1)(G)(p) + DB_residual += alpha / G_param * G * (1 / H) * jnp.dot(G, p) + + return DB_residual + + def _elem_thermal_residual( + self, u, u_prev, params, xi, xi_prev, elem_points, num_nodes_elem, + ndim, gauss_weights_3D, shape_3D, dshape_3D, dt): + + thermal_resid = jnp.zeros(num_nodes_elem) + + elem_disp = u[0:num_nodes_elem * ndim] + elem_theta = u[num_nodes_elem * ndim:num_nodes_elem * (ndim + 1)] + elem_disp_prev = u_prev[0:num_nodes_elem * ndim] + elem_theta_prev = u_prev[num_nodes_elem * ndim:num_nodes_elem * (ndim + 1)] + + E = params['elastic']['E'] + nu = params['elastic']['nu'] + K_param = E / (3 * (1 - 2 * nu)) + + rho_0 = params['thermal']['rho_0'] + alpha_0 = params['thermal']['alpha_0'] + k = params['thermal']['k'] + c_0 = params['thermal']['c_0'] + + # Taylor Quinney + beta = 0.9 + + num_quad_pts = len(gauss_weights_3D) + ep_dofs = 6 + elem_ep = xi[:num_quad_pts * ep_dofs] + elem_ep_prev = xi_prev[:num_quad_pts * ep_dofs] + elem_alpha = xi[-num_quad_pts:] + elem_alpha_prev = xi_prev[-num_quad_pts:] + + for gaussPt3D in range(num_quad_pts): + w_q = gauss_weights_3D[gaussPt3D] + + dshape_3D_q = dshape_3D[gaussPt3D, :, :] + shape_3D_q = shape_3D[gaussPt3D, :] + + dv_q, gradphiXYZ_q = compute_shape_jacobian(elem_points, dshape_3D_q) + + u_q, grad_u_q = interpolate_vector_3D(elem_disp, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + u_prev_q, grad_u_prev_q = interpolate_vector_3D (elem_disp_prev, shape_3D_q, + gradphiXYZ_q, num_nodes_elem) + + theta_q = interpolate_scalar(elem_theta, shape_3D_q) + theta_prev_q = interpolate_scalar(elem_theta_prev, shape_3D_q) + + ep_q = elem_ep[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + ep_prev_q = elem_ep_prev[gaussPt3D * ep_dofs: (gaussPt3D + 1) * ep_dofs] + alpha_q = jnp.array([elem_alpha[gaussPt3D - num_quad_pts]]) + alpha_prev_q = jnp.array([elem_alpha_prev[gaussPt3D - num_quad_pts]]) + + F_q = [grad_u_q + jnp.eye(ndim)] + F_prev_q = [grad_u_prev_q + jnp.eye(ndim)] + xi_recast = [ep_q, alpha_q] + xi_prev_recast = [ep_prev_q, alpha_prev_q] + + # theta-dot term + theta_dot_q = 1 / dt * (theta_q - theta_prev_q) + thermal_resid += w_q * shape_3D_q * c_0 * rho_0 * theta_dot_q * dv_q + + # heat flux term + grad_theta_q = gradphiXYZ_q @ elem_theta + q0 = -k * grad_theta_q + thermal_resid -= w_q * gradphiXYZ_q.T @ q0 * dv_q + + # plastic dissipation term + ep_dot_q = get_sym_tensor_from_vector((ep_q - ep_prev_q) / dt, ndim) + elastic_stress = self._cauchy_fun(xi_recast, xi_prev_recast, params, F_q, F_prev_q) + thermal_resid -= w_q * shape_3D_q * beta * jnp.sum(elastic_stress * ep_dot_q) * 1.e6 * dv_q + + # stress power term + e_q = 1 / 2 * (grad_u_q + grad_u_q.T) + e_prev_q = 1 / 2 * (grad_u_prev_q + grad_u_prev_q.T) + e_dot_q = (e_q - e_prev_q) / dt + ee_dot_q = e_dot_q - ep_dot_q + M_q = -K_param * alpha_0 * 1 / (1 + jnp.trace(grad_u_q)) * jnp.eye(ndim) + thermal_resid -= w_q * shape_3D_q * theta_q * jnp.sum(M_q * ee_dot_q) * 1.e6 * dv_q + + return thermal_resid * 1.e-4 + + @staticmethod + def _calc_element_heat_flux_3D( + surf_theta, surf_points, params, num_nodes_surf, + gauss_weights_2D, shape_2D, dshape_2D): + + QEL = jnp.zeros(num_nodes_surf) + h = params['thermal']['h'] + theta_inf = 300. + + for gaussPt2D in range(len(gauss_weights_2D)): + w_q = gauss_weights_2D[gaussPt2D] + shape_tri_q = shape_2D[gaussPt2D, :] + dshape_tri_q = dshape_2D[gaussPt2D, :, :] + + J_q = dshape_tri_q @ surf_points + + da_q = jnp.linalg.norm(jnp.cross(J_q[0, :], J_q[1, :])) + + theta_q = interpolate_scalar(surf_theta, shape_tri_q) + + q_bar = h * (theta_q - theta_inf) + + QEL += w_q * shape_tri_q * q_bar * da_q + + return QEL diff --git a/cmad/fem_utils/nonlinear_solvers/nonlinear_solver.py b/cmad/fem_utils/nonlinear_solvers/nonlinear_solver.py new file mode 100755 index 0000000..5c01dc8 --- /dev/null +++ b/cmad/fem_utils/nonlinear_solvers/nonlinear_solver.py @@ -0,0 +1,110 @@ +import numpy as np +import scipy.sparse.linalg +import scipy.sparse as sp +import time + +def newton_solve(model, num_steps, max_iters, tol): + model.initialize_variables() + for step in range(num_steps): + print('Step: ', step) + model.compute_surf_tractions(step) + model.set_prescribed_dofs(step) + for i in range(max_iters): + + model.set_global_fields() + + model.seed_none() + model.evaluate() + RF = model.scatter_rhs() + + print("||R||: ", np.linalg.norm(RF)) + if (np.linalg.norm(RF) < tol): + break + + model.seed_U() + model.evaluate() + KFF = model.scatter_lhs() + + # diag = KFF.diagonal() + # T = sp.diags(1 / np.sqrt(diag), 0) + + # delta = scipy.sparse.linalg.spsolve(T @ KFF @ T, -T.dot(RF)) + + KFF_factorized = scipy.sparse.linalg.factorized(KFF) + delta = KFF_factorized(-RF) + + model.add_to_UF(delta) + + model.save_global_fields() + model.advance_model() + +def halley_solve(model, num_steps, max_iters, tol, halley_threshold): + model.initialize_variables() + for step in range(num_steps): + print('Timestep', step) + # set displacement BCs + model.set_prescribed_dofs(step) + model.set_global_fields() + + #set traction BCs + model.compute_surf_tractions(step) + + model.seed_none() + model.evaluate() + RF = model.scatter_rhs() + norm_resid_0 = np.linalg.norm(RF) + + for i in range(max_iters): + norm_resid = np.linalg.norm(RF) + print("||R|| = ", norm_resid) + if (norm_resid < tol): + break + + model.seed_U() + model.evaluate() + KFF = model.scatter_lhs() + KFF_factorized = scipy.sparse.linalg.factorized(KFF) + delta = KFF_factorized(-RF) + + UF_curr = model.get_UF() + UF_new = UF_curr + delta + model.set_UF(UF_new) + model.set_global_fields() + + model.seed_none() + model.evaluate() + RF_new = model.scatter_rhs() + norm_resid_new = np.linalg.norm(RF_new) + S = np.log10(norm_resid_0 / norm_resid) + + if S < halley_threshold: + RF = RF_new.copy() + else: + if norm_resid_new < tol: + RF = RF_new.copy() + else: + print("Computing Halley correction...") + model.set_newton_increment(delta) + halley_rhs = model.evaluate_halley_correction() + halley_delta = delta ** 2 / (delta + 1 / 2 * KFF_factorized(halley_rhs)) + + UF_new = UF_curr + halley_delta + model.set_UF(UF_new) + model.set_global_fields() + + model.seed_none() + model.evaluate() + RF = model.scatter_rhs() + model.advance_model() + + + + + + + + + + + + diff --git a/cmad/fem_utils/nonlinear_solvers/nonlinear_solver_coupled.py b/cmad/fem_utils/nonlinear_solvers/nonlinear_solver_coupled.py new file mode 100755 index 0000000..bc66a23 --- /dev/null +++ b/cmad/fem_utils/nonlinear_solvers/nonlinear_solver_coupled.py @@ -0,0 +1,184 @@ +import numpy as np +import scipy.sparse.linalg + +def newton_solve(model, num_steps, max_iters, tol): + # model.initialize_plot() + model.initialize_variables() + for step in range(num_steps): + print('Step: ', step) + model.set_prescribed_dofs(step) + model.compute_surf_tractions(step) + for i in range(max_iters): + model.set_global_fields() + + model.compute_local_state_variables() + model.evaluate_local() + + model.evaluate_global() + RF = model.scatter_rhs() + norm_resid = np.linalg.norm(RF) + print('Iteration: ', i) + print(" ||C|| = ", np.max(np.abs(model.C())), + " ||R|| = ", norm_resid) + if (norm_resid < tol): + break + + model.evaluate_tang() + KFF = model.scatter_lhs() + + KFF_factorized = scipy.sparse.linalg.factorized(KFF) + delta = KFF_factorized(-RF) + + model.add_to_UF(delta) + model.reset_xi() + + model.save_global_fields() + # model.update_plot() + model.advance_model() + +def newton_solve_line_search(model, num_steps, max_iters, tol, s=0.8, m=8): + # model.initialize_plot() + model.initialize_variables() + for step in range(num_steps): + print('Timestep', step) + # set displacement BCs + model.set_prescribed_dofs(step) + model.set_global_fields() + + #set traction BCs + model.compute_surf_tractions(step) + + model.reset_xi() + print("Computing local state variables...") + model.compute_local_state_variables() + model.evaluate_local() + print("||C|| = ", np.max(np.abs(model.C()))) + + model.evaluate_global() + RF = model.scatter_rhs() + + for i in range(max_iters): + norm_resid = np.linalg.norm(RF) + print('Newton Iteration: ', i) + print("||R|| = ", norm_resid) + if (norm_resid < tol): + break + + model.evaluate_tang() + KFF = model.scatter_lhs() + # KFF_array = KFF.toarray() + # print("converted") + # cond = np.linalg.cond(KFF_array) + # print(cond) + KFF_factorized = scipy.sparse.linalg.factorized(KFF) + delta = KFF_factorized(-RF) + + UF_curr = model.get_UF() + UF_new = UF_curr + delta + model.set_UF(UF_new) + model.set_global_fields() + + model.reset_xi() + print("Computing local state variables...") + model.compute_local_state_variables() + model.evaluate_local() + print("||C|| = ", np.max(np.abs(model.C()))) + + model.evaluate_global() + RF_new = model.scatter_rhs() + if (np.abs(np.dot(delta, RF_new)) <= s * np.abs(np.dot(delta, RF))): + print('No line search') + RF = RF_new.copy() + else: + print('Performing line search') + for j in range(1, m + 1): + # print("Line search iteration: ", j) + eta = (m - j + 1) / (m + 1) + UF_new = UF_curr + eta * delta + model.set_UF(UF_new) + model.set_global_fields() + + model.reset_xi() + # print("Computing local state variables...") + model.compute_local_state_variables() + model.evaluate_local() + # print("||C|| = ", np.max(np.abs(model.C()))) + + model.evaluate_global() + RF_new = model.scatter_rhs() + if (np.abs(np.dot(delta, RF_new)) <= s * np.abs(np.dot(delta, RF)) or j == m): + RF = RF_new.copy() + break + + model.save_global_fields() + # model.update_plot() + model.advance_model() + +def halley_solve(model, num_steps, max_iters, tol, halley_threshold): + # model.initialize_plot() + model.initialize_variables() + for step in range(num_steps): + print('Timestep', step) + # set displacement BCs + model.set_prescribed_dofs(step) + model.set_global_fields() + + #set traction BCs + model.compute_surf_tractions(step) + + model.reset_xi() + model.compute_local_state_variables() + model.evaluate_local() + + model.evaluate_global() + RF = model.scatter_rhs() + norm_resid_0 = np.linalg.norm(RF) + + for i in range(max_iters): + norm_resid = np.linalg.norm(RF) + print(" ||C|| = ", np.max(np.abs(model.C())), + " ||R|| = ", norm_resid) + if (norm_resid < tol): + break + + model.evaluate_tang() + KFF = model.scatter_lhs() + KFF_factorized = scipy.sparse.linalg.factorized(KFF) + delta = KFF_factorized(-RF) + + UF_curr = model.get_UF() + UF_new = UF_curr + delta + model.set_UF(UF_new) + model.set_global_fields() + + model.reset_xi() + model.compute_local_state_variables() + model.evaluate_local() + + model.evaluate_global() + RF_new = model.scatter_rhs() + norm_resid_new = np.linalg.norm(RF_new) + S = np.log10(norm_resid_0 / norm_resid) + + if S < halley_threshold: + RF = RF_new.copy() + else: + if norm_resid_new < tol: + RF = RF_new.copy() + else: + print("Computing Halley correction...") + model.set_newton_increment(delta) + halley_rhs = model.evaluate_halley_correction_multi(24) + halley_delta = delta ** 2 / (delta + 1 / 2 * KFF_factorized(halley_rhs)) + + UF_new = UF_curr + halley_delta + model.set_UF(UF_new) + model.set_global_fields() + + model.reset_xi() + model.compute_local_state_variables() + model.evaluate_local() + + model.evaluate_global() + RF = model.scatter_rhs() + model.advance_model() \ No newline at end of file diff --git a/cmad/fem_utils/problems/fem_problem.py b/cmad/fem_utils/problems/fem_problem.py new file mode 100755 index 0000000..2309e6b --- /dev/null +++ b/cmad/fem_utils/problems/fem_problem.py @@ -0,0 +1,1229 @@ +import numpy as np +from cmad.fem_utils.mesh.mesh import Mesh +from cmad.fem_utils.interpolants import interpolants +from cmad.fem_utils.quadrature import quadrature_rule +import meshio + +class fem_problem(): + def __init__(self, problem_type, order, mixed = False): + + # evaluate triangle basis functions at quadrature points + quad_rule_tri \ + = quadrature_rule.create_quadrature_rule_on_triangle(order) + gauss_pts_tri = quad_rule_tri.xigauss + shape_func_tri = interpolants.shape_triangle(gauss_pts_tri) + + # evaluate tetrahedron basis functions at quadrature points + quad_rule_tetra \ + = quadrature_rule.create_quadrature_rule_on_tetrahedron(order) + gauss_pts_tetra = quad_rule_tetra.xigauss + shape_func_tetra = interpolants.shape_tetrahedron(gauss_pts_tetra) + + # evaluate brick basis functions at quadrature points + quad_rule_brick \ + = quadrature_rule.create_quadrature_rule_on_brick(order) + gauss_pts_brick = quad_rule_brick.xigauss + shape_func_brick = interpolants.shape_brick(gauss_pts_brick) + + # evaluate square basis functions at quadrature points + quad_rule_square \ + = quadrature_rule.create_quadrature_rule_on_quad(order) + gauss_pts_square = quad_rule_square.xigauss + shape_func_square = interpolants.shape_quad(gauss_pts_square) + + # evaluate 1D basis functions at quadrature points + self._quad_rule_1D \ + = quadrature_rule.create_quadrature_rule_1D(order) + gauss_pts_1D = self._quad_rule_1D.xigauss + self._shape_func_1D = interpolants.shape1d(gauss_pts_1D) + + self._is_mixed = mixed + + # Mechanical only problems + if problem_type == "uniaxial_stress": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("bar") + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # prescribe ux = 0 on x = 0, uy = 0 on y = 0, and uz = 0 on z = 0 + disp_node = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][0] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 2], dtype=int)) + if self._nodal_coords[i][2] == 0.0: + disp_node.append(np.array([i, 3], dtype=int)) + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.zeros(len(disp_node)) + + # normal traction on x = 2 + self._surf_traction_vector = np.array([0.1, 0.0, 0.0]) + pres_surf_traction = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 0] == 2 * np.ones(3)).all(): + pres_surf_traction.append(surface) + self._pres_surf_traction = np.array(pres_surf_traction) + + if problem_type == "patch_B": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("cube") + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + #impose linear displacement field on the boundary + disp_node = [] + disp_val = [] + self.UUR_true = np.zeros((self._num_nodes, self._dof_node)) + + for i in range(self._num_nodes): + coord = self._nodal_coords[i] + x = coord[0] + y = coord[1] + z = coord[2] + + scaling = 1e-4 + ux = scaling * (2 * x + y + z - 4) / 2 + uy = scaling * (x + 2 * y + z - 4) / 2 + uz = scaling * (x + y + 2 * z - 4) / 2 + + self.UUR_true[i, :] = np.array([ux, uy, uz]) + + if (x == 0.0 or x == 2.0 or y == 0.0 + or y == 2.0 or z == 0 or z == 2.0): + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(ux) + disp_val.append(uy) + disp_val.append(uz) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # no surface tractions + self._surf_traction_vector = np.array([0.0, 0.0, 0.0]) + self._pres_surf_traction = np.array([]) + + if problem_type == "simple_shear": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("cube") + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane z = 0, and set uz = 0 on plane z = 2 + disp_node = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][2] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + if self._nodal_coords[i][2] == 2.0: + disp_node.append(np.array([i, 3], dtype=int)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.zeros(len(disp_node)) + + # shear traction in x direction on plane z = 2 + self._surf_traction_vector = np.array([0.1, 0.0, 0.0]) + pres_surf_traction = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 2] == 2 * np.ones(3)).all(): + pres_surf_traction.append(surface) + self._pres_surf_traction = np.array(pres_surf_traction) + + if problem_type == "cook_membrane": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("cook") + + self._dt = 1. + self._num_steps = 10 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane x = 0 + # fix z displacments on plane z = 0 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][0] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + elif self._nodal_coords[i][2] == 0.0: + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + if mixed: + disp_node.append(np.array([0, 4], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # vertical traction on x = 48 + self._surf_traction_vector = np.zeros((self._num_steps, 3)) + self._surf_traction_vector[:, 1] = 2.0 * self._dt * np.arange(1, self._num_steps + 1) + pres_surf_traction = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 0] == 48 * np.ones(3)).all(): + pres_surf_traction.append(surface) + self._pres_surf_traction = np.array(pres_surf_traction) + + if problem_type == "vert_beam": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("vert_beam") + + self._dt = 1. + self._num_steps = 10 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane z = 0 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][2] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + if mixed: + disp_node.append(np.array([0, 4], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # diagonal traction on z = 5 + trac_increment = 1.0 + self._surf_traction_vector = np.zeros((self._num_steps, self._ndim)) + self._surf_traction_vector[:, 0] = trac_increment * self._dt * np.arange(1, self._num_steps + 1) + self._surf_traction_vector[:, 1] = trac_increment * self._dt * np.arange(1, self._num_steps + 1) + pres_surf_traction = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 2] == 5 * np.ones(3)).all(): + pres_surf_traction.append(surface) + self._pres_surf_traction = np.array(pres_surf_traction) + + if problem_type == "hole_block_disp_rigid": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("hole_block_half") + + self._dt = 1. + self._num_steps = 100 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane x = 0 + # set incremental displacements on plane x = 1 + increment = 0.0005 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][0] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + elif self._nodal_coords[i][0] == 1.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.linspace(increment * self._dt, + increment * self._dt * self._num_steps, + self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + elif self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 2], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + if mixed: + disp_node.append(np.array([0, 4], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + if problem_type == "hole_block_disp_sliding": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("hole_block_quarter") + + self._dt = 1. + self._num_steps = 50 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane x = 0 + # set incremental displacements on plane x = 1 + increment = 0.0005 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 2], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if np.abs(self._nodal_coords[i][0]) < 1.e-5: + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][2] == 0.0: + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][0] == 0.5: + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(np.linspace(increment * self._dt, + increment * self._dt * self._num_steps, + self._num_steps)) + + if mixed: + disp_node.append(np.array([0, 4], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + if problem_type == "notched_bar": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("notched_bar") + + self._dt = 1. + self._num_steps = 20 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane x = 0 + # set incremental displacements on plane x = 1 + increment = 0.0003 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + x = self._nodal_coords[i][0] + y = self._nodal_coords[i][1] + z = self._nodal_coords[i][2] + if x == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + + if x == 4.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.linspace(increment * self._dt, + increment * self._dt * self._num_steps, + self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + + if y == 0.0 and (x == 0.0 or x == 4.0): + disp_node.append(np.array([i, 2], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if mixed: + disp_node.append(np.array([0, 4], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + if problem_type == "hole_block_disp_sliding_2D": + self._quad_rule_2D = quad_rule_tri + self._shape_func_2D = shape_func_tri + + self._ndim = 2 + self._mesh = Mesh("hole_block_2D") + + self._dt = 1. + self._num_steps = 15 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 3 dofs per node if there are pressure dofs + self._dof_node = 2 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 3 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 2 + + # fix x displacement on x = 0 + # fix y displacement on y = 0 + # set incremental x displacements on x = 1 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 2], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][0] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][0] == 1.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(np.linspace(0.0001 * self._dt, + 0.0001 * self._dt * self._num_steps, + self._num_steps)) + if mixed: + disp_node.append(np.array([0, 3], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + if problem_type == "hole_block_traction": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("hole_block_half") + + self._dt = 1. + self._num_steps = 40 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane x = 0 + # set incremental displacements on plane x = 1 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 2], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][0] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + + if mixed: + disp_node.append(np.array([0, 4], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # normal traction on plane x = 1 + self._surf_traction_vector = np.zeros((self._num_steps, self._ndim)) + self._surf_traction_vector[:, 0] = 4.5 * self._dt * np.arange(1, self._num_steps + 1) + pres_surf_traction = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 0] == 1 * np.ones(3)).all(): + pres_surf_traction.append(surface) + self._pres_surf_traction = np.array(pres_surf_traction, dtype=int) + + if problem_type == "hole_block_traction_2D": + self._quad_rule_2D = quad_rule_tri + self._shape_func_2D = shape_func_tri + + self._ndim = 2 + self._mesh = Mesh("hole_block_2D") + + self._dt = 1. + self._num_steps = 30 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 3 dofs per node if there are pressure dofs + self._dof_node = 2 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 3 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 2 + + # fix x displacement on x = 0 + # fix y displacement on y = 0 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 2], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][0] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + if mixed: + disp_node.append(np.array([0, 3], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # normal traction on x = 1 + self._surf_traction_vector = np.zeros((self._num_steps, self._ndim)) + self._surf_traction_vector[:, 0] = 1.0 * self._dt * np.arange(1, self._num_steps + 1) + pres_surf_traction = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 0] == 1 * np.ones(2)).all(): + pres_surf_traction.append(surface) + self._pres_surf_traction = np.array(pres_surf_traction, dtype=int) + + if problem_type == "boat_fender": + + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("boat_fender") + + self._dt = 1. + self._num_steps = 29 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 3 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane x = 0 + # set incremental displacements on plane x = 1 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + + if mixed: + disp_node.append(np.array([0, 4], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # normal traction on plane x = 1 + increment = 0.001 + self._surf_traction_vector = np.zeros((self._num_steps, self._ndim)) + self._surf_traction_vector[:, 0] = increment * self._dt * np.arange(1, self._num_steps + 1) + self._surf_traction_vector[:, 1] = -2 * increment * self._dt * np.arange(1, self._num_steps + 1) + pres_surf_traction = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 1] == 0.5 * np.ones(3)).all(): + pres_surf_traction.append(surface) + self._pres_surf_traction = np.array(pres_surf_traction, dtype=int) + + # Thermomechanical problems + if problem_type == "rectangular_wall": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("rect_prism") + + self._dt = 0.1 + self._num_steps = 20 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 5 dofs per node if there are pressure dofs + self._dof_node = 4 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane y = 0 + # fix temperature to 300K on plane y = 0 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + # fix displacements + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + #fix temperatures + disp_node.append(np.array([i, 4], dtype=int)) + disp_val.append(300. * np.ones(self._num_steps)) + + if mixed: + disp_node.append(np.array([0, 5], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # prescribe convection bcs on x = 0, x = 2, y = 1, z = 0, and z = 0.1 + pres_surf_flux = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 0] == 2 * np.ones(3)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 0] == np.zeros(3)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 1] == 1. * np.ones(3)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 2] == np.zeros(3)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 2] == 0.1 * np.ones(3)).all(): + pres_surf_flux.append(surface) + self._pres_surf_flux = np.array(pres_surf_flux, dtype=int) + + # prescribe initial conditions + self._init_temp = np.zeros(self._num_nodes) + for i, node in enumerate(self._nodal_coords): + self._init_temp[i] = 300. + 500. * node[1] + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + if problem_type == "rectangular_wall_hex": + self._quad_rule_3D = quad_rule_brick + self._quad_rule_2D = quad_rule_square + self._shape_func_3D = shape_func_brick + self._shape_func_2D = shape_func_square + + self._ndim = 3 + self._mesh = Mesh("rect_prism_brick") + + self._dt = 0.05 + self._num_steps = 20 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 5 dofs per node if there are pressure dofs + self._dof_node = 4 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 8 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 4 + + # fix all nodes on plane y = 0 + # fix temperature to 300K on plane y = 0 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + # fix displacements + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + #fix temperatures + disp_node.append(np.array([i, 4], dtype=int)) + disp_val.append(300. * np.ones(self._num_steps)) + + if mixed: + disp_node.append(np.array([0, 5], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # prescribe convection bcs on x = 0, x = 2, y = 1, z = 0, and z = 0.1 + pres_surf_flux = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 0] == 2 * np.ones(4)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 0] == np.zeros(4)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 1] == 1. * np.ones(4)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 2] == np.zeros(4)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 2] == 0.5 * np.ones(4)).all(): + pres_surf_flux.append(surface) + self._pres_surf_flux = np.array(pres_surf_flux, dtype=int) + + # prescribe initial conditions + self._init_temp = np.zeros(self._num_nodes) + for i, node in enumerate(self._nodal_coords): + self._init_temp[i] = 300. + 500. * node[1] + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + if problem_type == "hole_block_traction_thermoplastic": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("hole_block_half") + + self._dt = 0.05 + self._num_steps = 36 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 4 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # uy = 0 on plane y = 0 + # ux = uz = 0 on plane x = 0 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 2], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][0] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + + if mixed: + disp_node.append(np.array([0, 5], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # normal traction on plane x = 1 + self._surf_traction_vector = np.zeros((self._num_steps, self._ndim)) + self._surf_traction_vector[:, 0] = 5.0 * np.arange(1, self._num_steps + 1) + pres_surf_traction = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 0] == 1 * np.ones(3)).all(): + pres_surf_traction.append(surface) + self._pres_surf_traction = np.array(pres_surf_traction, dtype=int) + + # prescribe initial conditions + self._init_temp = 300.0 * np.ones(self._num_nodes) + + # no convection BC + self._pres_surf_flux = None + + if problem_type == "hole_block_disp_thermoplastic": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("hole_block_quarter") + + self._dt = 0.1 + self._num_steps = 50 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + # 4 dofs per node if there are pressure dofs + self._dof_node = 4 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # fix all nodes on plane x = 0 + # set incremental displacements on plane x = 1 + increment = 0.0005 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 2], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if np.abs(self._nodal_coords[i][0]) < 1.e-5: + self._nodal_coords[i][0] = 0.0 + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][2] == 0.0: + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][0] == 0.5: + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(np.linspace(increment, + increment * self._num_steps, + self._num_steps)) + + if mixed: + disp_node.append(np.array([0, 5], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + # prescribe initial conditions + self._init_temp = 300.0 * np.ones(self._num_nodes) + + # no convection BC + self._pres_surf_flux = None + + if problem_type == "uniaxial_stress_thermoplastic": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("dogbone_quarter") + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + self._dt = 1. + self._num_steps = 60 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + # 4 dofs per node if there are pressure dofs + self._dof_node = 4 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # prescribe ux = 0 on x = 0, uy = 0 on y = 0, and uz = 0 on z = 0 + increment = 0.002 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + elif self._nodal_coords[i][1] == 1.0: + disp_node.append(np.array([i, 1], dtype=int)) + disp_node.append(np.array([i, 2], dtype=int)) + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + disp_val.append(np.linspace(increment, + increment * self._num_steps, + self._num_steps)) + disp_val.append(np.zeros(self._num_steps)) + else: + if self._nodal_coords[i][0] == 0.1425: + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][2] == 0.0: + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + if mixed: + disp_node.append(np.array([0, 5], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + # prescribe initial conditions + self._init_temp = 300.0 * np.ones(self._num_nodes) + + # no convection BC + self._pres_surf_flux = None + + if problem_type == "uniaxial_plane_strain_thermoplastic": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("rect_sheet_defect") + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + self._dt = 0.1 + self._num_steps = 13 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + # 4 dofs per node if there are pressure dofs + self._dof_node = 4 + mixed + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # prescribe ux = 0 on x = 0, uy = 0 on y = 0, and uz = 0 on z = 0 + increment = 0.001 + disp_node = [] + disp_val = [] + for i in range(self._num_nodes): + + # set all z displacements to 0 for plane strain + disp_node.append(np.array([i, 3], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][1] == 0.0: + disp_node.append(np.array([i, 2], dtype=int)) + disp_val.append(np.zeros(self._num_steps)) + + if self._nodal_coords[i][0] == 0.5: + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(np.linspace(increment, + increment * self._num_steps, + self._num_steps)) + + if self._nodal_coords[i][0] == -0.5: + disp_node.append(np.array([i, 1], dtype=int)) + disp_val.append(-np.linspace(increment, + increment * self._num_steps, + self._num_steps)) + if mixed: + disp_node.append(np.array([0, 5], dtype = int)) + disp_val.append(np.zeros(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + # prescribe initial conditions + self._init_temp = 300.0 * np.ones(self._num_nodes) + + # no convection BC + self._pres_surf_flux = None + # Thermo only problems + if problem_type == "rectangular_wall_thermo": + self._quad_rule_3D = quad_rule_tetra + self._quad_rule_2D = quad_rule_tri + self._shape_func_3D = shape_func_tetra + self._shape_func_2D = shape_func_tri + + self._ndim = 3 + self._mesh = Mesh("rect_prism") + + self._dt = 0.1 + self._num_steps = 1000 + self._times = np.linspace(self._dt, + self._dt * self._num_steps, + self._num_steps) + + self._nodal_coords = self._mesh.get_nodal_coordinates() + self._volume_conn = self._mesh.get_volume_connectivity() + self._surface_conn = self._mesh.get_surface_connectivity() + + self._dof_node = 1 + self._num_nodes = len(self._nodal_coords) + self._num_nodes_elem = 4 + self._num_elem = len(self._volume_conn) + self._num_nodes_surf = 3 + + # dirichlet temperature BCs + disp_node = [] + disp_val = [] + for i, node in enumerate(self._nodal_coords): + if node[1] == 0.0: + disp_node.append(i) + disp_val.append(300. * np.ones(self._num_steps)) + + self._disp_node = np.array(disp_node, dtype=int) + self._disp_val = np.array(disp_val) + + pres_surf_flux = [] + for surface in self._surface_conn: + surface_points = self._nodal_coords[surface, :] + if (surface_points[:, 0] == 2 * np.ones(3)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 0] == np.zeros(3)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 1] == 1. * np.ones(3)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 2] == np.zeros(3)).all(): + pres_surf_flux.append(surface) + if (surface_points[:, 2] == 0.1 * np.ones(3)).all(): + pres_surf_flux.append(surface) + self._pres_surf_flux = np.array(pres_surf_flux, dtype=int) + + # prescribe initial conditions + self._init_temp = np.zeros(self._num_nodes) + for i, node in enumerate(self._nodal_coords): + self._init_temp[i] = 300. + 500. * node[1] + + # no tractions + self._surf_traction_vector = None + self._pres_surf_traction = None + + def get_surface_basis_functions(self): + if self._ndim == 2: + return self._quad_rule_1D, self._shape_func_1D + else: + return self._quad_rule_2D, self._shape_func_2D + + def get_volume_basis_functions(self): + if self._ndim == 2: + return self._quad_rule_2D, self._shape_func_2D + else: + return self._quad_rule_3D, self._shape_func_3D + + def get_mesh_properties(self): + return self._dof_node, self._num_nodes, self._num_nodes_elem, \ + self._num_elem, self._num_nodes_surf, self._nodal_coords, \ + self._volume_conn, self._ndim + + def get_boundary_conditions(self): + return self._disp_node, self._disp_val, \ + self._pres_surf_traction, self._surf_traction_vector + + def get_convection_boundary_conditions(self): + return self._pres_surf_flux + + def get_initial_temp(self): + return self._init_temp + + def save_data(self, filename, point_data, cell_data=None): + points = self._mesh.get_nodal_coordinates() + cells = self._mesh.get_cells() + with meshio.xdmf.TimeSeriesWriter(filename) as writer: + writer.write_points_cells(points, cells) + for i, time in enumerate(self._times): + if cell_data != None: + writer.write_data(time, point_data=point_data[i], + cell_data=cell_data[i]) + else: + writer.write_data(time, point_data=point_data[i]) + + def is_mixed(self): + return self._is_mixed + + def num_steps(self): + return self._num_steps, self._dt \ No newline at end of file diff --git a/cmad/fem_utils/quadrature/quadrature_rule.py b/cmad/fem_utils/quadrature/quadrature_rule.py new file mode 100755 index 0000000..8433fe1 --- /dev/null +++ b/cmad/fem_utils/quadrature/quadrature_rule.py @@ -0,0 +1,372 @@ +import numpy as np +import scipy.special + +class QuadratureRule(): + + def __init__(self, xigauss, wgauss): + self.xigauss = xigauss + self.wgauss = wgauss + + def __len__(self): + return self.xigauss.shape[0] + +def create_quadrature_rule_1D(degree): + """Creates a Gauss-Legendre quadrature on the interval [-1, 1]. + + The rule can exactly integrate polynomials of degree up to + ``degree``. + + Parameters + ---------- + degree: Highest degree polynomial to be exactly integrated by the quadrature rule + + Returns + ------- + A ``QuadratureRule`` named tuple containing the quadrature point coordinates + and the weights. + """ + + n = np.ceil((degree + 1) / 2) + xi, w = scipy.special.roots_sh_legendre(n) + xi = -1 + 2 * xi + + xi_matrix = np.zeros((len(xi), 1)) + xi_matrix[:, 0]=xi + return QuadratureRule(xi_matrix, w * 2) + + +def create_quadrature_rule_on_triangle(degree): + """Creates a Gauss-Legendre quadrature on the unit triangle. + + The rule can exactly integrate 2D polynomials up to the value of + ``degree``. The domain is the triangle between the vertices + (0, 0)-(1, 0)-(0, 1). The rules here are guaranteed to be + cyclically symmetric in triangular coordinates and to have strictly + positive weights. + + Parameters + ---------- + degree: Highest degree polynomial to be exactly integrated by the quadrature rule + + Returns + ------- + A ``QuadratureRule`` named tuple containing the quadrature point coordinates + and the weights. + """ + if degree <= 1: + xi = np.array([[3.33333333333333333E-01, 3.33333333333333333E-01]]) + + w = np.array([ 5.00000000000000000E-01 ]) + elif degree == 2: + xi = np.array([[6.66666666666666667E-01, 1.66666666666666667E-01], + [1.66666666666666667E-01, 6.66666666666666667E-01], + [1.66666666666666667E-01, 1.66666666666666667E-01]]) + + w = np.array([1.66666666666666666E-01, + 1.66666666666666667E-01, + 1.66666666666666667E-01]) + elif degree <= 4: + xi = np.array([[1.081030181680700E-01, 4.459484909159650E-01], + [4.459484909159650E-01, 1.081030181680700E-01], + [4.459484909159650E-01, 4.459484909159650E-01], + [8.168475729804590E-01, 9.157621350977100E-02], + [9.157621350977100E-02, 8.168475729804590E-01], + [9.157621350977100E-02, 9.157621350977100E-02]]) + + w = np.array([1.116907948390055E-01, + 1.116907948390055E-01, + 1.116907948390055E-01, + 5.497587182766100E-02, + 5.497587182766100E-02, + 5.497587182766100E-02]) + elif degree <= 5: + xi = np.array([[3.33333333333333E-01, 3.33333333333333E-01], + [5.97158717897700E-02, 4.70142064105115E-01], + [4.70142064105115E-01, 5.97158717897700E-02], + [4.70142064105115E-01, 4.70142064105115E-01], + [7.97426985353087E-01, 1.01286507323456E-01], + [1.01286507323456E-01, 7.97426985353087E-01], + [1.01286507323456E-01, 1.01286507323456E-01]]) + + w = np.array([1.12500000000000E-01, + 6.61970763942530E-02, + 6.61970763942530E-02, + 6.61970763942530E-02, + 6.29695902724135E-02, + 6.29695902724135E-02, + 6.29695902724135E-02]) + elif degree <= 6: + xi = np.array([[5.01426509658179E-01, 2.49286745170910E-01], + [2.49286745170910E-01, 5.01426509658179E-01], + [2.49286745170910E-01, 2.49286745170910E-01], + [8.73821971016996E-01, 6.30890144915020E-02], + [6.30890144915020E-02, 8.73821971016996E-01], + [6.30890144915020E-02, 6.30890144915020E-02], + [5.31450498448170E-02, 3.10352451033784E-01], + [6.36502499121399E-01, 5.31450498448170E-02], + [3.10352451033784E-01, 6.36502499121399E-01], + [5.31450498448170E-02, 6.36502499121399E-01], + [6.36502499121399E-01, 3.10352451033784E-01], + [3.10352451033784E-01, 5.31450498448170E-02]]) + + w = np.array([5.83931378631895E-02, + 5.83931378631895E-02, + 5.83931378631895E-02, + 2.54224531851035E-02, + 2.54224531851035E-02, + 2.54224531851035E-02, + 4.14255378091870E-02, + 4.14255378091870E-02, + 4.14255378091870E-02, + 4.14255378091870E-02, + 4.14255378091870E-02, + 4.14255378091870E-02]) + elif degree <= 10: + xi = np.array([[0.33333333333333333E+00, 0.33333333333333333E+00], + [0.4269134091050342E-02, 0.49786543295447483E+00], + [0.49786543295447483E+00, 0.4269134091050342E-02], + [0.49786543295447483E+00, 0.49786543295447483E+00], + [0.14397510054188759E+00, 0.42801244972905617E+00], + [0.42801244972905617E+00, 0.14397510054188759E+00], + [0.42801244972905617E+00, 0.42801244972905617E+00], + [0.6304871745135507E+00, 0.18475641274322457E+00], + [0.18475641274322457E+00, 0.6304871745135507E+00], + [0.18475641274322457E+00, 0.18475641274322457E+00], + [0.9590375628566448E+00, 0.20481218571677562E-01], + [0.20481218571677562E-01, 0.9590375628566448E+00], + [0.20481218571677562E-01, 0.20481218571677562E-01], + [0.3500298989727196E-01, 0.1365735762560334E+00], + [0.1365735762560334E+00, 0.8284234338466947E+00], + [0.8284234338466947E+00, 0.3500298989727196E-01], + [0.1365735762560334E+00, 0.3500298989727196E-01], + [0.8284234338466947E+00, 0.1365735762560334E+00], + [0.3500298989727196E-01, 0.8284234338466947E+00], + [0.37549070258442674E-01, 0.3327436005886386E+00], + [0.3327436005886386E+00, 0.6297073291529187E+00], + [0.6297073291529187E+00, 0.37549070258442674E-01], + [0.3327436005886386E+00, 0.37549070258442674E-01], + [0.6297073291529187E+00, 0.3327436005886386E+00], + [0.37549070258442674E-01, 0.6297073291529187E+00]]) + + w = np.array([0.4176169990259819E-01, + 0.36149252960283717E-02, + 0.36149252960283717E-02, + 0.36149252960283717E-02, + 0.3724608896049025E-01, + 0.3724608896049025E-01, + 0.3724608896049025E-01, + 0.39323236701554264E-01, + 0.39323236701554264E-01, + 0.39323236701554264E-01, + 0.3464161543553752E-02, + 0.3464161543553752E-02, + 0.3464161543553752E-02, + 0.147591601673897E-01, + 0.147591601673897E-01, + 0.147591601673897E-01, + 0.147591601673897E-01, + 0.147591601673897E-01, + 0.147591601673897E-01, + 0.1978968359803062E-01, + 0.1978968359803062E-01, + 0.1978968359803062E-01, + 0.1978968359803062E-01, + 0.1978968359803062E-01, + 0.1978968359803062E-01]) + else: + raise ValueError("Quadrature of precision this high is not implemented.") + + return QuadratureRule(xi, w) + +def create_quadrature_rule_on_quad(degree): + """Creates a Gauss-Legendre quadrature on square. + + The rule can exactly integrate 2D polynomials up to the value of + ``degree``. The domain is the square between the vertices + (1, -1)-(1, 1)-(-1, 1)-(-1, -1). + + Parameters + ---------- + degree: Highest degree polynomial to be exactly integrated by the quadrature rule + + Returns + ------- + A ``QuadratureRule`` named tuple containing the quadrature point coordinates + and the weights. + """ + + if degree <= 1: + xi = np.array([[0, 0]]) + + w = np.array([4]) + elif degree <= 3: + a = 0.577350269189626 + xi = np.array([[-a, -a], + [a, -a], + [a, a], + [-a, a]]) + + w = np.array([1, 1, 1, 1]) + elif degree <= 5: + a = 0.774596669241483 + b = 0.888888888888889 + c = 0.555555555555556 + xi = np.array([[-a, -a], + [0, -a], + [a, -a], + [-a, 0], + [0, 0], + [a, 0], + [-a, a], + [0, a], + [a, a]]) + + w = np.array([c * c, b * c, c * c, c * b, + b * b, c * b, c * c, b * c, c * c]) / 4 + + else: + raise ValueError("Quadrature of precision this high is not implemented.") + + return QuadratureRule(xi, w) + +def create_quadrature_rule_on_tetrahedron(degree): + if degree <= 1: + xi = np.array([[0.25, 0.25, 0.25]]) + + w = np.array([1.0/6.0]) + + elif degree == 2: + xi = np.array([[0.138196601125011, 0.138196601125011, 0.138196601125011], + [0.585410196624969, 0.138196601125011, 0.138196601125011], + [0.138196601125011, 0.585410196624969, 0.138196601125011], + [0.138196601125011, 0.138196601125011, 0.585410196624969]]) + + w = np.array([0.25 / 6.0, 0.25 / 6.0, 0.25 / 6.0, 0.25 / 6.0]) + + elif degree == 3: + xi = np.array([[1. / 4., 1. / 4., 1. / 4.], + [1. / 6., 1. / 6., 1. / 6.], + [1. / 6., 1. / 6., 1. / 2.], + [1./6., 1./2., 1. / 6.], + [1. / 2., 1. / 6., 1. / 6.]]) + + w = np.array([-2 / 15, 3 / 40, 3 / 40, 3 / 40, 3 / 40]) + + elif degree == 4: + xi = np.array([[0.25, 0.25, 0.25], + [0.785714285714286, 0.0714285714285714, 0.0714285714285714], + [0.0714285714285714, 0.0714285714285714, 0.0714285714285714], + [0.0714285714285714, 0.0714285714285714, 0.785714285714286], + [0.0714285714285714, 0.785714285714286, 0.0714285714285714], + [0.100596423833201, 0.399403576166799, 0.399403576166799], + [0.399403576166799, 0.100596423833201, 0.399403576166799], + [0.399403576166799, 0.399403576166799, 0.100596423833201], + [0.399403576166799, 0.100596423833201, 0.100596423833201], + [0.100596423833201, 0.399403576166799, 0.100596423833201], + [0.100596423833201, 0.100596423833201, 0.399403576166799]]) + + w = np.array([-0.0131555555555556, 0.00762222222222222, 0.00762222222222222, + 0.00762222222222222, 0.00762222222222222, 0.0248888888888889, + 0.0248888888888889, 0.0248888888888889, 0.0248888888888889, + 0.0248888888888889, 0.0248888888888889]) + + elif degree == 5: + xi = np.array([[0.25, 0.25, 0.25], + [0, 1. / 3., 1. / 3.], + [1. / 3., 1. / 3., 1. / 3.], + [1. / 3., 1. / 3., 0], + [1. / 3., 0, 1. / 3.], + [8. / 11., 1. / 11., 1. / 11.], + [1. / 11., 1. / 11., 1. / 11.], + [1. / 11., 1. / 11., 8. / 11.], + [1. / 11., 8. / 11., 1. / 11.], + [0.433449846426336, 0.0665501535736643, 0.0665501535736643], + [0.0665501535736643, 0.433449846426336, 0.0665501535736643], + [0.0665501535736643, 0.0665501535736643, 0.433449846426336], + [0.0665501535736643, 0.433449846426336, 0.433449846426336], + [0.433449846426336, 0.0665501535736643, 0.433449846426336], + [0.433449846426336, 0.433449846426336, 0.0665501535736643]]) + + w = np.array([0.0302836780970892, 0.00602678571428572, 0.00602678571428572, + 0.00602678571428572, 0.00602678571428572, 0.011645249086029, + 0.011645249086029, 0.011645249086029, 0.011645249086029, + 0.0109491415613865, 0.0109491415613865, 0.0109491415613865, + 0.0109491415613865, 0.0109491415613865, 0.0109491415613865]) + elif degree == 6: + xi = np.array([[0.356191386222545, 0.214602871259152, 0.214602871259152], + [0.214602871259152, 0.214602871259152, 0.214602871259152], + [0.214602871259152, 0.214602871259152, 0.356191386222545], + [0.214602871259152, 0.356191386222545, 0.214602871259152], + [0.877978124396166, 0.0406739585346113, 0.0406739585346113], + [0.0406739585346113, 0.0406739585346113, 0.0406739585346113], + [0.0406739585346113, 0.0406739585346113, 0.877978124396166], + [0.0406739585346113, 0.877978124396166, 0.0406739585346113], + [0.0329863295731731, 0.322337890142276, 0.322337890142276], + [0.322337890142276, 0.322337890142276, 0.322337890142276], + [0.322337890142276, 0.322337890142276, 0.0329863295731731], + [0.322337890142276, 0.0329863295731731, 0.322337890142276], + [0.269672331458316, 0.0636610018750175, 0.0636610018750175], + [0.0636610018750175, 0.269672331458316, 0.0636610018750175], + [0.0636610018750175, 0.0636610018750175, 0.269672331458316], + [0.603005664791649, 0.0636610018750175, 0.0636610018750175], + [0.0636610018750175, 0.603005664791649, 0.0636610018750175], + [0.0636610018750175, 0.0636610018750175, 0.603005664791649], + [0.0636610018750175, 0.269672331458316, 0.603005664791649], + [0.269672331458316, 0.603005664791649, 0.0636610018750175], + [0.603005664791649, 0.0636610018750175, 0.269672331458316], + [0.0636610018750175, 0.603005664791649, 0.269672331458316], + [0.269672331458316, 0.0636610018750175, 0.603005664791649], + [0.603005664791649, 0.269672331458316, 0.0636610018750175]]) + + w = np.array([0.00665379170969465, 0.00665379170969465, 0.00665379170969465, + 0.00665379170969465, 0.00167953517588678, 0.00167953517588678, + 0.00167953517588678, 0.00167953517588678, 0.0092261969239424, + 0.0092261969239424, 0.0092261969239424, 0.0092261969239424, + 0.00803571428571428, 0.00803571428571428, 0.00803571428571428, + 0.00803571428571428, 0.00803571428571428, 0.00803571428571428, + 0.00803571428571428, 0.00803571428571428, 0.00803571428571428, + 0.00803571428571428, 0.00803571428571428, 0.00803571428571428]) + else: + raise ValueError("Quadrature of precision this high is not implemented.") + + return QuadratureRule(xi, w) + +def create_quadrature_rule_on_brick(degree): + """ + Create a Gauss-Legendre quadrature rule on the reference 8-node brick element + (hexahedron) defined over [-1, 1]^3. + + Parameters + ---------- + degree : int + The number of Gauss points per direction (not the polynomial degree). + + Returns + ------- + xi : ndarray of shape (num_points, 3) + Quadrature points in the reference cube [-1, 1]^3. + + w : ndarray of shape (num_points,) + Quadrature weights. + """ + # 1D quadrature rule + n = int(np.ceil((degree + 1) / 2)) + x_1d, w_1d = scipy.special.roots_sh_legendre(n) + x_1d = -1 + 2 * x_1d + w_1d = w_1d * 2 + + num_points = n ** 3 + xi = np.zeros((num_points, 3)) + w = np.zeros(num_points) + + idx = 0 + for i in range(n): + for j in range(n): + for k in range(n): + xi[idx, 0] = x_1d[i] + xi[idx, 1] = x_1d[j] + xi[idx, 2] = x_1d[k] + w[idx] = w_1d[i] * w_1d[j] * w_1d[k] + idx += 1 + + return QuadratureRule(xi, w) diff --git a/cmad/fem_utils/tests/test_fem.py b/cmad/fem_utils/tests/test_fem.py new file mode 100644 index 0000000..0f442a6 --- /dev/null +++ b/cmad/fem_utils/tests/test_fem.py @@ -0,0 +1,42 @@ +import numpy as np +from cmad.fem_utils.problems.fem_problem import fem_problem +import unittest + +class TestFEM(unittest.TestCase): + + # def test_patch_form_B(self): + + # order = 3 + # problem = fem_problem("patch_B", order) + + # dof_node, num_nodes, num_nodes_elem, num_elem, num_nodes_surf, \ + # nodal_coords, volume_conn = problem.get_mesh_properties() + + # disp_node, disp_val, pres_surf, surf_traction_vector \ + # = problem.get_boundary_conditions() + + # quad_rule_3D, shape_func_tetra = problem.get_3D_basis_functions() + # quad_rule_2D, shape_func_triangle = problem.get_2D_basis_functions() + + # params = np.array([200, 0.3]) + + # eq_num, num_free_dof, num_pres_dof \ + # = initialize_equation(num_nodes, dof_node, disp_node) + + # KPP, KPF, KFF, KFP, PF, PP, UP \ + # = assemble_module(num_pres_dof, num_free_dof, num_elem, num_nodes_elem, + # dof_node, num_nodes_surf,surf_traction_vector, params, + # disp_node, disp_val, eq_num, volume_conn, nodal_coords, + # pres_surf, quad_rule_3D, shape_func_tetra, quad_rule_2D, + # shape_func_triangle) + + # UUR, UF, R = solve_module(KPP, KPF, KFF, KFP, PP, PF, UP, eq_num) + # print(UUR) + # assert np.allclose(UUR, problem.UUR_true) + + assert True + +if __name__ == "__main__": + fem_test_suite = unittest.TestLoader().loadTestsFromTestCase( + TestFEM) + unittest.TextTestRunner(verbosity=2).run(fem_test_suite) \ No newline at end of file diff --git a/cmad/fem_utils/tests/test_interpolants.py b/cmad/fem_utils/tests/test_interpolants.py new file mode 100644 index 0000000..560292c --- /dev/null +++ b/cmad/fem_utils/tests/test_interpolants.py @@ -0,0 +1,73 @@ +from cmad.fem_utils.interpolants import interpolants +from cmad.fem_utils.quadrature import quadrature_rule +import unittest +import jax.numpy as np + +class TestInterpolants(unittest.TestCase): + + def test_1D_shape_functions(self): + for i in range(2, 8): + test_points = quadrature_rule.create_quadrature_rule_1D(i).xigauss + shape_func_1D = interpolants.shape1d(test_points) + shape = shape_func_1D.values + dshape = shape_func_1D.gradients + + assert np.allclose(np.sum(shape, axis = 1), np.ones(len(shape))) + assert np.allclose(np.sum(dshape, axis = 1), np.zeros(len(shape))) + + def test_triangle_shape_functions(self): + for i in range(2, 7): + test_points = quadrature_rule.create_quadrature_rule_on_triangle(i).xigauss + shape_func_triangle = interpolants.shape_triangle(test_points) + shape = shape_func_triangle.values + dshape = shape_func_triangle.gradients + + assert np.allclose(np.sum(shape, axis = 1), np.ones(len(shape))) + + for j in range(len(dshape)): + gradient = dshape[j] + assert np.allclose(np.sum(gradient, axis = 1), np.zeros(len(gradient))) + + def test_quad_shape_functions(self): + for i in range(2, 6): + test_points = quadrature_rule.create_quadrature_rule_on_quad(i).xigauss + shape_func_quad = interpolants.shape_quad(test_points) + shape = shape_func_quad.values + dshape = shape_func_quad.gradients + + assert np.allclose(np.sum(shape, axis = 1), np.ones(len(shape))) + + for j in range(len(dshape)): + gradient = dshape[j] + assert np.allclose(np.sum(gradient, axis = 1), np.zeros(len(gradient))) + + def test_tetrahedron_shape_functions(self): + for i in range(2, 7): + test_points = quadrature_rule.create_quadrature_rule_on_tetrahedron(i).xigauss + shape_func_tetra = interpolants.shape_tetrahedron(test_points) + shape = shape_func_tetra.values + dshape = shape_func_tetra.gradients + + assert np.allclose(np.sum(shape, axis = 1), np.ones(len(shape))) + + for j in range(len(dshape)): + gradient = dshape[j] + assert np.allclose(np.sum(gradient, axis = 1), np.zeros(len(gradient))) + + def test_brick_shape_functions(self): + for i in range(2, 7): + test_points = quadrature_rule.create_quadrature_rule_on_brick(i).xigauss + shape_func_brick = interpolants.shape_brick(test_points) + shape = shape_func_brick.values + dshape = shape_func_brick.gradients + + assert np.allclose(np.sum(shape, axis = 1), np.ones(len(shape))) + + for j in range(len(dshape)): + gradient = dshape[j] + assert np.allclose(np.sum(gradient, axis = 1), np.zeros(len(gradient))) + +if __name__ == "__main__": + Interpolants_test_suite = unittest.TestLoader().loadTestsFromTestCase( + TestInterpolants) + unittest.TextTestRunner(verbosity=2).run(Interpolants_test_suite) \ No newline at end of file diff --git a/cmad/fem_utils/utils/fem_utils.py b/cmad/fem_utils/utils/fem_utils.py new file mode 100755 index 0000000..f7fcec2 --- /dev/null +++ b/cmad/fem_utils/utils/fem_utils.py @@ -0,0 +1,152 @@ +import numpy as np +import jax.numpy as jnp + +def assemble_global_fields(eq_num, UF, UP): + + # combine free and prescribed displacements into one array + UUR = np.zeros_like(eq_num, dtype=UF.dtype) + + pos_mask = eq_num > 0 + neg_mask = eq_num < 0 + + # Fill in values from UF for positive indices + UUR[pos_mask] = UF[eq_num[pos_mask] - 1] + + # Fill in values from UP for negative indices + UUR[neg_mask] = UP[-eq_num[neg_mask] - 1] + + return UUR + +def initialize_equation(num_nodes, dof_node, disp_node): + + eq_num = np.zeros((num_nodes, dof_node), dtype=int) + for i, node in enumerate(disp_node): + node_number = node[0] + dof_number = node[1] + eq_num[node_number][dof_number - 1] = -(i + 1) + + num_free_dof = 0 + for i in range(len(eq_num)): + for j in range(len(eq_num[i]) - 1): + if (eq_num[i, j] == 0): + num_free_dof += 1 + eq_num[i, j] = num_free_dof + + for i in range(len(eq_num)): + if (eq_num[i, -1] == 0): + num_free_dof += 1 + eq_num[i, -1] = num_free_dof + + num_pres_dof = num_nodes * dof_node - num_free_dof + + return eq_num, num_free_dof, num_pres_dof + +def initialize_equation_thermo(num_nodes, disp_node): + eq_num = np.zeros(num_nodes, dtype = int) + for i, node in enumerate(disp_node): + eq_num[node] = -(i + 1) + + num_free_dof = 0 + for i in range(num_nodes): + if eq_num[i] == 0: + num_free_dof += 1 + eq_num[i] = num_free_dof + + num_pres_dof = num_nodes - num_free_dof + return eq_num, num_free_dof, num_pres_dof + +def compute_shape_jacobian(elem_points, dshape): + + J = (dshape @ elem_points).T + + dv = jnp.linalg.det(J) + + # derivatives of shape functions with respect to spacial coordinates + gradphi = jnp.linalg.inv(J).T @ dshape + + return dv, gradphi + +def interpolate_vector_3D(u_elem, shape_3D, gradphiXYZ, num_nodes_elem): + + ux = u_elem[0:num_nodes_elem] + uy = u_elem[num_nodes_elem:num_nodes_elem * 2] + uz = u_elem[num_nodes_elem * 2:num_nodes_elem * 3] + + u = jnp.array([jnp.dot(ux, shape_3D), + jnp.dot(uy, shape_3D), + jnp.dot(uz, shape_3D)]) + + grad_u = jnp.vstack([gradphiXYZ @ ux, + gradphiXYZ @ uy, + gradphiXYZ @ uz]) + + return u, grad_u + +def interpolate_vector_2D(u_elem, shape_2D, gradphiXY, num_nodes_elem): + + ux = u_elem[0:num_nodes_elem] + uy = u_elem[num_nodes_elem:num_nodes_elem * 2] + + u = jnp.array([jnp.dot(ux, shape_2D), + jnp.dot(uy, shape_2D)]) + + grad_u = jnp.vstack([gradphiXY @ ux, + gradphiXY @ uy]) + + return u, grad_u + +def interpolate_scalar(p, shape): + p = jnp.dot(p, shape) + return p + +def assemble_prescribed( + num_pres_dof, disp_node, disp_val, eq_num): + + UP = np.zeros(num_pres_dof) + for i, row in enumerate(disp_node): + node_number = row[0] + dof = row[1] + pres_value = disp_val[i] + eqn_number = -eq_num[node_number][dof - 1] + UP[eqn_number - 1] = pres_value + + return UP + +def calc_element_traction_vector_3D( + surf_points, surf_traction_vector, num_nodes_surf, ndim, + gauss_weights_2D, shape_2D, dshape_2D): + FEL = jnp.zeros(num_nodes_surf * ndim) + + for gaussPt2D in range(len(gauss_weights_2D)): + shape_tri_q = shape_2D[gaussPt2D, :] + dshape_tri_q = dshape_2D[gaussPt2D, :, :] + + J_q = dshape_tri_q @ surf_points + + da_q = jnp.linalg.norm(jnp.cross(J_q[0, :], J_q[1, :])) + + FEL += gauss_weights_2D[gaussPt2D] \ + * (jnp.column_stack([shape_tri_q, shape_tri_q, shape_tri_q]) \ + * surf_traction_vector).T.reshape(-1) * da_q + + return FEL + +def calc_element_traction_vector_2D( + surf_points, surf_traction_vector, num_nodes_surf, ndim, + gauss_weights_1D, shape_1D, dshape_1D): + FEL = jnp.zeros(num_nodes_surf * ndim) + + for gaussPt1D in range(len(gauss_weights_1D)): + shape_1D_q = shape_1D[gaussPt1D, :] + dshape_1D_q = dshape_1D[gaussPt1D, :] + + J12 = jnp.dot(dshape_1D_q, surf_points[:, 0]) + J22 = jnp.dot(dshape_1D_q, surf_points[:, 1]) + + da_q = jnp.sqrt(J12 ** 2 + J22 ** 2) + + FEL += gauss_weights_1D[gaussPt1D] \ + * (jnp.column_stack([shape_1D_q, shape_1D_q]) \ + * surf_traction_vector).T.reshape(-1) * da_q + + return FEL \ No newline at end of file diff --git a/cmad/models/model.py b/cmad/models/model.py index 9248863..de64996 100644 --- a/cmad/models/model.py +++ b/cmad/models/model.py @@ -64,6 +64,12 @@ def cauchy(xi, xi_prev, params, u, u_prev) -> Array: No side effects allowed! """ raise NotImplementedError + + def get_cauchy(self): + return self.cauchy + + def get_local_residual(self): + return self._residual def evaluate(self): """ diff --git a/cmad/objectives/tests/test_J2_fd_checks.py b/cmad/objectives/tests/test_J2_fd_checks.py index 6a9fa05..af3996d 100644 --- a/cmad/objectives/tests/test_J2_fd_checks.py +++ b/cmad/objectives/tests/test_J2_fd_checks.py @@ -2,7 +2,7 @@ import matplotlib.pyplot as plt import unittest -from jax import tree_map +from jax.tree_util import tree_map from cmad.models.deformation_types import DefType, def_type_ndims from cmad.models.small_elastic_plastic import SmallElasticPlastic @@ -431,7 +431,7 @@ def plane_stress_fd_checks(Model, scale_params): model.parameters.set_active_values_from_flat(offset_param_values, False) fs_fd_component_error, adjoint_fd_component_error = \ fd_grad_check_components(qoi) - fd_component_diff_tol = 1e-8 + fd_component_diff_tol = 3e-8 fd_components_diff = fs_fd_component_error \ - adjoint_fd_component_error assert np.linalg.norm(fd_components_diff) < fd_component_diff_tol diff --git a/cmad/parameters/parameters.py b/cmad/parameters/parameters.py index 2da3f45..88ad2e6 100644 --- a/cmad/parameters/parameters.py +++ b/cmad/parameters/parameters.py @@ -4,9 +4,9 @@ from functools import partial import jax.numpy as jnp -from jax import tree_map, jit +from jax import jit from jax.flatten_util import ravel_pytree -from jax.tree_util import tree_flatten, tree_flatten_with_path, tree_reduce +from jax.tree_util import tree_flatten, tree_flatten_with_path, tree_reduce, tree_map def bounds_transform(value, bounds, transform_from_canonical=True): diff --git a/cmad/test_support/test_problems.py b/cmad/test_support/test_problems.py index 8ec1e53..e036a4d 100644 --- a/cmad/test_support/test_problems.py +++ b/cmad/test_support/test_problems.py @@ -1,6 +1,6 @@ import numpy as np -from jax import tree_map +from jax.tree_util import tree_map from cmad.parameters.parameters import Parameters from cmad.verification.functions import J2_yield, J2_yield_normal diff --git a/environment.yml b/environment.yml index 5a1f48b..fd69734 100644 --- a/environment.yml +++ b/environment.yml @@ -18,6 +18,10 @@ dependencies: # documentation - sphinx - sphinx-gallery + - pyvista + - h5py + - meshio + - pygmsh - pip: - sphinx_copybutton - sphinx_rtd_theme