def block_reshape(AA, offsets): '''Group rows/cols according to offsets''' nblocks = len(offsets) mat = block_mat([[0]*nblocks for _ in range(nblocks)]) offsets = [0] + list(offsets) AA = AA.blocks for row, (ri, rj) in enumerate(zip(offsets[:-1], offsets[1:])): for col, (ci, cj) in enumerate(zip(offsets[:-1], offsets[1:])): if rj-ri == 1 and cj -ci == 1: mat[row][col] = AA[ri, ci] else: mat[row][col] = block_mat(AA[ri:rj, ci:cj]) return mat
def main(n): '''Solves grad-div problem in 2d with HypreAMS preconditioning''' # Exact solution x, y = sp.symbols('x[0] x[1]') u = sp.sin(pi*x*(1-x)*y*(1-y)) sp_div = lambda f: f[0].diff(x, 1) + f[1].diff(y, 1) sp_grad = lambda f: sp.Matrix([f.diff(x, 1), f.diff(y, 1)]) sigma = sp_grad(u) f = -sp_div(sigma) + u sigma_expr, u_expr, f_expr = map(as_expression, (sigma, u, f)) # The discrete problem mesh = UnitSquareMesh(n, n) V = FunctionSpace(mesh, 'RT', 1) Q = FunctionSpace(mesh, 'DG', 0) W = (V, Q) sigma, u = map(TrialFunction, W) tau, v = map(TestFunction, W) a00 = inner(sigma, tau)*dx a01 = inner(div(tau), u)*dx a10 = inner(div(sigma), v)*dx a11 = -inner(u, v)*dx L0 = inner(Constant((0, 0)), tau)*dx L1 = inner(-f_expr, v)*dx AA = block_assemble([[a00, a01], [a10, a11]]) bb = block_assemble([L0, L1]) # b00 = inner(sigma, tau)*dx + inner(div(sigma), div(tau))*dx # B00 = LU(assemble(b00)) B00 = HypreAMS(V) b11 = inner(u, v)*dx B11 = LumpedInvDiag(assemble(b11)) BB = block_mat([[B00, 0], [0, B11]]) AAinv = MinRes(AA, precond=BB, tolerance=1e-10, maxiter=500, show=2) # Compute solution sigma_h, u_h = AAinv * bb sigma_h, u_h = Function(V, sigma_h), Function(Q, u_h) niters = len(AAinv.residuals) - 1 # error = sqrt(errornorm(sigma_expr, sigma_h, 'Hdiv', degree_rise=1)**2 + # errornorm(u_expr, u_h, 'L2', degree_rise=1)**2) hmin = mesh.mpi_comm().tompi4py().allreduce(mesh.hmin(), pyMPI.MIN) error = 1. return hmin, V.dim()+Q.dim(), niters, error
def block_diag_mat(diagonal): '''A block diagonal matrix''' blocks = np.zeros((len(diagonal), ) * 2, dtype='object') for i, A in enumerate(diagonal): blocks[i, i] = A return block_mat(blocks)
def main(n): '''Solves grad-div problem in 2d with HypreAMS preconditioning''' # Exact solution x, y = sp.symbols('x[0] x[1]') u = sp.sin(pi * x * (1 - x) * y * (1 - y)) sp_div = lambda f: f[0].diff(x, 1) + f[1].diff(y, 1) sp_grad = lambda f: sp.Matrix([f.diff(x, 1), f.diff(y, 1)]) sigma = sp_grad(u) f = -sp_div(sigma) + u sigma_expr, u_expr, f_expr = list(map(as_expression, (sigma, u, f))) # The discrete problem mesh = UnitSquareMesh(n, n) V = FunctionSpace(mesh, 'RT', 1) Q = FunctionSpace(mesh, 'DG', 0) W = (V, Q) sigma, u = list(map(TrialFunction, W)) tau, v = list(map(TestFunction, W)) a00 = inner(sigma, tau) * dx a01 = inner(div(tau), u) * dx a10 = inner(div(sigma), v) * dx a11 = -inner(u, v) * dx L0 = inner(Constant((0, 0)), tau) * dx L1 = inner(-f_expr, v) * dx AA = block_assemble([[a00, a01], [a10, a11]]) bb = block_assemble([L0, L1]) # b00 = inner(sigma, tau)*dx + inner(div(sigma), div(tau))*dx # B00 = LU(assemble(b00)) B00 = HypreAMS(V) b11 = inner(u, v) * dx B11 = LumpedInvDiag(assemble(b11)) BB = block_mat([[B00, 0], [0, B11]]) AAinv = MinRes(AA, precond=BB, tolerance=1e-10, maxiter=500, show=2) # Compute solution sigma_h, u_h = AAinv * bb sigma_h, u_h = Function(V, sigma_h), Function(Q, u_h) niters = len(AAinv.residuals) - 1 # error = sqrt(errornorm(sigma_expr, sigma_h, 'Hdiv', degree_rise=1)**2 + # errornorm(u_expr, u_h, 'L2', degree_rise=1)**2) hmin = mesh.mpi_comm().tompi4py().allreduce(mesh.hmin(), pyMPI.MIN) error = 1. return hmin, V.dim() + Q.dim(), niters, error
def as_block(monolithic, blocks): '''Turn monolithic operator into block-structured one with indices specified in blocks''' comm = mpi_comm_world() # We want list of PETSc.IS-es elm_type, = element_types(blocks) if elm_type is FunctionSpace: offsets = np.cumsum(np.r_[0, [Wi.dim() for Wi in blocks]]) idx = [] for first, last in zip(offsets[:-1], offsets[1:]): idx.append(PETSc.IS().createStride(last - first, first, 1)) return as_block(monolithic, idx) elif elm_type in (list, np.ndarray): return as_block(monolithic, [ PETSc.IS().createGeneral(np.asarray(block, dtype='int32')) for block in blocks ]) assert elm_type is PETSc.IS # Break up Vector to block-vec if isinstance(monolithic, (GenericVector, Vector)): return as_block(as_backend_type(monolithic).vec(), blocks) if isinstance(monolithic, (GenericMatrix, Matrix)): return as_block(as_backend_type(monolithic).mat(), blocks) if isinstance(monolithic, PETSc.Vec): b = [ PETSc.Vec().createWithArray(monolithic.getValues(block), comm=comm) for block in blocks ] for bi in b: bi.assemblyBegin() bi.assemblyEnd() return block_vec(list(map(PETScVector, b))) # Otherwise we have a Matrix try: monolithic.getSubMatrix A = [[ PETScMatrix(monolithic.getSubMatrix(block_row, block_col)) for block_col in blocks ] for block_row in blocks] # NOTE: 3.8+ does not ahve getSubMatrix, there is getLocalSubMatrix # but cannot get that to work - petsc error 73. So for now everything # is done with scipy except AttributeError: monolithic = csr_matrix(monolithic.getValuesCSR()[::-1], shape=monolithic.size) A = [[ numpy_to_petsc(monolithic[block_row.array, :][:, block_col.array]) for block_col in blocks ] for block_row in blocks] # Done return block_mat(A)
def convert(bmat, algorithm='numpy'): ''' Attempt to convert bmat to a PETSc(Matrix/Vector) object. If succed this is at worst a number. ''' # Block vec conversion if isinstance(bmat, block_vec): array = block_vec_to_numpy(bmat) vec = PETSc.Vec().createWithArray(array) vec.assemble() return PETScVector(vec) # Conversion of bmat is bit more involved because of the possibility # that some of the blocks are numbers or composition of matrix operations if isinstance(bmat, block_mat): # Create collpsed bmat row_sizes, col_sizes = bmat_sizes(bmat) nrows, ncols = len(row_sizes), len(col_sizes) indices = itertools.product(list(range(nrows)), list(range(ncols))) blocks = np.zeros((nrows, ncols), dtype='object') for block, (i, j) in zip(bmat.blocks.flatten(), indices): # This might is guaranteed to be matrix or number A = collapse(block) if is_number(A): # Diagonal matrices can be anything provided square if i == j and row_sizes[i] == col_sizes[j]: A = diagonal_matrix(row_sizes[i], A) else: # Offdiagonal can only be zero A = zero_matrix(row_sizes[i], col_sizes[j]) #else: # A = 0 # The converted block blocks[i, j] = A # Now every block is a matrix/number and we can make a monolithic thing bmat = block_mat(blocks) assert all(is_petsc_mat(block) or is_number(block) for block in bmat.blocks.flatten()) # Opt out of monolithic if not algorithm: set_lg_map(bmat) return bmat # Monolithic via numpy (fast) # Convert to numpy array = block_mat_to_numpy(bmat) # Constuct from numpy bmat = numpy_to_petsc(array) set_lg_map(bmat) return bmat # Try with a composite return collapse(bmat)
def identity_matrix(V): '''u -> u for u in V''' if isinstance(V, FunctionSpace): return diagonal_matrix(V.dim(), 1) mat = block_mat([[0]*len(V) for _ in range(len(V))]) for i in range(len(mat)): mat[i][i] = identity_matrix(V[i]) return mat
def convert(bmat, algorithm='numpy'): ''' Attempt to convert bmat to a PETSc(Matrix/Vector) object. If succed this is at worst a number. ''' # Block vec conversion if isinstance(bmat, block_vec): array = block_vec_to_numpy(bmat) vec = PETSc.Vec().createWithArray(array) vec.assemble() return PETScVector(vec) # Conversion of bmat is bit more involved because of the possibility # that some of the blocks are numbers or composition of matrix operations if isinstance(bmat, block_mat): # Create collpsed bmat row_sizes, col_sizes = bmat_sizes(bmat) nrows, ncols = len(row_sizes), len(col_sizes) indices = itertools.product(range(nrows), range(ncols)) blocks = np.zeros((nrows, ncols), dtype='object') for block, (i, j) in zip(bmat.blocks.flatten(), indices): # This might is guaranteed to be matrix or number A = collapse(block) if is_number(A): # Diagonal matrices can be anything provided square if i == j and row_sizes[i] == col_sizes[j]: A = diagonal_matrix(row_sizes[i], A) else: # Offdiagonal can only be zero A = zero_matrix(row_sizes[i], col_sizes[j]) #else: # A = 0 # The converted block blocks[i, j] = A # Now every block is a matrix/number and we can make a monolithic thing bmat = block_mat(blocks) assert all(is_petsc_mat(block) or is_number(block) for block in bmat.blocks.flatten()) # Opt out of monolithic if not algorithm: return bmat # Monolithic via numpy (fast) # Convert to numpy array = block_mat_to_numpy(bmat) # Constuct from numpy return numpy_to_petsc(array) # Try with a composite return collapse(bmat)
def block_tensor(obj): """Return either a block_vec or a block_mat, depending on the shape of the object""" from block import block_mat, block_vec import numpy if isinstance(obj, (block_mat, block_vec)): return obj from ufl import Form if isinstance(obj, Form): from .splitting import split_form obj = split_form(obj) blocks = numpy.array(obj) if len(blocks.shape) == 2: return block_mat(blocks) elif len(blocks.shape) == 1: return block_vec(blocks) else: raise RuntimeError("Not able to create block container of rank %d" % len(blocks.shape))
def setup_problem(n, mms, params): '''Poisson solver''' mesh = UnitCubeMesh(n, n, n) V = FunctionSpace(mesh, 'CG', 1) # Just one boundaries = MeshFunction('size_t', mesh, 2, 0) for tag, subd in enumerate(mms.subdomains, 1): CompiledSubDomain(subd, tol=1E-10).mark(boundaries, tag) u_true, = mms.solution bc = DirichletBC(V, u_true, boundaries, 1) f, = mms.rhs u, v = TrialFunction(V), TestFunction(V) a = inner(grad(u), grad(v))*dx L = inner(f, v)*dx A, b = assemble_system(a, L, bc) return block_mat([[A]]), block_vec([b]), [V]
def set_lg_map(mat): '''Set local-to-global-map on the matrix''' # NOTE; serial only - so we own everything but still sometimes we need # to tell that to petsc (especiialy when bcs are to be applied) if is_number(mat): return mat assert is_petsc_mat(mat) or isinstance(mat, block_mat), (type(mat)) if isinstance(mat, block_mat): blocks = np.array(map(set_lg_map, mat.blocks.flatten())).reshape(mat.blocks.shape) return block_mat(blocks) comm = mpi_comm_world().tompi4py() # Work with matrix rowmap, colmap = range(mat.size(0)), range(mat.size(1)) row_lgmap = PETSc.LGMap().create(rowmap, comm=comm) col_lgmap = PETSc.LGMap().create(colmap, comm=comm) as_petsc(mat).setLGMap(row_lgmap, col_lgmap) return mat
L1 = inner(f, c) * dx from block import block_assemble, block_bc, block_mat from block.iterative import MinRes from block.algebraic.petsc import ML, Cholesky # lhs AA = block_assemble([[a00, a01, a02], [a10, a11, a12], [a20, a21, 0]]) # rhs b = block_assemble([L0, L1, 0]) # Collect boundary conditions bcs = [[bc0, bc1], [], []] block_bc(bcs, True).apply(AA).apply(b) b22 = inner(p, q) * dx B22 = assemble(b22) # Possible action of preconditioner BB = block_mat([[ML(AA[0, 0]), 0, 0], [0, Cholesky(AA[1, 1]), 0], [0, 0, Cholesky(B22)]]) AAinv = MinRes(AA, precond=BB, tolerance=1E-10, maxiter=500) U, B, P = AAinv * b p = Function(Q) p.vector()[:] = P # Plot solution plot(p) interactive()
def apply_bc(A, b, bcs, diag_val=1., return_apply_b=False): ''' Apply block boundary conditions to block system A, b ''' # Specs of do nothing if bcs is None or not bcs or (not any(bc for bc in bcs) and isinstance(bcs, list)): return A, b # Allow for A, b be simple matrices. To proceed we wrap them as # block objects has_wrapped_A = False if not isinstance(A, block_mat): A = block_mat([[A]]) has_wrapped_A = True if b is None: b = A.create_vec() if not isinstance(b, block_vec): assert has_wrapped_A b = block_vec([b]) if isinstance(bcs, dict): assert all(0 <= k < len(A) for k in list(bcs.keys())) assert all(isinstance(v, list) for v in list(bcs.values())) bcs = [bcs.get(i, []) for i in range(len(A))] else: if has_wrapped_A: bcs = [bcs] # block boundary conditions is a list with bcs for each block of A, b assert len(A) == len(b) == len(bcs), (len(A), len(b), len(bcs)) bcs_ = [] for bc in bcs: assert isinstance(bc, (DirichletBC, list)) if isinstance(bc, DirichletBC): bcs_.append([bc]) else: bcs_.append(bc) bcs = bcs_ if not bcs or not any(bcs): return A, b # Obtain a monolithic matrix AA, bb = list(map(convert, (A, b))) # PETSc guys AA, bb = as_backend_type(AA).mat(), as_backend_type(bb).vec() # We want to make a monotlithic matrix corresponding to function sp ace # where the spaces are serialized # Break apart for block offsets = [0] for bi in b: offsets.append(offsets[-1] + bi.size()) assert AA.size[0] == AA.size[1] == offsets[-1], (AA.size[0], AA.size[1], offsets) rows = [] x_values = [] # Each bc in the group has dofs numbered only wrt to its space. # We're after global numbering for shift, bcs_sub in zip(offsets, bcs): for bc in bcs_sub: # NOTE: bcs can be a dict or DirichletBC in which case we extract # the dict if isinstance(bc, DirichletBC): bc = bc.get_boundary_values() # Dofs and values for rhs rows.extend(shift + np.array(list(bc.keys()), dtype='int32')) x_values.extend(list(bc.values())) rows = np.hstack(rows) x_values = np.array(x_values) x = bb.copy() x.zeroEntries() x.setValues(rows, x_values) x.assemble() blocks = [] for first, last in zip(offsets[:-1], offsets[1:]): blocks.append(PETSc.IS().createStride(last - first, first, 1)) if return_apply_b: # We don't want to reassemble system and apply bcs (when they are # updated) to the (new) matrix and the rhs. Matrix is expensive # AND the change of bcs only effects the rhs. So what we build # here is a function which applies updated bcs only to rhs as # if by assemble_system (i.e. in a symmetric way) def apply_b(bb, AA=AA.copy(), dofs=rows, bcs=bcs, blocks=blocks): # Pick up the updated values values = np.array( sum((list(bc.get_boundary_values().values()) if isinstance( bc, DirichletBC) else [] for bcs_sub in bcs for bc in bcs_sub), [])) # Taken from cbc.block c, Ac = AA.createVecs() c.zeroEntries() c.setValues(dofs, values) # Forward c.assemble() AA.mult(c, Ac) # Note: we work with the deep copy of A b = convert(bb) # We cond't get view here ... b_vec = as_backend_type(b).vec() b_vec.axpy(-1, Ac) # Elliminate b_vec.setValues(dofs, values) b_vec.assemble() bb *= 0 # ... so copy bb += as_block(b, blocks) return bb # Apply to monolithic len(rows) and AA.zeroRowsColumns(rows, diag=diag_val, x=x, b=bb) # Reasamble into block shape b = as_block(bb, blocks) A = as_block(AA, blocks) if has_wrapped_A: return A[0][0], b[0] if return_apply_b: return A, b, apply_b return A, b
V = VectorFunctionSpace(mesh, 'CG', 1) u, v = TrialFunction(V), TestFunction(V) dofs_x = V.sub(0).dofmap().dofs() dofs_y = V.sub(1).dofmap().dofs() M = assemble(inner(div(u), div(v))*dx) # With cbc.block Q = FunctionSpace(mesh, 'CG', 1) p, q = TrialFunction(Q), TestFunction(Q) block00 = assemble(inner(p.dx(0), q.dx(0))*dx) block01 = assemble(inner(p.dx(1), q.dx(0))*dx) block10 = assemble(inner(p.dx(0), q.dx(1))*dx) block11 = assemble(inner(p.dx(1), q.dx(1))*dx) Mb = block_mat([[block00, block01], [block10, block11]]) # The equivalence: let's see how the two operators act on representation of the same # function. For cbc block the dofs of subspaces are serialized. x_array = np.random.rand(V.dim()) x = Function(V).vector(); x.set_local(x_array); x.apply('insert') # Reorder for cbc.block xx_array = x_array[dofs_x] xy_array = x_array[dofs_y] xx = Function(Q).vector(); xx.set_local(xx_array); xx.apply('insert') xy = Function(Q).vector(); xy.set_local(xy_array); xy.apply('insert') xb = block_vec([xx, xy]) # Action y = x.copy() M.mult(x, y)
b_array = b.array() print np.linalg.norm(b_array[dofs_y]) # (0, vy) - so first comp is 0 b = assemble(inner(vy, f) * dx) b_array = b.array() print np.linalg.norm(b_array[dofs_x]) # Consider the mass matrix u = TrialFunction(V) M = assemble(inner(u, v) * dx) # With cbc.block Q = FunctionSpace(mesh, 'CG', 1) p, q = TrialFunction(Q), TestFunction(Q) block = assemble(inner(p, q) * dx) Mb = block_mat([[block, 0], [0, block]]) # The equivalence: let's see how the two operators act on representation of the same # function. For cbc block the dofs of subspaces are serialized. x_array = np.random.rand(V.dim()) x = Function(V).vector() x.set_local(x_array) x.apply('insert') # Reorder for cbc.block xx_array = x_array[dofs_x] xy_array = x_array[dofs_y] xx = Function(Q).vector() xx.set_local(xx_array) xx.apply('insert') xy = Function(Q).vector() xy.set_local(xy_array)
b_array = b.array() print np.linalg.norm(b_array[dofs_y]) # (0, vy) - so first comp is 0 b = assemble(inner(vy, f)*dx) b_array = b.array() print np.linalg.norm(b_array[dofs_x]) # Consider the mass matrix u = TrialFunction(V) M = assemble(inner(u, v)*dx) # With cbc.block Q = FunctionSpace(mesh, 'CG', 1) p, q = TrialFunction(Q), TestFunction(Q) block = assemble(inner(p, q)*dx) Mb = block_mat([[block, 0], [0, block]]) # The equivalence: let's see how the two operators act on representation of the same # function. For cbc block the dofs of subspaces are serialized. x_array = np.random.rand(V.dim()) x = Function(V).vector(); x.set_local(x_array); x.apply('insert') # Reorder for cbc.block xx_array = x_array[dofs_x] xy_array = x_array[dofs_y] xx = Function(Q).vector(); xx.set_local(xx_array); xx.apply('insert') xy = Function(Q).vector(); xy.set_local(xy_array); xy.apply('insert') xb = block_vec([xx, xy]) # Action y = x.copy() M.mult(x, y)
mesh = UnitSquareMesh(32, 32) V = FunctionSpace(mesh, 'CG', 1) Q = FunctionSpace(mesh, 'DG', 0) W = [V, Q] u, p = map(TrialFunction, W) v, q = map(TestFunction, W) [[A00, A01], [A10, A11]] = [[assemble(inner(u, v) * dx), assemble(inner(v, p) * dx)], [assemble(inner(u, q) * dx), assemble(inner(p, q) * dx)]] blocks = [[A00 * A00, A01 + A01], [2 * A10 - A10, A11 * A11 * A11]] AA = block_mat(blocks) t = Timer('x') t.start() X = convert(AA) print t.stop() t = Timer('x') t.start() Y = convert(AA, 'foo') print t.stop() X_ = X.array() X_[:] -= Y.array() print np.linalg.norm(X_, np.inf)
def mini_block(n): '''Just check MMS''' mesh = UnitSquareMesh(n, n) # Just approx f_space = VectorFunctionSpace(mesh, 'DG', 1) h_space = TensorFunctionSpace(mesh, 'DG', 1) u_int = interpolate(u_exact, VectorFunctionSpace(mesh, 'CG', 2)) p_int = interpolate(p_exact, FunctionSpace(mesh, 'CG', 2)) f = project(-div(grad(u_int)) + grad(p_int), f_space) h = project(-p_int * Identity(2) + grad(u_int), h_space) # ---------------- V = VectorFunctionSpace(mesh, 'Lagrange', 1) Vb = VectorFunctionSpace(mesh, 'Bubble', 3) Q = FunctionSpace(mesh, 'Lagrange', 1) W = [V, Vb, Q] u, ub, p = list(map(TrialFunction, W)) v, vb, q = list(map(TestFunction, W)) n = FacetNormal(mesh) a = [[0] * len(W) for _ in range(len(W))] a[0][0] = inner(grad(u), grad(v)) * dx a[0][2] = -inner(div(v), p) * dx a[2][0] = -inner(div(u), q) * dx a[1][1] = inner(grad(ub), grad(vb)) * dx a[1][2] = -inner(div(vb), p) * dx a[2][1] = -inner(div(ub), q) * dx a[0][1] = inner(grad(v), grad(ub)) * dx a[1][0] = inner(grad(vb), grad(u)) * dx # NOTE: bubbles don't contribute to surface L = [ inner(dot(h, n), v) * ds + inner(f, v) * dx, inner(f, vb) * dx, inner(Constant(0), q) * dx ] # Bubbles don't have bcs on the surface bcs = [[DirichletBC(W[0], u_exact, 'near(x[0], 0)')], [], []] AA = block_assemble(a) bb = block_assemble(L) block_bc(bcs, True).apply(AA).apply(bb) # Preconditioner B0 = AMG(AA[0][0]) H1_Vb = inner(grad(ub), grad(vb)) * dx + inner(ub, vb) * dx B1 = LumpedInvDiag(assemble(H1_Vb)) L2_Q = assemble(inner(p, q) * dx) B2 = LumpedInvDiag(L2_Q) BB = block_mat([[B0, 0, 0], [0, B1, 0], [0, 0, B2]]) x0 = AA.create_vec() x0.randomize() AAinv = MinRes(AA, precond=BB, tolerance=1e-10, maxiter=500, relativeconv=True, show=2, initial_guess=x0) # Compute solution u, ub, p = AAinv * bb uh_ = Function(V, u) uhb = Function(Vb, ub) ph = Function(Q, p) uh = Sum([uh_, uhb], degree=1) return uh, ph, sum(Wi.dim() for Wi in W)
def apply_bc(A, b, bcs, diag_val=1.): ''' Apply block boundary conditions to block system A, b ''' if not any(bcs): return A, b # Allow for A, b be simple matrices. To proceed we wrap them as # block objects has_wrapped_A = False if not isinstance(A, block_mat): A = block_mat([[A]]) has_wrapped_A = True if b is None: b = A.create_vec() if not isinstance(b, block_vec): assert has_wrapped_A b = block_vec([b]) bcs = [bcs] # block boundary conditions is a list with bcs for each block of A, b assert len(A) == len(b) == len(bcs) bcs_ = [] for bc in bcs: assert isinstance(bc, (DirichletBC, list)) if isinstance(bc, DirichletBC): bcs_.append([bc]) else: bcs_.append(bc) bcs = bcs_ if not bcs or not any(bcs): return A, b # Obtain a monolithic matrix AA, bb = map(convert, (A, b)) # PETSc guys AA, bb = as_backend_type(AA).mat(), as_backend_type(bb).vec() # We want to make a monotlithic matrix corresponding to function sp ace # where the spaces are serialized # Break apart for block offsets = [0] for bi in b: offsets.append(offsets[-1] + bi.size()) rows = [] x_values = [] # Each bc in the group has dofs numbered only wrt to its space. # We're after global numbering for shift, bcs_sub in zip(offsets, bcs): for bc in bcs_sub: # NOTE: bcs can be a dict or DirichletBC in which case we extract # the dict if isinstance(bc, DirichletBC): bc = bc.get_boundary_values() # Dofs and values for rhs rows.extend(shift + np.array(bc.keys(), dtype='int32')) x_values.extend(bc.values()) rows = np.hstack(rows) x_values = np.array(x_values) x = bb.copy() x.zeroEntries() x.setValues(rows, x_values) # Apply to monolithic len(rows) and AA.zeroRowsColumns(rows, diag=diag_val, x=x, b=bb) blocks = [] for first, last in zip(offsets[:-1], offsets[1:]): blocks.append(PETSc.IS().createStride(last-first, first, 1)) # Reasamble comm = mpi_comm_world() b = [PETSc.Vec().createWithArray(bb.getValues(block), comm=comm) for block in blocks] for bi in b: bi.assemblyBegin() bi.assemblyEnd() b = block_vec(map(PETScVector, b)) # PETSc 3.7.x try: AA.getSubMatrix A = [[PETScMatrix(AA.getSubMatrix(block_row, block_col)) for block_col in blocks] for block_row in blocks] # NOTE: 3.8+ does not ahve getSubMatrix, there is getLocalSubMatrix # but cannot get that to work - petsc error 73. So for now everything # is done with scipy except AttributeError: AA = csr_matrix(AA.getValuesCSR()[::-1], shape=AA.size) A = [[numpy_to_petsc(AA[block_row.array, :][:, block_col.array]) for block_col in blocks] for block_row in blocks] # Block mat A = block_mat(A) if has_wrapped_A: return A[0][0], b[0] return A, b
if __name__ == '__main__': from dolfin import * mesh = UnitSquareMesh(32, 32) V = FunctionSpace(mesh, 'CG', 1) Q = FunctionSpace(mesh, 'DG', 0) W = [V, Q] u, p = map(TrialFunction, W) v, q = map(TestFunction, W) [[A00, A01], [A10, A11]] = [[assemble(inner(u, v)*dx), assemble(inner(v, p)*dx)], [assemble(inner(u, q)*dx), assemble(inner(p, q)*dx)]] blocks = [[A00*A00, A01+A01], [2*A10 - A10, A11*A11*A11]] AA = block_mat(blocks) t = Timer('x'); t.start() X = convert(AA) print t.stop() t = Timer('x'); t.start() Y = convert(AA, 'foo') print t.stop() X_ = X.array() X_[:] -= Y.array() print np.linalg.norm(X_, np.inf)
def pc_nest(pc, block_pc, Amat): '''Setup field split and its operators''' isets, jsets = Amat.getNestISs() # Sanity assert len(isets) == len(jsets) assert all(i.equal(j) for i, j in zip(isets, jsets)), [ (i.array[[0, -1]], j.array[[0, -1]]) for i, j in zip(isets, jsets) ] # We target block matrices if len(isets) == 1: A = pc_mat(pc, block_pc[0][0]) return as_petsc(A) grouped_isets = [] # It may be that block_pc requires different grouping if indinces. # This is signaled by presence of RestrictionOperator if isinstance(block_pc, block_mul): Rt, block_pc, R = block_pc.chain assert Rt.A == R offsets = R.offsets for first, last in zip(R.offsets[:-1], R.offsets[1:]): if last - first == 1: grouped_isets.append(isets[first]) else: group = isets[first] for i in range(first + 1, last): group = group.union(isets[i]) grouped_isets.append(group) else: grouped_isets = isets assert not isinstance(block_pc, block_mat) or set( block_pc.blocks.shape) == set((len(grouped_isets), )) pc.setFieldSplitIS(*[(str(i), iset) for i, iset in enumerate(grouped_isets)]) # The preconditioner will always be fieldsplit assert pc.getType() == 'fieldsplit' pc_ksps = pc.getFieldSplitSubKSP() nblocks, = set(block_pc.blocks.shape) # For now we can only do pure block_mats assert isinstance(block_pc, block_mat) # Consistency assert len(grouped_isets) == nblocks # ... and the have to be diagonal assert all(i == j or block_pc[i][j] == 0 for i in range(nblocks) for j in range(nblocks)) Bmat = block_mat([[0 for i in range(nblocks)] for j in range(nblocks)]) for i, pc_ksp in enumerate(pc_ksps): pc_ksp.setType('preonly') pci = pc_ksp.getPC() # Set individual preconds for blocks block = block_pc[i][i] mat = pc_mat(pci, block) Bmat[i][i] = mat # Return out for setOperators return nest(Bmat)
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 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
from block.iterative import MinRes from block.algebraic.petsc import ML, Cholesky # lhs AA = block_assemble([[a00, a01, a02], [a10, a11, a12], [a20, a21, 0]]) # rhs b = block_assemble([L0, L1, 0]) # Collect boundary conditions bcs = [[bc0, bc1], [], []] block_bc(bcs, True).apply(AA).apply(b) b22 = inner(p, q)*dx B22 = assemble(b22) # Possible action of preconditioner BB = block_mat([[ML(AA[0, 0]), 0, 0], [0, Cholesky(AA[1, 1]), 0], [0, 0, Cholesky(B22)]]) AAinv = MinRes(AA, precond=BB, tolerance=1E-10, maxiter=500) U, B, P = AAinv*b p = Function(Q) p.vector()[:] = P # Plot solution plot(p) interactive()
def mini_block(n): '''Just check MMS''' mesh = UnitSquareMesh(n, n) # Just approx f_space = VectorFunctionSpace(mesh, 'DG', 1) h_space = TensorFunctionSpace(mesh, 'DG', 1) u_int = interpolate(u_exact, VectorFunctionSpace(mesh, 'CG', 2)) p_int = interpolate(p_exact, FunctionSpace(mesh, 'CG', 2)) f = project(-div(grad(u_int)) + grad(p_int), f_space) h = project(-p_int*Identity(2) + grad(u_int), h_space) # ---------------- V = VectorFunctionSpace(mesh, 'Lagrange', 1) Vb = VectorFunctionSpace(mesh, 'Bubble', 3) Q = FunctionSpace(mesh, 'Lagrange', 1) W = [V, Vb, Q] u, ub, p = map(TrialFunction, W) v, vb, q = map(TestFunction, W) n = FacetNormal(mesh) a = [[0]*len(W) for _ in range(len(W))] a[0][0] = inner(grad(u), grad(v))*dx a[0][2] = - inner(div(v), p)*dx a[2][0] = - inner(div(u), q)*dx a[1][1] = inner(grad(ub), grad(vb))*dx a[1][2] = - inner(div(vb), p)*dx a[2][1] = - inner(div(ub), q)*dx a[0][1] = inner(grad(v), grad(ub))*dx a[1][0] = inner(grad(vb), grad(u))*dx # NOTE: bubbles don't contribute to surface L = [inner(dot(h, n), v)*ds + inner(f, v)*dx, inner(f, vb)*dx, inner(Constant(0), q)*dx] # Bubbles don't have bcs on the surface bcs = [[DirichletBC(W[0], u_exact, 'near(x[0], 0)')], [], []] AA = block_assemble(a) bb = block_assemble(L) block_bc(bcs, True).apply(AA).apply(bb) # Preconditioner B0 = AMG(AA[0][0]) H1_Vb = inner(grad(ub), grad(vb))*dx + inner(ub, vb)*dx B1 = LumpedInvDiag(assemble(H1_Vb)) L2_Q = assemble(inner(p, q)*dx) B2 = LumpedInvDiag(L2_Q) BB = block_mat([[B0, 0, 0], [0, B1, 0], [0, 0, B2]]) x0 = AA.create_vec() x0.randomize() AAinv = MinRes(AA, precond=BB, tolerance=1e-10, maxiter=500, relativeconv=True, show=2, initial_guess=x0) # Compute solution u, ub, p = AAinv * bb uh_ = Function(V, u) uhb = Function(Vb, ub) ph = Function(Q, p) uh = Sum([uh_, uhb], degree=1) return uh, ph, sum(Wi.dim() for Wi in W)