Example #1
0
def triangle_grid_from_gmsh(file_name, **kwargs):

    start_time = time.time()

    if file_name.endswith(".msh"):
        file_name = file_name[:-4]
    out_file = file_name + ".msh"

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

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

    # Invert phys_names dictionary to map from physical tags to corresponding
    # physical names.
    # As of meshio 1.10, the value of the physical name is defined as a numpy
    # array, with the first item being the tag, the second the dimension.
    phys_names = {v[0]: k for k, v in phys_names.items()}

    # Constants used in the gmsh.geo-file
    const = constants.GmshConstants()

    # Create grids from gmsh mesh.
    logger.info("Create grids of various dimensions")
    g_2d = mesh_2_grid.create_2d_grids(pts, cells, is_embedded=False)
    g_1d, _ = mesh_2_grid.create_1d_grids(
        pts, cells, phys_names, cell_info, line_tag=const.PHYSICAL_NAME_FRACTURES
    )
    g_0d = mesh_2_grid.create_0d_grids(pts, cells)
    grids = [g_2d, g_1d, g_0d]

    logger.info(
        "Grid creation completed. Elapsed time " + str(time.time() - start_time)
    )

    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"
            logger.info(s)

    return grids
Example #2
0
    def _write_fractures_1d(self):
        # Both fractures and compartments are
        constants = gridding_constants.GmshConstants()

        # We consider fractures, boundary tag, an auxiliary tag (fake fractures/mesh
        # constraints)
        ind = np.argwhere(
            np.logical_or.reduce((
                self.lines[2] == constants.COMPARTMENT_BOUNDARY_TAG,
                self.lines[2] == constants.FRACTURE_TAG,
                self.lines[2] == constants.AUXILIARY_TAG,
            ))).ravel()
        lines = self.lines[:, ind]
        tag = self.lines[2, ind]

        lines_id = lines[3, :]
        if lines_id.size == 0:
            return str()
        range_id = np.arange(np.amin(lines_id), np.amax(lines_id) + 1)

        s = "// Start specification of fractures/compartment boundary/auxiliary elements\n"
        seg_id = 0
        for i in range_id:
            local_seg_id = str()
            for mask in np.flatnonzero(lines_id == i):

                # give different name for fractures/boundary and auxiliary
                if tag[mask] != constants.AUXILIARY_TAG:
                    name = "frac_line_"
                    physical_name = constants.PHYSICAL_NAME_FRACTURES
                else:
                    name = "seg_line_"
                    physical_name = constants.PHYSICAL_NAME_AUXILIARY

                s += (name + str(seg_id) + " = newl; " + "Line(" + name +
                      str(seg_id) + ") = {" + "p" + str(int(lines[0, mask])) +
                      ", p" + str(int(lines[1, mask])) + "};\n")
                local_seg_id += name + str(seg_id) + ", "
                seg_id += 1

            local_seg_id = local_seg_id[:-2]
            s += ('Physical Line("' + physical_name + str(i) + '") = { ' +
                  local_seg_id + " };\n")
            s += "\n"

        s += "// End of /compartment boundary/auxiliary elements specification\n\n"
        return s
Example #3
0
    def __write_physical_points(self):
        ls = "\n"
        s = "// Start physical point specification" + ls
        constants = gridding_constants.GmshConstants()

        for i, p in enumerate(self.intersection_points):
            s += (
                'Physical Point("'
                + constants.PHYSICAL_NAME_FRACTURE_POINT
                + str(i)
                + '") = {p'
                + str(p)
                + "};"
                + ls
            )
        s += "// End of physical point specification" + ls + ls
        return s
Example #4
0
    def _write_boundary_2d(self):
        constants = gridding_constants.GmshConstants()
        bound_line_ind = np.argwhere(
            self.lines[2] == constants.DOMAIN_BOUNDARY_TAG).ravel()
        bound_line = self.lines[:, bound_line_ind]
        bound_line, _ = sort_points.sort_point_pairs(bound_line,
                                                     check_circular=True)

        s = "// Start of specification of domain"
        s += "// Define lines that make up the domain boundary\n"

        bound_id = bound_line[3, :]
        range_id = np.arange(np.amin(bound_id), np.amax(bound_id) + 1)
        seg_id = 0

        loop_str = "{"
        for i in range_id:
            local_bound_id = str()
            for mask in np.flatnonzero(bound_id == i):
                s += "bound_line_" + str(seg_id) + " = newl;\n"
                s += "Line(bound_line_" + str(seg_id) + ") ={"
                s += ("p" + str(int(bound_line[0, mask])) + ", p" +
                      str(int(bound_line[1, mask])) + "};\n")

                loop_str += "bound_line_" + str(seg_id) + ", "
                local_bound_id += "bound_line_" + str(seg_id) + ", "
                seg_id += 1

            local_bound_id = local_bound_id[:-2]
            s += ('Physical Line("' + constants.PHYSICAL_NAME_DOMAIN_BOUNDARY +
                  str(i) + '") = { ' + local_bound_id + " };\n")

        s += "\n"
        loop_str = loop_str[:-2]  # Remove last comma
        loop_str += "};\n"
        s += "// Line loop that makes the domain boundary\n"
        s += "Domain_loop = newll;\n"
        s += "Line Loop(Domain_loop) = " + loop_str
        s += "domain_surf = news;\n"
        s += "Plane Surface(domain_surf) = {Domain_loop};\n"
        s += ('Physical Surface("' + constants.PHYSICAL_NAME_DOMAIN +
              '") = {domain_surf};\n')
        s += "// End of domain specification\n\n"
        return s
Example #5
0
    def __write_lines(self, embed_in=None):
        l = self.lines
        num_lines = l.shape[1]
        ls = "\n"
        s = "// Define lines " + ls
        constants = gridding_constants.GmshConstants()
        if l.shape[0] > 2:
            lt = l[2]
            has_tags = True
        else:
            has_tags = False
        for i in range(num_lines):
            si = str(i)
            s += (
                "frac_line_"
                + si
                + "= newl; Line(frac_line_"
                + si
                + ") = {p"
                + str(l[0, i])
                + ", p"
                + str(l[1, i])
                + "};"
                + ls
            )
            if has_tags:
                s += 'Physical Line("'
                if l[2, i] == constants.FRACTURE_TIP_TAG:
                    s += constants.PHYSICAL_NAME_FRACTURE_TIP
                elif l[2, i] == constants.FRACTURE_INTERSECTION_LINE_TAG:
                    s += constants.PHYSICAL_NAME_FRACTURE_LINE
                else:
                    # This is a line that need not be physical (recognized by
                    # the parser of output from gmsh). Applies to boundary and
                    # subdomain boundary lines.
                    s += constants.PHYSICAL_NAME_AUXILIARY_LINE

                s += si + '") = {frac_line_' + si + "};" + ls

            s += ls
        s += "// End of line specification " + ls + ls
        return s
