Example #1
0
def grid_list_to_grid_bucket(grids: List[List[pp.Grid]],
                             time_tot: float = None,
                             **kwargs) -> pp.GridBucket:
    """Convert a list of grids to a full GridBucket.

    The list can come from several mesh constructors, both simplex and
    structured approaches uses this in 2D and 3D.

    The function can not be used on an arbitrary set of grids; they should
    contain information to glue grids together. This will be included for grids
    created by the standard mixed-dimensional grid constructors. In other
    words: Do *not* use this function directly unless you know what you are
    doing.

    Parameters:
        grids (list of lists of grids): Grids to enter into the bucket.
            Sorted per dimension.
        time_tot (double, optional): Start time for full mesh construction.
            Used for logging. Defaults to None, in which case no information
            on total time consumption is logged.
        **kwargs: Passed on to subfunctions.

    Returns:
        GridBucket: Final mixed-dimensional grid.

    """
    # Tag tip faces
    check_highest_dim = kwargs.get("check_highest_dim", False)
    _tag_faces(grids, check_highest_dim)

    logger.info("Assemble in bucket")
    tm_bucket = time.time()
    gb = _assemble_in_bucket(grids)
    logger.info("Done. Elapsed time " + str(time.time() - tm_bucket))

    logger.info("Compute geometry")
    tm_geom = time.time()
    gb.compute_geometry()
    # Split the grids.
    logger.info("Done. Elapsed time " + str(time.time() - tm_geom))
    logger.info("Split fractures")
    tm_split = time.time()
    split_grid.split_fractures(gb, **kwargs)
    logger.info("Done. Elapsed time " + str(time.time() - tm_split))

    create_mortar_grids(gb, **kwargs)

    gb.assign_node_ordering()

    if time_tot is not None:
        logger.info("Mesh construction completed. Total time " +
                    str(time.time() - time_tot))

    return gb
Example #2
0
def from_gmsh(file_name, dim, **kwargs):
    """
    Import an already generated grid from gmsh.
    NOTE: Only 2d grid is implemented so far.

    Parameters
    ----------
    file_name (string): Gmsh file name.
    dim (int): Spatial dimension of the grid.
    **kwargs: May contain fracture tags, options for gridding, etc.

    Returns
    -------
    Grid or GridBucket: If no fractures are present in the gmsh file a simple
        grid is returned. Otherwise, a complete bucket where all fractures are
        represented as lower dim grids. See the documentation of simplex_grid
        for further details.

    Examples
    --------
    gb = from_gmsh('grid.geo', 2)

    """
    # Call relevant method, depending on grid dimensions.
    if dim == 2:
        if file_name.endswith('.geo'):
            simplex.triangle_grid_run_gmsh(file_name, **kwargs)
            grids = simplex.triangle_grid_from_gmsh(file_name, **kwargs)
        elif file_name.endswith('.msh'):
            grids = simplex.triangle_grid_from_gmsh(file_name, **kwargs)

#    elif dim == 3:
#        grids = simplex.tetrahedral_grid_from_gmsh(file_name, **kwargs)
#   NOTE: function simplex.tetrahedral_grid needs to be split as did for
#   simplex.triangle_grid
    else:
        raise ValueError('Only support for 2 dimensions')

    # No fractures are specified, return a simple grid
    if len(grids[1]) == 0:
        grids[0][0].compute_geometry()
        return grids[0][0]

    # Tag tip faces
    tag_faces(grids)

    # Assemble grids in a bucket
    gb = assemble_in_bucket(grids)
    gb.compute_geometry()
    # Split the grids.
    split_grid.split_fractures(gb)
    return gb
def create(mesh_size=0.01):
    fn = "test_grid"
    grid_to_gmsh.write_geo(fn + ".geo", mesh_size=mesh_size)

    simplex.triangle_grid_run_gmsh(fn)

    grids = simplex.triangle_grid_from_gmsh(fn)

    meshing.tag_faces(grids)

    gb = meshing.assemble_in_bucket(grids)
    gb.compute_geometry()

    split_grid.split_fractures(gb)
    gb.assign_node_ordering()

    return gb
