コード例 #1
0
def tetrahedral_grid(fracs=None,
                     box=None,
                     network=None,
                     subdomains=[],
                     **kwargs):
    """
    Create grids for a domain with possibly intersecting fractures in 3d.
    The function can be call through the wrapper function meshing.simplex_grid.

    Based on the specified fractures, the method computes fracture
    intersections if necessary, creates a gmsh input file, runs gmsh and reads
    the result, and then constructs grids in 3d (the whole domain), 2d (one for
    each individual fracture), 1d (along fracture intersections), and 0d
    (meeting between intersections).

    The fractures can be specified is terms of the keyword 'fracs' (either as
    numpy arrays or Fractures, see below), or as a ready-made FractureNetwork
    by the keyword 'network'. For fracs, the boundary of the domain must be
    specified as well, by 'box'. For a ready network, the boundary will be
    imposed if provided. For a network will use pre-computed intersection and
    decomposition if these are available (attributes 'intersections' and
    'decomposition').

    Parameters
    ----------
    fracs (list, optional): List of either pre-defined fractures, or
        np.ndarrays, (each 3xn) of fracture vertexes.
    box (dictionary, optional). Domain specification. Should have keywords
        xmin, xmax, ymin, ymax, zmin, zmax.
    network (fractures.FractureNetwork, optional): A FractureNetwork
        containing fractures.
    subdomain (list, optional): List of planes partitioning the 3d domain
        into subdomains. The planes are defined in the same way as fracs.

    The fractures should be specified either by a combination of fracs and
    box, or by network (possibly combined with box). See above.

    **kwargs: To be explored.

    Returns
    -------
    list (length 4): For each dimension (3 -> 0), a list of all grids in
        that dimension.

    Examples
    --------
    frac1 = np.array([[1,1,4,4], [1,4,4,1], [2,2,2,2]])
    frac2 = np.array([[2,2,2,2], [1,1,4,4], [1,4,4,1]])
    fracs = [frac1, frac2]
    domain = {'xmin': 0, 'ymin': 0, 'zmin': 0,
              'xmax': 5, 'ymax': 5, 'zmax': 5,}
    path_to_gmsh = .... # Set the sytem path to gmsh
    gb = tetrahedral_grid(fracs, domain, gmsh_path = path_to_gmsh)
    """

    # Verbosity level
    verbose = kwargs.get("verbose", 0)

    # File name for communication with gmsh
    file_name = kwargs.pop("file_name", "gmsh_frac_file")

    if network is None:

        frac_list = []
        if fracs is not None:
            for f in fracs:
                # Input can be either numpy arrays or predifined fractures. As a
                # guide, we treat f as a fracture if it has an attribute p which is
                # a numpy array.
                # If f turns out not to be a fracture, strange errors will result
                # as the further program tries to access non-existing methods.
                # The correct treatment here would be several
                # isinstance-statements, but that became less than elegant. To
                # revisit.
                if hasattr(f, "p") and isinstance(f.p, np.ndarray):
                    frac_list.append(f)
                else:
                    # Convert the fractures from numpy representation to our 3D
                    # fracture data structure.
                    frac_list.append(fractures.Fracture(f))

        # Combine the fractures into a network
        network = fractures.FractureNetwork(frac_list,
                                            verbose=verbose,
                                            tol=kwargs.get("tol", 1e-4))
    # Add any subdomain boundaries:
    network.add_subdomain_boundaries(subdomains)
    # Impose external boundary. If box is None, a domain size somewhat larger
    # than the network will be assigned.
    if box is not None or not network.bounding_box_imposed:
        network.impose_external_boundary(box)
    # Find intersections and split them, preparing the way for dumping the
    # network to gmsh
    if not network.has_checked_intersections:
        network.find_intersections()
    else:
        logger.info("Use existing intersections")

    # If fields mesh_size_frac and mesh_size_min are provided, try to estimate mesh sizes.
    mesh_size_frac = kwargs.get("mesh_size_frac", None)
    mesh_size_min = kwargs.get("mesh_size_min", None)
    mesh_size_bound = kwargs.get("mesh_size_bound", None)
    if mesh_size_frac is not None and mesh_size_min is not None:
        if mesh_size_bound is None:
            mesh_size_bound = mesh_size_frac
        network.insert_auxiliary_points(mesh_size_frac, mesh_size_min,
                                        mesh_size_bound)
        # In this case we need to recompute intersection decomposition anyhow.
        network.split_intersections()

    if not hasattr(network, "decomposition"):
        network.split_intersections()
    else:
        logger.info("Use existing decomposition")

    in_file = file_name + ".geo"
    out_file = file_name + ".msh"

    network.to_gmsh(in_file, **kwargs)

    gmsh_opts = kwargs.get("gmsh_opts", {})
    gmsh_verbose = kwargs.get("gmsh_verbose", verbose)
    gmsh_opts["-v"] = gmsh_verbose
    gmsh_status = gmsh_interface.run_gmsh(in_file,
                                          out_file,
                                          dims=3,
                                          **gmsh_opts)

    return tetrahedral_grid_from_gmsh(file_name, network, **kwargs)