Example #6
0
    def _write_boundary_3d(self):
        ls = "\n"
        s = "// Start domain specification" + ls
        # Write surfaces:
        s += self._write_polygons(boundary=True)

        # Make a box out of them
        s += "domain_loop = newsl;" + ls
        s += "Surface Loop(domain_loop) = {"
        for pi in range(len(self.polygons[0])):
            if self.polygon_tags["boundary"][pi]:
                s += " boundary_surface_" + str(pi) + ","
        s = s[:-1]
        s += "};" + ls
        s += "Volume(1) = {domain_loop};" + ls
        s += ('Physical Volume("' +
              gridding_constants.GmshConstants().PHYSICAL_NAME_DOMAIN +
              '") = {1};' + ls)

        s += "// End of domain specification\n\n"
        return s
Example #7
0
    def __write_boundary_3d(self):
        ls = '\n'
        s = '// Start domain specification' + ls
        # Write surfaces:
        s += self.__write_polygons(boundary=True)

        # Make a box out of them
        s += 'domain_loop = newsl;' + ls
        s += 'Surface Loop(domain_loop) = {'
        for pi in range(len(self.polygons[0])):
            if self.polygon_tags['boundary'][pi]:
                s += 'auxiliary_' + str(pi) + ','
        s = s[:-1]
        s += '};' + ls
        s += 'Volume(1) = {domain_loop};' + ls
        s += 'Physical Volume(\"' + \
             gridding_constants.GmshConstants().PHYSICAL_NAME_DOMAIN + \
             '\") = {1};' + ls

        s += '// End of domain specification\n\n'
        return s
Example #8
0
def _find_intersection_points(lines):
    const = constants.GmshConstants()

    frac_id = np.ravel(lines[:2, lines[2] == const.FRACTURE_TAG])
    _, frac_ia, frac_count = np.unique(frac_id, True, False, True)

    # In the case we have auxiliary points remove do not create a 0d point in
    # case one intersects a single fracture. In the case of multiple fractures intersection
    # with an auxiliary point do consider the 0d.
    aux_id = lines[2] == const.AUXILIARY_TAG
    if np.any(aux_id):
        aux_id = np.ravel(lines[:2, aux_id])
        _, aux_ia, aux_count = np.unique(aux_id, True, False, True)

        # probably it can be done more efficiently but currently we rarely use the
        # auxiliary points in 2d
        for a in aux_id[aux_ia[aux_count > 1]]:
            # if a match is found decrease the frac_count only by one, this prevent
            # the multiple fracture case to be handle wrongly
            frac_count[frac_id[frac_ia] == a] -= 1

    return frac_id[frac_ia[frac_count > 1]]
Example #9
0
    def __write_boundary_2d(self):
        constants = gridding_constants.GmshConstants()
        bound_line_ind = np.argwhere(
            self.lines[2] == constants.DOMAIN_BOUNDARY_TAG
        ).ravel()
        bound_line = self.lines[:2, bound_line_ind]
        bound_line = sort_points.sort_point_pairs(bound_line, check_circular=True)

        s = "// Start of specification of domain"
        s += "// Define lines that make up the domain boundary\n"

        loop_str = "{"
        for i in range(bound_line.shape[1]):
            s += "bound_line_" + str(i) + " = newl; Line(bound_line_" + str(i) + ") ={"
            s += (
                "p"
                + str(int(bound_line[0, i]))
                + ", p"
                + str(int(bound_line[1, i]))
                + "};\n"
            )
            loop_str += "bound_line_" + str(i) + ", "

        s += "\n"
        loop_str = loop_str[:-2]  # Remove last comma
        loop_str += "};\n"
        s += "// Line loop that makes the domain boundary\n"
        s += "Domain_loop = newll;\n"
        s += "Line Loop(Domain_loop) = " + loop_str
        s += "domain_surf = news;\n"
        s += "Plane Surface(domain_surf) = {Domain_loop};\n"
        s += (
            'Physical Surface("'
            + constants.PHYSICAL_NAME_DOMAIN
            + '") = {domain_surf};\n'
        )
        s += "// End of domain specification\n\n"
        return s
Example #10
0
    def _write_physical_points(self):
        ls = "\n"
        s = "// Start physical point specification" + ls
        constants = gridding_constants.GmshConstants()

        for i, p in enumerate(self.intersection_points):
            s += ('Physical Point("' + constants.PHYSICAL_NAME_FRACTURE_POINT +
                  str(i) + '") = {p' + str(p) + "};" + ls)

        if self.domain_boundary_points is not None:
            for i, p in enumerate(self.domain_boundary_points):
                s += ('Physical Point("' +
                      constants.PHYSICAL_NAME_BOUNDARY_POINT + str(i) +
                      '") = {p' + str(p) + "};" + ls)

        if self.fracture_and_boundary_points is not None:
            for i, p in enumerate(self.fracture_and_boundary_points):
                s += ('Physical Point("' +
                      constants.PHYSICAL_NAME_FRACTURE_BOUNDARY_POINT +
                      str(i) + '") = {p' + str(p) + "};" + ls)

        s += "// End of physical point specification" + ls + ls
        return s
Example #11
0
    def __write_fractures_compartments_2d(self):
        # Both fractures and compartments are
        constants = gridding_constants.GmshConstants()

        frac_ind = np.argwhere(
            np.logical_or(self.lines[2] == constants.COMPARTMENT_BOUNDARY_TAG,
                          self.lines[2] == constants.FRACTURE_TAG)).ravel()
        frac_lines = self.lines[:, frac_ind]

        frac_id = frac_lines[3, :]
        if frac_id.size == 0:
            return str()
        range_id = np.arange(np.amin(frac_id), np.amax(frac_id) + 1)

        s = '// Start specification of fractures\n'
        seg_id = 0
        for i in range_id:
            local_seg_id = str()
            for mask in np.flatnonzero(frac_id == i):
                s += 'frac_line_' + str(seg_id) + ' = newl; ' + \
                     'Line(frac_line_' + str(seg_id) + ') = {' + \
                     'p' + str(int(frac_lines[0, mask])) + \
                     ', p' + str(int(frac_lines[1, mask])) + \
                     '};\n' + \
                     'Line{ frac_line_' + str(seg_id) + \
                     '} In Surface{domain_surf};\n'
                local_seg_id += 'frac_line_' + str(seg_id) + ', '
                seg_id += 1

            local_seg_id = local_seg_id[:-2]
            s += 'Physical Line(\"' + constants.PHYSICAL_NAME_FRACTURES \
                 + str(i) + '\") = { ' + local_seg_id + ' };\n'
            s += '\n'

        s += '// End of fracture specification\n\n'
        return s
