def test_P(n=4): '''Projections''' from block import block_transpose mesh = UnitCubeMesh(n, n, n) V = VectorFunctionSpace(mesh, 'CG', 1) Q = VectorFunctionSpace(mesh, 'R', 0, 6) Zh = RMBasis(V, Q) P = Projector(Zh) # Is a projector f = Function(V) x = f.vector() as_backend_type(x).vec().setRandom() Px = P * x assert any(abs(alpha) > 0 for alpha in P.alphas) PPx = P * Px tol = 1E-12 assert all(abs(alpha) < tol for alpha in P.alphas) assert abs((Px - PPx).norm('l2')) < tol y = assemble(inner(f, TestFunction(V)) * dx) Py = block_transpose(P) * y assert any(abs(beta) > 0 for beta in P.betas) PPy = block_transpose(P) * Py assert all(abs(beta) < tol for beta in P.betas) assert abs((Py - PPy).norm('l2')) < tol return True
def multTranspose(self, mat, x, y): '''y = A.T*x''' AT = block_transpose(self.A) y *= 0 x_bvec = PETScVector(x) y_bvec = AT * x_bvec y.axpy(1., as_petsc(y_bvec))
def multTranspose(self, mat, x, y): '''y = A.T*x''' AT = block_transpose(self.A) y *= 0 # Now x shall be comming as a nested vector # Convert x_bvec = block_vec(map(PETScVector, x.getNestSubVecs())) # Apply y_bvec = AT * x_bvec # Convert back y.axpy(1., as_petsc_nest(y_bvec))
def assemble(self, form, arity): '''Assemble a biliner(2), linear(1) form''' reduced_integrals = self.select_integrals(form) #! Selector # Signal to xii.assemble if not reduced_integrals: return None components = [] for integral in form.integrals(): # Delegate to friend if integral not in reduced_integrals: components.append( xii.assembler.xii_assembly.assemble(Form([integral]))) continue reduced_mesh = integral.ufl_domain().ufl_cargo() integrand = integral.integrand() # Split arguments in those that need to be and those that are # already restricted. terminals = set(traverse_unique_terminals(integrand)) # FIXME: is it enough info (in general) to decide terminals_to_restrict = sorted( self.restriction_filter(terminals, reduced_mesh), key=lambda t: self.is_compatible(t, reduced_mesh)) # You said this is a trace ingral! assert terminals_to_restrict # Let's pick a guy for restriction terminal = terminals_to_restrict.pop() # We have some assumption on the candidate assert self.is_compatible(terminal, reduced_mesh) data = self.reduction_matrix_data(terminal) integrand = ufl2uflcopy(integrand) # With sane inputs we can get the reduced element and setup the # intermediate function space where the reduction of terminal # lives V = terminal.function_space() TV = self.reduced_space(V, reduced_mesh) #! Space construc # Setup the matrix to from space of the trace_terminal to the # intermediate space. FIXME: normal and trace_mesh #! mat construct df.info('\tGetting reduction op') rop_timer = df.Timer('rop') T = self.reduction_matrix(V, TV, reduced_mesh, data) df.info('\tDone (reduction op) %g' % rop_timer.stop()) # T if is_test_function(terminal): replacement = df.TestFunction(TV) # Passing the args to get the comparison a make substitution integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) if arity == 2: # Make attempt on the substituted form A = xii.assembler.xii_assembly.assemble(trace_form) components.append(block_transpose(T) * A) else: b = xii.assembler.xii_assembly.assemble(trace_form) Tb = df.Function(V).vector() # Alloc and apply T.transpmult(b, Tb) components.append(Tb) if is_trial_function(terminal): assert arity == 2 replacement = df.TrialFunction(TV) # Passing the args to get the comparison integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) A = xii.assembler.xii_assembly.assemble(trace_form) components.append(A * T) # Okay, then this guy might be a function if isinstance(terminal, df.Function): replacement = df.Function(TV) # Replacement is not just a placeholder T.mult(terminal.vector(), replacement.vector()) # Substitute integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) components.append( xii.assembler.xii_assembly.assemble(trace_form)) # The whole form is then the sum of integrals return reduce(operator.add, components)
def lagrange_mixed(lmbda, mu, f, h, mesh, Z=None): ''' Solves -div(sigma) = f in Omega sigma.n = h on boundary where sigma(u) = 2*mu*eps(u) + lambda*div(u)*I. The problem is reformulated by Lagrange multiplier nu to inforce orthogonality with the space of rigid motions. To get robustnes in lmbda solid pressure p = lambda*div u is introduced. The system to be solved with MinRes is P*[A C B; *[u, = P*[L, C' D 0; p, 0, B' 0 0] nu] 0] with P a precondtioner. We run on series of meshes to show mesh independence of the solver. ''' if not isinstance(mesh, Mesh): # NOTE: You can precompute the 'symbolic' basis and pass it here return [lagrange_mixed(lmbda, mu, f, h, mesh_) for mesh_ in mesh] # For cube V = VectorFunctionSpace(mesh, 'CG', 2) Q = FunctionSpace(mesh, 'CG', 1) u, v = TrialFunction(V), TestFunction(V) p, q = TrialFunction(Q), TestFunction(Q) # Strain epsilon = lambda u: sym(grad(u)) # Stress gdim = mesh.geometry().dim() sigma = lambda u: 2*mu*epsilon(u) + lmbda*tr(epsilon(u))*Identity(gdim) a = 2*mu*inner(sym(grad(u)), sym(grad(v)))*dx A = assemble(a) c = inner(div(v), p)*dx C = assemble(c) d = -(inner(p, q)/Constant(lmbda))*dx D = assemble(d) m = inner(u, v)*dx M = assemble(m) # NOTE: Avoiding use of Q space in the assembly - dense blocks! X = VectorFunctionSpace(mesh, 'R', 0, dim=6) Zh = rigid_motions.RMBasis(V, X, Z) # L^2 orthogonal B = M*Zh # System operator AA = block_mat([[A, C, B], [block_transpose(C), D, 0], [block_transpose(B), 0, 0]]) # Right hand side L = inner(f, v)*dx + inner(h, v)*ds b0 = assemble(L) b1 = assemble(inner(Constant(0), q)*dx) # Equivalent to assemble(inner(Constant((0, )*6), q)*dx) but cheaper b2 = Function(X).vector() bb = block_vec([b0, b1, b2]) # Block diagonal preconditioner IV = assemble(a + m) IQ = assemble(inner(p, q)*dx) IX = rigid_motions.identity_matrix(X) BB = block_mat([[AMG(IV), 0, 0], [0, AMG(IQ), 0], [0, 0, IX]]) # Solve, using random initial guess x0 = AA.create_vec() [as_backend_type(xi).vec().setRandom() for xi in x0] AAinv = MinRes(AA, precond=BB, initial_guess=x0, maxiter=120, tolerance=1E-8, show=2, relativeconv=True) x = AAinv*bb # # Functions from coefficients # # uh = Function(V, x[0]) # Displacement # # ph = Function(Q, x[1]) # Solid pressure # # nuh = Zh.rigid_motion(x[2]) # Function in V niters = len(AAinv.residuals) - 1 assert niters < 120 P = rigid_motions.Projector(Zh) P*x0[0] # to get orthogonality if MPI.rank(mesh.mpi_comm()) == 0: print '\033[1;37;31m%s\033[0m' % ('Orthogonality %g' % max(P.alphas)) pass return V.dim() + Q.dim() + 6, niters
def solve_problem(ncells, eps, solver_params): '''Optim problem on [0, 1]^2''' # Made up f = Expression('x[0] + x[1]', degree=1) mesh = UnitSquareMesh(*(ncells, ) * 2) bmesh = BoundaryMesh(mesh, 'exterior') Q = FunctionSpace(mesh, 'DG', 0) V = FunctionSpace(mesh, 'CG', 1) B = FunctionSpace(mesh, 'CG', 1) W = [Q, V, B] p, u, lmbda = list(map(TrialFunction, W)) q, v, beta = list(map(TestFunction, W)) Tu = Trace(u, bmesh) Tv = Trace(v, bmesh) # The line integral dxGamma = Measure('dx', domain=bmesh) a = [[0] * len(W) for _ in range(len(W))] a[0][0] = Constant(eps) * inner(p, q) * dx a[0][2] = inner(q, lmbda) * dx # We put zero on the diagonal temporarily and then replace by # the fractional laplacian a[1][1] = 0 a[1][2] = inner(grad(v), grad(lmbda)) * dx + inner(v, lmbda) * dx a[2][0] = inner(p, beta) * dx a[2][1] = inner(grad(u), grad(beta)) * dx + inner(u, beta) * dx L = [ inner(Constant(0), q) * dx, inner(Constant(0), v) * dx, # Same replacement idea here inner(Constant(0), beta) * dx ] # All but (1, 1) and 1 are final AA, bb = list(map(ii_assemble, (a, L))) # Now I want and operator which corresponds to (Tv, (-Delta^{0.5} T_u))_bdry TV = FunctionSpace(bmesh, 'CG', 1) T = PETScMatrix(trace_mat_no_restrict(V, TV)) # The fractional laplacian nodal->dual fDelta = HsNorm(TV, s=0.5) # This should be it A11 = block_transpose(T) * fDelta * T # Now, while here we can also comute the rhs contribution # (Tv, (-Delta^{0.5}, f)) f_vec = interpolate(f, V).vector() b1 = A11 * f_vec bb[1] = b1 AA[1][1] = A11 wh = ii_Function(W) # Direct solve if not solver_params: AAm, bbm = list(map(ii_convert, (AA, bb))) LUSolver('umfpack').solve(AAm, wh.vector(), bbm) return wh, -1 # Magne like precond if False: # Preconditioner like in the L2 case but with L2 bdry replaced b00 = Constant(eps) * inner(p, q) * dx B00 = LumpedInvDiag(ii_assemble(b00)) H1 = ii_assemble(inner(grad(v), grad(u)) * dx + inner(v, u) * dx) # From dual to nodal R = LumpedInvDiag(ii_assemble(inner(u, v) * dx)) # The whole matrix to be inverted is then (second term is H2 approx) B11 = collapse(A11 + eps * H1 * R * H1) # And the inverse B11 = AMG(B11, parameters={'pc_hypre_boomeramg_cycle_type': 'W'}) b22 = Constant(1. / eps) * inner(lmbda, beta) * dx B22 = LumpedInvDiag(ii_assemble(b22)) # A bit like SZ else: b00 = Constant(eps) * inner(p, q) * dx B00 = LumpedInvDiag(ii_assemble(b00)) # L2 \cap eps H1 H1 = assemble( Constant(eps) * (inner(grad(v), grad(u)) * dx + inner(v, u) * dx)) B11 = AMG(collapse(A11 + H1)) # (1./eps)*L2 \cap 1./sqrt(eps)H1 a = Constant(1./eps)*inner(beta, lmbda)*dx + \ Constant(1./sqrt(eps))*(inner(grad(beta), grad(lmbda))*dx + inner(beta, lmbda)*dx) B22 = AMG(assemble(a)) BB = block_diag_mat([B00, B11, B22]) # Want the iterations to start from random (iterative) wh.block_vec().randomize() # Default is minres if '-ksp_type' not in solver_params: solver_params['-ksp_type'] = 'minres' opts = PETSc.Options() for key, value in solver_params.items(): opts.setValue(key, None if value == 'none' else value) ksp = PETSc.KSP().create() ksp.setOperators(ii_PETScOperator(AA)) ksp.setPC(ii_PETScPreconditioner(BB, ksp)) ksp.setNormType(PETSc.KSP.NormType.NORM_PRECONDITIONED) ksp.setFromOptions() ksp.solve(as_petsc_nest(bb), wh.petsc_vec()) niters = ksp.getIterationNumber() return wh, niters
def lagrange_primal(lmbda, mu, f, h, mesh, Z=None): ''' Solves -div(sigma) = f in Omega sigma.n = h on boundary where sigma(u) = 2*mu*eps(u) + lambda*div(u)*I. The problem is reformulated by Lagrange multiplier p to inforce orthogonality with the space of rigid motions. The system to be solved with MinRes is P*[A B;*[u; = P*[L, B' ] p] 0] with P a precondtioner. We run on series of meshes to show mesh independence of the solver. ''' if not isinstance(mesh, Mesh): # NOTE: You can precompute the 'symbolic' basis and pass it here return [lagrange_primal(lmbda, mu, f, h, mesh_) for mesh_ in mesh] # For cube V = VectorFunctionSpace(mesh, 'CG', 1) u, v = TrialFunction(V), TestFunction(V) # Strain epsilon = lambda u: sym(grad(u)) # Stress gdim = mesh.geometry().dim() sigma = lambda u: 2 * mu * epsilon(u) + lmbda * tr(epsilon(u)) * Identity( gdim) # Energy of elastic deformation a = inner(sigma(u), epsilon(v)) * dx A = assemble(a) # Mass matrix for B m = inner(u, v) * dx M = assemble(m) # NOTE: Avoiding use of Q space in the assembly - dense blocks! Q = VectorFunctionSpace(mesh, 'R', 0, dim=6) Zh = rigid_motions.RMBasis(V, Q, Z) # L^2 orthogonal B = M * Zh # System operator AA = block_mat([[A, B], [block_transpose(B), 0]]) # Right hand side L = inner(f, v) * dx + inner(h, v) * ds b0 = assemble(L) # Equivalent to assemble(inner(Constant((0, )*6), q)*dx) but cheaper b1 = Function(Q).vector() bb = block_vec([b0, b1]) # Block diagonal preconditioner AM = assemble(a + m) I = rigid_motions.identity_matrix(Q) BB = block_mat([[AMG(AM), 0], [0, I]]) # Solve, using random initial guess x0 = AA.create_vec() [as_backend_type(xi).vec().setRandom() for xi in x0] AAinv = MinRes(AA, precond=BB, initial_guess=x0, maxiter=100, tolerance=1E-8, show=2, relativeconv=True) x = AAinv * bb # Functions from coefficients # uh = Function(V, x[0]) # Displacement # zh = Zh.rigid_motion(x[1]) # Function in V niters = len(AAinv.residuals) - 1 assert niters < 100 P = rigid_motions.Projector(Zh) P * x[0] # to get orthogonality if MPI.rank(mesh.mpi_comm()) == 0: print '\033[1;37;31m%s\033[0m' % ('Orthogonality %g' % max(P.alphas)) return V.dim() + 6, niters
def solve_problem(ncells, eps, solver_params): '''Optim problem on [0, 1]^2''' # Made up f = Expression('x[0] + x[1]', degree=1) mesh = UnitSquareMesh(*(ncells, )*2) bmesh = BoundaryMesh(mesh, 'exterior') Q = FunctionSpace(mesh, 'DG', 0) V = FunctionSpace(mesh, 'CG', 1) B = FunctionSpace(mesh, 'CG', 1) W = [Q, V, B] p, u, lmbda = map(TrialFunction, W) q, v, beta = map(TestFunction, W) Tu = Trace(u, bmesh) Tv = Trace(v, bmesh) # The line integral dxGamma = Measure('dx', domain=bmesh) a = [[0]*len(W) for _ in range(len(W))] a[0][0] = Constant(eps)*inner(p, q)*dx a[0][2] = inner(q, lmbda)*dx # We put zero on the diagonal temporarily and then replace by # the fractional laplacian a[1][1] = 0 a[1][2] = inner(grad(v), grad(lmbda))*dx + inner(v, lmbda)*dx a[2][0] = inner(p, beta)*dx a[2][1] = inner(grad(u), grad(beta))*dx + inner(u, beta)*dx L = [inner(Constant(0), q)*dx, inner(Constant(0), v)*dx, # Same replacement idea here inner(Constant(0), beta)*dx] # All but (1, 1) and 1 are final AA, bb = map(ii_assemble, (a, L)) # Now I want and operator which corresponds to (Tv, (-Delta^{0.5} T_u))_bdry TV = FunctionSpace(bmesh, 'CG', 1) T = PETScMatrix(trace_mat_no_restrict(V, TV)) # The fractional laplacian nodal->dual fDelta = HsNorm(TV, s=0.5) # This should be it A11 = block_transpose(T)*fDelta*T # Now, while here we can also comute the rhs contribution # (Tv, (-Delta^{0.5}, f)) f_vec = interpolate(f, V).vector() b1 = A11*f_vec bb[1] = b1 AA[1][1] = A11 wh = ii_Function(W) # Direct solve if not solver_params: AAm, bbm = map(ii_convert, (AA, bb)) LUSolver('umfpack').solve(AAm, wh.vector(), bbm) return wh, -1 # Magne like precond if False: # Preconditioner like in the L2 case but with L2 bdry replaced b00 = Constant(eps)*inner(p, q)*dx B00 = LumpedInvDiag(ii_assemble(b00)) H1 = ii_assemble(inner(grad(v), grad(u))*dx + inner(v, u)*dx) # From dual to nodal R = LumpedInvDiag(ii_assemble(inner(u, v)*dx)) # The whole matrix to be inverted is then (second term is H2 approx) B11 = collapse(A11 + eps*H1*R*H1) # And the inverse B11 = AMG(B11, parameters={'pc_hypre_boomeramg_cycle_type': 'W'}) b22 = Constant(1./eps)*inner(lmbda, beta)*dx B22 = LumpedInvDiag(ii_assemble(b22)) # A bit like SZ else: b00 = Constant(eps)*inner(p, q)*dx B00 = LumpedInvDiag(ii_assemble(b00)) # L2 \cap eps H1 H1 = assemble(Constant(eps)*(inner(grad(v), grad(u))*dx + inner(v, u)*dx)) B11 = AMG(collapse(A11 + H1)) # (1./eps)*L2 \cap 1./sqrt(eps)H1 a = Constant(1./eps)*inner(beta, lmbda)*dx + \ Constant(1./sqrt(eps))*(inner(grad(beta), grad(lmbda))*dx + inner(beta, lmbda)*dx) B22 = AMG(assemble(a)) BB = block_diag_mat([B00, B11, B22]) # Want the iterations to start from random (iterative) wh.block_vec().randomize() # Default is minres if '-ksp_type' not in solver_params: solver_params['-ksp_type'] = 'minres' opts = PETSc.Options() for key, value in solver_params.iteritems(): opts.setValue(key, None if value == 'none' else value) ksp = PETSc.KSP().create() ksp.setOperators(ii_PETScOperator(AA)) ksp.setPC(ii_PETScPreconditioner(BB, ksp)) ksp.setNormType(PETSc.KSP.NormType.NORM_PRECONDITIONED) ksp.setFromOptions() ksp.solve(as_petsc_nest(bb), wh.petsc_vec()) niters = ksp.getIterationNumber() return wh, niters
def assemble(self, form, arity): '''Assemble a biliner(2), linear(1) form''' reduced_integrals = self.select_integrals(form) #! Selector # Signal to xii.assemble if not reduced_integrals: return None components = [] for integral in form.integrals(): # Delegate to friend if integral not in reduced_integrals: components.append(xii.assembler.xii_assembly.assemble(Form([integral]))) continue reduced_mesh = integral.ufl_domain().ufl_cargo() integrand = integral.integrand() # Split arguments in those that need to be and those that are # already restricted. terminals = set(traverse_unique_terminals(integrand)) # FIXME: is it enough info (in general) to decide terminals_to_restrict = self.restriction_filter(terminals, reduced_mesh) # You said this is a trace ingral! assert terminals_to_restrict # Let's pick a guy for restriction terminal = terminals_to_restrict.pop() # We have some assumption on the candidate assert self.is_compatible(terminal, reduced_mesh) data = self.reduction_matrix_data(terminal) integrand = ufl2uflcopy(integrand) # With sane inputs we can get the reduced element and setup the # intermediate function space where the reduction of terminal # lives V = terminal.function_space() TV = self.reduced_space(V, reduced_mesh) #! Space construc # Setup the matrix to from space of the trace_terminal to the # intermediate space. FIXME: normal and trace_mesh #! mat construct T = self.reduction_matrix(V, TV, reduced_mesh, data) # T if is_test_function(terminal): replacement = df.TestFunction(TV) # Passing the args to get the comparison a make substitution integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) if arity == 2: # Make attempt on the substituted form A = xii.assembler.xii_assembly.assemble(trace_form) components.append(block_transpose(T)*A) else: b = xii.assembler.xii_assembly.assemble(trace_form) Tb = df.Function(V).vector() # Alloc and apply T.transpmult(b, Tb) components.append(Tb) if is_trial_function(terminal): assert arity == 2 replacement = df.TrialFunction(TV) # Passing the args to get the comparison integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) A = xii.assembler.xii_assembly.assemble(trace_form) components.append(A*T) # Okay, then this guy might be a function if isinstance(terminal, df.Function): replacement = df.Function(TV) # Replacement is not just a placeholder T.mult(terminal.vector(), replacement.vector()) # Substitute integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) components.append(xii.assembler.xii_assembly.assemble(trace_form)) # The whole form is then the sum of integrals return reduce(operator.add, components)
def energy(lmbda, mu, f, h, mesh, Z=None): ''' Solves -div(sigma) = f in Omega sigma.n = h on boundary where sigma(u) = 2*mu*eps(u) + lambda*div(u)*I. The problem is reformulated by considering a complemented strain energy (for which rigid motions are not tranparent). The system to be solved with CG as P*[A+E]*[u] = P'*b with P a precondtioner. We run on series of meshes to show mesh independence of the solver. ''' if not isinstance(mesh, Mesh): # Precompute the 'symbolic' basis mesh0 = first(mesh) Z = rigid_motions.rm_basis(mesh0) return [energy(lmbda, mu, f, h, mesh_, Z) for mesh_ in mesh] # For cube V = VectorFunctionSpace(mesh, 'CG', 1) u, v = TrialFunction(V), TestFunction(V) # Strain epsilon = lambda u: sym(grad(u)) # Stress gdim = mesh.geometry().dim() sigma = lambda u: 2 * mu * epsilon(u) + lmbda * tr(epsilon(u)) * Identity( gdim) # Energy of elastic deformation a = inner(sigma(u), epsilon(v)) * dx A = assemble(a) # Mass matrix for B m = inner(u, v) * dx M = assemble(m) # NOTE: Avoiding use of Q space in the assembly - dense blocks! Q = VectorFunctionSpace(mesh, 'R', 0, dim=6) Zh = rigid_motions.RMBasis(V, Q, Z) # L^2 orthogonal B = M * Zh # System operator AA = A + B * block_transpose(B) # Right hand side L = inner(f, v) * dx + inner(h, v) * ds # Orthogonalize P = rigid_motions.Projector(Zh) b = assemble(L) b0 = block_transpose(P) * b # Preconditioner AM = assemble(a + m) BB = AMG(AM) # Solve, using random initial guess x0 = AA.create_vec() as_backend_type(x0).vec().setRandom() AAinv = ConjGrad(AA, precond=BB, initial_guess=x0, maxiter=100, tolerance=1E-8, show=2, relativeconv=True) x = AAinv * b0 # Functions from coefficients # uh = Function(V, x) # Displacement niters = len(AAinv.residuals) - 1 assert niters < 100 P * x # to get orthogonality if MPI.rank(mesh.mpi_comm()) == 0: print '\033[1;37;31m%s\033[0m' % ('Orthogonality %g' % max(P.alphas)) return V.dim(), niters