def compute_functionals(self, velocity, pressure, t): if self.args.wss: info('Computing stress tensor') I = Identity(velocity.geometric_dimension()) T = TensorFunctionSpace(self.mesh, 'Lagrange', 1) stress = project(-pressure*I + 2*sym(grad(velocity)), T) info('Generating boundary mesh') wall_mesh = BoundaryMesh(self.mesh, 'exterior') # wall_mesh = SubMesh(self.mesh, self.facet_function, 1) # QQ why does not work? # plot(wall_mesh, interactive=True) info(' Boundary mesh geometric dim: %d' % wall_mesh.geometry().dim()) info(' Boundary mesh topologic dim: %d' % wall_mesh.topology().dim()) info('Projecting stress to boundary mesh') Tb = TensorFunctionSpace(wall_mesh, 'Lagrange', 1) stress_b = interpolate(stress, Tb) self.fileDict['wss']['file'] << stress_b if False: # does not work info('Computing WSS') n = FacetNormal(wall_mesh) info(stress_b, True) # wss = stress_b*n - inner(stress_b*n, n)*n wss = dot(stress_b, n) - inner(dot(stress_b, n), n)*n # equivalent Vb = VectorFunctionSpace(wall_mesh, 'Lagrange', 1) Sb = FunctionSpace(wall_mesh, 'Lagrange', 1) # wss_func = project(wss, Vb) wss_norm = project(sqrt(inner(wss, wss)), Sb) plot(wss_norm, interactive=True)
def nonlinearity(function): if self.use_ema: return 2*inner(dot(sym(grad(function)), u_ext), v1) * dx + inner(div(function)*u_ext, v1) * dx # return 2*inner(dot(sym(grad(function)), u_ext), v) * dx + inner(div(u_ext)*function, v) * dx # QQ implement this way? else: return inner(dot(grad(function), u_ext), v1) * dx
def test_dolfin_expression_compilation_of_dot_with_index_notation(dolfin): # Define some PyDOLFIN coefficients mesh = dolfin.UnitSquareMesh(3, 3) # Using quadratic element deliberately for accuracy V = dolfin.VectorFunctionSpace(mesh, "CG", 2) u = dolfin.Function(V) u.interpolate(dolfin.Expression(("x[0]", "x[1]"))) # Define ufl expression with the simplest possible index notation uexpr = ufl.dot(u, u) # Needs expand_compounds, not testing here uexpr = u[0]*u[0] + u[1]*u[1] # Works i, = ufl.indices(1) uexpr = u[i]*u[i] # Works # Define expected output from compilation # Oneliner version (no reuse in this expression): xc, yc = ('v_w0[0]', 'v_w0[1]') expected_lines = ['double s[1];', 'Array<double> v_w0(2);', 'w0->eval(v_w0, x);', 's[0] = pow(%(x)s, 2) + pow(%(y)s, 2);' % {'x':xc, 'y':yc}, 'values[0] = s[0];'] # Define expected evaluation values: [(x,value), (x,value), ...] x, y = (0.6, 0.7) expected = (x*x+y*y) expected_values = [((0.0, 0.0), (0.0,)), ((x, y), (expected,)), ] # Execute all tests check_dolfin_expression_compilation(uexpr, expected_lines, expected_values, members={'w0':u})
def forms(arguments, coefficients): v, u = arguments c, f = coefficients n = FacetNormal(triangle) a = u * v * dx L = f * v * dx b = u * v * dx(0) + inner(c * grad(u), grad(v)) * \ dx(1) + dot(n, grad(u)) * v * ds + f * v * dx return (a, L, b)
def compute_functionals(self, velocity, pressure, t, step): if self.args.wss == 'all' or \ (step >= self.stepsInCycle and self.args.wss == 'peak' and (self.distance_from_chosen_steps < 0.5 * self.metadata['dt'])): # TODO check if choosing time steps works properly # QQ might skip step? change 0.5 to 0.51? self.tc.start('WSS') begin('WSS (%dth step)' % step) if self.args.wss_method == 'expression': stress = project(self.nu*2*sym(grad(velocity)), self.T) # pressure is not used as it contributes only to the normal component stress.set_allow_extrapolation(True) # need because of some inaccuracies in BoundaryMesh coordinates stress_b = interpolate(stress, self.Tb) # restrict stress to boundary mesh # self.fileDict['stress']['file'].write(stress_b, self.actual_time) # info('Saved stress tensor') info('Computing WSS') wss = dot(stress_b, self.nb) - inner(dot(stress_b, self.nb), self.nb)*self.nb wss_func = project(wss, self.Vb) wss_norm = project(sqrt_ufl(inner(wss, wss)), self.Sb) info('Saving WSS') self.fileDict['wss']['file'].write(wss_func, self.actual_time) self.fileDict['wss_norm']['file'].write(wss_norm, self.actual_time) if self.args.wss_method == 'integral': wss_norm = Function(self.SDG) mS = TestFunction(self.SDG) scaling = 1/FacetArea(self.mesh) stress = self.nu*2*sym(grad(velocity)) wss = dot(stress, self.normal) - inner(dot(stress, self.normal), self.normal)*self.normal wss_norm_form = scaling*mS*sqrt_ufl(inner(wss, wss))*ds # ds is integral over exterior facets only assemble(wss_norm_form, tensor=wss_norm.vector()) self.fileDict['wss_norm']['file'].write(wss_norm, self.actual_time) # to get vector WSS values: # NT this works, but in ParaView for (DG,1)-vector space glyphs are displayed in cell centers # wss_vector = [] # for i in range(3): # wss_component = Function(self.SDG) # wss_vector_form = scaling*wss[i]*mS*ds # assemble(wss_vector_form, tensor=wss_component.vector()) # wss_vector.append(wss_component) # wss_func = project(as_vector(wss_vector), self.VDG) # self.fileDict['wss']['file'].write(wss_func, self.actual_time) self.tc.end('WSS') end()
def diffusion(fce): if self.useLaplace: return nu * inner(grad(fce), grad(v1)) * dx else: form = inner(nu * 2 * sym(grad(fce)), sym(grad(v1))) * dx if self.bcv == 'CDN': return form if self.bcv == 'LAP': return form - inner(nu * dot(grad(fce).T, n), v1) * problem.get_outflow_measure_form() if self.bcv == 'DDN': return form # additional term must be added to non-constant part
def split_vector_laplace(cell, degree): m = Mesh(VectorElement('CG', cell, 1)) if cell.cellname() in ['interval * interval', 'quadrilateral']: hcurl_element = FiniteElement('RTCE', cell, degree) elif cell.cellname() == 'triangle * interval': U0 = FiniteElement('RT', triangle, degree) U1 = FiniteElement('CG', triangle, degree) V0 = FiniteElement('CG', interval, degree) V1 = FiniteElement('DG', interval, degree - 1) Wa = HCurlElement(TensorProductElement(U0, V0)) Wb = HCurlElement(TensorProductElement(U1, V1)) hcurl_element = EnrichedElement(Wa, Wb) elif cell.cellname() == 'quadrilateral * interval': hcurl_element = FiniteElement('NCE', cell, degree) RT = FunctionSpace(m, hcurl_element) CG = FunctionSpace(m, FiniteElement('Q', cell, degree)) sigma = TrialFunction(CG) u = TrialFunction(RT) tau = TestFunction(CG) v = TestFunction(RT) return [dot(u, grad(tau))*dx, dot(grad(sigma), v)*dx, dot(curl(u), curl(v))*dx]
def to_reference_coordinates(ufl_coordinate_element, parameters): # Set up UFL form cell = ufl_coordinate_element.cell() domain = ufl.Mesh(ufl_coordinate_element) K = ufl.JacobianInverse(domain) x = ufl.SpatialCoordinate(domain) x0_element = ufl.VectorElement("Real", cell, 0) x0 = ufl.Coefficient(ufl.FunctionSpace(domain, x0_element)) expr = ufl.dot(K, x - x0) # Translation to GEM C = ufl.Coefficient(ufl.FunctionSpace(domain, ufl_coordinate_element)) expr = ufl_utils.preprocess_expression(expr) expr = ufl_utils.simplify_abs(expr) builder = firedrake_interface.KernelBuilderBase() builder.domain_coordinate[domain] = C builder._coefficient(C, "C") builder._coefficient(x0, "x0") dim = cell.topological_dimension() point = gem.Variable('X', (dim,)) context = tsfc.fem.GemPointContext( interface=builder, ufl_cell=cell, precision=parameters["precision"], point_indices=(), point_expr=point, ) translator = tsfc.fem.Translator(context) ir = map_expr_dag(translator, expr) # Unroll result ir = [gem.Indexed(ir, alpha) for alpha in numpy.ndindex(ir.shape)] # Unroll IndexSums max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent ir = gem.optimise.unroll_indexsum(ir, predicate=predicate) # Translate to COFFEE ir = impero_utils.preprocess_gem(ir) return_variable = gem.Variable('dX', (dim,)) assignments = [(gem.Indexed(return_variable, (i,)), e) for i, e in enumerate(ir)] impero_c = impero_utils.compile_gem(assignments, ()) body = tsfc.coffee.generate(impero_c, {}, parameters["precision"]) body.open_scope = False return body
def diffusion(fce): if self.useLaplace: return nu*inner(grad(fce), grad(v1)) * dx else: form = inner(nu * 2 * sym(grad(fce)), sym(grad(v1))) * dx if self.bcv == 'CDN': # IMP will work only if p=0 on output, or we must add term # inner(p0*n, v)*problem.get_outflow_measure_form() to avoid boundary layer return form if self.bcv == 'LAP': return form - inner(nu*dot(grad(fce).T, n), v1) * problem.get_outflow_measure_form() if self.bcv == 'DDN': # IMP will work only if p=0 on output, or we must add term # inner(p0*n, v)*problem.get_outflow_measure_form() to avoid boundary layer return form # additional term must be added to non-constant part
def split_mixed_poisson(cell, degree): m = Mesh(VectorElement('CG', cell, 1)) if cell.cellname() in ['interval * interval', 'quadrilateral']: hdiv_element = FiniteElement('RTCF', cell, degree) elif cell.cellname() == 'triangle * interval': U0 = FiniteElement('RT', triangle, degree) U1 = FiniteElement('DG', triangle, degree - 1) V0 = FiniteElement('CG', interval, degree) V1 = FiniteElement('DG', interval, degree - 1) Wa = HDivElement(TensorProductElement(U0, V1)) Wb = HDivElement(TensorProductElement(U1, V0)) hdiv_element = EnrichedElement(Wa, Wb) elif cell.cellname() == 'quadrilateral * interval': hdiv_element = FiniteElement('NCF', cell, degree) RT = FunctionSpace(m, hdiv_element) DG = FunctionSpace(m, FiniteElement('DQ', cell, degree - 1)) sigma = TrialFunction(RT) u = TrialFunction(DG) tau = TestFunction(RT) v = TestFunction(DG) return [dot(sigma, tau) * dx, div(tau) * u * dx, div(sigma) * v * dx]
elif dimension == 3: ellipsoid_mesh(domain_radius, domain_radius, domain_radius, h) # Read mesh with XDMFFile(MPI.COMM_WORLD, "mesh.xdmf", "r", XDMFFile.Encoding.HDF5) as xdmf: mesh = xdmf.read_mesh(name="Grid") n = FacetNormal(mesh) x = ufl.geometry.SpatialCoordinate(mesh) if dimension == 2: di = ufl.as_vector([np.cos(angle), np.sin(angle)]) elif dimension == 3: di = ufl.as_vector([np.cos(angle), np.sin(angle), 0]) ui = ufl.exp(1j * k0 * ufl.dot(di, x)) # Create function space V = FunctionSpace(mesh, FiniteElement("Lagrange", mesh.ufl_cell(), degree)) def circle_refractive_index(x): r = np.sqrt(x[0]**2 + x[1]**2) inside = (r <= radius) outside = (r > radius) return inside * ref_index * k0 + outside * k0 def sphere_refractive_index(x): r = np.sqrt(x[0]**2 + x[1]**2 + x[2]**2) inside = (r <= radius)
def laplace(cell, degree): m = Mesh(VectorElement('Q', cell, 1)) V = FunctionSpace(m, FiniteElement('Q', cell, degree, variant='spectral')) u = TrialFunction(V) v = TestFunction(V) return dot(grad(u), grad(v))*dx(rule=gll_quadrature_rule(cell, degree))
def initialize(self, pc): """Set up the problem context. Take the original mixed problem and reformulate the problem as a hybridized mixed system. A KSP is created for the Lagrange multiplier system. """ from firedrake import (FunctionSpace, Function, Constant, TrialFunction, TrialFunctions, TestFunction, DirichletBC) from firedrake.assemble import (allocate_matrix, create_assembly_callable) from firedrake.formmanipulation import split_form from ufl.algorithms.replace import replace # Extract the problem context prefix = pc.getOptionsPrefix() + "hybridization_" _, P = pc.getOperators() self.ctx = P.getPythonContext() if not isinstance(self.ctx, ImplicitMatrixContext): raise ValueError("The python context must be an ImplicitMatrixContext") test, trial = self.ctx.a.arguments() V = test.function_space() mesh = V.mesh() if len(V) != 2: raise ValueError("Expecting two function spaces.") if all(Vi.ufl_element().value_shape() for Vi in V): raise ValueError("Expecting an H(div) x L2 pair of spaces.") # Automagically determine which spaces are vector and scalar for i, Vi in enumerate(V): if Vi.ufl_element().sobolev_space().name == "HDiv": self.vidx = i else: assert Vi.ufl_element().sobolev_space().name == "L2" self.pidx = i # Create the space of approximate traces. W = V[self.vidx] if W.ufl_element().family() == "Brezzi-Douglas-Marini": tdegree = W.ufl_element().degree() else: try: # If we have a tensor product element h_deg, v_deg = W.ufl_element().degree() tdegree = (h_deg - 1, v_deg - 1) except TypeError: tdegree = W.ufl_element().degree() - 1 TraceSpace = FunctionSpace(mesh, "HDiv Trace", tdegree) # Break the function spaces and define fully discontinuous spaces broken_elements = ufl.MixedElement([ufl.BrokenElement(Vi.ufl_element()) for Vi in V]) V_d = FunctionSpace(mesh, broken_elements) # Set up the functions for the original, hybridized # and schur complement systems self.broken_solution = Function(V_d) self.broken_residual = Function(V_d) self.trace_solution = Function(TraceSpace) self.unbroken_solution = Function(V) self.unbroken_residual = Function(V) shapes = (V[self.vidx].finat_element.space_dimension(), np.prod(V[self.vidx].shape)) domain = "{[i,j]: 0 <= i < %d and 0 <= j < %d}" % shapes instructions = """ for i, j w[i,j] = w[i,j] + 1 end """ self.weight = Function(V[self.vidx]) par_loop((domain, instructions), ufl.dx, {"w": (self.weight, INC)}, is_loopy_kernel=True) instructions = """ for i, j vec_out[i,j] = vec_out[i,j] + vec_in[i,j]/w[i,j] end """ self.average_kernel = (domain, instructions) # Create the symbolic Schur-reduction: # Original mixed operator replaced with "broken" # arguments arg_map = {test: TestFunction(V_d), trial: TrialFunction(V_d)} Atilde = Tensor(replace(self.ctx.a, arg_map)) gammar = TestFunction(TraceSpace) n = ufl.FacetNormal(mesh) sigma = TrialFunctions(V_d)[self.vidx] if mesh.cell_set._extruded: Kform = (gammar('+') * ufl.jump(sigma, n=n) * ufl.dS_h + gammar('+') * ufl.jump(sigma, n=n) * ufl.dS_v) else: Kform = (gammar('+') * ufl.jump(sigma, n=n) * ufl.dS) # Here we deal with boundaries. If there are Neumann # conditions (which should be enforced strongly for # H(div)xL^2) then we need to add jump terms on the exterior # facets. If there are Dirichlet conditions (which should be # enforced weakly) then we need to zero out the trace # variables there as they are not active (otherwise the hybrid # problem is not well-posed). # If boundary conditions are contained in the ImplicitMatrixContext: if self.ctx.row_bcs: # Find all the subdomains with neumann BCS # These are Dirichlet BCs on the vidx space neumann_subdomains = set() for bc in self.ctx.row_bcs: if bc.function_space().index == self.pidx: raise NotImplementedError("Dirichlet conditions for scalar variable not supported. Use a weak bc") if bc.function_space().index != self.vidx: raise NotImplementedError("Dirichlet bc set on unsupported space.") # append the set of sub domains subdom = bc.sub_domain if isinstance(subdom, str): neumann_subdomains |= set([subdom]) else: neumann_subdomains |= set(as_tuple(subdom, int)) # separate out the top and bottom bcs extruded_neumann_subdomains = neumann_subdomains & {"top", "bottom"} neumann_subdomains = neumann_subdomains - extruded_neumann_subdomains integrand = gammar * ufl.dot(sigma, n) measures = [] trace_subdomains = [] if mesh.cell_set._extruded: ds = ufl.ds_v for subdomain in sorted(extruded_neumann_subdomains): measures.append({"top": ufl.ds_t, "bottom": ufl.ds_b}[subdomain]) trace_subdomains.extend(sorted({"top", "bottom"} - extruded_neumann_subdomains)) else: ds = ufl.ds if "on_boundary" in neumann_subdomains: measures.append(ds) else: measures.extend((ds(sd) for sd in sorted(neumann_subdomains))) markers = [int(x) for x in mesh.exterior_facets.unique_markers] dirichlet_subdomains = set(markers) - neumann_subdomains trace_subdomains.extend(sorted(dirichlet_subdomains)) for measure in measures: Kform += integrand*measure trace_bcs = [DirichletBC(TraceSpace, Constant(0.0), subdomain) for subdomain in trace_subdomains] else: # No bcs were provided, we assume weak Dirichlet conditions. # We zero out the contribution of the trace variables on # the exterior boundary. Extruded cells will have both # horizontal and vertical facets trace_subdomains = ["on_boundary"] if mesh.cell_set._extruded: trace_subdomains.extend(["bottom", "top"]) trace_bcs = [DirichletBC(TraceSpace, Constant(0.0), subdomain) for subdomain in trace_subdomains] # Make a SLATE tensor from Kform K = Tensor(Kform) # Assemble the Schur complement operator and right-hand side self.schur_rhs = Function(TraceSpace) self._assemble_Srhs = create_assembly_callable( K * Atilde.inv * AssembledVector(self.broken_residual), tensor=self.schur_rhs, form_compiler_parameters=self.ctx.fc_params) mat_type = PETSc.Options().getString(prefix + "mat_type", "aij") schur_comp = K * Atilde.inv * K.T self.S = allocate_matrix(schur_comp, bcs=trace_bcs, form_compiler_parameters=self.ctx.fc_params, mat_type=mat_type, options_prefix=prefix) self._assemble_S = create_assembly_callable(schur_comp, tensor=self.S, bcs=trace_bcs, form_compiler_parameters=self.ctx.fc_params, mat_type=mat_type) self._assemble_S() self.S.force_evaluation() Smat = self.S.petscmat nullspace = self.ctx.appctx.get("trace_nullspace", None) if nullspace is not None: nsp = nullspace(TraceSpace) Smat.setNullSpace(nsp.nullspace(comm=pc.comm)) # Set up the KSP for the system of Lagrange multipliers trace_ksp = PETSc.KSP().create(comm=pc.comm) trace_ksp.setOptionsPrefix(prefix) trace_ksp.setOperators(Smat) trace_ksp.setUp() trace_ksp.setFromOptions() self.trace_ksp = trace_ksp split_mixed_op = dict(split_form(Atilde.form)) split_trace_op = dict(split_form(K.form)) # Generate reconstruction calls self._reconstruction_calls(split_mixed_op, split_trace_op)
def initialize(self, pc): """Set up the problem context. Take the original mixed problem and reformulate the problem as a hybridized mixed system. A KSP is created for the Lagrange multiplier system. """ from firedrake import (FunctionSpace, Function, Constant, TrialFunction, TrialFunctions, TestFunction, DirichletBC) from firedrake.assemble import (allocate_matrix, create_assembly_callable) from firedrake.formmanipulation import split_form from ufl.algorithms.replace import replace # Extract the problem context prefix = pc.getOptionsPrefix() + "hybridization_" _, P = pc.getOperators() self.ctx = P.getPythonContext() if not isinstance(self.ctx, ImplicitMatrixContext): raise ValueError( "The python context must be an ImplicitMatrixContext") test, trial = self.ctx.a.arguments() V = test.function_space() mesh = V.mesh() if len(V) != 2: raise ValueError("Expecting two function spaces.") if all(Vi.ufl_element().value_shape() for Vi in V): raise ValueError("Expecting an H(div) x L2 pair of spaces.") # Automagically determine which spaces are vector and scalar for i, Vi in enumerate(V): if Vi.ufl_element().sobolev_space().name == "HDiv": self.vidx = i else: assert Vi.ufl_element().sobolev_space().name == "L2" self.pidx = i # Create the space of approximate traces. W = V[self.vidx] if W.ufl_element().family() == "Brezzi-Douglas-Marini": tdegree = W.ufl_element().degree() else: try: # If we have a tensor product element h_deg, v_deg = W.ufl_element().degree() tdegree = (h_deg - 1, v_deg - 1) except TypeError: tdegree = W.ufl_element().degree() - 1 TraceSpace = FunctionSpace(mesh, "HDiv Trace", tdegree) # Break the function spaces and define fully discontinuous spaces broken_elements = ufl.MixedElement( [ufl.BrokenElement(Vi.ufl_element()) for Vi in V]) V_d = FunctionSpace(mesh, broken_elements) # Set up the functions for the original, hybridized # and schur complement systems self.broken_solution = Function(V_d) self.broken_residual = Function(V_d) self.trace_solution = Function(TraceSpace) self.unbroken_solution = Function(V) self.unbroken_residual = Function(V) shapes = (V[self.vidx].finat_element.space_dimension(), np.prod(V[self.vidx].shape)) domain = "{[i,j]: 0 <= i < %d and 0 <= j < %d}" % shapes instructions = """ for i, j w[i,j] = w[i,j] + 1 end """ self.weight = Function(V[self.vidx]) par_loop((domain, instructions), ufl.dx, {"w": (self.weight, INC)}, is_loopy_kernel=True) instructions = """ for i, j vec_out[i,j] = vec_out[i,j] + vec_in[i,j]/w[i,j] end """ self.average_kernel = (domain, instructions) # Create the symbolic Schur-reduction: # Original mixed operator replaced with "broken" # arguments arg_map = {test: TestFunction(V_d), trial: TrialFunction(V_d)} Atilde = Tensor(replace(self.ctx.a, arg_map)) gammar = TestFunction(TraceSpace) n = ufl.FacetNormal(mesh) sigma = TrialFunctions(V_d)[self.vidx] if mesh.cell_set._extruded: Kform = (gammar('+') * ufl.jump(sigma, n=n) * ufl.dS_h + gammar('+') * ufl.jump(sigma, n=n) * ufl.dS_v) else: Kform = (gammar('+') * ufl.jump(sigma, n=n) * ufl.dS) # Here we deal with boundaries. If there are Neumann # conditions (which should be enforced strongly for # H(div)xL^2) then we need to add jump terms on the exterior # facets. If there are Dirichlet conditions (which should be # enforced weakly) then we need to zero out the trace # variables there as they are not active (otherwise the hybrid # problem is not well-posed). # If boundary conditions are contained in the ImplicitMatrixContext: if self.ctx.row_bcs: # Find all the subdomains with neumann BCS # These are Dirichlet BCs on the vidx space neumann_subdomains = set() for bc in self.ctx.row_bcs: if bc.function_space().index == self.pidx: raise NotImplementedError( "Dirichlet conditions for scalar variable not supported. Use a weak bc" ) if bc.function_space().index != self.vidx: raise NotImplementedError( "Dirichlet bc set on unsupported space.") # append the set of sub domains subdom = bc.sub_domain if isinstance(subdom, str): neumann_subdomains |= set([subdom]) else: neumann_subdomains |= set( as_tuple(subdom, numbers.Integral)) # separate out the top and bottom bcs extruded_neumann_subdomains = neumann_subdomains & { "top", "bottom" } neumann_subdomains = neumann_subdomains - extruded_neumann_subdomains integrand = gammar * ufl.dot(sigma, n) measures = [] trace_subdomains = [] if mesh.cell_set._extruded: ds = ufl.ds_v for subdomain in sorted(extruded_neumann_subdomains): measures.append({ "top": ufl.ds_t, "bottom": ufl.ds_b }[subdomain]) trace_subdomains.extend( sorted({"top", "bottom"} - extruded_neumann_subdomains)) else: ds = ufl.ds if "on_boundary" in neumann_subdomains: measures.append(ds) else: measures.extend((ds(sd) for sd in sorted(neumann_subdomains))) markers = [int(x) for x in mesh.exterior_facets.unique_markers] dirichlet_subdomains = set(markers) - neumann_subdomains trace_subdomains.extend(sorted(dirichlet_subdomains)) for measure in measures: Kform += integrand * measure trace_bcs = [ DirichletBC(TraceSpace, Constant(0.0), subdomain) for subdomain in trace_subdomains ] else: # No bcs were provided, we assume weak Dirichlet conditions. # We zero out the contribution of the trace variables on # the exterior boundary. Extruded cells will have both # horizontal and vertical facets trace_subdomains = ["on_boundary"] if mesh.cell_set._extruded: trace_subdomains.extend(["bottom", "top"]) trace_bcs = [ DirichletBC(TraceSpace, Constant(0.0), subdomain) for subdomain in trace_subdomains ] # Make a SLATE tensor from Kform K = Tensor(Kform) # Assemble the Schur complement operator and right-hand side self.schur_rhs = Function(TraceSpace) self._assemble_Srhs = create_assembly_callable( K * Atilde.inv * AssembledVector(self.broken_residual), tensor=self.schur_rhs, form_compiler_parameters=self.ctx.fc_params) mat_type = PETSc.Options().getString(prefix + "mat_type", "aij") schur_comp = K * Atilde.inv * K.T self.S = allocate_matrix(schur_comp, bcs=trace_bcs, form_compiler_parameters=self.ctx.fc_params, mat_type=mat_type, options_prefix=prefix, appctx=self.get_appctx(pc)) self._assemble_S = create_assembly_callable( schur_comp, tensor=self.S, bcs=trace_bcs, form_compiler_parameters=self.ctx.fc_params, mat_type=mat_type) with timed_region("HybridOperatorAssembly"): self._assemble_S() Smat = self.S.petscmat nullspace = self.ctx.appctx.get("trace_nullspace", None) if nullspace is not None: nsp = nullspace(TraceSpace) Smat.setNullSpace(nsp.nullspace(comm=pc.comm)) # Create a SNESContext for the DM associated with the trace problem self._ctx_ref = self.new_snes_ctx(pc, schur_comp, trace_bcs, mat_type, self.ctx.fc_params, options_prefix=prefix) # dm associated with the trace problem trace_dm = TraceSpace.dm # KSP for the system of Lagrange multipliers trace_ksp = PETSc.KSP().create(comm=pc.comm) trace_ksp.incrementTabLevel(1, parent=pc) # Set the dm for the trace solver trace_ksp.setDM(trace_dm) trace_ksp.setDMActive(False) trace_ksp.setOptionsPrefix(prefix) trace_ksp.setOperators(Smat, Smat) # Option to add custom monitor monitor = self.ctx.appctx.get('custom_monitor', None) if monitor: monitor.add_reconstructor(self.backward_substitution) trace_ksp.setMonitor(monitor) self.trace_ksp = trace_ksp with dmhooks.add_hooks(trace_dm, self, appctx=self._ctx_ref, save=False): trace_ksp.setFromOptions() split_mixed_op = dict(split_form(Atilde.form)) split_trace_op = dict(split_form(K.form)) # Generate reconstruction calls self._reconstruction_calls(split_mixed_op, split_trace_op)
def test_diff_then_integrate(): # Define 1D geometry n = 21 mesh = UnitIntervalMesh(MPI.COMM_WORLD, n) # Shift and scale mesh x0, x1 = 1.5, 3.14 mesh.coordinates()[:] *= (x1 - x0) mesh.coordinates()[:] += x0 x = SpatialCoordinate(mesh)[0] xs = 0.1 + 0.8 * x / x1 # scaled to be within [0.1,0.9] # Define list of expressions to test, and configure # accuracies these expressions are known to pass with. # The reason some functions are less accurately integrated is # likely that the default choice of quadrature rule is not perfect F_list = [] def reg(exprs, acc=10): for expr in exprs: F_list.append((expr, acc)) # FIXME: 0*dx and 1*dx fails in the ufl-ffcx-jit framework somewhere # reg([Constant(0.0, cell=cell)]) # reg([Constant(1.0, cell=cell)]) monomial_list = [x**q for q in range(2, 6)] reg(monomial_list) reg([2.3 * p + 4.5 * q for p in monomial_list for q in monomial_list]) reg([x**x]) reg([x**(x**2)], 8) reg([x**(x**3)], 6) reg([x**(x**4)], 2) # Special functions: reg([atan(xs)], 8) reg([sin(x), cos(x), exp(x)], 5) reg([ln(xs), pow(x, 2.7), pow(2.7, x)], 3) reg([asin(xs), acos(xs)], 1) reg([tan(xs)], 7) try: import scipy except ImportError: scipy = None if hasattr(math, 'erf') or scipy is not None: reg([erf(xs)]) else: print( "Warning: skipping test of erf, old python version and no scipy.") # if 0: # print("Warning: skipping tests of bessel functions, doesn't build on all platforms.") # elif scipy is None: # print("Warning: skipping tests of bessel functions, missing scipy.") # else: # for nu in (0, 1, 2): # # Many of these are possibly more accurately integrated, # # but 4 covers all and is sufficient for this test # reg([bessel_J(nu, xs), bessel_Y(nu, xs), bessel_I(nu, xs), bessel_K(nu, xs)], 4) # To handle tensor algebra, make an x dependent input tensor # xx and square all expressions def reg2(exprs, acc=10): for expr in exprs: F_list.append((inner(expr, expr), acc)) xx = as_matrix([[2 * x**2, 3 * x**3], [11 * x**5, 7 * x**4]]) x3v = as_vector([3 * x**2, 5 * x**3, 7 * x**4]) cc = as_matrix([[2, 3], [4, 5]]) reg2([xx]) reg2([x3v]) reg2([cross(3 * x3v, as_vector([-x3v[1], x3v[0], x3v[2]]))]) reg2([xx.T]) reg2([tr(xx)]) reg2([det(xx)]) reg2([dot(xx, 0.1 * xx)]) reg2([outer(xx, xx.T)]) reg2([dev(xx)]) reg2([sym(xx)]) reg2([skew(xx)]) reg2([elem_mult(7 * xx, cc)]) reg2([elem_div(7 * xx, xx + cc)]) reg2([elem_pow(1e-3 * xx, 1e-3 * cc)]) reg2([elem_pow(1e-3 * cc, 1e-3 * xx)]) reg2([elem_op(lambda z: sin(z) + 2, 0.03 * xx)], 2) # pretty inaccurate... # FIXME: Add tests for all UFL operators: # These cause discontinuities and may be harder to test in the # above fashion: # 'inv', 'cofac', # 'eq', 'ne', 'le', 'ge', 'lt', 'gt', 'And', 'Or', 'Not', # 'conditional', 'sign', # 'jump', 'avg', # 'LiftingFunction', 'LiftingOperator', # FIXME: Test other derivatives: (but algorithms for operator # derivatives are the same!): # 'variable', 'diff', # 'Dx', 'grad', 'div', 'curl', 'rot', 'Dn', 'exterior_derivative', # Run through all operators defined above and compare integrals debug = 0 for F, acc in F_list: # Apply UFL differentiation f = diff(F, SpatialCoordinate(mesh))[..., 0] if debug: print(F) print(x) print(f) # Apply integration with DOLFINX # (also passes through form compilation and jit) M = f * dx f_integral = assemble_scalar(M) # noqa f_integral = mesh.mpi_comm().allreduce(f_integral, op=MPI.SUM) # Compute integral of f manually from anti-derivative F # (passes through pybind11 interface and uses UFL evaluation) F_diff = F((x1, )) - F((x0, )) # Compare results. Using custom relative delta instead # of decimal digits here because some numbers are >> 1. delta = min(abs(f_integral), abs(F_diff)) * 10**-acc assert f_integral - F_diff <= delta
u = ufl.TrialFunction(space) phi = ufl.TestFunction(space) dt = dune.ufl.Constant(0.01, "timeStep") t = dune.ufl.Constant(0.0, "time") # define storage for discrete solutions uh = space.interpolate(x, name="uh") uh_old = uh.copy() # problem definition # space form xForm = inner(grad(u), grad(phi)) * dx # add time discretization form = dot(u - uh_old, phi) * dx + dt * xForm # define dirichlet boundary conditions bc = dune.ufl.DirichletBC(space, x) # setup scheme dune.fem.parameter.append({"fem.verboserank": 0}) solverParameters =\ {"newton.tolerance": 1e-9, "newton.linear.tolerance": 1e-11, "newton.linear.preconditioning.method": "ilu", "newton.verbose": False, "newton.linear.verbose": False} scheme = solutionScheme([form == 0, bc], space, solver="cg",
def solve(self, problem): self.problem = problem doSave = problem.doSave save_this_step = False onlyVel = problem.saveOnlyVel dt = self.metadata['dt'] nu = Constant(self.problem.nu) self.tc.init_watch('init', 'Initialization', True, count_to_percent=False) self.tc.init_watch('rhs', 'Assembled right hand side', True, count_to_percent=True) self.tc.init_watch('applybc1', 'Applied velocity BC 1st step', True, count_to_percent=True) self.tc.init_watch('applybc3', 'Applied velocity BC 3rd step', True, count_to_percent=True) self.tc.init_watch('applybcP', 'Applied pressure BC or othogonalized rhs', True, count_to_percent=True) self.tc.init_watch('assembleMatrices', 'Initial matrix assembly', False, count_to_percent=True) self.tc.init_watch('solve 1', 'Running solver on 1st step', True, count_to_percent=True) self.tc.init_watch('solve 2', 'Running solver on 2nd step', True, count_to_percent=True) self.tc.init_watch('solve 3', 'Running solver on 3rd step', True, count_to_percent=True) self.tc.init_watch('solve 4', 'Running solver on 4th step', True, count_to_percent=True) self.tc.init_watch('assembleA1', 'Assembled A1 matrix (without stabiliz.)', True, count_to_percent=True) self.tc.init_watch('assembleA1stab', 'Assembled A1 stabilization', True, count_to_percent=True) self.tc.init_watch('next', 'Next step assignments', True, count_to_percent=True) self.tc.init_watch('saveVel', 'Saved velocity', True) self.tc.start('init') # Define function spaces (P2-P1) mesh = self.problem.mesh self.V = VectorFunctionSpace(mesh, "Lagrange", 2) # velocity self.Q = FunctionSpace(mesh, "Lagrange", 1) # pressure self.PS = FunctionSpace(mesh, "Lagrange", 2) # partial solution (must be same order as V) self.D = FunctionSpace(mesh, "Lagrange", 1) # velocity divergence space problem.initialize(self.V, self.Q, self.PS, self.D) # Define trial and test functions u = TrialFunction(self.V) v = TestFunction(self.V) p = TrialFunction(self.Q) q = TestFunction(self.Q) n = FacetNormal(mesh) I = Identity(find_geometric_dimension(u)) # Initial conditions: u0 velocity at previous time step u1 velocity two time steps back p0 previous pressure [u1, u0, p0] = self.problem.get_initial_conditions([{'type': 'v', 'time': -dt}, {'type': 'v', 'time': 0.0}, {'type': 'p', 'time': 0.0}]) u_ = Function(self.V) # current tentative velocity u_cor = Function(self.V) # current corrected velocity p_ = Function(self.Q) # current pressure or pressure help function from rotation scheme p_mod = Function(self.Q) # current modified pressure from rotation scheme # Define coefficients k = Constant(self.metadata['dt']) f = Constant((0, 0, 0)) # Define forms # step 1: Tentative velocity, solve to u_ u_ext = 1.5 * u0 - 0.5 * u1 # extrapolation for convection term # Stabilisation h = CellSize(mesh) if self.args.cbc_tau: # used in Simula cbcflow project tau = Constant(self.stabCoef) * h / (sqrt(inner(u_ext, u_ext)) + h) else: # proposed in R. Codina: On stabilized finite element methods for linear systems of # convection-diffusion-reaction equations. tau = Constant(self.stabCoef) * k * h ** 2 / ( 2 * nu * k + k * h * sqrt(DOLFIN_EPS + inner(u_ext, u_ext)) + h ** 2) # DOLFIN_EPS is added because of FEniCS bug that inner(u_ext, u_ext) can be negative when u_ext = 0 if self.use_full_SUPG: v1 = v + tau * 0.5 * dot(grad(v), u_ext) parameters['form_compiler']['quadrature_degree'] = 6 else: v1 = v def nonlinearity(function): if self.args.ema: return 2 * inner(dot(sym(grad(function)), u_ext), v1) * dx + inner(div(function) * u_ext, v1) * dx else: return inner(dot(grad(function), u_ext), v1) * dx def diffusion(fce): if self.useLaplace: return nu * inner(grad(fce), grad(v1)) * dx else: form = inner(nu * 2 * sym(grad(fce)), sym(grad(v1))) * dx if self.bcv == 'CDN': return form if self.bcv == 'LAP': return form - inner(nu * dot(grad(fce).T, n), v1) * problem.get_outflow_measure_form() if self.bcv == 'DDN': return form # additional term must be added to non-constant part def pressure_rhs(): if self.args.bc == 'outflow': return inner(p0, div(v1)) * dx else: return inner(p0, div(v1)) * dx - inner(p0 * n, v1) * problem.get_outflow_measure_form() a1_const = (1. / k) * inner(u, v1) * dx + diffusion(0.5 * u) a1_change = nonlinearity(0.5 * u) if self.bcv == 'DDN': # does not penalize influx for current step, only for the next one # this can lead to oscilation: # DDN correct next step, but then u_ext is OK so in next step DDN is not used, leading to new influx... # u and u_ext cannot be switched, min_value is nonlinear function a1_change += -0.5 * min_value(Constant(0.), inner(u_ext, n)) * inner(u, v1) * problem.get_outflow_measure_form() # NT works only with uflacs compiler L1 = (1. / k) * inner(u0, v1) * dx - nonlinearity(0.5 * u0) - diffusion(0.5 * u0) + pressure_rhs() if self.bcv == 'DDN': L1 += 0.5 * min_value(0., inner(u_ext, n)) * inner(u0, v1) * problem.get_outflow_measure_form() # Non-consistent SUPG stabilisation if self.stabilize and not self.use_full_SUPG: # a1_stab = tau*inner(dot(grad(u), u_ext), dot(grad(v), u_ext))*dx a1_stab = 0.5 * tau * inner(dot(grad(u), u_ext), dot(grad(v), u_ext)) * dx(None, {'quadrature_degree': 6}) # optional: to use Crank Nicolson in stabilisation term following change of RHS is needed: # L1 += -0.5*tau*inner(dot(grad(u0), u_ext), dot(grad(v), u_ext))*dx(None, {'quadrature_degree': 6}) outflow_area = Constant(problem.outflow_area) need_outflow = Constant(0.0) if self.useRotationScheme: # Rotation scheme F2 = inner(grad(p), grad(q)) * dx + (1. / k) * q * div(u_) * dx else: # Projection, solve to p_ if self.forceOutflow and problem.can_force_outflow: info('Forcing outflow.') F2 = inner(grad(p - p0), grad(q)) * dx + (1. / k) * q * div(u_) * dx for m in problem.get_outflow_measures(): F2 += (1. / k) * (1. / outflow_area) * need_outflow * q * m else: F2 = inner(grad(p - p0), grad(q)) * dx + (1. / k) * q * div(u_) * dx a2, L2 = system(F2) # step 3: Finalize, solve to u_ if self.useRotationScheme: # Rotation scheme F3 = (1. / k) * inner(u - u_, v) * dx + inner(grad(p_), v) * dx else: F3 = (1. / k) * inner(u - u_, v) * dx + inner(grad(p_ - p0), v) * dx a3, L3 = system(F3) if self.useRotationScheme: # Rotation scheme: modify pressure F4 = (p - p0 - p_ + nu * div(u_)) * q * dx a4, L4 = system(F4) # Assemble matrices self.tc.start('assembleMatrices') A1_const = assemble(a1_const) # must be here, so A1 stays one Python object during repeated assembly A1_change = A1_const.copy() # copy to get matrix with same sparse structure (data will be overwritten) if self.stabilize and not self.use_full_SUPG: A1_stab = A1_const.copy() # copy to get matrix with same sparse structure (data will be overwritten) A2 = assemble(a2) A3 = assemble(a3) if self.useRotationScheme: A4 = assemble(a4) self.tc.end('assembleMatrices') if self.solvers == 'direct': self.solver_vel_tent = LUSolver('mumps') self.solver_vel_cor = LUSolver('mumps') self.solver_p = LUSolver('mumps') if self.useRotationScheme: self.solver_rot = LUSolver('mumps') else: # NT 2016-1 KrylovSolver >> PETScKrylovSolver # not needed, chosen not to use hypre_parasails: # if self.prec_v == 'hypre_parasails': # in FEniCS 1.6.0 inaccessible using KrylovSolver class # self.solver_vel_tent = PETScKrylovSolver('gmres') # PETSc4py object # self.solver_vel_tent.ksp().getPC().setType('hypre') # PETScOptions.set('pc_hypre_type', 'parasails') # # this is global setting, but preconditioners for pressure solvers are set by their constructors # else: self.solver_vel_tent = PETScKrylovSolver('gmres', self.args.precV) # nonsymetric > gmres # cannot use 'ilu' in parallel self.solver_vel_cor = PETScKrylovSolver('cg', self.args.precVC) self.solver_p = PETScKrylovSolver(self.args.solP, self.args.precP) # almost (up to BC) symmetric > CG if self.useRotationScheme: self.solver_rot = PETScKrylovSolver('cg', 'hypre_amg') # setup Krylov solvers if self.solvers == 'krylov': # Get the nullspace if there are no pressure boundary conditions foo = Function(self.Q) # auxiliary vector for setting pressure nullspace if self.args.bc == 'nullspace': null_vec = Vector(foo.vector()) self.Q.dofmap().set(null_vec, 1.0) null_vec *= 1.0 / null_vec.norm('l2') self.null_space = VectorSpaceBasis([null_vec]) as_backend_type(A2).set_nullspace(self.null_space) # apply global options for Krylov solvers solver_options = {'monitor_convergence': True, 'maximum_iterations': 10000, 'nonzero_initial_guess': True} # 'nonzero_initial_guess': True with solver.solve(A, u, b) means that # Solver will use anything stored in u as an initial guess for solver in [self.solver_vel_tent, self.solver_vel_cor, self.solver_rot, self.solver_p] if \ self.useRotationScheme else [self.solver_vel_tent, self.solver_vel_cor, self.solver_p]: for key, value in solver_options.items(): try: solver.parameters[key] = value except KeyError: info('Invalid option %s for KrylovSolver' % key) return 1 if self.args.solP == 'richardson': self.solver_p.parameters['monitor_convergence'] = False self.solver_vel_tent.parameters['relative_tolerance'] = 10 ** (-self.args.prv1) self.solver_vel_tent.parameters['absolute_tolerance'] = 10 ** (-self.args.pav1) self.solver_vel_cor.parameters['relative_tolerance'] = 10E-12 self.solver_vel_cor.parameters['absolute_tolerance'] = 10E-4 self.solver_p.parameters['relative_tolerance'] = 10 ** (-self.args.prp) self.solver_p.parameters['absolute_tolerance'] = 10 ** (-self.args.pap) if self.useRotationScheme: self.solver_rot.parameters['relative_tolerance'] = 10E-10 self.solver_rot.parameters['absolute_tolerance'] = 10E-10 if self.args.Vrestart > 0: self.solver_vel_tent.parameters['gmres']['restart'] = self.args.Vrestart if self.args.solP == 'gmres' and self.args.Prestart > 0: self.solver_p.parameters['gmres']['restart'] = self.args.Prestart # boundary conditions bcu, bcp = problem.get_boundary_conditions(self.args.bc == 'outflow', self.V, self.Q) self.tc.end('init') # Time-stepping info("Running of Incremental pressure correction scheme n. 1") ttime = self.metadata['time'] t = dt step = 1 # debug function if problem.args.debug_rot: plot_cor_v = Function(self.V) while t < (ttime + dt / 2.0): self.problem.update_time(t, step) if self.MPI_rank == 0: problem.write_status_file(t) if doSave: save_this_step = problem.save_this_step # assemble matrix (it depends on solution) self.tc.start('assembleA1') assemble(a1_change, tensor=A1_change) # assembling into existing matrix is faster than assembling new one A1 = A1_const.copy() # we dont want to change A1_const A1.axpy(1, A1_change, True) self.tc.end('assembleA1') self.tc.start('assembleA1stab') if self.stabilize and not self.use_full_SUPG: assemble(a1_stab, tensor=A1_stab) # assembling into existing matrix is faster than assembling new one A1.axpy(1, A1_stab, True) self.tc.end('assembleA1stab') # Compute tentative velocity step begin("Computing tentative velocity") self.tc.start('rhs') b = assemble(L1) self.tc.end('rhs') self.tc.start('applybc1') [bc.apply(A1, b) for bc in bcu] self.tc.end('applybc1') try: self.tc.start('solve 1') self.solver_vel_tent.solve(A1, u_.vector(), b) self.tc.end('solve 1') if save_this_step: self.tc.start('saveVel') problem.save_vel(True, u_) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(True, u_) problem.compute_err(True, u_, t) problem.compute_div(True, u_) except RuntimeError as inst: problem.report_fail(t) return 1 end() if self.useRotationScheme: begin("Computing tentative pressure") else: begin("Computing pressure") if self.forceOutflow and problem.can_force_outflow: out = problem.compute_outflow(u_) info('Tentative outflow: %f' % out) n_o = -problem.last_inflow - out info('Needed outflow: %f' % n_o) need_outflow.assign(n_o) self.tc.start('rhs') b = assemble(L2) self.tc.end('rhs') self.tc.start('applybcP') [bc.apply(A2, b) for bc in bcp] if self.args.bc == 'nullspace': self.null_space.orthogonalize(b) self.tc.end('applybcP') try: self.tc.start('solve 2') self.solver_p.solve(A2, p_.vector(), b) self.tc.end('solve 2') except RuntimeError as inst: problem.report_fail(t) return 1 if self.useRotationScheme: foo = Function(self.Q) foo.assign(p_ + p0) if save_this_step and not onlyVel: problem.averaging_pressure(foo) problem.save_pressure(True, foo) else: foo = Function(self.Q) foo.assign(p_) # we do not want to change p_ by averaging if save_this_step and not onlyVel: problem.averaging_pressure(foo) problem.save_pressure(False, foo) end() begin("Computing corrected velocity") self.tc.start('rhs') b = assemble(L3) self.tc.end('rhs') if not self.args.B: self.tc.start('applybc3') [bc.apply(A3, b) for bc in bcu] self.tc.end('applybc3') try: self.tc.start('solve 3') self.solver_vel_cor.solve(A3, u_cor.vector(), b) self.tc.end('solve 3') problem.compute_err(False, u_cor, t) problem.compute_div(False, u_cor) except RuntimeError as inst: problem.report_fail(t) return 1 if save_this_step: self.tc.start('saveVel') problem.save_vel(False, u_cor) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(False, u_cor) end() if self.useRotationScheme: begin("Rotation scheme pressure correction") self.tc.start('rhs') b = assemble(L4) self.tc.end('rhs') try: self.tc.start('solve 4') self.solver_rot.solve(A4, p_mod.vector(), b) self.tc.end('solve 4') except RuntimeError as inst: problem.report_fail(t) return 1 if save_this_step and not onlyVel: problem.averaging_pressure(p_mod) problem.save_pressure(False, p_mod) end() if problem.args.debug_rot: # save applied pressure correction (expressed as a term added to RHS of next tentative vel. step) # see comment next to argument definition plot_cor_v.assign(project(k * grad(nu * div(u_)), self.V)) problem.fileDict['grad_cor']['file'].write(plot_cor_v, t) # compute functionals (e. g. forces) problem.compute_functionals(u_cor, p_mod if self.useRotationScheme else p_, t, step) # Move to next time step self.tc.start('next') u1.assign(u0) u0.assign(u_cor) u_.assign(u_cor) # use corrected velocity as initial guess in first step if self.useRotationScheme: p0.assign(p_mod) else: p0.assign(p_) t = round(t + dt, 6) # round time step to 0.000001 step += 1 self.tc.end('next') info("Finished: Incremental pressure correction scheme n. 1") problem.report() return 0
def damage_dissipation_density(self, alpha): w_1 = self.w(1) return self.w(alpha) + w_1 * self.ell**2 * dot(grad(alpha), grad(alpha))
def nitsche_ufl(mesh: dmesh.Mesh, mesh_data: Tuple[_cpp.mesh.MeshTags_int32, int, int], physical_parameters: dict = {}, nitsche_parameters: Dict[str, float] = {}, plane_loc: float = 0.0, vertical_displacement: float = -0.1, nitsche_bc: bool = True, quadrature_degree: int = 5, form_compiler_params: Dict = {}, jit_params: Dict = {}, petsc_options: Dict = {}, newton_options: Dict = {}) -> _fem.Function: """ Use UFL to compute the one sided contact problem with a mesh coming into contact with a rigid surface (not meshed). Parameters ========== mesh The input mesh mesh_data A triplet with a mesh tag for facets and values v0, v1. v0 should be the value in the mesh tags for facets to apply a Dirichlet condition on. v1 is the value for facets which should have applied a contact condition on physical_parameters Optional dictionary with information about the linear elasticity problem. Valid (key, value) tuples are: ('E': float), ('nu', float), ('strain', bool) nitsche_parameters Optional dictionary with information about the Nitsche configuration. Valid (keu, value) tuples are: ('gamma', float), ('theta', float) where theta can be -1, 0 or 1 for skew-symmetric, penalty like or symmetric enforcement of Nitsche conditions plane_loc The location of the plane in y-coordinate (2D) and z-coordinate (3D) vertical_displacement The amount of verticial displacment enforced on Dirichlet boundary nitsche_bc Use Nitche's method to enforce Dirichlet boundary conditions quadrature_degree The quadrature degree to use for the custom contact kernels form_compiler_params Parameters used in FFCX compilation of this form. Run `ffcx --help` at the commandline to see all available options. Takes priority over all other parameter values, except for `scalar_type` which is determined by DOLFINX. jit_params Parameters used in CFFI JIT compilation of C code generated by FFCX. See https://github.com/FEniCS/dolfinx/blob/main/python/dolfinx/jit.py for all available parameters. Takes priority over all other parameter values. petsc_options Parameters that is passed to the linear algebra backend PETSc. For available choices for the 'petsc_options' kwarg, see the `PETSc-documentation <https://petsc4py.readthedocs.io/en/stable/manual/ksp/>` newton_options Dictionary with Newton-solver options. Valid (key, item) tuples are: ("atol", float), ("rtol", float), ("convergence_criterion", "str"), ("max_it", int), ("error_on_nonconvergence", bool), ("relaxation_parameter", float) """ # Compute lame parameters plane_strain = physical_parameters.get("strain", False) E = physical_parameters.get("E", 1e3) nu = physical_parameters.get("nu", 0.1) mu_func, lambda_func = lame_parameters(plane_strain) mu = mu_func(E, nu) lmbda = lambda_func(E, nu) sigma = sigma_func(mu, lmbda) # Nitche parameters and variables theta = nitsche_parameters.get("theta", 1) gamma = nitsche_parameters.get("gamma", 1) (facet_marker, top_value, bottom_value) = mesh_data assert(facet_marker.dim == mesh.topology.dim - 1) # Normal vector pointing into plane (but outward of the body coming into contact) # Similar to computing the normal by finding the gap vector between two meshes n_vec = np.zeros(mesh.geometry.dim) n_vec[mesh.geometry.dim - 1] = -1 n_2 = ufl.as_vector(n_vec) # Normal of plane (projection onto other body) # Scaled Nitsche parameter h = ufl.CellDiameter(mesh) gamma_scaled = gamma * E / h # Mimicking the plane y=-plane_loc x = ufl.SpatialCoordinate(mesh) gap = x[mesh.geometry.dim - 1] + plane_loc g_vec = [i for i in range(mesh.geometry.dim)] g_vec[mesh.geometry.dim - 1] = gap V = _fem.VectorFunctionSpace(mesh, ("CG", 1)) u = _fem.Function(V) v = ufl.TestFunction(V) metadata = {"quadrature_degree": quadrature_degree} dx = ufl.Measure("dx", domain=mesh) ds = ufl.Measure("ds", domain=mesh, metadata=metadata, subdomain_data=facet_marker) a = ufl.inner(sigma(u), epsilon(v)) * dx zero = np.asarray([0, ] * mesh.geometry.dim, dtype=_PETSc.ScalarType) L = ufl.inner(_fem.Constant(mesh, zero), v) * dx # Derivation of one sided Nitsche with gap function n = ufl.FacetNormal(mesh) def sigma_n(v): # NOTE: Different normals, see summary paper return ufl.dot(sigma(v) * n, n_2) F = a - theta / gamma_scaled * sigma_n(u) * sigma_n(v) * ds(bottom_value) - L F += 1 / gamma_scaled * R_minus(sigma_n(u) + gamma_scaled * (gap - ufl.dot(u, n_2))) * \ (theta * sigma_n(v) - gamma_scaled * ufl.dot(v, n_2)) * ds(bottom_value) # Compute corresponding Jacobian du = ufl.TrialFunction(V) q = sigma_n(u) + gamma_scaled * (gap - ufl.dot(u, n_2)) J = ufl.inner(sigma(du), epsilon(v)) * ufl.dx - theta / gamma_scaled * sigma_n(du) * sigma_n(v) * ds(bottom_value) J += 1 / gamma_scaled * 0.5 * (1 - ufl.sign(q)) * (sigma_n(du) - gamma_scaled * ufl.dot(du, n_2)) * \ (theta * sigma_n(v) - gamma_scaled * ufl.dot(v, n_2)) * ds(bottom_value) # Nitsche for Dirichlet, another theta-scheme. # https://doi.org/10.1016/j.cma.2018.05.024 if nitsche_bc: disp_vec = np.zeros(mesh.geometry.dim) disp_vec[mesh.geometry.dim - 1] = vertical_displacement u_D = ufl.as_vector(disp_vec) F += - ufl.inner(sigma(u) * n, v) * ds(top_value)\ - theta * ufl.inner(sigma(v) * n, u - u_D) * \ ds(top_value) + gamma_scaled / h * ufl.inner(u - u_D, v) * ds(top_value) bcs = [] J += - ufl.inner(sigma(du) * n, v) * ds(top_value)\ - theta * ufl.inner(sigma(v) * n, du) * \ ds(top_value) + gamma_scaled / h * ufl.inner(du, v) * ds(top_value) else: # strong Dirichlet boundary conditions def _u_D(x): values = np.zeros((mesh.geometry.dim, x.shape[1])) values[mesh.geometry.dim - 1] = vertical_displacement return values u_D = _fem.Function(V) u_D.interpolate(_u_D) u_D.name = "u_D" u_D.x.scatter_forward() tdim = mesh.topology.dim dirichlet_dofs = _fem.locate_dofs_topological(V, tdim - 1, facet_marker.find(top_value)) bc = _fem.dirichletbc(u_D, dirichlet_dofs) bcs = [bc] # DEBUG: Write each step of Newton iterations # Create nonlinear problem and Newton solver # def form(self, x: _PETSc.Vec): # x.ghostUpdate(addv=_PETSc.InsertMode.INSERT, mode=_PETSc.ScatterMode.FORWARD) # self.i += 1 # xdmf.write_function(u, self.i) # setattr(_fem.petsc.NonlinearProblem, "form", form) problem = _fem.petsc.NonlinearProblem(F, u, bcs, J=J, jit_params=jit_params, form_compiler_params=form_compiler_params) # DEBUG: Write each step of Newton iterations # problem.i = 0 # xdmf = _io.XDMFFile(mesh.comm, "results/tmp_sol.xdmf", "w") # xdmf.write_mesh(mesh) solver = _nls.petsc.NewtonSolver(mesh.comm, problem) null_space = rigid_motions_nullspace(V) solver.A.setNearNullSpace(null_space) # Set Newton solver options solver.atol = newton_options.get("atol", 1e-9) solver.rtol = newton_options.get("rtol", 1e-9) solver.convergence_criterion = newton_options.get("convergence_criterion", "incremental") solver.max_it = newton_options.get("max_it", 50) solver.error_on_nonconvergence = newton_options.get("error_on_nonconvergence", True) solver.relaxation_parameter = newton_options.get("relaxation_parameter", 0.8) def _u_initial(x): values = np.zeros((mesh.geometry.dim, x.shape[1])) values[-1] = -0.01 - plane_loc return values # Set initial_condition: u.interpolate(_u_initial) # Define solver and options ksp = solver.krylov_solver opts = _PETSc.Options() option_prefix = ksp.getOptionsPrefix() # Set PETSc options opts = _PETSc.Options() opts.prefixPush(option_prefix) for k, v in petsc_options.items(): opts[k] = v opts.prefixPop() ksp.setFromOptions() # Solve non-linear problem _log.set_log_level(_log.LogLevel.INFO) num_dofs_global = V.dofmap.index_map_bs * V.dofmap.index_map.size_global with _common.Timer(f"{num_dofs_global} Solve Nitsche"): n, converged = solver.solve(u) u.x.scatter_forward() if solver.error_on_nonconvergence: assert(converged) print(f"{num_dofs_global}, Number of interations: {n:d}") return u
def sigma_n(v): # NOTE: Different normals, see summary paper return ufl.dot(sigma(v) * n, n_2)
import math from ufl import SpatialCoordinate, sin, pi, dot, as_vector from dune.grid import structuredGrid, gridFunction from dune.fem.space import lagrange grid = structuredGrid([0, 0], [1, 1], [5, 5]) space = lagrange(grid, order=3, dimRange=2) x = SpatialCoordinate(space) df = space.interpolate([ sin(2**i * pi * x[0] * x[1] / (0.25**(2 * i) + dot(x, x))) for i in range(1, 3) ], name="femgf") @gridFunction(grid) def f(x): return [ math.sin(2**i * math.pi * x[0] * x[1] / (0.25**(2 * i) + x[0] * x[0] + x[1] * x[1])) for i in range(1, 3) ] from dune.vtk import vtkWriter writer = vtkWriter(grid, "testFem", pointScalar={ "f": f,
ds = Measure("ds", subdomain_data=boundary_parts) # Define forms (dont redefine functions used here) # step 1 u0 = Function(V) u1 = Function(V) p0 = Function(Q) u_tent = TrialFunction(V) v = TestFunction(V) # U_ = 1.5*u0 - 0.5*u1 # nonlinearity = inner(dot(0.5 * (u_tent.dx(0) + u0.dx(0)), U_), v) * dx # F_tent = (1./dt)*inner(u_tent - u0, v) * dx + nonlinearity\ # + nu*inner(0.5 * (u_tent.dx(0) + u0.dx(0)), v.dx(0)) * dx + inner(p0.dx(0), v) * dx\ # - inner(f, v)*dx # solve to u_ # using explicite scheme: so LHS has interpretation as heat equation, RHS are sources F_tent = (1./dt)*inner(u_tent - u0, v)*dx + inner(dot(u0.dx(0), u0), v)*dx + nu*inner((u_tent.dx(0) + u0.dx(0)), v.dx(0)) * dx + inner(p0.dx(0), v) * dx\ - inner(f, v)*dx # solve to u_ a_tent, L_tent = system(F_tent) # step 2 u_tent_computed = Function(V) p = TrialFunction(Q) q = TestFunction(Q) F_p = inner(grad(p - p0), grad(q)) * dx + (1. / dt) * u_tent_computed.dx( 0) * q * dx # + 2*p.dx(0)*q*ds(1) # tried to force dp/dn=0 on inflow # TEST: prescribe Neumann outflow BC # F_p = inner(grad(p-p0), grad(q))*dx + (1./dt)*u_tent_computed.dx(0)*q*dx + (1./dt)*(v_in_expr-u_tent_computed)*q*ds(2) a_p, L_p = system(F_p) A_p = assemble(a_p) as_backend_type(A_p).set_nullspace(null_space) print(A_p.array()) # step 2 rotation
# # Author: Martin Sandve Alnes # Date: 2008-10-03 # from ufl import Coefficient, FiniteElement, dot, dx, grad, triangle element = FiniteElement("Lagrange", triangle, 1) f = Coefficient(element) a = (f * f + dot(grad(f), grad(f))) * dx
def nonlinearity(function): if self.args.ema: return 2 * inner(dot(sym(grad(function)), u_ext), v1) * dx + inner(div(function) * u_ext, v1) * dx else: return inner(dot(grad(function), u_ext), v1) * dx
def solve(self, problem): self.problem = problem doSave = problem.doSave save_this_step = False onlyVel = problem.saveOnlyVel dt = self.metadata['dt'] nu = Constant(self.problem.nu) # TODO check proper use of watches self.tc.init_watch('init', 'Initialization', True, count_to_percent=False) self.tc.init_watch('rhs', 'Assembled right hand side', True, count_to_percent=True) self.tc.init_watch('updateBC', 'Updated velocity BC', True, count_to_percent=True) self.tc.init_watch('applybc1', 'Applied velocity BC 1st step', True, count_to_percent=True) self.tc.init_watch('applybc3', 'Applied velocity BC 3rd step', True, count_to_percent=True) self.tc.init_watch('applybcP', 'Applied pressure BC or othogonalized rhs', True, count_to_percent=True) self.tc.init_watch('assembleMatrices', 'Initial matrix assembly', False, count_to_percent=True) self.tc.init_watch('solve 1', 'Running solver on 1st step', True, count_to_percent=True) self.tc.init_watch('solve 2', 'Running solver on 2nd step', True, count_to_percent=True) self.tc.init_watch('solve 3', 'Running solver on 3rd step', True, count_to_percent=True) self.tc.init_watch('solve 4', 'Running solver on 4th step', True, count_to_percent=True) self.tc.init_watch('assembleA1', 'Assembled A1 matrix (without stabiliz.)', True, count_to_percent=True) self.tc.init_watch('assembleA1stab', 'Assembled A1 stabilization', True, count_to_percent=True) self.tc.init_watch('next', 'Next step assignments', True, count_to_percent=True) self.tc.init_watch('saveVel', 'Saved velocity', True) self.tc.start('init') # Define function spaces (P2-P1) mesh = self.problem.mesh self.V = VectorFunctionSpace(mesh, "Lagrange", 2) # velocity self.Q = FunctionSpace(mesh, "Lagrange", 1) # pressure self.PS = FunctionSpace(mesh, "Lagrange", 2) # partial solution (must be same order as V) self.D = FunctionSpace(mesh, "Lagrange", 1) # velocity divergence space if self.bc == 'lagrange': L = FunctionSpace(mesh, "R", 0) QL = self.Q*L problem.initialize(self.V, self.Q, self.PS, self.D) # Define trial and test functions u = TrialFunction(self.V) v = TestFunction(self.V) if self.bc == 'lagrange': (pQL, rQL) = TrialFunction(QL) (qQL, lQL) = TestFunction(QL) else: p = TrialFunction(self.Q) q = TestFunction(self.Q) n = FacetNormal(mesh) I = Identity(u.geometric_dimension()) # Initial conditions: u0 velocity at previous time step u1 velocity two time steps back p0 previous pressure [u1, u0, p0] = self.problem.get_initial_conditions([{'type': 'v', 'time': -dt}, {'type': 'v', 'time': 0.0}, {'type': 'p', 'time': 0.0}]) if doSave: problem.save_vel(False, u0, 0.0) problem.save_vel(True, u0, 0.0) u_ = Function(self.V) # current tentative velocity u_cor = Function(self.V) # current corrected velocity if self.bc == 'lagrange': p_QL = Function(QL) # current pressure or pressure help function from rotation scheme pQ = Function(self.Q) # auxiliary function for conversion between QL.sub(0) and Q else: p_ = Function(self.Q) # current pressure or pressure help function from rotation scheme p_mod = Function(self.Q) # current modified pressure from rotation scheme # Define coefficients k = Constant(self.metadata['dt']) f = Constant((0, 0, 0)) # Define forms # step 1: Tentative velocity, solve to u_ u_ext = 1.5*u0 - 0.5*u1 # extrapolation for convection term # Stabilisation h = CellSize(mesh) # CBC delta: if self.cbcDelta: delta = Constant(self.stabCoef)*h/(sqrt(inner(u_ext, u_ext))+h) else: delta = Constant(self.stabCoef)*h**2/(2*nu*k + k*h*inner(u_ext, u_ext)+h**2) if self.use_full_SUPG: v1 = v + delta*0.5*k*dot(grad(v), u_ext) parameters['form_compiler']['quadrature_degree'] = 6 else: v1 = v def nonlinearity(function): if self.use_ema: return 2*inner(dot(sym(grad(function)), u_ext), v1) * dx + inner(div(function)*u_ext, v1) * dx # return 2*inner(dot(sym(grad(function)), u_ext), v) * dx + inner(div(u_ext)*function, v) * dx # QQ implement this way? else: return inner(dot(grad(function), u_ext), v1) * dx def diffusion(fce): if self.useLaplace: return nu*inner(grad(fce), grad(v1)) * dx else: form = inner(nu * 2 * sym(grad(fce)), sym(grad(v1))) * dx if self.bcv == 'CDN': # IMP will work only if p=0 on output, or we must add term # inner(p0*n, v)*problem.get_outflow_measure_form() to avoid boundary layer return form if self.bcv == 'LAP': return form - inner(nu*dot(grad(fce).T, n), v1) * problem.get_outflow_measure_form() if self.bcv == 'DDN': # IMP will work only if p=0 on output, or we must add term # inner(p0*n, v)*problem.get_outflow_measure_form() to avoid boundary layer return form # additional term must be added to non-constant part def pressure_rhs(): if self.useLaplace or self.bcv == 'LAP': return inner(p0, div(v1)) * dx - inner(p0*n, v1) * problem.get_outflow_measure_form() # NT term inner(inner(p, n), v) is 0 when p=0 on outflow else: return inner(p0, div(v1)) * dx a1_const = (1./k)*inner(u, v1)*dx + diffusion(0.5*u) a1_change = nonlinearity(0.5*u) if self.bcv == 'DDN': # IMP Problem: Does not penalize influx for current step, only for the next one # IMP this can lead to oscilation: DDN correct next step, but then u_ext is OK so in next step DDN is not used, leading to new influx... # u and u_ext cannot be switched, min_value is nonlinear function a1_change += -0.5*min_value(Constant(0.), inner(u_ext, n))*inner(u, v1)*problem.get_outflow_measure_form() # IMP works only with uflacs compiler L1 = (1./k)*inner(u0, v1)*dx - nonlinearity(0.5*u0) - diffusion(0.5*u0) + pressure_rhs() if self.bcv == 'DDN': L1 += 0.5*min_value(0., inner(u_ext, n))*inner(u0, v1)*problem.get_outflow_measure_form() # Non-consistent SUPG stabilisation if self.stabilize and not self.use_full_SUPG: # a1_stab = delta*inner(dot(grad(u), u_ext), dot(grad(v), u_ext))*dx a1_stab = 0.5*delta*inner(dot(grad(u), u_ext), dot(grad(v), u_ext))*dx(None, {'quadrature_degree': 6}) # NT optional: use Crank Nicolson in stabilisation term: change RHS # L1 += -0.5*delta*inner(dot(grad(u0), u_ext), dot(grad(v), u_ext))*dx(None, {'quadrature_degree': 6}) outflow_area = Constant(problem.outflow_area) need_outflow = Constant(0.0) if self.useRotationScheme: # Rotation scheme if self.bc == 'lagrange': F2 = inner(grad(pQL), grad(qQL))*dx + (1./k)*qQL*div(u_)*dx + pQL*lQL*dx + qQL*rQL*dx else: F2 = inner(grad(p), grad(q))*dx + (1./k)*q*div(u_)*dx else: # Projection, solve to p_ if self.bc == 'lagrange': F2 = inner(grad(pQL - p0), grad(qQL))*dx + (1./k)*qQL*div(u_)*dx + pQL*lQL*dx + qQL*rQL*dx else: if self.forceOutflow and problem.can_force_outflow: info('Forcing outflow.') F2 = inner(grad(p - p0), grad(q))*dx + (1./k)*q*div(u_)*dx for m in problem.get_outflow_measures(): F2 += (1./k)*(1./outflow_area)*need_outflow*q*m else: F2 = inner(grad(p - p0), grad(q))*dx + (1./k)*q*div(u_)*dx a2, L2 = system(F2) # step 3: Finalize, solve to u_ if self.useRotationScheme: # Rotation scheme if self.bc == 'lagrange': F3 = (1./k)*inner(u - u_, v)*dx + inner(grad(p_QL.sub(0)), v)*dx else: F3 = (1./k)*inner(u - u_, v)*dx + inner(grad(p_), v)*dx else: if self.bc == 'lagrange': F3 = (1./k)*inner(u - u_, v)*dx + inner(grad(p_QL.sub(0) - p0), v)*dx else: F3 = (1./k)*inner(u - u_, v)*dx + inner(grad(p_ - p0), v)*dx a3, L3 = system(F3) if self.useRotationScheme: # Rotation scheme: modify pressure if self.bc == 'lagrange': pr = TrialFunction(self.Q) qr = TestFunction(self.Q) F4 = (pr - p0 - p_QL.sub(0) + nu*div(u_))*qr*dx else: F4 = (p - p0 - p_ + nu*div(u_))*q*dx # TODO zkusit, jestli to nebude rychlejsi? nepocitat soustavu, ale p.assign(...), nutno project(div(u),Q) coz je pocitani podobne soustavy # TODO zkusit v project zadat solver_type='lu' >> primy resic by mel byt efektivnejsi a4, L4 = system(F4) # Assemble matrices self.tc.start('assembleMatrices') A1_const = assemble(a1_const) # need to be here, so A1 stays one Python object during repeated assembly A1_change = A1_const.copy() # copy to get matrix with same sparse structure (data will be overwriten) if self.stabilize and not self.use_full_SUPG: A1_stab = A1_const.copy() # copy to get matrix with same sparse structure (data will be overwriten) A2 = assemble(a2) A3 = assemble(a3) if self.useRotationScheme: A4 = assemble(a4) self.tc.end('assembleMatrices') if self.solvers == 'direct': self.solver_vel_tent = LUSolver('mumps') self.solver_vel_cor = LUSolver('mumps') self.solver_p = LUSolver('umfpack') if self.useRotationScheme: self.solver_rot = LUSolver('umfpack') else: # NT not needed, chosen not to use hypre_parasails # if self.prec_v == 'hypre_parasails': # in FEniCS 1.6.0 inaccessible using KrylovSolver class # self.solver_vel_tent = PETScKrylovSolver('gmres') # PETSc4py object # self.solver_vel_tent.ksp().getPC().setType('hypre') # PETScOptions.set('pc_hypre_type', 'parasails') # # this is global setting, but preconditioners for pressure solvers are set by their constructors # else: self.solver_vel_tent = KrylovSolver('gmres', self.prec_v) # nonsymetric > gmres # IMP cannot use 'ilu' in parallel (choose different default option) self.solver_vel_cor = KrylovSolver('cg', 'hypre_amg') # nonsymetric > gmres self.solver_p = KrylovSolver('cg', self.prec_p) # symmetric > CG if self.useRotationScheme: self.solver_rot = KrylovSolver('cg', self.prec_p) solver_options = {'monitor_convergence': True, 'maximum_iterations': 1000, 'nonzero_initial_guess': True} # 'nonzero_initial_guess': True with solver.solbe(A, u, b) means that # Solver will use anything stored in u as an initial guess # Get the nullspace if there are no pressure boundary conditions foo = Function(self.Q) # auxiliary vector for setting pressure nullspace if self.bc in ['nullspace', 'nullspace_s']: null_vec = Vector(foo.vector()) self.Q.dofmap().set(null_vec, 1.0) null_vec *= 1.0/null_vec.norm('l2') self.null_space = VectorSpaceBasis([null_vec]) if self.bc == 'nullspace': as_backend_type(A2).set_nullspace(self.null_space) # apply global options for Krylov solvers self.solver_vel_tent.parameters['relative_tolerance'] = 10 ** (-self.precision_rel_v_tent) self.solver_vel_tent.parameters['absolute_tolerance'] = 10 ** (-self.precision_abs_v_tent) self.solver_vel_cor.parameters['relative_tolerance'] = 10E-12 self.solver_vel_cor.parameters['absolute_tolerance'] = 10E-4 self.solver_p.parameters['relative_tolerance'] = 10**(-self.precision_p) self.solver_p.parameters['absolute_tolerance'] = 10E-10 if self.useRotationScheme: self.solver_rot.parameters['relative_tolerance'] = 10**(-self.precision_p) self.solver_rot.parameters['absolute_tolerance'] = 10E-10 if self.solvers == 'krylov': for solver in [self.solver_vel_tent, self.solver_vel_cor, self.solver_p, self.solver_rot] if \ self.useRotationScheme else [self.solver_vel_tent, self.solver_vel_cor, self.solver_p]: for key, value in solver_options.items(): try: solver.parameters[key] = value except KeyError: info('Invalid option %s for KrylovSolver' % key) return 1 solver.parameters['preconditioner']['structure'] = 'same' # matrices A2-A4 do not change, so we can reuse preconditioners self.solver_vel_tent.parameters['preconditioner']['structure'] = 'same_nonzero_pattern' # matrix A1 changes every time step, so change of preconditioner must be allowed if self.bc == 'lagrange': fa = FunctionAssigner(self.Q, QL.sub(0)) # boundary conditions bcu, bcp = problem.get_boundary_conditions(self.bc == 'outflow', self.V, self.Q) self.tc.end('init') # Time-stepping info("Running of Incremental pressure correction scheme n. 1") ttime = self.metadata['time'] t = dt step = 1 while t < (ttime + dt/2.0): info("t = %f" % t) self.problem.update_time(t, step) if self.MPI_rank == 0: problem.write_status_file(t) if doSave: save_this_step = problem.save_this_step # DDN debug # u_ext_in = assemble(inner(u_ext, n)*problem.get_outflow_measure_form()) # DDN_triggered = assemble(min_value(Constant(0.), inner(u_ext, n))*problem.get_outflow_measure_form()) # print('DDN: u_ext*n dSout = ', u_ext_in) # print('DDN: negative part of u_ext*n dSout = ', DDN_triggered) # assemble matrix (it depends on solution) self.tc.start('assembleA1') assemble(a1_change, tensor=A1_change) # assembling into existing matrix is faster than assembling new one A1 = A1_const.copy() # we dont want to change A1_const A1.axpy(1, A1_change, True) self.tc.end('assembleA1') self.tc.start('assembleA1stab') if self.stabilize and not self.use_full_SUPG: assemble(a1_stab, tensor=A1_stab) # assembling into existing matrix is faster than assembling new one A1.axpy(1, A1_stab, True) self.tc.end('assembleA1stab') # Compute tentative velocity step begin("Computing tentative velocity") self.tc.start('rhs') b = assemble(L1) self.tc.end('rhs') self.tc.start('applybc1') [bc.apply(A1, b) for bc in bcu] self.tc.end('applybc1') try: self.tc.start('solve 1') self.solver_vel_tent.solve(A1, u_.vector(), b) self.tc.end('solve 1') if save_this_step: self.tc.start('saveVel') problem.save_vel(True, u_, t) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(True, u_) problem.compute_err(True, u_, t) problem.compute_div(True, u_) except RuntimeError as inst: problem.report_fail(t) return 1 end() # DDN debug # u_ext_in = assemble(inner(u_, n)*problem.get_outflow_measure_form()) # DDN_triggered = assemble(min_value(Constant(0.), inner(u_, n))*problem.get_outflow_measure_form()) # print('DDN: u_tent*n dSout = ', u_ext_in) # print('DDN: negative part of u_tent*n dSout = ', DDN_triggered) if self.useRotationScheme: begin("Computing tentative pressure") else: begin("Computing pressure") if self.forceOutflow and problem.can_force_outflow: out = problem.compute_outflow(u_) info('Tentative outflow: %f' % out) n_o = -problem.last_inflow-out info('Needed outflow: %f' % n_o) need_outflow.assign(n_o) self.tc.start('rhs') b = assemble(L2) self.tc.end('rhs') self.tc.start('applybcP') [bc.apply(A2, b) for bc in bcp] if self.bc in ['nullspace', 'nullspace_s']: self.null_space.orthogonalize(b) self.tc.end('applybcP') try: self.tc.start('solve 2') if self.bc == 'lagrange': self.solver_p.solve(A2, p_QL.vector(), b) else: self.solver_p.solve(A2, p_.vector(), b) self.tc.end('solve 2') except RuntimeError as inst: problem.report_fail(t) return 1 if self.useRotationScheme: foo = Function(self.Q) if self.bc == 'lagrange': fa.assign(pQ, p_QL.sub(0)) foo.assign(pQ + p0) else: foo.assign(p_+p0) problem.averaging_pressure(foo) if save_this_step and not onlyVel: problem.save_pressure(True, foo) else: if self.bc == 'lagrange': fa.assign(pQ, p_QL.sub(0)) problem.averaging_pressure(pQ) if save_this_step and not onlyVel: problem.save_pressure(False, pQ) else: # we do not want to change p=0 on outflow, it conflicts with do-nothing conditions foo = Function(self.Q) foo.assign(p_) problem.averaging_pressure(foo) if save_this_step and not onlyVel: problem.save_pressure(False, foo) end() begin("Computing corrected velocity") self.tc.start('rhs') b = assemble(L3) self.tc.end('rhs') if not self.B: self.tc.start('applybc3') [bc.apply(A3, b) for bc in bcu] self.tc.end('applybc3') try: self.tc.start('solve 3') self.solver_vel_cor.solve(A3, u_cor.vector(), b) self.tc.end('solve 3') problem.compute_err(False, u_cor, t) problem.compute_div(False, u_cor) except RuntimeError as inst: problem.report_fail(t) return 1 if save_this_step: self.tc.start('saveVel') problem.save_vel(False, u_cor, t) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(False, u_cor) end() # DDN debug # u_ext_in = assemble(inner(u_cor, n)*problem.get_outflow_measure_form()) # DDN_triggered = assemble(min_value(Constant(0.), inner(u_cor, n))*problem.get_outflow_measure_form()) # print('DDN: u_cor*n dSout = ', u_ext_in) # print('DDN: negative part of u_cor*n dSout = ', DDN_triggered) if self.useRotationScheme: begin("Rotation scheme pressure correction") self.tc.start('rhs') b = assemble(L4) self.tc.end('rhs') try: self.tc.start('solve 4') self.solver_rot.solve(A4, p_mod.vector(), b) self.tc.end('solve 4') except RuntimeError as inst: problem.report_fail(t) return 1 problem.averaging_pressure(p_mod) if save_this_step and not onlyVel: problem.save_pressure(False, p_mod) end() # compute functionals (e. g. forces) problem.compute_functionals(u_cor, p_mod if self.useRotationScheme else (pQ if self.bc == 'lagrange' else p_), t) # Move to next time step self.tc.start('next') u1.assign(u0) u0.assign(u_cor) u_.assign(u_cor) # use corretced velocity as initial guess in first step if self.useRotationScheme: p0.assign(p_mod) else: if self.bc == 'lagrange': p0.assign(pQ) else: p0.assign(p_) t = round(t + dt, 6) # round time step to 0.000001 step += 1 self.tc.end('next') info("Finished: Incremental pressure correction scheme n. 1") problem.report() return 0
def helmholtz(cell, degree): m = Mesh(VectorElement('CG', cell, 1)) V = FunctionSpace(m, FiniteElement('CG', cell, degree)) u = TrialFunction(V) v = TestFunction(V) return (u*v + dot(grad(u), grad(v)))*dx
def setup_problem(self, debug=False): # # assemble the matrix, if necessary (once for all time points) # if not hasattr(self, 'A'): self.drho_integral = self.tdrho*self.wrho*self.dx self.dU_integral = sum( [tdUi*wUi*self.dx for tdUi,wUi in zip(self.tdUs, self.wUs)] ) self.A = fe.assemble(self.drho_integral + self.dU_integral) self.dsol = Function(self.VS) dsolsplit = self.dsol.split() self.drho, self.dUs = dsolsplit[0], dsolsplit[1:] # # assemble RHS (for each time point, but compile only once) # if not hasattr(self, 'rho_terms'): self.sigma = self.params['sigma'] self.s2 = self.sigma * self.sigma / 2 self.rho_min = self.params['rho_min'] self.rhopen = self.params['rhopen'] self.grhopen = self.params['grhopen'] self.v = -ufl.grad(self.V(self.iUs, self.irho)) - ( self.s2*ufl.grad(self.irho)/ufl.max_value(self.irho, self.rho_min) ) self.flux = self.v * self.irho self.vn = ufl.max_value(ufl.dot(self.v, self.n), 0) self.facet_flux = ( self.vn('+')*ufl.max_value(self.irho('+'), 0.0) - self.vn('-')*ufl.max_value(self.irho('-'), 0.0) ) self.rho_flux_jump = -self.facet_flux*ufl.jump(self.wrho)*self.dS self.rho_grad_move = ufl.dot(self.flux, ufl.grad(self.wrho))*self.dx self.rho_penalty = -( (self.rhopen * self.degree**2 / self.havg) * ufl.dot(ufl.jump(self.irho, self.n), ufl.jump(self.wrho, self.n)) * self.dS ) self.grho_penalty = -( self.grhopen * self.degree**2 * (ufl.jump(ufl.grad(self.irho), self.n) * ufl.jump(ufl.grad(self.wrho), self.n)) * self.dS ) self.rho_terms = ( self.rho_flux_jump + self.rho_grad_move + self.rho_penalty + self.grho_penalty ) if not hasattr(self, 'U_terms'): self.U_min = self.params['U_min'] self.Upen = self.params['Upen'] self.gUpen = self.params['gUpen'] self.U_decay = sum( [-lig.gamma * iUi * wUi * self.dx for lig,iUi,wUi in zip(self.ligands.ligands(), self.iUs, self.wUs)] ) self.U_secretion = sum( [lig.s * self.irho * wUi * self.dx for lig,wUi in zip(self.ligands.ligands(), self.wUs)] ) self.jump_gUw = sum( [lig.D * ufl.jump(wUi * ufl.grad(iUi), self.n) * self.dS for lig,wUi,iUi in zip(self.ligands.ligands(), self.wUs, self.iUs)] ) self.U_diffusion = sum( [-lig.D * ufl.dot(ufl.grad(iUi), ufl.grad(wUi))*self.dx for lig,iUi,wUi in zip(self.ligands.ligands(), self.iUs, self.wUs)] ) self.U_penalty = sum( [-(self.Upen*self.degree**2/self.havg) * ufl.dot(ufl.jump(iUi, self.n), ufl.jump(wUi, self.n))*self.dS for iUi,wUi in zip(self.iUs, self.wUs)] ) self.gU_penalty = -self.gUpen * self.degree**2 * sum( [ufl.jump(ufl.grad(iUi), self.n) * ufl.jump(ufl.grad(wUi), self.n) * self.dS for iUi,wUi in zip(self.iUs, self.wUs)] ) self.U_terms = ( # decay and secretion self.U_decay + self.U_secretion + # diffusion self.jump_gUw + self.U_diffusion + # penalties (to enforce continuity) self.U_penalty + self.gU_penalty ) if not hasattr(self, 'all_terms'): self.all_terms = self.rho_terms + self.U_terms if not hasattr(self, 'J_terms'): self.J_terms = fe.derivative(self.all_terms, self.sol)
# <codecell> from ufl import pi, atan, atan_2, tan, grad, as_vector, inner, dot psi = pi / 8.0 + atan_2(grad(u_h_n[0])[1], (grad(u_h_n[0])[0])) Phi = tan(N / 2.0 * psi) beta = (1.0 - Phi * Phi) / (1.0 + Phi * Phi) dbeta_dPhi = -2.0 * N * Phi / (1.0 + Phi * Phi) fac = 1.0 + c * beta diag = fac * fac offdiag = -fac * c * dbeta_dPhi d0 = as_vector([diag, offdiag]) d1 = as_vector([-offdiag, diag]) m = u[0] - 0.5 - kappa1 / pi * atan(kappa2 * u[1]) s = as_vector([dt / tau * u[0] * (1.0 - u[0]) * m, u[0]]) a_im = (alpha * alpha * dt / tau * (inner(dot(d0, grad(u[0])), grad(v[0])[0]) + inner(dot(d1, grad(u[0])), grad(v[0])[1])) + 2.25 * dt * inner(grad(u[1]), grad(v[1])) + inner(u, v) - inner(s, v)) * dx # <markdowncell> # We set up the scheme with some parameters. # <codecell> from dune.fem.scheme import galerkin as solutionScheme solverParameters = { "newton.tolerance": 1e-8, "newton.linear.tolerance": 1e-10, "newton.linear.preconditioning.method": "jacobi", "newton.verbose": False, "newton.linear.verbose": False
ds = Measure("ds", subdomain_data=boundary_parts) # Define forms (dont redefine functions used here) # step 1 u0 = Function(V) u1 = Function(V) p0 = Function(Q) u_tent = TrialFunction(V) v = TestFunction(V) # U_ = 1.5*u0 - 0.5*u1 # nonlinearity = inner(dot(0.5 * (u_tent.dx(0) + u0.dx(0)), U_), v) * dx # F_tent = (1./dt)*inner(u_tent - u0, v) * dx + nonlinearity\ # + nu*inner(0.5 * (u_tent.dx(0) + u0.dx(0)), v.dx(0)) * dx + inner(p0.dx(0), v) * dx\ # - inner(f, v)*dx # solve to u_ # using explicite scheme: so LHS has interpretation as heat equation, RHS are sources F_tent = (1./dt)*inner(u_tent - u0, v)*dx + inner(dot(u0.dx(0), u0), v)*dx + nu*inner((u_tent.dx(0) + u0.dx(0)), v.dx(0)) * dx + inner(p0.dx(0), v) * dx\ - inner(f, v)*dx # solve to u_ a_tent, L_tent = system(F_tent) # step 2 u_tent_computed = Function(V) p = TrialFunction(Q) q = TestFunction(Q) F_p = inner(grad(p-p0), grad(q))*dx + (1./dt)*u_tent_computed.dx(0)*q*dx # + 2*p.dx(0)*q*ds(1) # tried to force dp/dn=0 on inflow # TEST: prescribe Neumann outflow BC # F_p = inner(grad(p-p0), grad(q))*dx + (1./dt)*u_tent_computed.dx(0)*q*dx + (1./dt)*(v_in_expr-u_tent_computed)*q*ds(2) a_p, L_p = system(F_p) A_p = assemble(a_p) as_backend_type(A_p).set_nullspace(null_space) print(A_p.array()) # step 2 rotation # F_p_rot = inner(grad(p), grad(q))*dx + (1./dt)*u_tent_computed.dx(0)*q*dx + (1./dt)*(v_in_expr-u_tent_computed)*q*ds(2)
uD_i = dolfinx.Function(V_i) uD_i.interpolate(lambda x: 1 + x[0]**2 + 2 * x[1]**2) uD_i.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) fdim_i = mesh_i.topology.dim - 1 mesh_i.topology.create_connectivity(fdim_i, mesh_i.topology.dim) boundary_facets_i = np.where( np.array(dolfinx.cpp.mesh.compute_boundary_facets(mesh_i.topology)) == 1)[0] boundary_dofs_i = dolfinx.fem.locate_dofs_topological( V_i, fdim_i, boundary_facets_i) bc_i = dolfinx.DirichletBC(uD_i, boundary_dofs_i) u_i = ufl.TrialFunction(V_i) v_i = ufl.TestFunction(V_i) f_i = dolfinx.Constant(mesh_i, -6) a_i = ufl.dot(ufl.grad(u_i), ufl.grad(v_i)) * ufl.dx A_i = dolfinx.fem.assemble_matrix(a_i, bcs=[bc_i]) A_i.assemble() assert isinstance(A_i, PETSc.Mat) ai, aj, av = A_i.getValuesCSR() A_sp_i = scp.sparse.csr_matrix((av, aj, ai)) del A_i, av, ai, aj # Stores the Sparse version of the Stiffness Matrices A_sp_dict[i] = (A_sp_i, i) L_i = f_i * v_i * ufl.dx b_i = dolfinx.fem.create_vector(L_i) with b_i.localForm() as loc_b: loc_b.set(0) dolfinx.fem.assemble_vector(b_i, L_i) dolfinx.fem.apply_lifting(b_i, [a_i], [[bc_i]]) b_i.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES,
N = dolfinx.fem.VectorFunctionSpace(mesh, ("DG", q), mesh.geometry.dim) B = dolfinx.fem.TensorFunctionSpace(mesh, ("DG", q), (mesh.topology.dim, mesh.topology.dim)) # Normal vector (gdim x 1) and curvature tensor (tdim x tdim) n0i = dolfinx.fem.Function(N) B0i = dolfinx.fem.Function(B) # Jacobi matrix of map reference -> undeformed J0 = ufl.geometry.Jacobian(mesh) # Tangent basis gs = J0[:, 0] gη = ufl.as_vector([0, 1, 0]) # unit vector e_y (assume curve in x-z plane) gξ = ufl.cross(gs, gη) # Unit tangent basis gs /= ufl.sqrt(ufl.dot(gs, gs)) gη /= ufl.sqrt(ufl.dot(gη, gη)) gξ /= ufl.sqrt(ufl.dot(gξ, gξ)) # Interpolate normal vector dolfiny.interpolation.interpolate(gξ, n0i) # Contravariant basis K0 = ufl.geometry.JacobianInverse(mesh).T # Curvature tensor B0 = -ufl.dot(ufl.dot(ufl.grad(n0i), J0).T, K0) # = ufl.dot(n0i, ufl.dot(ufl.grad(K0), J0)) # Interpolate curvature tensor dolfiny.interpolation.interpolate(B0, B0i) # ---------------------------------------------------------------------------- # DERIVATIVE with respect to arc-length coordinate s of straight reference configuration: du/ds = du/dx * dx/dr * dr/ds
I3_C = J**2 # Green strain tensor E = (C - I) / 2 # Mapping of strain in fiber directions Ef = A * E * A.T # Strain energy function W(Q(Ef)) Q = c00 * Ef[0, 0]**2 + c11 * Ef[1, 1]**2 + c22 * Ef[2, 2]**2 # FIXME: insert some simple law here W = (K / 2) * (exp(Q) - 1) # + p stuff # First Piola-Kirchoff stress tensor P = diff(W, F) # Acceleration term discretized with finite differences k = dt / rho acc = (u - 2 * up + upp) # Residual equation # FIXME: Can contain errors, not tested! a_F = inner(acc, v) * dx \ + k * inner(P, grad(v)) * dx \ - k * dot(J * Finv * T, v) * ds(0) \ - k * dot(J * Finv * p0 * N, v) * ds(1) # Jacobi matrix of residual equation a_J = derivative(a_F, u, w) # Export forms forms = [a_F, a_J]
colorbar() fig.set_label(name) u = TrialFunction(Q1D) u_ext = interpolate(Expression("1."), Q1D) v = TestFunction(Q1D) a_mass = inner(u, v)*dx A_mass = assemble(a_mass) # plot_matrix(A_mass) a_diffusion_1D = inner(u.dx(0), v.dx(0))*dx A_diffusion_1D = assemble(a_diffusion_1D) # plot_matrix(A_diffusion_1D) # plot_matrix(A_diffusion_1D) a_convection_1D = inner(dot(u.dx(0), u_ext), v)*dx A_convection_1D = assemble(a_convection_1D) # plot_matrix(A_convection_1D) u_exts = [interpolate(Expression(('1.', '1.')), V2D), interpolate(Expression(('1.', '1.', '1.')), V3D)] Qspaces = [Q2D, Q3D] Vspaces = [V2D, V3D] names = ['2D', '3D'] for i in [0, 1]: Qspace = Qspaces[i] Vspace = Vspaces[i] u = TrialFunction(Vspace) v = TestFunction(Vspace) a_mass = inner(u, v)*dx A_mass = assemble(a_mass)
def GRAD(u): return ufl.dot(ufl.grad(u), J0[:, 0]) * 1 / ufl.geometry.JacobianDeterminant(mesh)
def test_div_grad_then_integrate_over_cells_and_boundary(): # Define 2D geometry n = 10 mesh = RectangleMesh( [numpy.array([0.0, 0.0, 0.0]), numpy.array([2.0, 3.0, 0.0])], 2 * n, 3 * n) x, y = SpatialCoordinate(mesh) xs = 0.1 + 0.8 * x / 2 # scaled to be within [0.1,0.9] # ys = 0.1 + 0.8 * y / 3 # scaled to be within [0.1,0.9] n = FacetNormal(mesh) # Define list of expressions to test, and configure accuracies # these expressions are known to pass with. The reason some # functions are less accurately integrated is likely that the # default choice of quadrature rule is not perfect F_list = [] def reg(exprs, acc=10): for expr in exprs: F_list.append((expr, acc)) # FIXME: 0*dx and 1*dx fails in the ufl-ffcx-jit framework somewhere # reg([Constant(0.0, cell=cell)]) # reg([Constant(1.0, cell=cell)]) monomial_list = [x**q for q in range(2, 6)] reg(monomial_list) reg([2.3 * p + 4.5 * q for p in monomial_list for q in monomial_list]) reg([xs**xs]) reg( [xs**(xs**2)], 8 ) # Note: Accuracies here are from 1D case, not checked against 2D results. reg([xs**(xs**3)], 6) reg([xs**(xs**4)], 2) # Special functions: reg([atan(xs)], 8) reg([sin(x), cos(x), exp(x)], 5) reg([ln(xs), pow(x, 2.7), pow(2.7, x)], 3) reg([asin(xs), acos(xs)], 1) reg([tan(xs)], 7) # To handle tensor algebra, make an x dependent input tensor # xx and square all expressions def reg2(exprs, acc=10): for expr in exprs: F_list.append((inner(expr, expr), acc)) xx = as_matrix([[2 * x**2, 3 * x**3], [11 * x**5, 7 * x**4]]) xxs = as_matrix([[2 * xs**2, 3 * xs**3], [11 * xs**5, 7 * xs**4]]) x3v = as_vector([3 * x**2, 5 * x**3, 7 * x**4]) cc = as_matrix([[2, 3], [4, 5]]) reg2( [xx] ) # TODO: Make unit test for UFL from this, results in listtensor with free indices reg2([x3v]) reg2([cross(3 * x3v, as_vector([-x3v[1], x3v[0], x3v[2]]))]) reg2([xx.T]) reg2([tr(xx)]) reg2([det(xx)]) reg2([dot(xx, 0.1 * xx)]) reg2([outer(xx, xx.T)]) reg2([dev(xx)]) reg2([sym(xx)]) reg2([skew(xx)]) reg2([elem_mult(7 * xx, cc)]) reg2([elem_div(7 * xx, xx + cc)]) reg2([elem_pow(1e-3 * xxs, 1e-3 * cc)]) reg2([elem_pow(1e-3 * cc, 1e-3 * xx)]) reg2([elem_op(lambda z: sin(z) + 2, 0.03 * xx)], 2) # pretty inaccurate... # FIXME: Add tests for all UFL operators: # These cause discontinuities and may be harder to test in the # above fashion: # 'inv', 'cofac', # 'eq', 'ne', 'le', 'ge', 'lt', 'gt', 'And', 'Or', 'Not', # 'conditional', 'sign', # 'jump', 'avg', # 'LiftingFunction', 'LiftingOperator', # FIXME: Test other derivatives: (but algorithms for operator # derivatives are the same!): # 'variable', 'diff', # 'Dx', 'grad', 'div', 'curl', 'rot', 'Dn', 'exterior_derivative', # Run through all operators defined above and compare integrals debug = 0 if debug: F_list = F_list[1:] for F, acc in F_list: if debug: print('\n', "F:", str(F)) # Integrate over domain and its boundary int_dx = assemble(div(grad(F)) * dx(mesh)) # noqa int_ds = assemble(dot(grad(F), n) * ds(mesh)) # noqa if debug: print(int_dx, int_ds) # Compare results. Using custom relative delta instead of # decimal digits here because some numbers are >> 1. delta = min(abs(int_dx), abs(int_ds)) * 10**-acc assert int_dx - int_ds <= delta
destC = space.interpolate(as_vector([ 0, ] * dimR), name='destC') destD = space.interpolate(as_vector([ 0, ] * dimR), name='destD') destE = space.interpolate(as_vector([ 0, ] * dimR), name='destE') u = TrialFunction(space) v = TestFunction(space) x = SpatialCoordinate(space.cell()) ubar = space.interpolate(as_vector([ dot(x, x), ] * dimR), name="ubar") a = (inner(0.5 * dot(u, u), v[0]) + inner(u[0] * grad(u), grad(v))) * dx op = create.operator("galerkin", a, space) A = linearOperator(op) op.jacobian(ubar, A) A(arg, destA) da = apply_derivatives(derivative(action(a, ubar), ubar, u)) dop = create.operator("galerkin", da, space) dop(arg, destB) err = integrate(grid, (destA - destB)**2, 5) # print("error=",err) assert (err < 1e-15) A = linearOperator(dop)