Example #12
0
def create_2d_grids(
    pts: np.ndarray,
    cells: Dict[str, np.ndarray],
    phys_names: Dict[str, str],
    cell_info: Dict,
    is_embedded: bool = False,
    surface_tag: str = None,
    constraints: np.ndarray = None,
) -> List[pp.Grid]:
    """Create 2d grids for lines of a specified type from a gmsh tessalation.

    Only surfaces that were defined as 'physical' in the gmsh sense may have a grid
    created, but then only if the physical name matches specified line_tag.

    It is assumed that the mesh is read by meshio. See porepy.fracs.simplex for how to
    do this.

    Parameters:
        pts (np.ndarray, npt x 3): Global point set from gmsh
        cells (dict): Should have a key 'triangle' which maps to a np.ndarray with
            indices of the points that form 2d grids.
        phys_names (dict): mapping from the gmsh tags assigned to physical entities
            to the physical name of that tag.
        cell_info (dictionary): Should have a key 'triangle' that contains the
            physical names (in the gmsh sense) of the points.
        is_embedded (boolean, optional): If True, the triangle grids are embedded in
            3d space. If False (default), the grids are truly 2d.
        surface_tag (str, optional): The target physical name, all surfaces that have
            this tag will be assigned a grid. The string is assumed to be on the from
            BASE_NAME_OF_TAG_{INDEX}, where _INDEX is a number. The comparison is made
            between the physical names and the line, up to the last
            underscore. If not provided, the physical names of fracture surfaces will be
            used as target.
        constraints (np.array, optional): Array with lists of lines that should not
            become grids. The array items should match the INDEX in line_tag, see above.

    Returns:
        list of grids: List of 2d grids for all physical surfaces that matched with the
            specified target tag.

    """

    gmsh_constants = constants.GmshConstants()
    if surface_tag is None:
        surface_tag = gmsh_constants.PHYSICAL_NAME_FRACTURES

    if constraints is None:
        constraints = np.array([], dtype=np.int)

    if is_embedded:
        # List of 2D grids, one for each surface
        g_list: List[pp.Grid] = []

        # Special treatment of the case with no fractures
        if "triangle" not in cells:
            return g_list
        # Recover cells on fracture surfaces, and create grids
        tri_cells = cells["triangle"]

        # Tags of all triangle grids
        tri_tags = cell_info["triangle"]

        # Loop over all gmsh tags associated with triangle grids
        for pn_ind in np.unique(tri_tags):

            # Split the physical name into a category and a number - which will become
            # the fracture number
            pn = phys_names[pn_ind]
            offset = pn.rfind("_")
            frac_num = int(pn[offset + 1:])
            plane_type = pn[:offset]

            # Check if the surface is of the target type, or if the surface is tagged
            # as a constraint
            if plane_type != surface_tag[:-1] or int(
                    pn[offset + 1:]) in constraints:
                continue

            # Cells of this surface
            loc_cells = np.where(tri_tags == pn_ind)[0]
            loc_tri_cells = tri_cells[loc_cells, :].astype(np.int)

            # Find unique points, and a mapping from local to global points
            pind_loc, p_map = np.unique(loc_tri_cells, return_inverse=True)
            loc_tri_ind = p_map.reshape((-1, 3))
            g = pp.TriangleGrid(pts[pind_loc, :].transpose(),
                                loc_tri_ind.transpose())
            # Add mapping to global point numbers
            g.global_point_ind = pind_loc

            # Associate a fracture id (corresponding to the ordering of the
            # frature planes in the original fracture list provided by the
            # user)
            g.frac_num = frac_num

            # Append to list of 2d grids
            g_list.append(g)

        # Done with all surfaces, return
        return g_list

    else:
        # Single grid

        triangles = cells["triangle"].transpose()
        # Construct grid
        g_2d: pp.Grid = pp.TriangleGrid(pts.transpose(), triangles)

        # we need to add the face tags from gmsh to the current mesh, however,
        # since there is not a cell-face relation from gmsh but only a cell-node
        # relation we need to recover the corresponding face-line map.
        # First find the nodes of each face
        faces = np.reshape(g_2d.face_nodes.indices, (2, -1), order="F")
        faces = np.sort(faces, axis=0)

        # Then we do a bunch of sorting to make sure faces and lines has the same
        # node ordering:
        idxf = np.lexsort(faces)

        line = np.sort(cells["line"].T, axis=0)
        idxl = np.lexsort(line)
        IC = np.empty(line.shape[1], dtype=int)
        IC[idxl] = np.arange(line.shape[1])

        # Next change the faces and line to string format ("node_idx0,node_idx1").
        # The reason to do so is because we want to compare faces and line columnwise,
        # i.e., is_line[i] should be true iff faces[:, i] == line[:, j] for ONE j. If
        # you can make numpy do this, you can remove the string formating.
        tmp = np.core.defchararray.add(faces[0, idxf].astype(str), ",")
        facestr = np.core.defchararray.add(tmp, faces[1, idxf].astype(str))
        tmp = np.core.defchararray.add(line[0, idxl].astype(str), ",")
        linestr = np.core.defchararray.add(tmp, line[1, idxl].astype(str))

        is_line = np.isin(facestr, linestr, assume_unique=True)

        # Now find the face index that correspond to each line. line2face is of length
        # line.shape[1] and we have: face[:, line2face] == line.
        line2face = idxf[is_line][IC]
        # Sanity check
        if not np.allclose(faces[:, line2face], line):
            raise RuntimeError(
                "Could not find mapping from gmsh lines to pp.Grid faces")

        # Now we can assign the correct tags to the grid.
        # First we add them as False and after we change for the correct
        # faces. The new tag name become the lower version of what gmsh gives
        # in the cell_info["line"]. The map phys_names recover the literal name.
        for tag in np.unique(cell_info["line"]):
            tag_name = phys_names[tag].lower() + "_faces"
            g_2d.tags[tag_name] = np.zeros(g_2d.num_faces, dtype=np.bool)
            # Add correct tag
            faces = line2face[cell_info["line"] == tag]
            g_2d.tags[tag_name][faces] = True

        # Create mapping to global numbering (will be a unit mapping, but is
        # crucial for consistency with lower dimensions)
        g_2d.global_point_ind = np.arange(pts.shape[0])

        # Convert to list to be consistent with lower dimensions
        # This may also become useful in the future if we ever implement domain
        # decomposition approaches based on gmsh.
        return [g_2d]