コード例 #2
0
ファイル: structured.py プロジェクト: zabonah/porepy
def cart_grid_3d(fracs, nx, physdims=None):
    """
    Create grids for a domain with possibly intersecting fractures in 3d.

    Based on rectangles describing the individual fractures, the method
    constructs grids in 3d (the whole domain), 2d (one for each individual
    fracture), 1d (along fracture intersections), and 0d (meeting between
    intersections).

    Parameters
    ----------
    fracs (list of np.ndarray, each 3x4): Vertexes in the rectangle for each
        fracture. The vertices must be sorted and aligned to the axis.
        The fractures will snap to the closest grid faces.
    nx (np.ndarray): Number of cells in each direction. Should be 3D.
    physdims (np.ndarray): Physical dimensions in each direction.
        Defaults to same as nx, that is, cells of unit size.

    Returns
    -------
    list (length 4): For each dimension (3 -> 0), a list of all grids in
        that dimension.

    Examples
    --------
    frac1 = np.array([[1,1,4,4], [1,4,4,1], [2,2,2,2]])
    frac2 = np.array([[2,2,2,2], [1,1,4,4], [1,4,4,1]])
    fracs = [frac1, frac2]
    gb = cart_grid_3d(fracs, [5,5,5])
    """

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

    # We create a 3D cartesian grid. The global node mapping is trivial.
    g_3d = structured.CartGrid(nx, physdims=physdims)
    g_3d.global_point_ind = np.arange(g_3d.num_nodes)
    g_3d.compute_geometry()
    g_2d = []
    g_1d = []
    g_0d = []
    # We set the tolerance for finding points in a plane. This can be any
    # small number, that is smaller than .25 of the cell sizes.
    tol = 0.1 * physdims / nx

    # Create 2D grids
    for fi, f in enumerate(fracs):
        assert np.all(f.shape == (3, 4)), "fractures must have shape [3,4]"
        is_xy_frac = np.allclose(f[2, 0], f[2])
        is_xz_frac = np.allclose(f[1, 0], f[1])
        is_yz_frac = np.allclose(f[0, 0], f[0])
        assert (is_xy_frac + is_xz_frac +
                is_yz_frac == 1), "Fracture must align to x-, y- or z-axis"
        # snap to grid
        f_s = (np.round(f * nx[:, np.newaxis] / physdims[:, np.newaxis]) *
               physdims[:, np.newaxis] / nx[:, np.newaxis])
        if is_xy_frac:
            flat_dim = [2]
            active_dim = [0, 1]
        elif is_xz_frac:
            flat_dim = [1]
            active_dim = [0, 2]
        else:
            flat_dim = [0]
            active_dim = [1, 2]
        # construct normal vectors. If the rectangle is ordered
        # clockwise we need to flip the normals so they point
        # outwards.
        sign = 2 * pp.geometry_property_checks.is_ccw_polygon(
            f_s[active_dim]) - 1
        tangent = f_s.take(np.arange(f_s.shape[1]) + 1, axis=1,
                           mode="wrap") - f_s
        normal = tangent
        normal[active_dim] = tangent[active_dim[1::-1]]
        normal[active_dim[1]] = -normal[active_dim[1]]
        normal = sign * normal
        # We find all the faces inside the convex hull defined by the
        # rectangle. To find the faces on the fracture plane, we remove any
        # faces that are further than tol from the snapped fracture plane.
        in_hull = half_space.half_space_int(normal, f_s, g_3d.face_centers)
        f_tag = np.logical_and(
            in_hull,
            np.logical_and(
                f_s[flat_dim, 0] - tol[flat_dim] <=
                g_3d.face_centers[flat_dim],
                g_3d.face_centers[flat_dim] < f_s[flat_dim, 0] + tol[flat_dim],
            ),
        )
        f_tag = f_tag.ravel()
        nodes = sps.find(g_3d.face_nodes[:, f_tag])[0]
        nodes = np.unique(nodes)
        loc_coord = g_3d.nodes[:, nodes]
        g = _create_embedded_2d_grid(loc_coord, nodes)

        g.frac_num = fi
        g_2d.append(g)

    # Create 1D grids:
    # Here we make use of the network class to find the intersection of
    # fracture planes. We could maybe avoid this by doing something similar
    # as for the 2D-case, and count the number of faces belonging to each edge,
    # but we use the FractureNetwork class for now.
    frac_list = []
    for f in fracs:
        frac_list.append(fractures.Fracture(f))
    # Combine the fractures into a network
    network = pp.FractureNetwork3d(frac_list)
    # Impose domain boundary. For the moment, the network should be immersed in
    # the domain, or else gmsh will complain.
    box = {
        "xmin": 0,
        "ymin": 0,
        "zmin": 0,
        "xmax": physdims[0],
        "ymax": physdims[1],
        "zmax": physdims[2],
    }
    network.impose_external_boundary(box)

    # Find intersections and split them.
    network.find_intersections()
    network.split_intersections()

    # Extract geometrical network information.
    pts = network.decomposition["points"]
    edges = network.decomposition["edges"]
    poly = network._poly_2_segment()
    # And tags identifying points and edges corresponding to normal
    # fractures, domain boundaries and subdomain boundaries. Only the
    # entities corresponding to normal fractures should actually be gridded.
    edge_tags, intersection_points = network._classify_edges(poly)
    const = constants.GmshConstants()
    auxiliary_points, edge_tags = network.on_domain_boundary(edges, edge_tags)
    bound_and_aux = np.array([const.DOMAIN_BOUNDARY_TAG, const.AUXILIARY_TAG])
    edges = np.vstack((edges, edge_tags))

    # Loop through the edges to make 1D grids. Ommit the auxiliary edges.
    for e in np.ravel(
            np.where(edges[2] == const.FRACTURE_INTERSECTION_LINE_TAG)):
        # We find the start and end point of each fracture intersection (1D
        # grid) and then the corresponding global node index.
        if np.isin(edge_tags[e], bound_and_aux):
            continue
        s_pt = pts[:, edges[0, e]]
        e_pt = pts[:, edges[1, e]]
        nodes = _find_nodes_on_line(g_3d, nx, s_pt, e_pt)
        loc_coord = g_3d.nodes[:, nodes]
        assert (loc_coord.shape[1] > 1), "1d grid in intersection should span\
            more than one node"

        g = mesh_2_grid.create_embedded_line_grid(loc_coord, nodes)
        g_1d.append(g)

    # Create 0D grids
    # Here we also use the intersection information from the FractureNetwork
    # class. No grids for auxiliary points.
    for p in intersection_points:
        if auxiliary_points[p]:
            continue
        node = np.argmin(pp.distances.point_pointset(pts[:, p], g_3d.nodes))
        assert np.allclose(g_3d.nodes[:, node], pts[:, p])
        g = point_grid.PointGrid(g_3d.nodes[:, node])
        g.global_point_ind = np.asarray(node)
        g_0d.append(g)

    grids = [[g_3d], g_2d, g_1d, g_0d]
    return grids