Example #4
0
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
Example #5
0
def read_dfn_grid(folder, num_fractures, case_id, **kwargs):

    # TODO: tag tip faces

    offset_name = kwargs.get('offset_name', 1)
    folder += "/"
    g_2d = np.empty(num_fractures, dtype=np.object)
    gb = grid_bucket.GridBucket()

    global_node_id = 0
    for f_id in np.arange(num_fractures):
        post = "_F" + str(f_id + offset_name) + "_" + str(case_id) + ".txt"
        nodes_2d, face_nodes_2d, cell_faces_2d = _dfn_grid_2d(
            folder, post, **kwargs)
        g_2d[f_id] = grid.Grid(2, nodes_2d, face_nodes_2d, cell_faces_2d,
                               "fracture_" + str(f_id) + "_" + str(case_id))

        bnd_faces = g_2d[f_id].get_all_boundary_faces()
        g_2d[f_id].tags['domain_boundary_faces'][bnd_faces] = True

        g_2d[f_id].global_point_ind = np.arange(g_2d[f_id].num_nodes) + \
            global_node_id
        global_node_id += g_2d[f_id].num_nodes

    gb.add_nodes(g_2d)

    for f_id in np.arange(num_fractures):
        post = "_F" + str(f_id + offset_name) + "_" + str(case_id) + ".txt"

        face_name = kwargs.get("face_name", "Faces")
        face_file_name = folder + face_name + post

        with open(face_file_name, 'r') as f:
            skip_lines = int(f.readline().split()[0]) + 1
            lines = np.array(f.readlines()[skip_lines:])
            conn = np.atleast_2d(
                [np.fromstring(l, dtype=np.int, sep=' ') for l in lines])
            # Consider only the new intersections
            conn = conn[conn[:, 2] > f_id, :]

            for g_id in np.unique(conn[:, 2]):
                other_f_id = g_id - 1
                mask = conn[:, 2] == g_id

                nodes_id = _nodes_faces_2d(g_2d[f_id], conn[mask, 0])
                nodes_1d, face_nodes_1d, cell_faces_1d = _dfn_grid_1d(
                    g_2d[f_id], nodes_id)
                g_1d = grid.Grid(
                    1, nodes_1d, face_nodes_1d, cell_faces_1d,
                    "intersection_" + str(f_id) + "_" + str(g_id - 1) + "_" +
                    str(case_id))

                global_point_ind = g_2d[f_id].global_point_ind[nodes_id]
                g_1d.global_point_ind = global_point_ind

                nodes_id = _nodes_faces_2d(g_2d[other_f_id], conn[mask, 1])
                for g, _ in gb:  # TODO: better access
                    if g is g_2d[other_f_id]:
                        g.global_point_ind[nodes_id] = global_point_ind
                        break

                gb.add_nodes(g_1d)

                shape = (g_1d.num_cells, g_2d[f_id].num_faces)
                data = np.ones(g_1d.num_cells, dtype=np.bool)
                face_cells = sps.csc_matrix(
                    (data, (np.arange(g_1d.num_cells), conn[mask, 0])), shape)
                gb.add_edge([g_2d[f_id], g_1d], face_cells)

                shape = (g_1d.num_cells, g_2d[other_f_id].num_faces)
                face_cells = sps.csc_matrix(
                    (data, (np.arange(g_1d.num_cells), conn[mask, 1])), shape)
                gb.add_edge([g_2d[other_f_id], g_1d], face_cells)

    gb.compute_geometry()
    # Split the grids.
    split_grid.split_fractures(gb, offset=0.1)
    return gb
Example #6
0
def cart_grid(fracs, nx, **kwargs):
    """
    Creates a cartesian fractured GridBucket in 2- or 3-dimensions.

    Parameters
    ----------
    fracs (list of np.ndarray): One list item for each fracture. Each item
        consist of a (nd x 3) array describing fracture vertices. The
        fractures has to be rectangles(3D) or straight lines(2D) that
        alignes with the axis. The fractures may be intersecting.
        The fractures will snap to closest grid faces.
    nx (np.ndarray): Number of cells in each direction. Should be 2D or 3D
    **kwargs:
        physdims (np.ndarray): Physical dimensions in each direction.
            Defaults to same as nx, that is, cells of unit size.
        May also 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],[2,2]])
    frac2 = np.array([[2,2],[4,1]])
    fracs = [frac1, frac2]
    gb = cart_grid(fracs, [5,5])
    """
    ndim = np.asarray(nx).size
    physdims = kwargs.get('physdims', None)

    if physdims is None:
        physdims = nx
    elif np.asarray(physdims).size != ndim:
        raise ValueError('Physical dimension must equal grid dimension')

    # Call relevant method, depending on grid dimensions
    if ndim == 2:
        grids = structured.cart_grid_2d(fracs, nx, physdims=physdims)
    elif ndim == 3:
        grids = structured.cart_grid_3d(fracs, nx, physdims=physdims)
    else:
        raise ValueError('Only support for 2 and 3 dimensions')

    # Tag tip faces.
    tag_faces(grids)

    # Asemble in bucket
    gb = assemble_in_bucket(grids)
    gb.compute_geometry()

    # Split grid.
    split_grid.split_fractures(gb, **kwargs)
    gb.assign_node_ordering()
    return gb