Example #13
0
    def __write_polygons(self, boundary=False):
        """
        Writes either all fractures or all boundary planes.
        """
        constants = gridding_constants.GmshConstants()
        bound_tags = self.polygon_tags.get("boundary", [False] * len(self.polygons[0]))
        subd_tags = self.polygon_tags.get("subdomain", [False] * len(self.polygons[0]))

        ls = "\n"
        # Name boundary or fracture
        f_or_b = "auxiliary" if boundary else "fracture"
        if not boundary:
            s = "// Start fracture specification" + ls
        else:
            s = ""
        for pi in range(len(self.polygons[0])):
            if bound_tags[pi] != boundary:
                continue
            # Check if the polygon is a subdomain boundary, i.e., auxiliary
            # polygon.
            auxiliary = subd_tags[pi]
            if auxiliary:
                # Keep track of "fake fractures", i.e., subdomain
                # boundaries.
                f_or_b = "auxiliary"
            p = self.polygons[0][pi].astype("int")
            reverse = self.polygons[1][pi]
            # First define line loop
            s += "frac_loop_" + str(pi) + " = newll; " + ls
            s += "Line Loop(frac_loop_" + str(pi) + ") = { "
            for i, li in enumerate(p):
                if reverse[i]:
                    s += "-"
                s += "frac_line_" + str(li)
                if i < p.size - 1:
                    s += ", "

            s += "};" + ls

            n = f_or_b + "_"
            # Then the surface
            s += n + str(pi) + " = news; "
            s += (
                "Plane Surface(" + n + str(pi) + ") = {frac_loop_" + str(pi) + "};" + ls
            )

            if bound_tags[pi] or auxiliary:
                # Domain boundary or "fake fracture" = subdomain boundary
                s += (
                    'Physical Surface("'
                    + constants.PHYSICAL_NAME_AUXILIARY
                    + str(pi)
                    + '") = {auxiliary_'
                    + str(pi)
                    + "};"
                    + ls
                )

            else:
                # Normal fracture
                s += (
                    'Physical Surface("'
                    + constants.PHYSICAL_NAME_FRACTURES
                    + str(pi)
                    + '") = {fracture_'
                    + str(pi)
                    + "};"
                    + ls
                )
                if self.domain is not None:
                    s += "Surface{" + n + str(pi) + "} In Volume{1};" + ls + ls

            for li in self.e2f[pi]:
                s += "Line{frac_line_" + str(li) + "} In Surface{" + n
                s += str(pi) + "};" + ls
            s += ls

        if not boundary:
            s += "// End of fracture specification" + ls + ls

        return s
Example #14
0
def create_1d_grids(
        pts,
        cells,
        phys_names,
        cell_info,
        line_tag=constants.GmshConstants().PHYSICAL_NAME_FRACTURE_LINE,
        tol=1e-4,
        constraints=None,
        **kwargs):

    if constraints is None:
        constraints = np.empty(0, dtype=np.int)
    # Recover lines
    # There will be up to three types of physical lines: intersections (between
    # fractures), fracture tips, and auxiliary lines (to be disregarded)

    # All intersection lines and points on boundaries are non-physical in 3d.
    # I.e., they are assigned boundary conditions, but are not gridded.

    g_1d = []

    # If there are no fracture intersections, we return empty lists
    if not "line" in cells:
        return g_1d, np.empty(0)

    gmsh_const = constants.GmshConstants()

    line_tags = cell_info["line"]["gmsh:physical"]
    line_cells = cells["line"]

    gmsh_tip_num = []
    tip_pts = np.empty(0)

    for i, pn_ind in enumerate(np.unique(line_tags)):
        # Index of the final underscore in the physical name. Chars before this
        # will identify the line type, the one after will give index
        pn = phys_names[pn_ind]
        offset_index = pn.rfind("_")
        loc_line_cell_num = np.where(line_tags == pn_ind)[0]
        loc_line_pts = line_cells[loc_line_cell_num, :]

        assert loc_line_pts.size > 1

        line_type = pn[:offset_index]

        # Try to get the fracture number from the physical name of the object
        # This will only work if everything after the '_' can be interpreted
        # as a number. Specifically, it should work for all meshes generated by
        # the standard PorePy procedure, but it may fail for externally generated
        # geo-files. If it fails, we simply set the frac_num to None in this case.
        try:
            frac_num = int(pn[offset_index + 1:])
        except ValueError:
            frac_num = None

        # If this is a meshing constraint, but not a fracture, we don't need to do anything
        if frac_num in constraints:
            continue

        if line_type == gmsh_const.PHYSICAL_NAME_FRACTURE_TIP[:-1]:
            gmsh_tip_num.append(i)

            # We need not know which fracture the line is on the tip of (do
            # we?)
            tip_pts = np.append(tip_pts, np.unique(loc_line_pts))

        elif line_type == line_tag[:-1]:
            loc_pts_1d = np.unique(loc_line_pts)  # .flatten()
            loc_coord = pts[loc_pts_1d, :].transpose()
            g = create_embedded_line_grid(loc_coord, loc_pts_1d, tol=tol)
            g.frac_num = int(frac_num)
            g_1d.append(g)

        else:  # Auxiliary line
            pass
    return g_1d, tip_pts
