coupling_boundary = CouplingBoundary() remaining_boundary = OuterBoundary() bcs = [DirichletBC(V, u_D, remaining_boundary)] # Define initial value u_n = interpolate(u_D, V) u_n.rename("Temperature", "") precice = Adapter(adapter_config_filename, interpolation_strategy=interpolation_strategy) if problem is ProblemType.DIRICHLET: precice_dt = precice.initialize(coupling_subdomain=coupling_boundary, mesh=mesh, read_field=u_D_function, write_field=f_N_function, u_n=u_n) elif problem is ProblemType.NEUMANN: precice_dt = precice.initialize(coupling_subdomain=coupling_boundary, mesh=mesh, read_field=f_N_function, write_field=u_D_function, u_n=u_n) dt = Constant(0) dt.assign(np.min([fenics_dt, precice_dt])) # Define variational problem u = TrialFunction(V) v = TestFunction(V)
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])
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))
u_D_function = interpolate(u_D, V) # Define flux in x direction on coupling interface (grad(u_D) in normal direction) f_N = Constant('0') f_N_function = interpolate(f_N, V) coupling_boundary = TopBoundary() bottom_boundary = BottomBoundary() # Define initial value u_n = interpolate(u_D, V) u_n.rename("T", "") # Adapter definition and initialization precice = Adapter(adapter_config_filename="precice-adapter-config.json") precice_dt = precice.initialize(coupling_boundary, mesh, V) # Create a FEniCS Expression to define and control the coupling boundary values coupling_expression = precice.create_coupling_expression() # Assigning appropriate dt dt = Constant(0) dt.assign(np.min([fenics_dt, precice_dt])) # Define variational problem u = TrialFunction(V) v = TestFunction(V) F = u * v / dt * dx + alpha * dot(grad(u), grad(v)) * dx - u_n * v / dt * dx # apply constant Dirichlet boundary condition at bottom edge # apply Dirichlet boundary condition on coupling interface
# get the adapter ready # read fenics-adapter json-config-file) adapter_config_filename = "precice-adapter-config-fsi-s.json" # create Adapter precice = Adapter(adapter_config_filename) # create subdomains used by the adapter clamped_boundary_domain = AutoSubDomain(left_boundary) force_boundary = AutoSubDomain(remaining_boundary) precice_dt = precice.initialize(coupling_subdomain=coupling_boundary, mesh=mesh, read_field=f_N_function, write_field=u_function, u_n=u_n, dimension=dim, dirichlet_boundary=clamped_boundary_domain) fenics_dt = precice_dt # if fenics_dt == precice_dt, no subcycling is applied #fenics_dt = 0.02 # if fenics_dt < precice_dt, subcycling is applied dt = Constant(np.min([precice_dt, fenics_dt])) # generalized alpha method (time stepping) parameters alpha_m = Constant(0.2) alpha_f = Constant(0.4) gamma = Constant(0.5 + alpha_f - alpha_m) beta = Constant((gamma + 0.5)**2 * 0.25) # clamp (u == 0) the beam at the left
# Function to calculate displacement Deltas u_delta = Function(V) u_ref = Function(V) f_N_function = interpolate(Expression(("1", "0"), degree=1), V) u_function = interpolate(Expression(("0", "0"), degree=1), V) coupling_boundary = AutoSubDomain(neumann_boundary) fixed_boundary = AutoSubDomain(clamped_boundary) precice = Adapter(adapter_config_filename="precice-adapter-config-fsi-s.json") # Initialize the coupling interface precice_dt = precice.initialize(coupling_boundary, read_function_space=V, write_object=V, fixed_boundary=fixed_boundary) fenics_dt = precice_dt # if fenics_dt == precice_dt, no subcycling is applied # fenics_dt = 0.02 # if fenics_dt < precice_dt, subcycling is applied dt = Constant(np.min([precice_dt, fenics_dt])) # clamp the beam at the bottom bc = DirichletBC(V, Constant((0, 0)), fixed_boundary) # alpha method parameters alpha_m = Constant(0.2) alpha_f = Constant(0.4) gamma = Constant(0.5 + alpha_f - alpha_m) beta = Constant((gamma + 0.5)**2 / 4.)