Example #7
0
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
Example #8
0
def dfn(fracs,
        conforming,
        intersections=None,
        keep_geo=False,
        tol=1e-4,
        **kwargs):
    """ Create a mesh of a DFN model, that is, only of fractures.

    The mesh can eihter be conforming along fracture intersections, or each
    fracture is meshed independently. The latter case will typically require
    some sort of sewing together external to this funciton.

    TODO: What happens if we give in a non-connected network?

    Parameters:
        fracs (either Fractures, or a FractureNetwork).
        conforming (boolean): If True, the mesh will be conforming along 1d
            intersections.
        intersections (list of lists, optional): Each item corresponds to an
            intersection between two fractures. In each sublist, the first two
            indices gives fracture ids (refering to order in fracs). The third
            item is a numpy array representing intersection coordinates. If no
            intersections provided, intersections will be detected using
            function in FractureNetwork.
        **kwargs: Parameters passed to gmsh.

    Returns:
        GridBucket (if conforming is True): Mixed-dimensional mesh that
            represents all fractures, and intersection poitns and line.

    """

    if isinstance(fracs, FractureNetwork) \
       or isinstance(fracs, FractureNetwork_full):
        network = fracs
    else:
        network = FractureNetwork(fracs)

    # Populate intersections in FractureNetowrk, or find intersections if not
    # provided.

    if intersections is not None:
        logger.warn('FractureNetwork use pre-computed intersections')
        network.intersections = [Intersection(*i) for i in intersections]
    else:
        logger.warn('FractureNetwork find intersections in DFN')
        tic = time.time()
        network.find_intersections()
        logger.warn('Done. Elapsed time ' + str(time.time() - tic))

    if conforming:
        logger.warn('Create conforming mesh for DFN network')
        grids = simplex.triangle_grid_embedded(network,
                                               find_isect=False,
                                               **kwargs)
    else:
        logger.warn('Create non-conforming mesh for DFN network')
        tic = time.time()
        grid_list = []
        neigh_list = []

        for fi in range(len(network._fractures)):
            logger.info('Meshing of fracture ' + str(fi))
            # Rotate fracture vertexes and intersection points
            fp, ip, other_frac, rot, cp = network.fracture_to_plane(fi)
            frac_i = network[fi]

            f_lines = np.reshape(np.arange(ip.shape[1]), (2, -1), order='F')
            frac_dict = {'points': ip, 'edges': f_lines}
            if keep_geo:
                file_name = 'frac_mesh_' + str(fi)
                kwargs['file_name'] = file_name
            # Create mesh on this fracture surface.
            grids = simplex.triangle_grid(frac_dict,
                                          fp,
                                          verbose=False,
                                          **kwargs)

            irot = rot.T
            # Loop over grids, rotate back again to 3d coordinates
            for gl in grids:
                for g in gl:
                    g.nodes = irot.dot(g.nodes) + cp

            # Nodes of main (fracture) grid, in 3d coordinates1
            main_nodes = grids[0][0].nodes
            main_global_point_ind = grids[0][0].global_point_ind
            # Loop over intersections, check if the intersection is on the
            # boundary of this fracture.
            for ind, isect in enumerate(network.intersections_of_fracture(fi)):
                of = isect.get_other_fracture(frac_i)
                if isect.on_boundary_of_fracture(frac_i):
                    dist, _, _ = cg.dist_points_polygon(main_nodes, of.p)
                    hit = np.argwhere(dist < tol).reshape((1, -1))[0]
                    nodes_1d = main_nodes[:, hit]
                    global_point_ind = main_global_point_ind[hit]

                    assert cg.is_collinear(nodes_1d, tol=tol)
                    sort_ind = cg.argsort_point_on_line(nodes_1d, tol=tol)
                    g_aux = TensorGrid(np.arange(nodes_1d.shape[1]))
                    g_aux.nodes = nodes_1d[:, sort_ind]
                    g_aux.global_point_ind = global_point_ind[sort_ind]
                    grids[1].insert(ind, g_aux)


            assert len(grids[0]) == 1, 'Fracture should be covered by single'\
                'mesh'

            grid_list.append(grids)
            neigh_list.append(other_frac)

        logger.warn('Finished creating grids. Elapsed time ' +
                    str(time.time() - tic))
        logger.warn('Merge grids')
        tic = time.time()
        grids = non_conforming.merge_grids(grid_list, neigh_list)
        logger.warn('Done. Elapsed time ' + str(time.time() - tic))

        print('\n')
        for g_set in grids:
            if len(g_set) > 0:
                s = 'Created ' + str(len(g_set)) + ' ' + str(g_set[0].dim) + \
                    '-d grids with '
                num = 0
                for g in g_set:
                    num += g.num_cells
                s += str(num) + ' cells'
                print(s)
        print('\n')

    tag_faces(grids, check_highest_dim=False)
    logger.warn('Assemble in bucket')
    tic = time.time()
    gb = assemble_in_bucket(grids)
    logger.warn('Done. Elapsed time ' + str(time.time() - tic))
    logger.warn('Compute geometry')
    tic = time.time()
    gb.compute_geometry()
    logger.warn('Done. Elapsed time ' + str(time.time() - tic))
    logger.warn('Split fractures')
    tic = time.time()
    split_grid.split_fractures(gb)
    logger.warn('Done. Elapsed time ' + str(time.time() - tic))
    return gb