Example #15
0
    def _write_polygons(self, boundary=False):
        """
        Writes either all fractures or all boundary planes.
        """
        constants = gridding_constants.GmshConstants()
        bound_tags = self.polygon_tags.get("boundary",
                                           [False] * len(self.polygons[0]))
        constraint_tags = self.polygon_tags["constraint"]

        ls = "\n"
        # Name boundary or fracture
        if not boundary:
            s = "// Start fracture specification" + ls
        else:
            s = "// Start boundary surface specification" + ls
        for pi in range(len(self.polygons[0])):
            if bound_tags[pi] != boundary:
                continue
            p = self.polygons[0][pi].astype("int")
            reverse = self.polygons[1][pi]
            # First define line loop
            s += "frac_loop_" + str(pi) + " = newll; " + ls
            s += "Line Loop(frac_loop_" + str(pi) + ") = { "
            for i, li in enumerate(p):
                if reverse[i]:
                    s += "-"
                s += "frac_line_" + str(li)
                if i < p.size - 1:
                    s += ", "

            s += "};" + ls

            if boundary:
                surf_stem = "boundary_surface_"
            else:
                if constraint_tags[pi]:
                    surf_stem = "auxiliary_surface_"
                else:
                    surf_stem = "fracture_"

            # Then the surface
            s += surf_stem + str(pi) + " = news; "
            s += ("Plane Surface(" + surf_stem + str(pi) + ") = {frac_loop_" +
                  str(pi) + "};" + ls)

            if bound_tags[pi]:
                # Domain boundary
                s += ('Physical Surface("' +
                      constants.PHYSICAL_NAME_DOMAIN_BOUNDARY_SURFACE +
                      str(pi) + '") = {' + surf_stem + str(pi) + "};" + ls)
            elif constraint_tags[pi]:
                s += ('Physical Surface("' +
                      constants.PHYSICAL_NAME_AUXILIARY + str(pi) + '") = {' +
                      surf_stem + str(pi) + "};" + ls)
            else:
                # Normal fracture
                s += ('Physical Surface("' +
                      constants.PHYSICAL_NAME_FRACTURES + str(pi) + '") = {' +
                      surf_stem + str(pi) + "};" + ls)
            if not bound_tags[pi] and self.domain is not None:
                s += "Surface{" + surf_stem + str(
                    pi) + "} In Volume{1};" + ls + ls

            for li in self.e2f[pi]:
                s += "Line{frac_line_" + str(li) + "} In Surface{" + surf_stem
                s += str(pi) + "};" + ls
            s += ls

        if not boundary:
            s += "// End of fracture specification" + ls + ls

        return s
Example #16
0
    def __write_polygons(self, boundary=False):
        """
        Writes either all fractures or all boundary planes.
        """
        constants = gridding_constants.GmshConstants()
        bound_tags = self.polygon_tags.get('boundary',
                                           [False] * len(self.polygons[0]))
        subd_tags = self.polygon_tags.get('subdomain',
                                          [False] * len(self.polygons[0]))

        ls = '\n'
        # Name boundary or fracture
        f_or_b = 'auxiliary' if boundary else 'fracture'
        if not boundary:
            s = '// Start fracture specification' + ls
        else:
            s = ''
        for pi in range(len(self.polygons[0])):
            if bound_tags[pi] != boundary:
                continue
            # Check if the polygon is a subdomain boundary, i.e., auxiliary
            # polygon.
            auxiliary = subd_tags[pi]
            if auxiliary:
                # Keep track of "fake fractures", i.e., subdomain
                # boundaries.
                f_or_b = 'auxiliary'
            p = self.polygons[0][pi].astype('int')
            reverse = self.polygons[1][pi]
            # First define line loop
            s += 'frac_loop_' + str(pi) + ' = newll; ' + ls
            s += 'Line Loop(frac_loop_' + str(pi) + ') = { '
            for i, li in enumerate(p):
                if reverse[i]:
                    s += '-'
                s += 'frac_line_' + str(li)
                if i < p.size - 1:
                    s += ', '

            s += '};' + ls

            n = f_or_b + '_'
            # Then the surface
            s += n + str(pi) + ' = news; '
            s += 'Plane Surface(' + n + str(pi) + ') = {frac_loop_' \
                + str(pi) + '};' + ls

            if bound_tags[pi] or auxiliary:
                # Domain boundary or "fake fracture" = subdomain boundary
                s += 'Physical Surface(\"' + constants.PHYSICAL_NAME_AUXILIARY \
                      + str(pi) + '\") = {auxiliary_' + str(pi) + '};' + ls

            else:
                # Normal fracture
                s += 'Physical Surface(\"' + constants.PHYSICAL_NAME_FRACTURES \
                     + str(pi) + '\") = {fracture_' + str(pi) + '};' + ls
                if self.domain is not None:
                    s += 'Surface{' + n + str(pi) + '} In Volume{1};' + ls + ls

            for li in self.e2f[pi]:
                s += 'Line{frac_line_' + str(li) + '} In Surface{' + n
                s += str(pi) + '};' + ls
            s += ls

        if not boundary:
            s += '// End of fracture specification' + ls + ls

        return s
