def create_grid(**mesh_kwargs): """ Create a grid bucket containing grids from gmsh. NOTE: The line setting 'path_to_gmsh' *must* be modified for this to work. Parameters concerning mesh size, domain size etc. may also be changed, see below. Returns: grid_bucket: A grid_bucket containing the full hierarchy of grids. """ num_fracs = mesh_kwargs.get('num_fracs', 39) # If the # Don't change the path, or move the file data = _soultz_data() data = data[:num_fracs, :] # Data format of the data file is (frac_num, fracture center_xyz, major # axis, minor axis, dip direction, dip angle) centers = data[:, 1:4] major_axis = data[:, 4] minor_axis = data[:, 5] dip_direction = data[:, 6] / 180 * np.pi dip_angle = data[:, 7] / 180 * np.pi # We will define the fractures as elliptic fractures. For this we need # strike angle, rather than dip direction. strike_angle = dip_direction + np.pi / 2 # Modifications of the fracture definition: # These are carried out to ease the gridding; without these, we will end up # with gridding polygons that have very close points. The result may be # Minor axis angle. This is specified as zero (the fractures are # interpreted as circles), but we rotate them in an attempt to avoid close # points in the fracture specification. # Also note that since the fractures are defined as circles, any # orientation of the approximating polygon is equally correct major_axis_angle = np.zeros(num_fracs) # major_axis_angle[14] = 5 * np.pi / 180 ## major_axis_angle[24] = 5 * np.pi / 180 # major_axis_angle[26] = 5 * np.pi / 180 ## major_axis_angle[32-1] = 5 * np.pi / 180 # Also modify some centers. This may potentially have some impact on the # properties of the fracture network, but they been selected as to not # modify the fracture network properties. # centers[3, 2] += 30 if num_fracs > 10: centers[11, 2] += 15 # centers[8, 2] -= 10 # centers[19, 2] -= 20 # centers[22, 2] -= 10 # centers[23, 1:3] -= 15 # centers[24, 2] += 30 # centers[25, 2] += 10 # centers[29, 2] -= 30 # centers[30, 2] += 30 # centers[31, 2] += 30 # centers[34, 2] -= 10 # centers[38, 2] -= 10 # Create a set of fractures frac_list = [] num_points = mesh_kwargs.get('num_points', 16) for fi in range(data.shape[0]): frac_new = EllipticFracture(centers[fi], major_axis[fi], minor_axis[fi], major_axis_angle[fi], strike_angle[fi], dip_angle[fi], num_points=num_points) frac_list.append(frac_new) # Create the network, dump to vtu network = FractureNetwork(frac_list, verbose=1, tol=1e-4) network.to_vtk('soultz_fractures_full.vtu') # Impose domain boundaries. These are set large enough to not be in # conflict with the network. # This may be changed if desirable. domain = { 'xmin': -4000, 'xmax': 4000, 'ymin': -3000, 'ymax': 3000, 'zmin': 0, 'zmax': 8000 } domain = mesh_kwargs.get('domain', domain) network.impose_external_boundary(domain) # Find intersections, and split these network.find_intersections() network.split_intersections() # This may be changed, if desirable. if mesh_kwargs is None: mesh_size = {'mode': 'constant', 'value': 150, 'bound_value': 500} mesh_kwargs = {'mesh_size': mesh_size, 'file_name': 'soultz_fracs'} # Since we have a ready network (and may want to take this file into # jupyter and study the network before gridding), we abuse the workflow # slightly by calling simplex_tetrahedral directly, rather than to go the # way through meshing.simplex_grid (the latter is, for now, restricted to # specifying the grid by individual fractures, rather than networks). grids = simplex.tetrahedral_grid(network=network, **mesh_kwargs) # Convert the grids into a bucket meshing.tag_faces(grids) gb = meshing.assemble_in_bucket(grids) gb.compute_geometry() split_grid.split_fractures(gb) return gb
def simplex_grid(fracs=None, domain=None, network=None, subdomains=[], verbose=0, **kwargs): """ Main function for grid generation. Creates a fractured simiplex grid in 2 or 3 dimensions. NOTE: For some fracture networks, what appears to be a bug in Gmsh leads to surface grids with cells that does not have a corresponding face in the 3d grid. The problem may have been resolved (at least partly) by newer versions of Gmsh, but can still be an issue for our purposes. If this behavior is detected, an assertion error is raised. To avoid the issue, and go on with a surface mesh that likely is problematic, kwargs should contain a keyword ensure_matching_face_cell=False. Parameters ---------- fracs (list of np.ndarray): One list item for each fracture. Each item consist of a (nd x n) array describing fracture vertices. The fractures may be intersecting. domain (dict): Domain specification, determined by xmin, xmax, ... subdomains (list of np.ndarray or list of Fractures): One list item for each fracture, same format as fracs. Specifies internal boundaries for the gridding. Only available in 3D. **kwargs: May contain fracture tags, options for gridding, etc. Gridding options: The mesh parameters are: mesh_size_frac (double): Ideal mesh size. Will be added to all points that are sufficiently far away from other points. mesh_size_min (double): Minimal mesh size; we will make no attempts to enforce even smaller mesh sizes upon Gmsh. mesh_size_bound (double): Optional boundary mesh size, defaults to the value of mesh_size_frac. Will be added to the points defining the boundary. In 2d, this parameter dictates the size at the boundary corners. In 3d, it is assigned unless there are any fractures in the immediate vicinity influencing the size. In other words, mesh_size_bound is the boundary point equivalent of mesh_size_frac. TODO: Update 2d implementation to adhere to 3d in porepy.fracs.tools.determine_mesh_size. Returns ------- GridBucket: A complete bucket where all fractures are represented as lower dim grids. The higher dim fracture faces are split in two, and on the edges of the GridBucket graph the mapping from lower dim cells to higher dim faces are stored as 'face_cells'. Each face is given boolean tags depending on the type: domain_boundary_faces: All faces that lie on the domain boundary (i.e. should be given a boundary condition). fracture_faces: All faces that are split (i.e. has a connection to a lower dim grid). tip_faces: A boundary face that is not on the domain boundary, nor coupled to a lower domentional domain. The union of the above three is the tag boundary_faces. Examples -------- frac1 = np.array([[1,4],[1,4]]) frac2 = np.array([[1,4],[4,1]]) fracs = [frac1, frac2] domain = {'xmin': 0, 'ymin': 0, 'xmax':5, 'ymax':5} gb = simplex_grid(fracs, domain) """ if domain is None: if fracs is not None: ndim = fracs[0].shape[0] else: ndim = network[0].p.shape[0] elif 'zmax' in domain: ndim = 3 elif 'ymax' in domain: ndim = 2 else: raise ValueError('simplex_grid only supported for 2 or 3 dimensions') logger.info('Construct mesh') tm_tot = time.time() # Call relevant method, depending on grid dimensions. if ndim == 2: assert fracs is not None, '2d requires definition of fractures' assert domain is not None, '2d requires definition of domain' # Convert the fracture to a fracture dictionary. if len(fracs) == 0: f_lines = np.zeros((2, 0)) f_pts = np.zeros((2, 0)) else: f_lines = np.reshape(np.arange(2 * len(fracs)), (2, -1), order='F') f_pts = np.hstack(fracs) frac_dic = {'points': f_pts, 'edges': f_lines} grids = simplex.triangle_grid(frac_dic, domain, **kwargs) elif ndim == 3: grids = simplex.tetrahedral_grid( fracs, domain, network, subdomains, **kwargs) else: raise ValueError('Only support for 2 and 3 dimensions') return grid_list_to_grid_bucket(grids, time_tot=tm_tot, **kwargs)
def simplex_grid(fracs=None, domain=None, network=None, subdomains=[], verbose=0, **kwargs): """ Main function for grid generation. Creates a fractured simiplex grid in 2 or 3 dimensions. NOTE: For some fracture networks, what appears to be a bug in Gmsh leads to surface grids with cells that does not have a corresponding face in the 3d grid. The problem may have been resolved (at least partly) by newer versions of Gmsh, but can still be an issue for our purposes. If this behavior is detected, an assertion error is raised. To avoid the issue, and go on with a surface mesh that likely is problematic, kwargs should contain a keyword ensure_matching_face_cell=False. Parameters ---------- fracs (list of np.ndarray): One list item for each fracture. Each item consist of a (nd x n) array describing fracture vertices. The fractures may be intersecting. domain (dict): Domain specification, determined by xmin, xmax, ... subdomains (list of np.ndarray or list of Fractures): One list item for each fracture, same format as fracs. Specifies internal boundaries for the gridding. Only available in 3D. **kwargs: May contain fracture tags, options for gridding, etc. Returns ------- GridBucket: A complete bucket where all fractures are represented as lower dim grids. The higher dim fracture faces are split in two, and on the edges of the GridBucket graph the mapping from lower dim cells to higher dim faces are stored as 'face_cells'. Each face is given a FaceTag depending on the type: NONE: None of the below (i.e. an internal face) DOMAIN_BOUNDARY: All faces that lie on the domain boundary (i.e. should be given a boundary condition). FRACTURE: All faces that are split (i.e. has a connection to a lower dim grid). TIP: A boundary face that is not on the domain boundary, nor coupled to a lower domentional domain. Examples -------- frac1 = np.array([[1,4],[1,4]]) frac2 = np.array([[1,4],[4,1]]) fracs = [frac1, frac2] domain = {'xmin': 0, 'ymin': 0, 'xmax':5, 'ymax':5} gb = simplex_grid(fracs, domain) """ if domain is None: ndim = 3 elif 'zmax' in domain: ndim = 3 elif 'ymax' in domain: ndim = 2 else: raise ValueError('simplex_grid only supported for 2 or 3 dimensions') if verbose > 0: print('Construct mesh') tm_msh = time.time() tm_tot = time.time() # Call relevant method, depending on grid dimensions. if ndim == 2: assert fracs is not None, '2d requires definition of fractures' assert domain is not None, '2d requires definition of domain' # Convert the fracture to a fracture dictionary. if len(fracs) == 0: f_lines = np.zeros((2, 0)) f_pts = np.zeros((2, 0)) else: f_lines = np.reshape(np.arange(2 * len(fracs)), (2, -1), order='F') f_pts = np.hstack(fracs) frac_dic = {'points': f_pts, 'edges': f_lines} grids = simplex.triangle_grid(frac_dic, domain, **kwargs) elif ndim == 3: grids = simplex.tetrahedral_grid(fracs, domain, network, subdomains, **kwargs) else: raise ValueError('Only support for 2 and 3 dimensions') if verbose > 0: print('Done. Elapsed time ' + str(time.time() - tm_msh)) # Tag tip faces tag_faces(grids) # Assemble grids in a bucket if verbose > 0: print('Assemble in bucket') tm_bucket = time.time() gb = assemble_in_bucket(grids, **kwargs) if verbose > 0: print('Done. Elapsed time ' + str(time.time() - tm_bucket)) print('Compute geometry') tm_geom = time.time() gb.compute_geometry() # Split the grids. if verbose > 0: print('Done. Elapsed time ' + str(time.time() - tm_geom)) print('Split fractures') tm_split = time.time() split_grid.split_fractures(gb, **kwargs) if verbose > 0: print('Done. Elapsed time ' + str(time.time() - tm_split)) gb.assign_node_ordering() if verbose > 0: print('Mesh construction completed. Total time ' + str(time.time() - tm_tot)) return gb