def collapse_mul(bmat): '''A*B*C to single matrix''' # A0 * A1 * ... A, B = bmat.chain[0], bmat.chain[1:] if len(B) == 1: B = B[0] # Two matrices if is_petsc_mat(A) and is_petsc_mat(B): A_ = as_petsc(A) B_ = as_petsc(B) assert A_.size[1] == B_.size[0] C_ = PETSc.Mat() A_.matMult(B_, C_) return PETScMatrix(C_) # One of them is a number elif is_petsc_mat(A) and is_number(B): A_ = as_petsc(A) C_ = A_.copy() C_.scale(B) return PETScMatrix(C_) elif is_petsc_mat(B) and is_number(A): B_ = as_petsc(B) C_ = B_.copy() C_.scale(A) return PETScMatrix(C_) # Some compositions else: return collapse(collapse(A) * collapse(B)) # Recurse else: return collapse_mul(collapse(A) * collapse(reduce(operator.mul, B)))
def collapse_mul(bmat): '''A*B*C to single matrix''' # A0 * A1 * ... A, B = bmat.chain[0], bmat.chain[1:] if len(B) == 1: B = B[0] # Two matrices if is_petsc_mat(A) and is_petsc_mat(B): A_ = as_petsc(A) B_ = as_petsc(B) assert A_.size[1] == B_.size[0] C_ = PETSc.Mat() A_.matMult(B_, C_) return PETScMatrix(C_) # One of them is a number elif is_petsc_mat(A) and is_number(B): A_ = as_petsc(A) C_ = A_.copy() C_.scale(B) return PETScMatrix(C_) elif is_petsc_mat(B) and is_number(A): B_ = as_petsc(B) C_ = B_.copy() C_.scale(A) return PETScMatrix(C_) # Some compositions else: return collapse(collapse(A)*collapse(B)) # Recurse else: return collapse_mul(collapse(A)*collapse(reduce(operator.mul, B)))
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 get_dims(thing): ''' Size of Rn vector or operator Rn to Rm. We return None for scalars and raise when such an operator cannot be established, i.e. there are consistency checks going on ''' if is_petsc_vec(thing): return thing.size() if is_petsc_mat(thing): return (thing.size(0), thing.size(1)) if is_number(thing): return None # Now let's handdle block stuff # Multiplication if isinstance(thing, block_mul): A, B = thing.chain[0], thing.chain[1:] dims_A, dims_B = get_dims(A), get_dims(B[0]) # A number does not change if dims_A is None: return dims_B if dims_B is None: return dims_A # Otherwise, consistency if len(B) == 1: assert len(dims_A) == len(dims_B) assert dims_A[1] == dims_B[0] return (dims_A[0], dims_B[1]) else: dims_B = get_dims(reduce(operator.mul, B)) assert len(dims_A) == len(dims_B) assert dims_A[1] == dims_B[0] return (dims_A[0], dims_B[1]) # +, - elif isinstance(thing, (block_add, block_sub)): A, B = thing.A, thing.B if is_number(A): return get_dims(B) if is_number(B): return get_dims(A) dims = get_dims(A) assert dims == get_dims(B), (dims, get_dims(B)) return dims # T elif isinstance(thing, block_transpose): dims = get_dims(thing.A) return (dims[1], dims[0]) # Some things in cbc.block know their matrix representation # E.g. InvLumpDiag... elif hasattr(thing, 'A'): assert is_petsc_mat(thing.A) return get_dims(thing.A) raise ValueError('Cannot get_dims of %r, %s' % (type(thing), thing))
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 assemble(form): '''Assemble multidimensional form''' # In the base case we want to fall trough the custom assemblers # for trace/average/restriction problems until something that # dolfin can handle (hopefully) modules = ( xii.assembler.trace_assembly, # Codimension 1 xii.assembler.average_assembly, # Codimension 2 xii.assembler.restriction_assembly, # Codimension 0 xii.assembler.point_trace_assembly) # Dimensiom 0 if isinstance(form, Form): arity = form_arity(form) # Try with our reduced assemblers for module in modules: tensor = module.assemble_form(form, arity) if tensor is not None: return tensor # Fallback return df.assemble(form) # We might get number if is_number(form): return form # Recurse if isinstance(form, list): form = np.array(form, dtype='object') blocks = np.array(map(assemble, form.flatten())).reshape(form.shape) return (block_vec if blocks.ndim == 1 else block_mat)(blocks)
def assemble(form): '''Assemble multidimensional form''' # In the base case we want to fall trough the custom assemblers # for trace/average/restriction problems until something that # dolfin can handle (hopefully) modules = ( xii.assembler.trace_assembly, # To Codimension 1 xii.assembler. average_assembly, # To Codimension 2 via surface of bding curve xii.assembler.extension_assembly, # From dim 1 to 2 xii.assembler.restriction_assembly, xii.assembler.injection_assembly) # Between Codimension 0 names = ('trace', 'average', 'extension', 'restriction', 'injection') if isinstance(form, Form): arity = form_arity(form) # Try with our reduced assemblers for name, module in zip(names, modules): tensor = module.assemble_form(form, arity) if tensor is not None: return tensor # Fallback return df.assemble(form) # We might get number if is_number(form): return form shape = shape_list(form) # Recurse blocks = reshape_list(map(assemble, flatten_list(form)), shape) return (block_vec if len(shape) == 1 else block_mat)(blocks)
def assemble(form): '''Assemble multidimensional form''' # In the base case we want to fall trough the custom assemblers # for trace/average/restriction problems until something that # dolfin can handle (hopefully) modules = ( xii.assembler.trace_assembly, # Codimension 1 xii.assembler.average_assembly, # Codimension 2 xii.assembler.restriction_assembly) # Codimension 0 if isinstance(form, Form): arity = form_arity(form) # Try with our reduced assemblers for module in modules: tensor = module.assemble_form(form, arity) if tensor is not None: return tensor # Fallback return df.assemble(form) # We might get number if is_number(form): return form shape = shape_list(form) # Recurse blocks = reshape_list(map(assemble, flatten_list(form)), shape) return (block_vec if len(shape) == 1 else block_mat)(blocks)
def __init__(self, radius, degree): # Make constant function if is_number(radius): assert radius > 0 self.radius = lambda x0, r=radius: r # Then this must map points on centerline to radius else: self.radius = radius # NOTE: Let s by the arch length coordinate of the centerline of the # cylinder. A tangent to 1d mesh at s is a normal to the plane in which # we draw a circle C(n, r) with radius r. For reduction of 3d we compute # # |2*pi*R(s)|^-1\int_{C(n, r)} u dl = # # |2*pi*R(s)|^-1\int_{-pi}^{pi} u(s + t1*R(s)*cos(theta) + t2*R(s)*sin(theta))R*d(theta) = # # |2*pi*R(s)|^-1\int_{-1}^{1} u(s + t1*R(s)*cos(pi*t) + t2*R(s)*sin(pi*t))*pi*R*dt = # # 1/2*sum_q u(s + t1*R(s)*cos(pi*x_q) + t2*R(s)*sin(pi*x_q)) xq, wq = leggauss(degree) self.__weights__ = wq * 0.5 # Scale down by 0.5 # Precompute trigonometric part (only shift by tangents t1, t2) self.cos_xq = np.cos(np.pi * xq).reshape((-1, 1)) self.sin_xq = np.sin(np.pi * xq).reshape((-1, 1))
def assemble(form): '''Assemble multidimensional form''' # In the base case we want to fall trough the custom assemblers # for trace/average/restriction problems until something that # dolfin can handle (hopefully) modules = (xii.assembler.trace_assembly, # Codimension 1 xii.assembler.average_assembly, # Codimension 2 xii.assembler.restriction_assembly) # Codimension 0 if isinstance(form, Form): arity = form_arity(form) # Try with our reduced assemblers for module in modules: tensor = module.assemble_form(form, arity) if tensor is not None: return tensor # Fallback return df.assemble(form) # We might get number if is_number(form): return form shape = shape_list(form) # Recurse blocks = reshape_list(map(assemble, flatten_list(form)), shape) return (block_vec if len(shape) == 1 else block_mat)(blocks)
def collapse(bmat): '''Collapse what are blocks of bmat''' # Single block cases # Do nothing if is_petsc_mat(bmat) or is_number(bmat) or is_petsc_vec(bmat): return bmat # Multiplication if isinstance(bmat, block_mul): return collapse_mul(bmat) # + elif isinstance(bmat, block_add): return collapse_add(bmat) # - elif isinstance(bmat, block_sub): return collapse_sub(bmat) # T elif isinstance(bmat, block_transpose): return collapse_tr(bmat) # Some things in cbc.block know their matrix representation # E.g. InvLumpDiag... elif hasattr(bmat, 'A'): assert is_petsc_mat(bmat.A) return bmat.A raise ValueError('Do not know how to collapse %r' % type(bmat))
def __init__(self, P, degree): if isinstance(P, (tuple, list, np.ndarray)): assert all(is_number(Pi) for Pi in P) self.P = lambda x0, p=P: p else: self.P = P # Weights for [-1, 1] for 2d will do the tensor product self.xq, self.wq = leggauss(degree)
def collapse(bmat): '''Collapse what are blocks of bmat''' # Single block cases # Do nothing if is_petsc_mat(bmat) or is_number(bmat) or is_petsc_vec(bmat): return bmat if isinstance(bmat, (Vector, Matrix, GenericVector)): return bmat # Multiplication if isinstance(bmat, block_mul): return collapse_mul(bmat) # + elif isinstance(bmat, block_add): return collapse_add(bmat) # - elif isinstance(bmat, block_sub): return collapse_sub(bmat) # T elif isinstance(bmat, block_transpose): return collapse_tr(bmat) # Some things in cbc.block know their matrix representation # This is typically diagonals like InvLumpDiag etc elif hasattr(bmat, 'v'): # So now we make that diagonal matrix diagonal = bmat.v n = diagonal.size mat = PETSc.Mat().createAIJ(comm=COMM, size=[[n, n], [n, n]], nnz=1) mat.assemblyBegin() mat.setDiagonal(diagonal) mat.assemblyEnd() return PETScMatrix(mat) # Some operators actually have matrix repre (HsMG) elif hasattr(bmat, 'matrix'): return bmat.matrix # Try: elif hasattr(bmat, 'collapse'): return bmat.collapse() elif hasattr(bmat, 'create_vec'): x = bmat.create_vec() columns = [] for ei in Rn_basis(x): y = bmat*ei columns.append(csr_matrix(convert(y).get_local())) bmat = (sp_vstack(columns).T).tocsr() return numpy_to_petsc(bmat) raise ValueError('Do not know how to collapse %r' % type(bmat))
def __init__(self, P, degree): if isinstance(P, (tuple, list, np.ndarray)): assert all(is_number(Pi) for Pi in P) self.P = lambda x0, p=P: p else: self.P = P xq, wq = leggauss(degree) # Repeat for each edge self.__weights__ = 0.5 * np.tile(wq, 4) self.__xq__ = xq # Point for edge -1 -- 1
def __init__(self, radius, degree): # Make constant function if is_number(radius): assert radius > 0 self.radius = lambda x0, r=radius: r # Then this must map points on centerline to radius else: self.radius = radius # Will use Gauss quadrature on [-1, 1] self.xq, self.wq = leggauss(degree)
def collect(bmat): '''Extract aplha_j, s_j pairs''' if isinstance(bmat, InterpolationMatrix): return [[1, bmat.s]] if isinstance(bmat, block_add): return collect(bmat.A) + collect(bmat.B) if isinstance(bmat, block_sub): b = collect(bmat.B) return collect(bmat.A) + [[-bi[0], bi[1]] for bi in b] if isinstance(bmat, block_mul): assert len(bmat.chain) == 2 A, B = bmat.chain if isinstance(A, InterpolationMatrix): assert is_number(B) return [[B, A.s]] else: assert is_number(A) and isinstance(B, InterpolationMatrix), (A, B) return [[A, B.s]]
def __init__(self, radius, degree): # Make constant function if is_number(radius): assert radius > 0 self.radius = lambda x0, r=radius: r # Then this must map points on centerline to radius else: self.radius = radius # Will use quadrature from quadpy over unit disk in z=0 plane # and center (0, 0, 0) quad = quadpy.disk.Lether(degree) self.xq, self.wq = quad.points, quad.weights
def block_mat_to_numpy(bmat): '''Collapsing block mat of matrices to scipy's bmat''' # A single matrix if is_petsc_mat(bmat): bmat = as_petsc(bmat) return csr_matrix(bmat.getValuesCSR()[::-1], shape=bmat.size) # 0 if is_number(bmat): return None # What bmat accepts # Recurse on blocks blocks = np.array(map(block_mat_to_numpy, bmat.blocks.flatten())) blocks = blocks.reshape(bmat.blocks.shape) # The bmat return numpy_block_mat(blocks).tocsr()
def __init__(self, radius, degree): # Make constant function if is_number(radius): assert radius > 0 self.radius = lambda x0, r=radius: r # Then this must map points on centerline to radius else: self.radius = radius from quadpy.sphere import Lebedev integrator = Lebedev(str(degree)) self.xq = integrator.points self.__weights__ = integrator.weights
def is_well_defined(bmat): '''Is expression of the form that inverse accepts''' A, M = None, None for mat in crawl(bmat): if is_number(mat): continue if A is None: A, M = mat.A, mat.M else: if not ((A.size(0) == mat.A.size(0)) and (A.size(1) == mat.A.size(1))): return False if not ((M.size(0) == mat.M.size(0)) and (M.size(1) == mat.M.size(1))): return False return True
def crawl(bmat): '''Return terminals in cbc.block expression that are relevant for inverse''' if isinstance(bmat, block_mul): for block in bmat.chain: for item in crawl(block): yield item if isinstance(bmat, (block_add, block_sub)): for item in crawl(bmat.A): yield item for item in crawl(bmat.B): yield item if is_number(bmat) or isinstance(bmat, InterpolationMatrix): yield bmat
def Average(v, line_mesh, radius, quadrature_degree=8, surface='cylinder'): ''' Annotated function for being surface average around line mesh. For surface == 'cylinder' this means that (Average(v))(x) = |C_R(x)|\int_{C_R(x)} v(y) dy for every point x on the line_mesh. C_R(x) is a center of radius R(x) centered at x with normal vector determined by tangent of the line_mesh segment @ x. For surface == 'sphere' we integrate over a surface of a ball at x with radius. This integral is computed numerically with given quad. degree. If radius == 0, this reduction is understood as 3d-1d trace. In this case the reduced function must be in some CG space! ''' # Prevent Trace(grad(u)). But it could be interesting to have this assert is_terminal(v) assert average_cell(v) == line_mesh.ufl_cell() # Some sanity check for the radius if isinstance(radius, int) and radius == 0: v_family = v.ufl_element().family() # If we have en embedded mesh this mean that we want trace on en # edge and this make it well defined (only?) for CG if hasattr(line_mesh, 'parent_entity_map'): assert v_family == 'Lagrange', '3d1d trace undefined for %s' % v_family # Otherise the hope is that we will eval in cell interior which print '\tUsing 3d-1d trace!!!!' radius = None # Signal to avg_mat if is_number(radius): assert radius > 0 if isinstance(v, df.Coefficient): v = df.Function(v.function_space(), v.vector()) v.average_ = { 'mesh': line_mesh, 'radius': radius, 'quad_degree': quadrature_degree, 'surface': surface } return v
def Average(v, line_mesh, radius, quadrature_degree=8, surface='cylinder'): ''' Annotated function for being surface average around line mesh. For surface == 'cylinder' this means that (Average(v))(x) = |C_R(x)|\int_{C_R(x)} v(y) dy for every point x on the line_mesh. C_R(x) is a center of radius R(x) centered at x with normal vector determined by tangent of the line_mesh segment @ x. For surface == 'sphere' we integrate over a surface of a ball at x with radius. This integral is computed numerically with given quad. degree. If radius == 0, this reduction is understood as 3d-1d trace. In this case the reduced function must be in some CG space! ''' # Prevent Trace(grad(u)). But it could be interesting to have this assert is_terminal(v) assert average_cell(v) == line_mesh.ufl_cell() # Some sanity check for the radius if isinstance(radius, int) and radius == 0: v_family = v.ufl_element().family() # If we have en embedded mesh this mean that we want trace on en # edge and this make it well defined (only?) for CG if hasattr(line_mesh, 'parent_entity_map'): assert v_family == 'Lagrange', '3d1d trace undefined for %s' % v_family # Otherise the hope is that we will eval in cell interior which print '\tUsing 3d-1d trace!!!!' radius = None # Signal to avg_mat if is_number(radius): assert radius > 0 if isinstance(v, df.Coefficient): v = df.Function(v.function_space(), v.vector()) v.average_ = {'mesh': line_mesh, 'radius': radius, 'quad_degree': quadrature_degree, 'surface': surface} return v
def collapse(bmat): '''Collapse what are blocks of bmat''' # Single block cases # Do nothing if is_petsc_mat(bmat) or is_number(bmat) or is_petsc_vec(bmat): return bmat if isinstance(bmat, (Vector, Matrix, GenericVector)): return bmat # Multiplication if isinstance(bmat, block_mul): return collapse_mul(bmat) # + elif isinstance(bmat, block_add): return collapse_add(bmat) # - elif isinstance(bmat, block_sub): return collapse_sub(bmat) # T elif isinstance(bmat, block_transpose): return collapse_tr(bmat) # Some things in cbc.block know their matrix representation # This is typically diagonals like InvLumpDiag etc elif hasattr(bmat, 'v'): # So now we make that diagonal matrix diagonal = bmat.v n = diagonal.size mat = PETSc.Mat().createAIJ(comm=COMM, size=[[n, n], [n, n]], nnz=1) mat.assemblyBegin() mat.setDiagonal(diagonal) mat.assemblyEnd() return PETScMatrix(mat) # Some operators actually have matrix repre (HsMG) elif hasattr(bmat, 'matrix'): return bmat.matrix raise ValueError('Do not know how to collapse %r' % type(bmat))
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
def cylinder_average_matrix(V, TV, radius, quad_degree): '''Averaging matrix''' mesh = V.mesh() line_mesh = TV.mesh() # We are going to perform the integration with Gauss quadrature at # the end (PI u)(x): # A cell of mesh (an edge) defines a normal vector. Let P be the plane # that is defined by the normal vector n and some point x on Gamma. Let L # be the circle that is the intersect of P and S. The value of q (in Q) at x # is defined as # # q(x) = (1/|L|)*\int_{L}g(x)*dL # # which simplifies to g(x) = (1/(2*pi*R))*\int_{-pi}^{pi}u(L)*R*d(theta) and # or = (1/2) * \int_{-1}^{1} u (L(pi*s)) * ds # This can be integrated no problemo once we figure out L. To this end, let # t_1 and t_2 be two unit mutually orthogonal vectors that are orthogonal to # n. Then L(pi*s) = p + R*t_1*cos(pi*s) + R*t_2*sin(pi*s) can be seen to be # such that i) |x-p| = R and ii) x.n = 0 [i.e. this the suitable # parametrization] # Clearly we can scale the weights as well as precompute # cos and sin terms. xq, wq = leggauss(quad_degree) wq *= 0.5 cos_xq = np.cos(np.pi*xq).reshape((-1, 1)) sin_xq = np.sin(np.pi*xq).reshape((-1, 1)) if is_number(radius): radius = lambda x, radius=radius: radius mesh_x = TV.mesh().coordinates() # The idea for point evaluation/computing dofs of TV is to minimize # the number of evaluation. I mean a vector dof if done naively would # have to evaluate at same x number of component times. value_size = TV.ufl_element().value_size() # Eval at points will require serch tree = mesh.bounding_box_tree() limit = mesh.num_cells() TV_coordinates = TV.tabulate_dof_coordinates().reshape((TV.dim(), -1)) TV_dm = TV.dofmap() V_dm = V.dofmap() # For non scalar we plan to make compoenents by shift if value_size > 1: TV_dm = TV.sub(0).dofmap() Vel = V.element() basis_values = np.zeros(V.element().space_dimension()*value_size) with petsc_serial_matrix(TV, V) as mat: for line_cell in cells(line_mesh): # Get the tangent => orthogonal tangent vectors v0, v1 = mesh_x[line_cell.entities(0)] n = v0 - v1 t1 = np.array([n[1]-n[2], n[2]-n[0], n[0]-n[1]]) t2 = np.cross(n, t1) t1 /= np.linalg.norm(t1) t2 = t2/np.linalg.norm(t2) # The idea is now to minimize the point evaluation scalar_dofs = TV_dm.cell_dofs(line_cell.index()) scalar_dofs_x = TV_coordinates[scalar_dofs] for scalar_row, avg_point in zip(scalar_dofs, scalar_dofs_x): # Get radius and integration points rad = radius(avg_point) integration_points = avg_point + rad*t1*sin_xq + rad*t2*cos_xq data = {} for index, ip in enumerate(integration_points): c = tree.compute_first_entity_collision(Point(*ip)) if c >= limit: continue Vcell = Cell(mesh, c) vertex_coordinates = Vcell.get_vertex_coordinates() cell_orientation = Vcell.orientation() Vel.evaluate_basis_all(basis_values, ip, vertex_coordinates, cell_orientation) cols_ip = V_dm.cell_dofs(c) values_ip = basis_values*wq[index] # Add for col, value in zip(cols_ip, values_ip.reshape((-1, value_size))): if col in data: data[col] += value else: data[col] = value # The thing now that with data we can assign to several # rows of the matrix column_indices = np.array(data.keys(), dtype='int32') for shift in range(value_size): row = scalar_row + shift column_values = np.array([data[col][shift] for col in column_indices]) mat.setValues([row], column_indices, column_values, PETSc.InsertMode.INSERT_VALUES) # On to next avg point # On to next cell return PETScMatrix(mat)
def sphere_average_matrix(V, TV, radius, quad_degree): '''Averaging matrix over the sphere''' mesh = V.mesh() line_mesh = TV.mesh() # Lebedev below need off degrees if quad_degree % 2 == 0: quad_degree += 1 # NOTE: this is a dependency from quadpy.sphere import Lebedev integrator = Lebedev(quad_degree) xq = integrator.points wq = integrator.weights if is_number(radius): radius = lambda x, radius=radius: radius mesh_x = TV.mesh().coordinates() # The idea for point evaluation/computing dofs of TV is to minimize # the number of evaluation. I mean a vector dof if done naively would # have to evaluate at same x number of component times. value_size = TV.ufl_element().value_size() # Eval at points will require serch tree = mesh.bounding_box_tree() limit = mesh.num_cells() TV_coordinates = TV.tabulate_dof_coordinates().reshape((TV.dim(), -1)) TV_dm = TV.dofmap() V_dm = V.dofmap() # For non scalar we plan to make compoenents by shift if value_size > 1: TV_dm = TV.sub(0).dofmap() Vel = V.element() basis_values = np.zeros(V.element().space_dimension()*value_size) with petsc_serial_matrix(TV, V) as mat: for line_cell in cells(line_mesh): # The idea is now to minimize the point evaluation scalar_dofs = TV_dm.cell_dofs(line_cell.index()) scalar_dofs_x = TV_coordinates[scalar_dofs] for scalar_row, avg_point in zip(scalar_dofs, scalar_dofs_x): # Get radius and integration points rad = radius(avg_point) # Scale and shift the unit sphere to the point integration_points = xq*rad + avg_point data = {} for index, ip in enumerate(integration_points): c = tree.compute_first_entity_collision(Point(*ip)) if c >= limit: continue Vcell = Cell(mesh, c) vertex_coordinates = Vcell.get_vertex_coordinates() cell_orientation = Vcell.orientation() Vel.evaluate_basis_all(basis_values, ip, vertex_coordinates, cell_orientation) cols_ip = V_dm.cell_dofs(c) values_ip = basis_values*wq[index] # Add for col, value in zip(cols_ip, values_ip.reshape((-1, value_size))): if col in data: data[col] += value else: data[col] = value # The thing now that with data we can assign to several # rows of the matrix column_indices = np.array(data.keys(), dtype='int32') for shift in range(value_size): row = scalar_row + shift column_values = np.array([data[col][shift] for col in column_indices]) mat.setValues([row], column_indices, column_values, PETSc.InsertMode.INSERT_VALUES) # On to next avg point # On to next cell return PETScMatrix(mat)
def cylinder_average_matrix(V, TV, radius, quad_degree): '''Averaging matrix''' mesh = V.mesh() line_mesh = TV.mesh() # We are going to perform the integration with Gauss quadrature at # the end (PI u)(x): # A cell of mesh (an edge) defines a normal vector. Let P be the plane # that is defined by the normal vector n and some point x on Gamma. Let L # be the circle that is the intersect of P and S. The value of q (in Q) at x # is defined as # # q(x) = (1/|L|)*\int_{L}g(x)*dL # # which simplifies to g(x) = (1/(2*pi*R))*\int_{-pi}^{pi}u(L)*R*d(theta) and # or = (1/2) * \int_{-1}^{1} u (L(pi*s)) * ds # This can be integrated no problemo once we figure out L. To this end, let # t_1 and t_2 be two unit mutually orthogonal vectors that are orthogonal to # n. Then L(pi*s) = p + R*t_1*cos(pi*s) + R*t_2*sin(pi*s) can be seen to be # such that i) |x-p| = R and ii) x.n = 0 [i.e. this the suitable # parametrization] # Clearly we can scale the weights as well as precompute # cos and sin terms. xq, wq = leggauss(quad_degree) wq *= 0.5 cos_xq = np.cos(np.pi * xq).reshape((-1, 1)) sin_xq = np.sin(np.pi * xq).reshape((-1, 1)) if is_number(radius): radius = lambda x, radius=radius: radius mesh_x = TV.mesh().coordinates() # The idea for point evaluation/computing dofs of TV is to minimize # the number of evaluation. I mean a vector dof if done naively would # have to evaluate at same x number of component times. value_size = TV.ufl_element().value_size() # Eval at points will require serch tree = mesh.bounding_box_tree() limit = mesh.num_cells() TV_coordinates = TV.tabulate_dof_coordinates().reshape((TV.dim(), -1)) TV_dm = TV.dofmap() V_dm = V.dofmap() # For non scalar we plan to make compoenents by shift if value_size > 1: TV_dm = TV.sub(0).dofmap() Vel = V.element() basis_values = np.zeros(V.element().space_dimension() * value_size) with petsc_serial_matrix(TV, V) as mat: for line_cell in cells(line_mesh): # Get the tangent => orthogonal tangent vectors v0, v1 = mesh_x[line_cell.entities(0)] n = v0 - v1 t1 = np.array([n[1] - n[2], n[2] - n[0], n[0] - n[1]]) t2 = np.cross(n, t1) t1 /= np.linalg.norm(t1) t2 = t2 / np.linalg.norm(t2) # The idea is now to minimize the point evaluation scalar_dofs = TV_dm.cell_dofs(line_cell.index()) scalar_dofs_x = TV_coordinates[scalar_dofs] for scalar_row, avg_point in zip(scalar_dofs, scalar_dofs_x): # Get radius and integration points rad = radius(avg_point) integration_points = avg_point + rad * t1 * sin_xq + rad * t2 * cos_xq data = {} for index, ip in enumerate(integration_points): c = tree.compute_first_entity_collision(Point(*ip)) if c >= limit: continue Vcell = Cell(mesh, c) vertex_coordinates = Vcell.get_vertex_coordinates() cell_orientation = Vcell.orientation() Vel.evaluate_basis_all(basis_values, ip, vertex_coordinates, cell_orientation) cols_ip = V_dm.cell_dofs(c) values_ip = basis_values * wq[index] # Add for col, value in zip(cols_ip, values_ip.reshape((-1, value_size))): if col in data: data[col] += value else: data[col] = value # The thing now that with data we can assign to several # rows of the matrix column_indices = np.array(data.keys(), dtype='int32') for shift in range(value_size): row = scalar_row + shift column_values = np.array( [data[col][shift] for col in column_indices]) mat.setValues([row], column_indices, column_values, PETSc.InsertMode.INSERT_VALUES) # On to next avg point # On to next cell return PETScMatrix(mat)
def sphere_average_matrix(V, TV, radius, quad_degree): '''Averaging matrix over the sphere''' mesh = V.mesh() line_mesh = TV.mesh() # Lebedev below need off degrees if quad_degree % 2 == 0: quad_degree += 1 # NOTE: this is a dependency from quadpy.sphere import Lebedev integrator = Lebedev(quad_degree) xq = integrator.points wq = integrator.weights if is_number(radius): radius = lambda x, radius=radius: radius mesh_x = TV.mesh().coordinates() # The idea for point evaluation/computing dofs of TV is to minimize # the number of evaluation. I mean a vector dof if done naively would # have to evaluate at same x number of component times. value_size = TV.ufl_element().value_size() # Eval at points will require serch tree = mesh.bounding_box_tree() limit = mesh.num_cells() TV_coordinates = TV.tabulate_dof_coordinates().reshape((TV.dim(), -1)) TV_dm = TV.dofmap() V_dm = V.dofmap() # For non scalar we plan to make compoenents by shift if value_size > 1: TV_dm = TV.sub(0).dofmap() Vel = V.element() basis_values = np.zeros(V.element().space_dimension() * value_size) with petsc_serial_matrix(TV, V) as mat: for line_cell in cells(line_mesh): # The idea is now to minimize the point evaluation scalar_dofs = TV_dm.cell_dofs(line_cell.index()) scalar_dofs_x = TV_coordinates[scalar_dofs] for scalar_row, avg_point in zip(scalar_dofs, scalar_dofs_x): # Get radius and integration points rad = radius(avg_point) # Scale and shift the unit sphere to the point integration_points = xq * rad + avg_point data = {} for index, ip in enumerate(integration_points): c = tree.compute_first_entity_collision(Point(*ip)) if c >= limit: continue Vcell = Cell(mesh, c) vertex_coordinates = Vcell.get_vertex_coordinates() cell_orientation = Vcell.orientation() Vel.evaluate_basis_all(basis_values, ip, vertex_coordinates, cell_orientation) cols_ip = V_dm.cell_dofs(c) values_ip = basis_values * wq[index] # Add for col, value in zip(cols_ip, values_ip.reshape((-1, value_size))): if col in data: data[col] += value else: data[col] = value # The thing now that with data we can assign to several # rows of the matrix column_indices = np.array(data.keys(), dtype='int32') for shift in range(value_size): row = scalar_row + shift column_values = np.array( [data[col][shift] for col in column_indices]) mat.setValues([row], column_indices, column_values, PETSc.InsertMode.INSERT_VALUES) # On to next avg point # On to next cell return PETScMatrix(mat)
def get_dims(thing): ''' Size of Rn vector or operator Rn to Rm. We return None for scalars and raise when such an operator cannot be established, i.e. there are consistency checks going on ''' if is_petsc_vec(thing): return thing.size() if is_petsc_mat(thing): return (thing.size(0), thing.size(1)) if is_number(thing): return None # Now let's handdle block stuff # Multiplication if isinstance(thing, block_mul): A, B = thing.chain[0], thing.chain[1:] dims_A, dims_B = get_dims(A), get_dims(B[0]) # A number does not change if dims_A is None: return dims_B if dims_B is None: return dims_A # Otherwise, consistency if len(B) == 1: assert len(dims_A) == len(dims_B) assert dims_A[1] == dims_B[0], (dims_A, dims_B) return (dims_A[0], dims_B[1]) else: dims_B = get_dims(reduce(operator.mul, B)) assert len(dims_A) == len(dims_B) assert dims_A[1] == dims_B[0], (dims_A, dims_B) return (dims_A[0], dims_B[1]) # +, - if isinstance(thing, (block_add, block_sub)): A, B = thing.A, thing.B if is_number(A): return get_dims(B) if is_number(B): return get_dims(A) dims = get_dims(A) assert dims == get_dims(B), (dims, get_dims(B)) return dims # T if isinstance(thing, block_transpose): dims = get_dims(thing.A) return (dims[1], dims[0]) # Some things in cbc.block know their maE.g. InvLumpDiag...Almost last resort if hasattr(thing, 'A'): assert is_petsc_mat(thing.A) return get_dims(thing.A) if hasattr(thing, '__sizes__'): return thing.__sizes__ if hasattr(thing, 'create_vec'): return (thing.create_vec(0).size(), thing.create_vec(1).size()) raise ValueError('Cannot get_dims of %r, %s' % (type(thing), thing))