Example #17
0
def create_1d_grids(
    pts: np.ndarray,
    cells: Dict[str, np.ndarray],
    phys_names: Dict,
    cell_info: Dict,
    line_tag: str = None,
    tol: float = 1e-4,
    constraints: np.ndarray = None,
    return_fracture_tips: bool = True,
) -> Tuple[List[pp.Grid], np.ndarray]:
    """ Create 1d grids for lines of a specified type from a gmsh tessalation.

    Only lines that were defined as 'physical' in the gmsh sense may have a grid
    created, but then only if the physical name matches specified line_tag.

    It is assumed that the mesh is read by meshio. See porepy.fracs.simplex for how to
    do this.

    Parameters:
        pts (np.ndarray, npt x 3): Global point set from gmsh
        cells (dict): Should have a key 'line', which maps to a np.ndarray with indices
            of the lines that form 1d grids.
        phys_names (dict): mapping from the gmsh tags assigned to physical entities
            to the physical name of that tag.
        cell_info (dictionary): Should have a key 'line', that contains the
            physical names (in the gmsh sense) of the points.
        line_tag (str, optional): The target physical name, all lines that have
            this tag will be assigned a grid. The string is assumed to be on the from
            BASE_NAME_OF_TAG_{INDEX}, where _INDEX is a number. The comparison is made
            between the physical names and the line, up to the last
            underscore. If not provided, the physical names of fracture lines will be
            used as target.
        tol (double, optional): Tolerance used when comparing points in the creation of
            line grids. Defaults to 1e-4.
        constraints (np.array, optional): Array with lists of lines that should not
            become grids. The array items should match the INDEX in line_tag, see above.
        return_fracture_tips (boolean, optional): If True (default), fracture tips will
            be found and returned.

    Returns:
        list of grids: List of 1d grids for all physical lines that matched with the
            specified target tag.
        np.array, each item is an array of indices of points on a fracture tip. Only
            returned in return_fracture_tips is True.

    """
    gmsh_constants = constants.GmshConstants()
    if line_tag is None:
        line_tag = gmsh_constants.PHYSICAL_NAME_FRACTURE_LINE

    if constraints is None:
        constraints = np.empty(0, dtype=np.int)
    # Recover lines
    # There will be up to three types of physical lines: intersections (between
    # fractures), fracture tips, and auxiliary lines (to be disregarded)

    # Data structure for the point grids
    g_1d = []

    # If there are no fracture intersections, we return empty lists
    if "line" not in cells:
        return g_1d, np.empty(0)

    line_tags = cell_info["line"]
    line_cells = cells["line"]

    gmsh_tip_num = []
    tip_pts = np.empty(0)

    for i, pn_ind in enumerate(np.unique(line_tags)):
        # Index of the final underscore in the physical name. Chars before this
        # will identify the line type, the one after will give index
        pn = phys_names[pn_ind]
        offset_index = pn.rfind("_")
        loc_line_cell_num = np.where(line_tags == pn_ind)[0]
        loc_line_pts = line_cells[loc_line_cell_num, :]

        assert loc_line_pts.size > 1

        line_type = pn[:offset_index]

        # Try to get the fracture number from the physical name of the object
        # This will only work if everything after the '_' can be interpreted
        # as a number. Specifically, it should work for all meshes generated by
        # the standard PorePy procedure, but it may fail for externally generated
        # geo-files. If it fails, we simply set the frac_num to None in this case.
        try:
            frac_num = int(pn[offset_index + 1:])
        except ValueError:
            frac_num = None

        # If this is a meshing constraint, but not a fracture, we don't need to do anything
        if frac_num in constraints:
            continue

        if line_type == gmsh_constants.PHYSICAL_NAME_FRACTURE_TIP[:-1]:
            gmsh_tip_num.append(i)

            # We need not know which fracture the line is on the tip of (do
            # we?)
            tip_pts = np.append(tip_pts, np.unique(loc_line_pts))

        elif line_type == line_tag[:-1]:
            loc_pts_1d = np.unique(loc_line_pts)  # .flatten()
            loc_coord = pts[loc_pts_1d, :].transpose()
            g = create_embedded_line_grid(loc_coord, loc_pts_1d, tol=tol)
            g.frac_num = int(frac_num)
            g_1d.append(g)

        else:  # Auxiliary line
            pass

    if return_fracture_tips:
        return g_1d, tip_pts
    else:
        return g_1d
Example #18
0
    def _find_and_split_intersections(self, constraints):
        # Unified description of points and lines for domain, and fractures

        points = self.pts
        edges = self.edges

        if not np.all(np.diff(edges[:2], axis=0) != 0):
            raise ValueError("Found a point edge in splitting of edges")

        const = constants.GmshConstants()

        tags = np.zeros((2, edges.shape[1]), dtype=np.int)
        tags[0][np.logical_not(self.tags["boundary"])] = const.FRACTURE_TAG
        tags[0][self.tags["boundary"]] = const.DOMAIN_BOUNDARY_TAG
        tags[0][constraints] = const.AUXILIARY_TAG
        tags[1] = np.arange(edges.shape[1])

        edges = np.vstack((edges, tags))

        # Ensure unique description of points
        pts_all, _, old_2_new = unique_columns_tol(points, tol=self.tol)
        edges[:2] = old_2_new[edges[:2]]
        to_remove = np.where(edges[0, :] == edges[1, :])[0]
        lines = np.delete(edges, to_remove, axis=1)

        self.decomposition["domain_boundary_points"] = old_2_new[
            self.decomposition["domain_boundary_points"]]

        # In some cases the fractures and boundaries impose the same constraint
        # twice, although it is not clear why. Avoid this by uniquifying the lines.
        # This may disturb the line tags in lines[2], but we should not be
        # dependent on those.
        li = np.sort(lines[:2], axis=0)
        _, new_2_old, old_2_new = unique_columns_tol(li, tol=self.tol)
        lines = lines[:, new_2_old]

        if not np.all(np.diff(lines[:2], axis=0) != 0):
            raise ValueError(
                "Found a point edge in splitting of edges after merging points"
            )

        # We split all fracture intersections so that the new lines do not
        # intersect, except possible at the end points
        logger.info("Remove edge crossings")
        tm = time.time()

        pts_split, lines_split = pp.intersections.split_intersecting_segments_2d(
            pts_all, lines, tol=self.tol)
        logger.info("Done. Elapsed time " + str(time.time() - tm))

        # Ensure unique description of points
        pts_split, _, old_2_new = unique_columns_tol(pts_split, tol=self.tol)
        lines_split[:2] = old_2_new[lines_split[:2]]
        to_remove = np.where(lines[0, :] == lines[1, :])[0]
        lines = np.delete(lines, to_remove, axis=1)

        self.decomposition["domain_boundary_points"] = old_2_new[
            self.decomposition["domain_boundary_points"]]

        # Remove lines with the same start and end-point.
        # This can be caused by L-intersections, or possibly also if the two
        # endpoints are considered equal under tolerance tol.
        remove_line_ind = np.where(np.diff(lines_split[:2], axis=0)[0] == 0)[0]
        lines_split = np.delete(lines_split, remove_line_ind, axis=1)

        # TODO: This operation may leave points that are not referenced by any
        # lines. We should probably delete these.

        # We find the end points that are shared by more than one intersection
        intersections = self._find_intersection_points(lines_split)

        self.decomposition.update({
            "points": pts_split,
            "edges": lines_split,
            "intersections": intersections,
            "domain": self.domain,
        })
Example #19
0
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
Example #20
0
def __find_intersection_points(lines):
    const = constants.GmshConstants()
    frac_id = np.ravel(lines[:2, lines[2] == const.FRACTURE_TAG])
    _, ia, count = np.unique(frac_id, True, False, True)
    return frac_id[ia[count > 1]]
