def setBC(self): mesh = self.mesh V = sp.diags(mesh.cell_volumes) self.Div = V @ mesh.face_divergence self.Grad = self.Div.T if self.bc_type == "Dirichlet": if self.verbose: print( "Homogeneous Dirichlet is the natural BC for this CC discretization." ) # do nothing return elif self.bc_type == "Neumann": alpha, beta, gamma = 0, 1, 0 else: # self.bc_type == "Mixed": boundary_faces = mesh.boundary_faces boundary_normals = mesh.boundary_face_outward_normals n_bf = len(boundary_faces) # Top gets 0 Nuemann alpha = np.zeros(n_bf) beta = np.ones(n_bf) gamma = 0 # assume a source point at the middle of the top of the mesh middle = np.median(mesh.nodes, axis=0) top_v = np.max(mesh.nodes[:, -1]) source_point = np.r_[middle[:-1], top_v] # Others: Robin: alpha * phi + d phi dn = 0 # where alpha = 1 / r * r_hat_dot_n # TODO: Implement Zhang et al. (1995) r_vec = boundary_faces - source_point r = np.linalg.norm(r_vec, axis=-1) r_hat = r_vec / r[:, None] r_dot_n = np.einsum("ij,ij->i", r_hat, boundary_normals) # determine faces that are on the sides and bottom of the mesh... if mesh._meshType.lower() == "tree": not_top = boundary_faces[:, -1] != top_v else: # mesh faces are ordered, faces_x, faces_y, faces_z so... if mesh.dim == 2: is_b = make_boundary_bool(mesh.shape_faces_y) is_t = np.zeros(mesh.shape_faces_y, dtype=bool, order="F") is_t[:, -1] = True else: is_b = make_boundary_bool(mesh.shape_faces_z) is_t = np.zeros(mesh.shape_faces_z, dtype=bool, order="F") is_t[:, :, -1] = True is_t = is_t.reshape(-1, order="F")[is_b] not_top = np.zeros(boundary_faces.shape[0], dtype=bool) not_top[-len(is_t):] = ~is_t alpha[not_top] = (r_dot_n / r)[not_top] B, bc = mesh.cell_gradient_weak_form_robin(alpha, beta, gamma) # bc should always be 0 because gamma was always 0 above self.Grad = self.Grad - B
def boundary_edges(self): """Boundary edge locations This property returns the locations of the edges on the boundary of the mesh as a numpy array. The shape of the numpy array is the number of boundary edges by the dimension of the mesh. Returns ------- (n_boundary_edges, dim) numpy.ndarray of float Boundary edge locations """ dim = self.dim if dim == 1: return None # no boundary edges in 1D if dim == 2: ex = ndgrid(self.cell_centers_x, self.nodes_y[[0, -1]]) ey = ndgrid(self.nodes_x[[0, -1]], self.cell_centers_y) return np.r_[ex, ey] if dim == 3: ex = self.edges_x[make_boundary_bool(self.shape_edges_x, dir="yz")] ey = self.edges_y[make_boundary_bool(self.shape_edges_y, dir="xz")] ez = self.edges_z[make_boundary_bool(self.shape_edges_z, dir="xy")] return np.r_[ex, ey, ez]
def boundary_faces(self): fx = self.faces_x[make_boundary_bool(self.shape_faces_x, dir="x")] fy = self.faces_y[make_boundary_bool(self.shape_faces_y, dir="y")] if self.dim == 2: return np.r_[fx, fy] elif self.dim == 3: fz = self.faces_z[make_boundary_bool(self.shape_faces_z, dir="z")] return np.r_[fx, fy, fz]
def boundary_edges(self): if self.dim == 2: ex = self.edges_x[make_boundary_bool(self.shape_edges_x, dir="y")] ey = self.edges_y[make_boundary_bool(self.shape_edges_y, dir="x")] return np.r_[ex, ey] elif self.dim == 3: ex = self.edges_x[make_boundary_bool(self.shape_edges_x, dir="yz")] ey = self.edges_y[make_boundary_bool(self.shape_edges_y, dir="xz")] ez = self.edges_z[make_boundary_bool(self.shape_edges_z, dir="xy")] return np.r_[ex, ey, ez]
def boundary_faces(self): """Gridded locations of non-hanging x-faces This property returns a numpy array of shape (n_faces_x, dim) containing gridded locations for all non-hanging x-faces. Returns ------- (n_faces_x, dim) numpy.ndarray of float Gridded locations of all non-hanging x-faces """ fx = self.faces_x[make_boundary_bool(self.shape_faces_x, dir="x")] fy = self.faces_y[make_boundary_bool(self.shape_faces_y, dir="y")] if self.dim == 2: return np.r_[fx, fy] elif self.dim == 3: fz = self.faces_z[make_boundary_bool(self.shape_faces_z, dir="z")] return np.r_[fx, fy, fz]
def setBC(self, ky=None): if self.bc_type == "Dirichlet": return if getattr(self, "_MBC", None) is None: self._MBC = {} if ky in self._MBC: # I have already created the BC matrix for this wavenumber return if self.bc_type == "Neumann": alpha, beta, gamma = 0, 1, 0 else: mesh = self.mesh boundary_faces = mesh.boundary_faces boundary_normals = mesh.boundary_face_outward_normals n_bf = len(boundary_faces) # Top gets 0 Neumann alpha = np.zeros(n_bf) beta = np.ones(n_bf) gamma = 0 # assume a source point at the middle of the top of the mesh middle = np.median(mesh.nodes, axis=0) top_v = np.max(mesh.nodes[:, -1]) source_point = np.r_[middle[:-1], top_v] r_vec = boundary_faces - source_point r = np.linalg.norm(r_vec, axis=-1) r_hat = r_vec / r[:, None] r_dot_n = np.einsum("ij,ij->i", r_hat, boundary_normals) # determine faces that are on the sides and bottom of the mesh... if mesh._meshType.lower() == "tree": not_top = boundary_faces[:, -1] != top_v else: # mesh faces are ordered, faces_x, faces_y, faces_z so... is_b = make_boundary_bool(mesh.shape_faces_y) is_t = np.zeros(mesh.shape_faces_y, dtype=bool, order="F") is_t[:, -1] = True is_t = is_t.reshape(-1, order="F")[is_b] not_top = np.zeros(boundary_faces.shape[0], dtype=bool) not_top[-len(is_t):] = ~is_t # use the exponentialy scaled modified bessel function of second kind, # (the division will cancel out the scaling) # This is more stable for large values of ky * r # actual ratio is k1/k0... alpha[not_top] = (ky * k1e(ky * r) / k0e(ky * r) * r_dot_n)[not_top] B, bc = self.mesh.cell_gradient_weak_form_robin(alpha, beta, gamma) # bc should always be 0 because gamma was always 0 above self._MBC[ky] = B
def boundary_edges(self): """Gridded boundary edge locations This property returns a numpy array of shape (n_boundary_edges, dim) containing the gridded locations of the edges on the boundary of the mesh. The returned quantity is organized *np.r_[edges_x, edges_y, edges_z]* . Returns ------- (n_boundary_edges, dim) numpy.ndarray of float Gridded boundary edge locations """ if self.dim == 2: ex = self.edges_x[make_boundary_bool(self.shape_edges_x, dir="y")] ey = self.edges_y[make_boundary_bool(self.shape_edges_y, dir="x")] return np.r_[ex, ey] elif self.dim == 3: ex = self.edges_x[make_boundary_bool(self.shape_edges_x, dir="yz")] ey = self.edges_y[make_boundary_bool(self.shape_edges_y, dir="xz")] ez = self.edges_z[make_boundary_bool(self.shape_edges_z, dir="xy")] return np.r_[ex, ey, ez]
def boundary_nodes(self): """Gridded boundary node locations This property returns a numpy array of shape (n_boundary_nodes, dim) containing the gridded locations of the nodes on the boundary of the mesh. Returns ------- (n_boundary_nodes, dim) numpy.ndarray of float Gridded boundary node locations """ return self.nodes[make_boundary_bool(self.shape_nodes)]
def boundary_face_outward_normals(self): is_bxm = np.zeros(self.shape_faces_x, order="F", dtype=bool) is_bxm[0, :] = True is_bxm = is_bxm.reshape(-1, order="F") is_bym = np.zeros(self.shape_faces_y, order="F", dtype=bool) is_bym[:, 0] = True is_bym = is_bym.reshape(-1, order="F") is_b = np.r_[ make_boundary_bool(self.shape_faces_x, dir="x"), make_boundary_bool(self.shape_faces_y, dir="y"), ] switch = np.r_[is_bxm, is_bym] if self.dim == 3: is_bzm = np.zeros(self.shape_faces_z, order="F", dtype=bool) is_bzm[:, :, 0] = True is_bzm = is_bzm.reshape(-1, order="F") is_b = np.r_[is_b, make_boundary_bool(self.shape_faces_z, dir="z")] switch = np.r_[switch, is_bzm] face_normals = self.face_normals.copy() face_normals[switch] *= -1 return face_normals[is_b]
def boundary_face_outward_normals(self): """Outward normals of boundary faces For all boundary faces in the mesh, this property returns the unit vectors denoting the outward normals to the boundary. The returned quantity is a numpy array of shape (n_boundary_faces, dim). Returns ------- (n_boundary_faces, dim) numpy.ndarray of float Outward normals of boundary faces """ is_bxm = np.zeros(self.shape_faces_x, order="F", dtype=bool) is_bxm[0, :] = True is_bxm = is_bxm.reshape(-1, order="F") is_bym = np.zeros(self.shape_faces_y, order="F", dtype=bool) is_bym[:, 0] = True is_bym = is_bym.reshape(-1, order="F") is_b = np.r_[ make_boundary_bool(self.shape_faces_x, dir="x"), make_boundary_bool(self.shape_faces_y, dir="y"), ] switch = np.r_[is_bxm, is_bym] if self.dim == 3: is_bzm = np.zeros(self.shape_faces_z, order="F", dtype=bool) is_bzm[:, :, 0] = True is_bzm = is_bzm.reshape(-1, order="F") is_b = np.r_[is_b, make_boundary_bool(self.shape_faces_z, dir="z")] switch = np.r_[switch, is_bzm] face_normals = self.face_normals.copy() face_normals[switch] *= -1 return face_normals[is_b]
def boundary_nodes(self): """Boundary node locations This property returns the locations of the nodes on the boundary of the mesh as a numpy array. The shape of the numpy array is the number of boundary nodes by the dimension of the mesh. Returns ------- (n_boundary_nodes, dim) numpy.ndarray of float Boundary node locations """ dim = self.dim if dim == 1: return self.nodes_x[[0, -1]] return self.nodes[make_boundary_bool(self.shape_nodes)]
def boundary_nodes(self): return self.nodes[make_boundary_bool(self.shape_nodes)]
def boundary_nodes(self): dim = self.dim if dim == 1: return self.nodes_x[[0, -1]] return self.nodes[make_boundary_bool(self.shape_nodes)]
def setBC(self, ky=None): if self.bc_type == "Dirichlet": # do nothing raise ValueError( "Dirichlet conditions are not supported in the Nodal formulation" ) elif self.bc_type == "Neumann": if self.verbose: print( "Homogeneous Neumann is the natural BC for this Nodal discretization." ) return else: if getattr(self, "_AvgBC", None) is None: self._AvgBC = {} if ky in self._AvgBC: return mesh = self.mesh # calculate alpha, beta, gamma at the boundary faces boundary_faces = mesh.boundary_faces boundary_normals = mesh.boundary_face_outward_normals n_bf = len(boundary_faces) alpha = np.zeros(n_bf) # assume a source point at the middle of the top of the mesh middle = np.median(mesh.nodes, axis=0) top_v = np.max(mesh.nodes[:, -1]) source_point = np.r_[middle[:-1], top_v] r_vec = boundary_faces - source_point r = np.linalg.norm(r_vec, axis=-1) r_hat = r_vec / r[:, None] r_dot_n = np.einsum("ij,ij->i", r_hat, boundary_normals) # determine faces that are on the sides and bottom of the mesh... if mesh._meshType.lower() == "tree": not_top = boundary_faces[:, -1] != top_v else: # mesh faces are ordered, faces_x, faces_y, faces_z so... is_b = make_boundary_bool(mesh.shape_faces_y) is_t = np.zeros(mesh.shape_faces_y, dtype=bool, order="F") is_t[:, -1] = True is_t = is_t.reshape(-1, order="F")[is_b] not_top = np.zeros(boundary_faces.shape[0], dtype=bool) not_top[-len(is_t):] = ~is_t # use the exponentiall scaled modified bessel function of second kind, # (the division will cancel out the scaling) # This is more stable for large values of ky * r # actual ratio is k1/k0... alpha[not_top] = (ky * k1e(ky * r) / k0e(ky * r) * r_dot_n)[not_top] P_bf = self.mesh.project_face_to_boundary_face AvgN2Fb = P_bf @ self.mesh.average_node_to_face AvgCC2Fb = P_bf @ self.mesh.average_cell_to_face AvgCC2Fb = sdiag(alpha * (P_bf @ self.mesh.face_areas)) @ AvgCC2Fb self._AvgBC[ky] = AvgN2Fb.T @ AvgCC2Fb
def setBC(self): if self.bc_type == "Dirichlet": # do nothing raise ValueError( "Dirichlet conditions are not supported in the Nodal formulation" ) elif self.bc_type == "Neumann": if self.verbose: print( "Homogeneous Neumann is the natural BC for this nodal discretization." ) return else: mesh = self.mesh # calculate alpha, beta, gamma at the boundary faces boundary_faces = mesh.boundary_faces boundary_normals = mesh.boundary_face_outward_normals n_bf = len(boundary_faces) # Top gets 0 Nuemann alpha = np.zeros(n_bf) # beta = np.ones(n_bf) = 1.0 # not top get Robin condition # assume a source point at the middle of the top of the mesh middle = np.median(mesh.nodes, axis=0) top_v = np.max(mesh.nodes[:, -1]) source_point = np.r_[middle[:-1], top_v] # Others: Robin: alpha * phi + d phi dn = 0 # where alpha = 1 / r * r_hat_dot_n # TODO: Implement Zhang et al. (1995) r_vec = boundary_faces - source_point r = np.linalg.norm(r_vec, axis=-1) r_hat = r_vec / r[:, None] r_dot_n = np.einsum("ij,ij->i", r_hat, boundary_normals) # determine faces that are on the sides and bottom of the mesh... if mesh._meshType.lower() == "tree": not_top = boundary_faces[:, -1] != top_v else: # mesh faces are ordered, faces_x, faces_y, faces_z so... if mesh.dim == 2: is_b = make_boundary_bool(mesh.shape_faces_y) is_t = np.zeros(mesh.shape_faces_y, dtype=bool, order="F") is_t[:, -1] = True else: is_b = make_boundary_bool(mesh.shape_faces_z) is_t = np.zeros(mesh.shape_faces_z, dtype=bool, order="F") is_t[:, :, -1] = True is_t = is_t.reshape(-1, order="F")[is_b] not_top = np.zeros(boundary_faces.shape[0], dtype=bool) not_top[-len(is_t):] = ~is_t alpha[not_top] = (r_dot_n / r)[not_top] P_bf = self.mesh.project_face_to_boundary_face AvgN2Fb = P_bf @ self.mesh.average_node_to_face AvgCC2Fb = P_bf @ self.mesh.average_cell_to_face AvgCC2Fb = sp.diags(alpha * (P_bf @ self.mesh.face_areas)) @ AvgCC2Fb self._AvgBC = AvgN2Fb.T @ AvgCC2Fb