コード例 #3
0
def tetrahedral_grid(fracs=None,
                     box=None,
                     network=None,
                     subdomains=[],
                     **kwargs):
    """
    Create grids for a domain with possibly intersecting fractures in 3d.
    The function can be call through the wrapper function meshing.simplex_grid.

    Based on the specified fractures, the method computes fracture
    intersections if necessary, creates a gmsh input file, runs gmsh and reads
    the result, and then constructs grids in 3d (the whole domain), 2d (one for
    each individual fracture), 1d (along fracture intersections), and 0d
    (meeting between intersections).

    The fractures can be specified is terms of the keyword 'fracs' (either as
    numpy arrays or Fractures, see below), or as a ready-made FractureNetwork
    by the keyword 'network'. For fracs, the boundary of the domain must be
    specified as well, by 'box'. For a ready network, the boundary will be
    imposed if provided. For a network will use pre-computed intersection and
    decomposition if these are available (attributes 'intersections' and
    'decomposition').

    Parameters
    ----------
    fracs (list, optional): List of either pre-defined fractures, or
        np.ndarrays, (each 3xn) of fracture vertexes.
    box (dictionary, optional). Domain specification. Should have keywords
        xmin, xmax, ymin, ymax, zmin, zmax.
    network (fractures.FractureNetwork, optional): A FractureNetwork
        containing fractures.
    subdomain (list, optional): List of planes partitioning the 3d domain
        into subdomains. The planes are defined in the same way as fracs.

    The fractures should be specified either by a combination of fracs and
    box, or by network (possibly combined with box). See above.

    **kwargs: To be explored.

    Returns
    -------
    list (length 4): For each dimension (3 -> 0), a list of all grids in
        that dimension.

    Examples
    --------
    frac1 = np.array([[1,1,4,4], [1,4,4,1], [2,2,2,2]])
    frac2 = np.array([[2,2,2,2], [1,1,4,4], [1,4,4,1]])
    fracs = [frac1, frac2]
    domain = {'xmin': 0, 'ymin': 0, 'zmin': 0,
              'xmax': 5, 'ymax': 5, 'zmax': 5,}
    path_to_gmsh = .... # Set the sytem path to gmsh
    gb = tetrahedral_grid(fracs, domain, gmsh_path = path_to_gmsh)
    """

    # Verbosity level
    verbose = kwargs.get('verbose', 1)

    # File name for communication with gmsh
    file_name = kwargs.pop('file_name', 'gmsh_frac_file')

    if network is None:

        frac_list = []
        for f in fracs:
            # Input can be either numpy arrays or predifined fractures. As a
            # guide, we treat f as a fracture if it has an attribute p which is
            # a numpy array.
            # If f turns out not to be a fracture, strange errors will result
            # as the further program tries to access non-existing methods.
            # The correct treatment here would be several
            # isinstance-statements, but that became less than elegant. To
            # revisit.
            if hasattr(f, 'p') and isinstance(f.p, np.ndarray):
                frac_list.append(f)
            else:
                # Convert the fractures from numpy representation to our 3D
                # fracture data structure.
                frac_list.append(fractures.Fracture(f))

        # Combine the fractures into a network
        network = fractures.FractureNetwork(frac_list,
                                            verbose=verbose,
                                            tol=kwargs.get('tol', 1e-4))
    # Add any subdomain boundaries:
    network.add_subdomain_boundaries(subdomains)
    # Impose external boundary. If box is None, a domain size somewhat larger
    # than the network will be assigned.
    network.impose_external_boundary(box)
    # Find intersections and split them, preparing the way for dumping the
    # network to gmsh
    if not network.has_checked_intersections:
        network.find_intersections()
    else:
        print('Use existing intersections')

    start_time = time.time()

    # If fields h_ideal and h_min are provided, try to estimate mesh sizes.
    h_ideal = kwargs.get('h_ideal', None)
    h_min = kwargs.get('h_min', None)
    if h_ideal is not None and h_min is not None:
        network.insert_auxiliary_points(h_ideal, h_min)
        # In this case we need to recompute intersection decomposition anyhow.
        network.split_intersections()

    if not hasattr(network, 'decomposition'):
        network.split_intersections()
    else:
        print('Use existing decomposition')

    in_file = file_name + '.geo'
    out_file = file_name + '.msh'

    network.to_gmsh(in_file, **kwargs)

    gmsh_opts = kwargs.get('gmsh_opts', {})
    gmsh_verbose = kwargs.get('gmsh_verbose', verbose)
    gmsh_opts['-v'] = gmsh_verbose
    gmsh_status = gmsh_interface.run_gmsh(in_file,
                                          out_file,
                                          dims=3,
                                          **gmsh_opts)

    if verbose > 0:
        start_time = time.time()
        if gmsh_status == 0:
            print('Gmsh processed file successfully')
        else:
            print('Gmsh failed with status ' + str(gmsh_status))

    pts, cells, _, cell_info, phys_names = gmsh_io.read(out_file)

    # Invert phys_names dictionary to map from physical tags to corresponding
    # physical names
    phys_names = {v[0]: k for k, v in phys_names.items()}

    # Call upon helper functions to create grids in various dimensions.
    # The constructors require somewhat different information, reflecting the
    # different nature of the grids.
    g_3d = mesh_2_grid.create_3d_grids(pts, cells)
    g_2d = mesh_2_grid.create_2d_grids(pts,
                                       cells,
                                       is_embedded=True,
                                       phys_names=phys_names,
                                       cell_info=cell_info,
                                       network=network)
    g_1d, _ = mesh_2_grid.create_1d_grids(pts, cells, phys_names, cell_info)
    g_0d = mesh_2_grid.create_0d_grids(pts, cells)

    grids = [g_3d, g_2d, g_1d, g_0d]

    if verbose > 0:
        print('\n')
        print('Grid creation completed. Elapsed time ' +
              str(time.time() - start_time))
        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')

    return grids