Example #21
0
def triangle_grid_from_gmsh(file_name, constraints=None, **kwargs):
    """Generate a list of grids dimensions {2, 1, 0}, starting from a gmsh mesh.

    Parameters:
        file_name (str): Path to file of gmsh.msh specification.
        constraints (np.array, optional): Index of fracture lines that are
            constraints in the meshing, but should not have a lower-dimensional
            mesh. Defaults to empty.

    Returns:
        list of list of grids: grids in 2d, 1d and 0d. If no grids exist in a
            specified dimension, the inner list will be empty.

    """

    if constraints is None:
        constraints = np.empty(0, dtype=np.int)

    start_time = time.time()

    if file_name.endswith(".msh"):
        file_name = file_name[:-4]
    out_file = file_name + ".msh"

    pts, cells, cell_info, phys_names = _read_gmsh_file(out_file)

    # Constants used in the gmsh.geo-file
    const = constants.GmshConstants()

    # Create grids from gmsh mesh.
    logger.info("Create grids of various dimensions")
    g_2d = mesh_2_grid.create_2d_grids(pts,
                                       cells,
                                       is_embedded=False,
                                       phys_names=phys_names,
                                       cell_info=cell_info)
    g_1d, _ = mesh_2_grid.create_1d_grids(
        pts,
        cells,
        phys_names,
        cell_info,
        line_tag=const.PHYSICAL_NAME_FRACTURES,
        constraints=constraints,
        **kwargs,
    )
    g_0d = mesh_2_grid.create_0d_grids(pts, cells, phys_names, cell_info)
    grids = [g_2d, g_1d, g_0d]

    logger.info("Grid creation completed. Elapsed time " +
                str(time.time() - start_time))

    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"
            logger.info(s)

    return grids
Example #22
0
def _merge_domain_fracs_2d(dom, frac_p, frac_l, constraints):
    """
    Merge fractures, domain boundaries and lines for compartments.
    The unified description is ready for feeding into meshing tools such as
    gmsh

    Parameters:
    dom: dictionary defining domain. fields xmin, xmax, ymin, ymax
    frac_p: np.ndarray. Points used in fracture definition. 2 x num_points.
    frac_l: np.ndarray. Connection between fracture points. 2 x num_fracs

    returns:
    p: np.ndarary. Merged list of points for fractures, compartments and domain
        boundaries.
    l: np.ndarray. Merged list of line connections (first two rows), tag
        identifying which type of line this is (third row), and a running index
        for all lines (fourth row)
    """
    if frac_p is None:
        frac_p = np.zeros((2, 0))
        frac_l = np.zeros((2, 0))

    # Use constants set outside. If we ever
    const = constants.GmshConstants()

    if dom is None:
        dom_lines = np.empty((2, 0))
    elif isinstance(dom, dict):
        # First create lines that define the domain
        x_min = dom["xmin"]
        x_max = dom["xmax"]
        y_min = dom["ymin"]
        y_max = dom["ymax"]
        dom_p = np.array([[x_min, x_max, x_max, x_min],
                          [y_min, y_min, y_max, y_max]])
        dom_lines = np.array([[0, 1], [1, 2], [2, 3], [3, 0]]).T
    else:
        dom_p = dom
        tmp = np.arange(dom_p.shape[1])
        dom_lines = np.vstack((tmp, (tmp + 1) % dom_p.shape[1]))

    num_dom_lines = dom_lines.shape[1]  # Should be 4 for the dictionary case

    # The  lines will have all fracture-related tags set to zero.
    # The plan is to ignore these tags for the boundary and compartments,
    # so it should not matter
    dom_tags = const.DOMAIN_BOUNDARY_TAG * np.ones((1, num_dom_lines))
    dom_l = np.vstack((dom_lines, dom_tags))

    # Also add a tag to the fractures, signifying that these are fractures
    frac_l = np.vstack((frac_l, const.FRACTURE_TAG * np.ones(frac_l.shape[1])))
    is_constraint = np.in1d(np.arange(frac_l.shape[1]), constraints)
    frac_l[-1][is_constraint] = const.AUXILIARY_TAG

    # Merge the point arrays, compartment points first
    p = np.hstack((frac_p, dom_p))

    # Adjust index of fracture points to account for the compartment points
    dom_l[:2] += frac_p.shape[1]

    l = np.hstack((frac_l, dom_l)).astype(np.int)

    # Add a second tag as an identifier of each line.
    l = np.vstack((l, np.arange(l.shape[1])))

    return p, l, dom_p
