|
| 1 | +import os |
| 2 | + |
| 3 | +import dolfinx as dfx |
| 4 | +import numpy as np |
| 5 | +import ufl |
| 6 | +from dolfinx.fem.petsc import ( |
| 7 | + LinearProblem, |
| 8 | + apply_lifting, |
| 9 | + assemble_matrix_mat, |
| 10 | + assemble_vector, |
| 11 | + set_bc, |
| 12 | +) |
| 13 | +from dolfinx.mesh import CellType, create_rectangle |
| 14 | +from fenicsxprecice import Adapter |
| 15 | +from mpi4py import MPI |
| 16 | +from petsc4py import PETSc |
| 17 | + |
| 18 | +# ------ # |
| 19 | +# SOLVER # |
| 20 | +# ------ # |
| 21 | + |
| 22 | +# EXTEND the linear petsc.LinearProblem to add values into the bilinear form matrix on |
| 23 | +# desired DOFs. Wee need this due to the incoming Force are discrete vector, so wee need |
| 24 | +# to avoid domain integration |
| 25 | +class DiscreteLinearProblem(LinearProblem): |
| 26 | + def __init__( |
| 27 | + self, |
| 28 | + a, |
| 29 | + L, |
| 30 | + bcs, |
| 31 | + u, |
| 32 | + point_dofs, |
| 33 | + petsc_options=None, |
| 34 | + form_compiler_options=None, |
| 35 | + jit_options=None, |
| 36 | + ): |
| 37 | + if point_dofs is not None: |
| 38 | + if not isinstance(type(point_dofs), np.ndarray): |
| 39 | + point_dofs = np.array([point_dofs], dtype="int32") |
| 40 | + self._point_dofs = point_dofs |
| 41 | + |
| 42 | + super().__init__(a, L, bcs, u, petsc_options, form_compiler_options, jit_options) |
| 43 | + |
| 44 | + def solve(self, values): |
| 45 | + """Solve the problem.""" |
| 46 | + # Assemble lhs |
| 47 | + self._A.zeroEntries() |
| 48 | + assemble_matrix_mat(self._A, self._a, bcs=self.bcs) |
| 49 | + self._A.assemble() |
| 50 | + |
| 51 | + # Assemble rhs |
| 52 | + with self._b.localForm() as b_loc: |
| 53 | + b_loc.set(0) |
| 54 | + |
| 55 | + if self._point_dofs is not None and values is not None: |
| 56 | + # apply load in dofs |
| 57 | + self._b[self._point_dofs] = values |
| 58 | + self._b.assemble() |
| 59 | + |
| 60 | + assemble_vector(self._b, self._L) |
| 61 | + |
| 62 | + # Apply boundary conditions to the rhs |
| 63 | + apply_lifting(self._b, [self._a], bcs=[self.bcs]) |
| 64 | + self._b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE) |
| 65 | + set_bc(self._b, self.bcs) |
| 66 | + |
| 67 | + # Solve linear system and update ghost values in the solution |
| 68 | + self._solver.solve(self._b, self._x) |
| 69 | + self.u.x.scatter_forward() |
| 70 | + |
| 71 | + return self.u |
| 72 | + |
| 73 | + |
| 74 | +# --------- # |
| 75 | +# CONSTANTS # |
| 76 | +# --------- # |
| 77 | + |
| 78 | +MPI_COMM = MPI.COMM_WORLD |
| 79 | +CURRENT_FOLDER = os.path.dirname(__file__) |
| 80 | +PARTICIPANT_CONFIG = os.path.join(CURRENT_FOLDER, "precice-config.json") |
| 81 | +RESULTS_DIR = os.path.join(CURRENT_FOLDER, "results") |
| 82 | +os.makedirs(RESULTS_DIR, exist_ok=True) |
| 83 | +os.chdir(CURRENT_FOLDER) |
| 84 | + |
| 85 | +WRITER = dfx.io.VTKFile(MPI_COMM, f"{RESULTS_DIR}/result.pvd", "w") |
| 86 | + |
| 87 | +WIDTH, HEIGHT = 0.1, 1 |
| 88 | +NX, NY = 2, 15 * 8 |
| 89 | + |
| 90 | +E = 4000000.0 |
| 91 | +NU = 0.3 |
| 92 | +RHO = 3000.0 |
| 93 | + |
| 94 | +BETA_ = 0.25 |
| 95 | +GAMMA_ = 0.5 |
| 96 | + |
| 97 | +# ------- # |
| 98 | +# MESHING # |
| 99 | +# ------- # |
| 100 | + |
| 101 | +domain = create_rectangle( |
| 102 | + MPI_COMM, |
| 103 | + [np.array([-WIDTH / 2, 0]), np.array([WIDTH / 2, HEIGHT])], |
| 104 | + [NX, NY], |
| 105 | + cell_type=CellType.quadrilateral, |
| 106 | +) |
| 107 | +dim = domain.topology.dim |
| 108 | + |
| 109 | +# -------------- # |
| 110 | +# Function Space # |
| 111 | +# -------------- # |
| 112 | +degree = 2 |
| 113 | +shape = (dim,) |
| 114 | +V = dfx.fem.functionspace(domain, ("P", degree, shape)) |
| 115 | +u = dfx.fem.Function(V, name="Displacement") |
| 116 | +f = dfx.fem.Function(V, name="Force") |
| 117 | + |
| 118 | +# ------------------- # |
| 119 | +# Boundary conditions # |
| 120 | +# ------------------- # |
| 121 | +tol = 1e-14 |
| 122 | + |
| 123 | + |
| 124 | +def clamped_boundary(x): |
| 125 | + return abs(x[1]) < tol |
| 126 | + |
| 127 | + |
| 128 | +def neumann_boundary(x): |
| 129 | + """Determines whether a node is on the coupling boundary.""" |
| 130 | + return np.logical_or((np.abs(x[1] - HEIGHT) < tol), np.abs(np.abs(x[0]) - WIDTH / 2) < tol) |
| 131 | + |
| 132 | + |
| 133 | +fixed_boundary = dfx.fem.locate_dofs_geometrical(V, clamped_boundary) |
| 134 | +coupling_boundary = dfx.mesh.locate_entities_boundary(domain, dim - 1, neumann_boundary) |
| 135 | + |
| 136 | +bcs = [dfx.fem.dirichletbc(np.zeros((dim,)), fixed_boundary, V)] |
| 137 | + |
| 138 | +# ------------ # |
| 139 | +# PRECICE INIT # |
| 140 | +# ------------ # |
| 141 | +participant = Adapter(MPI_COMM, PARTICIPANT_CONFIG) |
| 142 | +participant.initialize(coupling_boundary, V, u) |
| 143 | +dt = participant.dt |
| 144 | + |
| 145 | +# ------------------------ # |
| 146 | +# linear elastic equations # |
| 147 | +# ------------------------ # |
| 148 | +E = dfx.fem.Constant(domain, E) |
| 149 | +nu = dfx.fem.Constant(domain, NU) |
| 150 | +rho = dfx.fem.Constant(domain, RHO) |
| 151 | + |
| 152 | +lmbda = E * nu / (1 + nu) / (1 - 2 * nu) |
| 153 | +mu = E / 2 / (1 + nu) |
| 154 | + |
| 155 | + |
| 156 | +def epsilon(v): |
| 157 | + return ufl.sym(ufl.grad(v)) |
| 158 | + |
| 159 | + |
| 160 | +def sigma(v): |
| 161 | + return lmbda * ufl.tr(epsilon(v)) * ufl.Identity(dim) + 2 * mu * epsilon(v) |
| 162 | + |
| 163 | + |
| 164 | +# ------------------- # |
| 165 | +# Time discretization # |
| 166 | +# ------------------- # |
| 167 | +# prev time step |
| 168 | +u_old = dfx.fem.Function(V) |
| 169 | +v_old = dfx.fem.Function(V) |
| 170 | +a_old = dfx.fem.Function(V) |
| 171 | + |
| 172 | +# current time step |
| 173 | +a_new = dfx.fem.Function(V) |
| 174 | +v_new = dfx.fem.Function(V) |
| 175 | + |
| 176 | +beta = dfx.fem.Constant(domain, BETA_) |
| 177 | +gamma = dfx.fem.Constant(domain, GAMMA_) |
| 178 | + |
| 179 | +dx = ufl.Measure("dx", domain=domain) |
| 180 | + |
| 181 | +a = (1 / (beta * dt**2)) * (u - u_old - dt * v_old) - ((1 - 2 * beta) / (2 * beta)) * a_old |
| 182 | +a_expr = dfx.fem.Expression(a, V.element.interpolation_points()) |
| 183 | + |
| 184 | +v = v_old + dt * ((1 - gamma) * a_old + gamma * a) |
| 185 | +v_expr = dfx.fem.Expression(v, V.element.interpolation_points()) |
| 186 | + |
| 187 | +# ------------------ # |
| 188 | +# mass, a stiffness # |
| 189 | +# ------------------ # |
| 190 | +u_ = ufl.TestFunction(V) |
| 191 | +du = ufl.TrialFunction(V) |
| 192 | + |
| 193 | + |
| 194 | +def mass(u, u_): |
| 195 | + return rho * ufl.dot(u, u_) * dx |
| 196 | + |
| 197 | + |
| 198 | +def stiffness(u, u_): |
| 199 | + return ufl.inner(sigma(u), epsilon(u_)) * dx |
| 200 | + |
| 201 | + |
| 202 | +Residual = mass(a, u_) + stiffness(u, u_) |
| 203 | +Residual_du = ufl.replace(Residual, {u: du}) |
| 204 | +a_form = ufl.lhs(Residual_du) |
| 205 | +L_form = ufl.rhs(Residual_du) |
| 206 | + |
| 207 | + |
| 208 | +problem = DiscreteLinearProblem( |
| 209 | + a=a_form, L=L_form, u=u, bcs=bcs, point_dofs=participant.interface_dof |
| 210 | +) |
| 211 | + |
| 212 | + |
| 213 | +# parameters for Time-Stepping |
| 214 | +t = 0.0 |
| 215 | + |
| 216 | +while participant.is_coupling_ongoing(): |
| 217 | + if participant.requires_writing_checkpoint(): # write checkpoint |
| 218 | + participant.store_checkpoint((u_old, v_old, a_old, t)) |
| 219 | + |
| 220 | + read_data = participant.read_data() |
| 221 | + |
| 222 | + u = problem.solve(read_data) |
| 223 | + |
| 224 | + # Write new displacements to preCICE |
| 225 | + participant.write_data(u) |
| 226 | + |
| 227 | + # Call to advance coupling, also returns the optimum time step value |
| 228 | + participant.advance(dt) |
| 229 | + |
| 230 | + # Either revert to old step if timestep has not converged or move to next timestep |
| 231 | + if participant.requires_reading_checkpoint(): |
| 232 | + ( |
| 233 | + u_cp, |
| 234 | + v_cp, |
| 235 | + a_cp, |
| 236 | + t_cp, |
| 237 | + ) = participant.retrieve_checkpoint() |
| 238 | + u_old.vector.copy(u_cp.vector) |
| 239 | + v_old.vector.copy(v_cp.vector) |
| 240 | + a_old.vector.copy(a_cp.vector) |
| 241 | + t = t_cp |
| 242 | + else: |
| 243 | + v_new.interpolate(v_expr) |
| 244 | + a_new.interpolate(a_expr) |
| 245 | + u.vector.copy(u_old.vector) |
| 246 | + v_new.vector.copy(v_old.vector) |
| 247 | + a_new.vector.copy(a_old.vector) |
| 248 | + t += dt |
| 249 | + |
| 250 | + if participant.is_time_window_complete(): |
| 251 | + f.vector[participant.interface_dof] = read_data |
| 252 | + f.vector.assemble() |
| 253 | + WRITER.write_function(u, t) |
| 254 | + WRITER.write_function(f, t) |
| 255 | + WRITER.close() |
| 256 | + |
| 257 | +WRITER.close() |
| 258 | +participant.finalize() |
0 commit comments