def create_cell_circumcenters_and_volumes(self): '''Computes the center of the circumsphere of each cell. ''' cell_coords = self.node_coords[self.cells['nodes']] a = cell_coords[:, 1, :] - cell_coords[:, 0, :] b = cell_coords[:, 2, :] - cell_coords[:, 0, :] c = cell_coords[:, 3, :] - cell_coords[:, 0, :] omega = _row_dot(a, numpy.cross(b, c)) self.cell_circumcenters = cell_coords[:, 0, :] + ( numpy.cross(b, c) * _row_dot(a, a)[:, None] + numpy.cross(c, a) * _row_dot(b, b)[:, None] + numpy.cross(a, b) * _row_dot(c, c)[:, None] ) / (2.0 * omega[:, None]) # https://en.wikipedia.org/wiki/Tetrahedron#Volume self.cell_volumes = abs(omega) / 6.0 return
def compute_control_volumes(self): '''Compute the control volumes of all nodes in the mesh. ''' self.control_volumes = numpy.zeros(len(self.node_coords), dtype=float) # 1/3. * (0.5 * edge_length) * covolume # = 1/6 * edge_length**2 * ce_ratio_edge_ratio e = self.node_coords[self.edges['nodes'][:, 1]] - \ self.node_coords[self.edges['nodes'][:, 0]] vals = _row_dot(e, e) * self.ce_ratios / 6.0 edge_nodes = self.edges['nodes'] numpy.add.at(self.control_volumes, edge_nodes[:, 0], vals) numpy.add.at(self.control_volumes, edge_nodes[:, 1], vals) return
def compute_ce_ratios_algebraic(self): # Precompute edges. edges = \ self.node_coords[self.edges['nodes'][:, 1]] - \ self.node_coords[self.edges['nodes'][:, 0]] # create cells -> edges num_cells = len(self.cells['nodes']) cells_edges = numpy.empty((num_cells, 6), dtype=int) for cell_id, face_ids in enumerate(self.cells['faces']): edges_set = set(self.faces['edges'][face_ids].flatten()) cells_edges[cell_id] = list(edges_set) self.cells['edges'] = cells_edges # Build the equation system: # The equation # # |simplex| ||u||^2 = \sum_i \alpha_i <u,e_i> <e_i,u> # # has to hold for all vectors u in the plane spanned by the edges, # particularly by the edges themselves. cells_edges = edges[self.cells['edges']] # <http://stackoverflow.com/a/38110345/353337> A = numpy.einsum('ijk,ilk->ijl', cells_edges, cells_edges) A = A**2 # Compute the RHS cell_volume * <edge, edge>. # The dot product <edge, edge> is also on the diagonals of A (before # squaring), but simply computing it again is cheaper than extracting # it from A. edge_dot_edge = _row_dot(edges, edges) rhs = edge_dot_edge[self.cells['edges']] * self.cell_volumes[..., None] # Solve all k-by-k systems at once ("broadcast"). (`k` is the number of # edges per simplex here.) # If the matrix A is (close to) singular if and only if the cell is # (close to being) degenerate. Hence, it has volume 0, and so all the # edge coefficients are 0, too. Hence, do nothing. sol = numpy.linalg.solve(A, rhs) return sol
def compute_curl(self, vector_field): '''Computes the curl of a vector field. While the vector field is point-based, the curl will be cell-based. The approximation is based on .. math:: n\cdot curl(F) = \lim_{A\\to 0} |A|^{-1} \int_{dGamma} F dr; see <https://en.wikipedia.org/wiki/Curl_(mathematics)>. Actually, to approximate the integral, one would only need the projection of the vector field onto the edges at the midpoint of the edges. ''' edge_coords = \ self.node_coords[self.edges['nodes'][:, 1]] - \ self.node_coords[self.edges['nodes'][:, 0]] barycenters = 1./3. * numpy.sum( self.node_coords[self.cells['nodes']], axis=1 ) # Compute the projection of A on the edge at each edge midpoint. nodes = self.edges['nodes'] x = self.node_coords[nodes] A = 0.5 * numpy.sum(vector_field[nodes], axis=1) edge_dot_A = _row_dot(edge_coords, A) directions = numpy.cross( x[self.cells['edges'], 0] - barycenters[:, None, :], x[self.cells['edges'], 1] - barycenters[:, None, :] ) dir_nrms = numpy.sqrt(numpy.sum(directions**2, axis=2)) directions /= dir_nrms[..., None] # a: directions scaled with edge_dot_a a = directions * edge_dot_A[self.cells['edges']][..., None] # sum over all local edges curl = numpy.sum(a, axis=1) # Divide by cell volumes curl /= self.cell_volumes[..., None] return curl