Example #23
0
def create_2d_grids(
    pts: np.ndarray,
    cells: Dict[str, np.ndarray],
    phys_names: Dict[str, str],
    cell_info: Dict,
    is_embedded: bool = False,
    surface_tag: str = None,
    constraints: np.ndarray = None,
) -> List[pp.Grid]:
    """ Create 2d grids for lines of a specified type from a gmsh tessalation.

    Only surfaces that were defined as 'physical' in the gmsh sense may have a grid
    created, but then only if the physical name matches specified line_tag.

    It is assumed that the mesh is read by meshio. See porepy.fracs.simplex for how to
    do this.

    Parameters:
        pts (np.ndarray, npt x 3): Global point set from gmsh
        cells (dict): Should have a key 'triangle' which maps to a np.ndarray with
            indices of the points that form 2d grids.
        phys_names (dict): mapping from the gmsh tags assigned to physical entities
            to the physical name of that tag.
        cell_info (dictionary): Should have a key 'triangle' that contains the
            physical names (in the gmsh sense) of the points.
        is_embedded (boolean, optional): If True, the triangle grids are embedded in
            3d space. If False (default), the grids are truly 2d.
        surface_tag (str, optional): The target physical name, all surfaces that have
            this tag will be assigned a grid. The string is assumed to be on the from
            BASE_NAME_OF_TAG_{INDEX}, where _INDEX is a number. The comparison is made
            between the physical names and the line, up to the last
            underscore. If not provided, the physical names of fracture surfaces will be
            used as target.
        constraints (np.array, optional): Array with lists of lines that should not
            become grids. The array items should match the INDEX in line_tag, see above.

    Returns:
        list of grids: List of 2d grids for all physical surfaces that matched with the
            specified target tag.

    """

    # List of 2D grids, one for each surface
    g_2d = []

    gmsh_constants = constants.GmshConstants()
    if surface_tag is None:
        surface_tag = gmsh_constants.PHYSICAL_NAME_FRACTURES

    if constraints is None:
        constraints = np.array([], dtype=np.int)

    if is_embedded:

        # Special treatment of the case with no fractures
        if "triangle" not in cells:
            return g_2d
        # Recover cells on fracture surfaces, and create grids
        tri_cells = cells["triangle"]

        # Tags of all triangle grids
        tri_tags = cell_info["triangle"]

        # Loop over all gmsh tags associated with triangle grids
        for pn_ind in np.unique(tri_tags):

            # Split the physical name into a category and a number - which will become
            # the fracture number
            pn = phys_names[pn_ind]
            offset = pn.rfind("_")
            frac_num = int(pn[offset + 1:])
            plane_type = pn[:offset]

            # Check if the surface is of the target type, or if the surface is tagged
            # as a constraint
            if plane_type != surface_tag[:-1] or int(
                    pn[offset + 1:]) in constraints:
                continue

            # Cells of this surface
            loc_cells = np.where(tri_tags == pn_ind)[0]
            loc_tri_cells = tri_cells[loc_cells, :].astype(np.int)

            # Find unique points, and a mapping from local to global points
            pind_loc, p_map = np.unique(loc_tri_cells, return_inverse=True)
            loc_tri_ind = p_map.reshape((-1, 3))
            g = pp.TriangleGrid(pts[pind_loc, :].transpose(),
                                loc_tri_ind.transpose())
            # Add mapping to global point numbers
            g.global_point_ind = pind_loc

            # Associate a fracture id (corresponding to the ordering of the
            # frature planes in the original fracture list provided by the
            # user)
            g.frac_num = frac_num

            # Append to list of 2d grids
            g_2d.append(g)

    else:

        triangles = cells["triangle"].transpose()
        # Construct grid
        g_2d = pp.TriangleGrid(pts.transpose(), triangles)

        # we need to add the face tags from gmsh to the current mesh,
        # first we add them as False and after we change for the correct
        # faces. The new tag name become the lower version of what gmsh gives
        # in the cell_info["line"]. The map phys_names recover the literal name.

        # create all the extra tags for the grids, by default they're false
        for tag in np.unique(cell_info["line"]):
            tag_name = phys_names[tag].lower() + "_faces"
            g_2d.tags[tag_name] = np.zeros(g_2d.num_faces, dtype=np.bool)

        # since there is not a cell-face relation from gmsh but only a cell-node
        # relation we need to recover the corresponding face.
        for tag_id, tag in enumerate(cell_info["line"]):
            tag_name = phys_names[tag].lower() + "_faces"
            # check where is the first node indipendent on the position
            # in the triangle, being first, second or third node
            first = (triangles == cells["line"][tag_id, 0]).any(axis=0)
            # check where is the second node, same approach as before
            second = (triangles == cells["line"][tag_id, 1]).any(axis=0)
            # select which are the cells that have this edge
            tria = np.logical_and(first, second).astype(np.int)
            # with the cell_faces map we get the faces associated to
            # the selected triangles
            face = np.abs(g_2d.cell_faces).dot(tria)
            # we have two case, the face is internal or is at the boundary
            # we consider them separately
            if np.any(face > 1):
                # select the face if it is internal
                g_2d.tags[tag_name][face > 1] = True
            else:
                # the face is on a boundary
                face = np.logical_and(face, g_2d.tags["domain_boundary_faces"])
                if np.sum(face) == 2:
                    # the triangle has two faces at the boundary, check if it
                    # is the first otherwise it is the other.
                    face_id = np.where(face)[0]

                    first_face = np.zeros(face.size, dtype=np.bool)
                    first_face[face_id[0]] = True

                    nodes = g_2d.face_nodes.dot(first_face)
                    # check if the nodes of the first face are the same
                    if np.all(nodes[cells["line"][tag_id, :]]):
                        face[face_id[1]] = False
                    else:
                        face[face_id[0]] = False

                g_2d.tags[tag_name][face] = True

        # Create mapping to global numbering (will be a unit mapping, but is
        # crucial for consistency with lower dimensions)
        g_2d.global_point_ind = np.arange(pts.shape[0])

        # Convert to list to be consistent with lower dimensions
        # This may also become useful in the future if we ever implement domain
        # decomposition approaches based on gmsh.
        g_2d = [g_2d]
    return g_2d
Example #24
0
def create_0d_grids(
    pts: np.ndarray,
    cells: Dict[str, np.ndarray],
    phys_names: Dict[int, str],
    cell_info: Dict[str, np.ndarray],
    target_tag_stem: str = None,
) -> List[pp.Grid]:
    """ Create 0d grids for points of a specified type from a gmsh tessalation.

    Only points that were defined as 'physical' in the gmsh sense may have a grid
    created, but then only if the physical name matches specified target_tag_stem.

    It is assumed that the mesh is read by meshio. See porepy.fracs.simplex for how to
    do this.

    Parameters:
        pts (np.ndarray, npt x 3): Global point set from gmsh
        cells (dict): Should have a key vertex, which maps to a np.ndarray if indices
            of the points that form point grids.
        phys_names (dict): mapping from the gmsh tags assigned to physical entities
            to the physical name of that tag.
        cell_info (dictionary): Should have a key 'vertex', that contains the
            physical names (in the gmsh sense) of the points.
        target_tag_stem (str, optional): The target physical name, all points that have
            this tag will be assigned a grid. The string is assumed to be on the from
            BASE_NAME_OF_TAG_{INDEX}, where _INDEX is a number. The comparison is made
            between the physical names and the target_tag_stem, up to the last
            underscore. If not provided, the physical names of fracture points will be
            used as target.

    Returns:
        list of grids: List of 0d grids for all physical points that matched with the
            specified target tag.

    """
    if target_tag_stem is None:
        target_tag_stem = constants.GmshConstants(
        ).PHYSICAL_NAME_FRACTURE_POINT

    g_0d = []

    if "vertex" in cells:
        # Index (in the array pts) of the points that are specified as physical in the
        # .geo-file
        point_cells = cells["vertex"].ravel()

        # Keys to the physical names table of the points that have been decleared as
        # physical
        physical_name_indices = cell_info["vertex"]

        # Loop over all physical points
        for pi, phys_names_ind in enumerate(physical_name_indices):
            pn = phys_names[phys_names_ind]
            offset_index = pn.rfind("_")

            phys_name_vertex = pn[:offset_index]

            # Check if this is the target. The -1 is needed to avoid the extra _ in
            # the defined constantnt
            if phys_name_vertex == target_tag_stem[:-1]:
                # This should be a new grid
                g = pp.PointGrid(pts[point_cells[pi]])
                g.global_point_ind = np.atleast_1d(np.asarray(point_cells[pi]))

                # Store the index of this physical name tag.
                g.physical_name_index = int(pn[offset_index + 1:])

                g_0d.append(g)
            else:
                continue
    return g_0d