class Fluid(object): def __init__(self, mesh, coupling_boundary, complementary_boundary, bndry, dt, theta, v_max, mu_f, rho_f, result, *args, **kwargs): # initialize meshes and boundaries self.mesh = mesh self.coupling_boundary = coupling_boundary self.complementary_boundary = complementary_boundary bnd_mesh = BoundaryMesh(mesh, 'exterior') self.bndry = bndry # boundary-marking function for integration self.fenics_dt = float(dt) self.dt = Constant(dt) self.theta = theta self.t = 0.0 self.v_max = v_max self.mu_f = mu_f self.rho_f = rho_f # bounding box tree self.bb = BoundingBoxTree() self.bb.build(self.mesh) # Define finite elements eV = VectorElement("CG", mesh.ufl_cell(), 2) # velocity element eU = VectorElement("CG", mesh.ufl_cell(), 2) # displacement element eP = FiniteElement("CG", mesh.ufl_cell(), 1) # pressure element eW = MixedElement([eV, eU, eP]) # mixed element W = FunctionSpace(self.mesh, eW) # function space for ALE fluid equation self.W = W self.U = FunctionSpace(self.mesh, eU) # function space for projected functions self.W_boundary = VectorFunctionSpace(bnd_mesh, 'CG', 2) # boundary function space self.fun4forces = Function(self.W) # function for Variational formulation # Set boundary conditions self.v_in = Expression(("t<2.0? 0.5*(1.0 - cos(0.5*pi*t))*v_max*4/(gW*gW)*(x[1]*(gW - x[1])): \ v_max*4/(gW*gW)*(x[1]*(gW - x[1]))", "0.0"), degree = 2, v_max = Constant(self.v_max), gW = Constant(gW), t = self.t) #info("Expression set.") bc_v_in = DirichletBC(self.W.sub(0), self.v_in, bndry, _INFLOW) bc_v_walls = DirichletBC(self.W.sub(0), Constant((0.0, 0.0)), bndry, _WALLS) bc_v_circle = DirichletBC(self.W.sub(0), Constant((0.0, 0.0)), bndry, _FLUID_CYLINDER) bc_u_in = DirichletBC(self.W.sub(1), Constant((0.0, 0.0)), bndry, _INFLOW) bc_u_walls = DirichletBC(self.W.sub(1).sub(1), Constant(0.0), bndry, _WALLS) bc_u_circle = DirichletBC(self.W.sub(1), Constant((0.0, 0.0)), bndry, _FLUID_CYLINDER) bc_u_out = DirichletBC(self.W.sub(1), Constant((0.0, 0.0)), bndry, _OUTFLOW) self.bcs = [bc_v_in, bc_v_walls, bc_v_circle, bc_u_in, bc_u_walls, bc_u_circle, bc_u_out] #info("Normal and Circumradius.") self.n = FacetNormal(self.mesh) I = Identity(self.W.mesh().geometry().dim()) # Define functions self.w = Function(self.W) self.w0 = Function(self.W) (v_, u_, p_) = TestFunctions(self.W) (self.v, self.u, self.p) = split(self.w) (self.v0, self.u0, self.p0) = split(self.w0) # define coupling BCs self.u_D = Constant((0.0, 0.0)) # Dirichlet BC (for Fluid) self.u_D_function = interpolate(self.u_D, self.U) # Dirichlet BC as function self.f_N = Constant((0.0, 0.0)) # Neumann BC (for Solid) self.f_N_function = interpolate(self.f_N, self.U) # Neumann BC as function # start preCICE adapter info('Initialize Adapter.') self.precice = Adapter() # read forces(Neumann condition for solid) and write displacement(Dirichlet condition for fluid) info('Call precice.initialize(...).') self.precice_dt = self.precice.initialize( coupling_subdomain=self.coupling_boundary, mesh=self.mesh, read_field=self.u_D_function, write_field=self.f_N_function, u_n=self.w,#u_n, coupling_marker=self.bndry) # TODO: need to set DirichletBC for displacement AND velocity # functions for displacement BC self.u_bc = self.precice.create_coupling_dirichlet_boundary_condition(self.U) self.u_bc0 = self.u_bc dt = self.dt.values()[0] self.v_bc = self.u_bc # wrong condition, but code runs #self.v_bc = (1.0/self.dt)*(self.u_bc - self.u_bc0) # I need to do this, but raises error bc_u_FSI = DirichletBC(self.W.sub(1), self.u_bc, coupling_boundary) bc_v_FSI = DirichletBC(self.W.sub(0), self.v_bc, coupling_boundary) self.bcs.append(bc_u_FSI) self.bcs.append(bc_v_FSI) # define deformation gradient, Jacobian self.FF = I + grad(self.u) self.FF0 = I + grad(self.u0) self.JJ = det(self.FF) self.JJ0 = det(self.FF0) # mesh-moving eqaution (pseudoelasticity) self.gamma = 9.0/8.0 h = CellVolume(self.mesh)**(self.gamma) # makes mesh stiffer when mesh finer # (our mesh is finer by the FSI -moving- interface) E = Constant(1.0) E_mesh = E/h # Young Modulus nu_mesh = Constant(-0.02) # Poisson ratio mu_mesh = E_mesh/(2*(1.0+nu_mesh)) # Lame 1 lambda_mesh = (nu_mesh*E_mesh)/((1+nu_mesh)*(1-2*nu_mesh)) # Lame 2 # variational formulation for mesh motion F_mesh = inner(mu_mesh*2*sym(grad(self.u)), grad(u_))*dx(0) \ + lambda_mesh*inner(div(self.u), div(u_))*dx(0) # define referential Grad and Div shortcuts def Grad(f, F): return dot( grad(f), inv(F) ) def Div(f, F): return tr( Grad(f, F) ) # approximate time derivatives du = (1.0/self.dt)*(self.u - self.u0) dv = (1.0/self.dt)*(self.v - self.v0) # compute velocuty part of Cauchy stress tensor for fluid self.T_f = -self.p*I + 2*self.mu_f*sym(Grad(self.v, self.FF)) self.T_f0 = -self.p*I + 2*self.mu_f*sym(Grad(self.v0, self.FF0)) # compute 1st Piola-Kirchhoff tensor for fluid self.S_f = self.JJ *( -self.p*I + self.T_f )*inv(self.FF).T self.S_f0 = -self.JJ*self.p*I*inv(self.FF).T \ + self.JJ0*self.T_f0*inv(self.FF0).T # write equations for fluid a_fluid = inner(self.S_f , Grad(v_, self.FF))*self.JJ*dx \ + inner(self.rho_f*Grad(self.v, self.FF )*(self.v - du), v_)*self.JJ*dx a_fluid0 = inner(self.S_f0, Grad(v_, self.FF0))*self.JJ0*dx \ + inner(self.rho_f*Grad(self.v0, self.FF0)*(self.v0 - du), v_)*self.JJ0*dx b_fluid = inner(Div( self.v, self.FF ), p_)*self.JJ*dx b_fluid0 = inner(Div( self.v, self.FF ), p_)*self.JJ*dx # final variationl formulation self.F_fluid = (self.theta*self.JJ+(1.0 - self.theta)*self.JJ0)*self.rho_f*inner(dv, v_)*dx\ + self.theta*(a_fluid + b_fluid) + (1.0 - self.theta)*(a_fluid0 + b_fluid0) \ + F_mesh # differentiate w.r.t. unknown dF_fluid = derivative(self.F_fluid, self.w) # define problem and its solver self.problem = NonlinearVariationalProblem(self.F_fluid, self.w, bcs=self.bcs, J=dF_fluid) self.solver = NonlinearVariationalSolver(self.problem) # Variational problem for extracting forces (v, u, p) = TrialFunctions(self.W) self.traction = self.F_fluid - inner(v, v_)*ds(_FSI) self.hBC = DirichletBC(self.W.sub(0), Constant((0.0, 0.0)), self.complementary_boundary) # configure solver parameters self.solver.parameters['newton_solver']['relative_tolerance'] = 1e-6 self.solver.parameters['newton_solver']['absolute_tolerance'] = 1e-10 self.solver.parameters['newton_solver']['maximum_iterations'] = 50 self.solver.parameters['newton_solver']['linear_solver'] = 'mumps' # create files for saving if my_rank == 0: if not os.path.exists(result): os.makedirs(result) self.vfile = XDMFFile("%s/velocity.xdmf" % result) self.ufile = XDMFFile("%s/displacement.xdmf" % result) self.pfile = XDMFFile("%s/pressure.xdmf" % result) self.sfile = XDMFFile("%s/stress.xdmf" % result) self.vfile.parameters["flush_output"] = True self.ufile.parameters["flush_output"] = True self.pfile.parameters["flush_output"] = True self.sfile.parameters["flush_output"] = True with open(result+'/data.csv', 'w') as data_file: writer = csv.writer(data_file, delimiter=';', lineterminator='\n') writer.writerow(['time', 'x-coordinate of end of beam', 'y-coordinate of end of beam', 'drag', 'lift']) #info("Fluid __init__ done") def solve(self, t, n): self.t = t self.v_in.t = t self.dt.assign(np.min([self.fenics_dt, self.precice_dt])) # solve fluid equations self.solver.solve() # force-extracting procedure ABdry = assemble(lhs(self.traction),keep_diagonal=True) bBdry = assemble(rhs(self.traction)) self.hBC.apply(ABdry, bBdry) solve(ABdry, self.fun4forces.vector(), bBdry) self.fun4forces.set_allow_extrapolation(True) (self.forces, _, _) = split(self.fun4forces) self.forces = project(self.forces, self.U) forces_for_solid = interpolate(self.forces, self.W_boundary) # precice coupling step t, n, precice_timestep_complete, self.precice_dt \ = self.precice.advance(forces_for_solid, self.w, self.w0, t, self.dt.values()[0], n) return t, n, precice_timestep_complete def save(self, t): (v, u, p) = self.w.split() # save functions to files v.rename("v", "velocity") u.rename("u", "displacement") p.rename("p", "pressure") self.vfile.write(v, t) self.ufile.write(u, t) self.pfile.write(p, t) # Compute drag and lift w_ = Function(self.W) Fbc1 = DirichletBC(self.W.sub(0), Constant((1.0, 0.0)), self.bndry, _FLUID_CYLINDER) Fbc2 = DirichletBC(self.W.sub(0), Constant((1.0, 0.0)), self.bndry, _FSI) Fbc1.apply(w_.vector()) Fbc2.apply(w_.vector()) drag = -assemble(action(self.F_fluid,w_)) w_ = Function(self.W) Fbc1 = DirichletBC(self.W.sub(0), Constant((0.0, 1.0)), self.bndry, _FLUID_CYLINDER) Fbc2 = DirichletBC(self.W.sub(0), Constant((0.0, 1.0)), self.bndry, _FSI) Fbc1.apply(w_.vector()) Fbc2.apply(w_.vector()) lift = -assemble(action(self.F_fluid,w_)) # MPI trick to extract beam displacement self.w.set_allow_extrapolation(True) Ax_loc = self.u[0]((A.x(), A.y())) Ay_loc = self.u[1]((A.x(), A.y())) self.w.set_allow_extrapolation(False) pi = 0 if self.bb.compute_first_collision(A) < 4294967295: pi = 1 else: Ax_loc = 0.0 Ay_loc = 0.0 Ax = MPI.sum(comm, Ax_loc) / MPI.sum(comm, pi) Ay = MPI.sum(comm, Ay_loc) / MPI.sum(comm, pi) pi = 0 if self.bb.compute_first_collision(B) < 4294967295: pi = 1 else: pB_loc = 0.0 pB = MPI.sum(comm, pB_loc) / MPI.sum(comm, pi) p_diff = pB - pA # write data to data file if my_rank == 0: with open(result+'/data.csv', 'a') as data_file: writer = csv.writer(data_file, delimiter=';', lineterminator='\n') writer.writerow([t, Ax, Ay, drag, lift])
V_g = VectorFunctionSpace(mesh, 'P', 1) flux = Function(V_g) flux.rename("Flux", "") while precice.is_coupling_ongoing(): # Compute solution u^n+1, use bcs u_D^n+1, u^n and coupling bcs solve(a == L, u_np1, bcs) if problem is ProblemType.DIRICHLET: # Dirichlet problem obtains flux from solution and sends flux on boundary to Neumann problem determine_gradient(V_g, u_np1, flux) flux_x, flux_y = flux.split() if domain_part is DomainPart.RIGHT: flux_x = -1 * flux_x t, n, precice_timestep_complete, precice_dt = precice.advance( flux_x, u_np1, u_n, t, dt(0), n) elif problem is ProblemType.NEUMANN: # Neumann problem obtains sends temperature on boundary to Dirichlet problem t, n, precice_timestep_complete, precice_dt = precice.advance( u_np1, u_np1, u_n, t, dt(0), n) dt.assign( np.min([fenics_dt, precice_dt]) ) # todo we could also consider deciding on time stepping size inside the adapter if precice_timestep_complete: u_ref = interpolate(u_D, V) u_ref.rename("reference", " ") error, error_pointwise = compute_errors(u_n, u_ref, V,
class Solid(object): """ Solid is defined as a object for which the subroutines solve() and save() are called externally in the time loop. """ def __init__(self, mesh, coupling_boundary, complementary_boundary, bndry, dt, theta, lambda_s, mu_s, rho_s, result, *args, **kwargs): self.mesh = mesh self.coupling_boundary = coupling_boundary self.complementary_boundary = complementary_boundary self.fenics_dt = dt self.dt = Constant(dt) self.theta = theta self.lambda_s = lambda_s self.mu_s = mu_s self.rho_s = rho_s self.bndry = bndry # bounding box tree - in parallel computations takes care about proper parallelization self.bb = BoundingBoxTree() self.bb.build(self.mesh) # Define finite elements eV = VectorElement("CG", mesh.ufl_cell(), 2) # velocity space eU = VectorElement("CG", mesh.ufl_cell(), 2) # displacement space eW = MixedElement([eV, eU]) # function space W = FunctionSpace(self.mesh, eW) self.W = W self.U = FunctionSpace(self.mesh, eU) # function space for exchanging BCs # define Dirichlet boundary conditions bc_u = DirichletBC(self.W.sub(1), Constant((0.0, 0.0)), self.complementary_boundary) bc_v = DirichletBC(self.W.sub(0), Constant((0.0, 0.0)), self.complementary_boundary) self.bcs = [bc_v, bc_u] # define normal self.n = FacetNormal(self.mesh) # define Identity I = Identity(self.W.mesh().geometry().dim()) # Define functions self.w = Function(self.W) # solution in current time step self.w0 = Function(self.W) # solution from previous time step (v_, u_) = TestFunctions(self.W) # test functions # split mixed functions into velocity (v) and displacement part (u) (self.v, self.u) = split(self.w) (self.v0, self.u0) = split(self.w0) # define coupling BCs self.u_D = Constant((0.0, 0.0)) # Dirichlet BC (for Fluid) self.u_D_function = interpolate(self.u_D, self.U) # Dirichlet BC as function self.f_N = Constant((0.0, 0.0)) # Neumann BC (for Solid) self.f_N_function = interpolate(self.f_N, self.U) # Neumann BC as function # initial value of Dirichlet BC self.u_n = interpolate(self.u_D, self.U) # create self.displacement function for exchanging BCs self.displacement = Function(self.U) self.displacement.assign(project(self.u, self.U)) self.displacement.rename("Displacement", "") # start preCICE adapter info('Initialize Adapter.') self.precice = Adapter() # read forces(Neumann condition for solid) and write displacement(Dirichlet condition for fluid) info('Call precice.initialize(...).') self.precice_dt = self.precice.initialize( coupling_subdomain=self.coupling_boundary, mesh=self.mesh, read_field=self.f_N_function, write_field=self.u_D_function, u_n=self.w, #u_n, coupling_marker=self.bndry) # choose time step self.dt.assign(np.min([self.precice_dt, self.fenics_dt])) # define deformation gradient self.FF = I + grad(self.u) self.FF0 = I + grad(self.u0) # approximate time derivatives du = (1.0 / self.dt) * (self.u - self.u0) dv = (1.0 / self.dt) * (self.v - self.v0) # write Green-St. Venant strain tensor E_s = 0.5 * (self.FF.T * self.FF - I) E_s0 = 0.5 * (self.FF0.T * self.FF0 - I) # compute 1st Piola-Kirchhoff tensor for solid (St. Venant - Kirchhoff model) S_s = self.FF * (self.lambda_s * tr(E_s) * I + 2.0 * self.mu_s * (E_s)) S_s0 = self.FF0 * (self.lambda_s * tr(E_s0) * I + 2.0 * self.mu_s * (E_s0)) #delta_W = 0.01 alpha = Constant(1.0) # Constant(1.0/delta_W) # Constant(1000) # get surface forces from precice self.f_surface = alpha * self.precice.create_coupling_neumann_boundary_condition( v_, 1, self.U) #self.f_surface = Function(self.U) # write equation for solid self.F_solid = rho_s*inner(dv, v_)*dx \ + self.theta*inner(S_s , grad(v_))*dx \ + (1.0 - self.theta)*inner(S_s0, grad(v_))*dx \ + inner(du - (self.theta*self.v + (1.0 - self.theta)*self.v0), u_)*dx \ - inner(self.f_surface, v_)*dss(1) # apply Neumann boundary condition on coupling interface #self.F_solid += self.precice.create_coupling_neumann_boundary_condition(v_, 1, self.U) # differentiate equation for solid for the Newton scheme self.dF_solid = derivative(self.F_solid, self.w) # define Nonlinear Variational problem and Newton solver self.problem = NonlinearVariationalProblem(self.F_solid, self.w, bcs=self.bcs, J=self.dF_solid) self.solver = NonlinearVariationalSolver(self.problem) # configure solver parameters self.solver.parameters['newton_solver']['relative_tolerance'] = 1e-6 self.solver.parameters['newton_solver']['absolute_tolerance'] = 1e-10 self.solver.parameters['newton_solver']['maximum_iterations'] = 50 self.solver.parameters['newton_solver']['linear_solver'] = 'mumps' # create files for saving if not os.path.exists(result): os.makedirs(result) self.vfile = XDMFFile("%s/velocity.xdmf" % result) self.ufile = XDMFFile("%s/displacement.xdmf" % result) self.sfile = XDMFFile("%s/stress.xdmf" % result) #self.ffile = XDMFFile("%s/forces.xdmf" % result) self.vfile.parameters["flush_output"] = True self.ufile.parameters["flush_output"] = True self.sfile.parameters["flush_output"] = True #self.ffile.parameters["flush_output"] = True with open(result + '/data.csv', 'w') as data_file: writer = csv.writer(data_file, delimiter=';', lineterminator='\n') writer.writerow( ['time', 'Ax_displacement', 'Ay_displacement', 'lift', 'drag']) info("Solid __init__ done") def solve(self, t, n): self.t = t self.dt.assign(np.min([self.fenics_dt, self.precice_dt])) # solve problem self.solver.solve() # extract velocity v and displacement u from mixed vector w (v, u) = self.w.split() # update displacement (Dirichlet BC for fluid) self.displacement.assign(project(u, self.U)) #self.displacement0.assign(self.displacement) #self.displacement.assign(project(self.u, self.U)) # precice coupling step t, n, precice_timestep_complete, self.precice_dt \ = self.precice.advance(self.displacement, self.w, self.w0,#self.displacement, self.displacement0, t, self.dt.values()[0], n) return t, n, precice_timestep_complete def save(self, t): (v, u) = self.w.split() # save velocity and displacement v.rename("v", "velocity") u.rename("u", "displacement") self.vfile.write(v, t) self.ufile.write(u, t) #self.ffile.write(self.f_surface, t) # extract values of displacament in point A = (0.6, 0.2) self.w.set_allow_extrapolation(True) Ax_loc = self.u[0]((A.x(), A.y())) Ay_loc = self.u[1]((A.x(), A.y())) self.w.set_allow_extrapolation(False) # MPI trick to extract nodal values in case of more processes pi = 0 if self.bb.compute_first_collision(A) < 4294967295: pi = 1 else: Ax_loc = 0.0 Ay_loc = 0.0 Ax = MPI.sum(comm, Ax_loc) / MPI.sum(comm, pi) Ay = MPI.sum(comm, Ay_loc) / MPI.sum(comm, pi) # evaluate forces exerted by the fluid (lift and drag) drag = -assemble(self.f_surface[0] * dss(1)) lift = -assemble(self.f_surface[1] * dss(1)) # write displacement and forces to file with open(result + '/data.csv', 'a') as data_file: writer = csv.writer(data_file, delimiter=';', lineterminator='\n') writer.writerow([t, Ax, Ay, lift, drag]) info(' Ax: {}\n Ay: {} \n lift: {} \n drag: {}'.format( Ax, Ay, lift, drag))
elif Case is StructureCase.RFERENCE: displacement_out = File("Reference/u_ref.pvd") displacement_out << u_n #time loop for coupling if Case is not StructureCase.RFERENCE: while precice.is_coupling_ongoing(): #solve for new displacements solve(a_form == L_form, u_np1, bc) # call precice.advance t, n, precice_timestep_complete, precice_dt = precice.advance( u_np1, u_np1, u_n, t, float(dt), n) if precice_timestep_complete: update_fields(u_np1, saved_u_old, v_n, a_n) if n % 10 == 0: displacement_out << (u_n, t) u_tip.append(u_n(0., 1.)[0]) time.append(t) # stop coupling precice.finalize() #time loop for reference calculation
read_data = precice.read_data() # Update the coupling expression with the new read data precice.update_coupling_expression(coupling_expression, read_data) dt.assign(np.min([fenics_dt, precice_dt])) # Compute solution solve(a == L, u_np1, bcs) # Dirichlet problem obtains flux from solution and sends flux on boundary to Neumann problem fluxes = fluxes_from_temperature_full_domain(F_known_u, V, k) precice.write_data(fluxes) precice_dt = precice.advance(dt(0)) if precice.is_action_required(precice.action_read_iteration_checkpoint()): # roll back to checkpoint u_cp, t_cp, n_cp = precice.retrieve_checkpoint() u_n.assign(u_cp) t = t_cp n = n_cp else: # update solution u_n.assign(u_np1) t += dt n += 1 if precice.is_time_window_complete(): # if abs(t % dt_out) < 10e-5: # output if t is a multiple of dt_out file_out << u_n
u_np1 = Function(V) F_known_u = u_np1 * v / dt * dx + alpha * dot(grad(u_np1), grad(v)) * dx - u_n * v / dt * dx t = 0 u_D.t = t + dt file_out = File("Solid/VTK/%s.pvd" % precice._solver_name) n=0 while precice.is_coupling_ongoing(): # Compute solution solve(a == L, u_np1, bcs) # Dirichlet problem obtains flux from solution and sends flux on boundary to Neumann problem fluxes = fluxes_from_temperature_full_domain(F_known_u, V, k) t, n, precice_timestep_is_complete, precice_dt = precice.advance(fluxes, u_np1, u_n, t, dt, n) dt = np.min([fenics_dt, precice_dt]) # todo we could also consider deciding on time stepping size inside the adapter if precice_timestep_is_complete: if abs(t % dt_out) < 10e-5 or abs(t % dt_out) < 10e-5: # just a very complicated way to only produce output if t is a multiple of dt_out file_out << u_n # Update dirichlet BC u_D.t = t + dt file_out << u_n # Hold plot precice.finalize()
grad(v)) * dx - u_n * v / dt * dx u_np1.rename("T", "") t = 0 u_D.t = t + precice._precice_tau assert (dt == precice._precice_tau) file_out = File("Solid/VTK/%s.pvd" % solver_name) while precice.is_coupling_ongoing(): # Compute solution solve(a == L, u_np1, bcs) # Dirichlet problem obtains flux from solution and sends flux on boundary to Neumann problem fluxes = fluxes_from_temperature_full_domain(F_known_u, V, k) is_converged = precice.advance(fluxes, dt) if is_converged: # Update previous solution if abs(t % dt_out) < 10e-5 or abs( t % dt_out ) < 10e-5: # just a very complicated way to only produce output if t is a multiple of dt_out file_out << u_np1 # Update current time t += precice._precice_tau # Update dirichlet BC u_D.t = t + precice._precice_tau u_n.assign(u_np1) file_out << u_np1