コード例 #1
0
ファイル: gmsh.py プロジェクト: danglive/pymor
    def parse_elements(elements):
        try:
            num_elements = int(elements[0])
        except ValueError:
            raise GmshError(f'first line of elements sections is not a number: {elements[0]}')
        if len(elements) != num_elements + 1:
            raise GmshError('number-of-elements field does not match number of lines in elements section')

        elements = [e.split(' ') for e in elements[1:]]
        try:
            elements = [tuple(int(f) for f in e) for e in elements]
        except ValueError:
            raise GmshError('malformed elements section')

        element_types = {1: 'line', 2: 'triangle'}
        element_nodes = {'line': 2, 'triangle': 3}

        def parse_line(fields):
            if fields[1] not in element_types:
                raise GmshError(f'element type {fields[0]} not supported')
            element_type = element_types[fields[1]]
            num_nodes = element_nodes[element_type]
            num_tags = fields[2]
            if len(fields) != num_nodes + num_tags + 3:
                raise GmshError('malformed elements section')
            return element_type, (fields[0], tuple(fields[3:3 + num_tags]), fields[3 + num_tags:])

        elements_by_type = defaultdict(list)
        for e in elements:
            t, l = parse_line(e)
            elements_by_type[t].append(l)

        return elements_by_type
コード例 #2
0
ファイル: gmsh.py プロジェクト: danglive/pymor
 def parse_line(fields):
     if fields[1] not in element_types:
         raise GmshError(f'element type {fields[0]} not supported')
     element_type = element_types[fields[1]]
     num_nodes = element_nodes[element_type]
     num_tags = fields[2]
     if len(fields) != num_nodes + num_tags + 3:
         raise GmshError('malformed elements section')
     return element_type, (fields[0], tuple(fields[3:3 + num_tags]), fields[3 + num_tags:])
コード例 #3
0
ファイル: gmsh.py プロジェクト: danglive/pymor
    def parse_names(physical_names):
        try:
            num_elements = int(physical_names[0])
        except ValueError:
            raise GmshError(f'first line of physical names sections is not a number: {physical_names[0]}')
        if len(physical_names) != num_elements + 1:
            raise GmshError('number-of-names field does not match number of lines in physical names section')

        physical_names = [pn.split(' ') for pn in physical_names[1:]]
        if not all(len(pn) == 3 for pn in physical_names):
            raise GmshError('malformed physical names section')

        try:
            physical_names = [(int(b), int(a), str(c).replace('"', '')) for a, b, c in physical_names]
        except ValueError:
            raise GmshError('malformed physical names section')

        return physical_names
コード例 #4
0
ファイル: gmsh.py プロジェクト: danglive/pymor
    def parse_nodes(nodes):
        try:
            num_nodes = int(nodes[0])
        except ValueError:
            raise GmshError(f'first line of nodes sections is not a number: {nodes[0]}')
        if len(nodes) != num_nodes + 1:
            raise GmshError('number-of-nodes field does not match number of lines in nodes section')

        nodes = [n.split(' ') for n in nodes[1:]]
        if not all(len(n) == 4 for n in nodes):
            raise GmshError('malformed nodes section')

        try:
            nodes = [(int(a), (float(b), float(c), float(d))) for a, b, c, d in nodes]
        except ValueError:
            raise GmshError('malformed nodes section')

        return nodes
コード例 #5
0
ファイル: gmsh.py プロジェクト: danglive/pymor
def _parse_gmsh_file(f):

    allowed_sections = ['Nodes', 'Elements', 'PhysicalNames', 'Periodic', 'NodeData',
                        'ElementData', 'ElementNodeData']

    supported_sections = ['Nodes', 'Elements', 'PhysicalNames']

    try:

        l = next(f).strip()
        if l != '$MeshFormat':
            raise GmshError(f'expected $MeshFormat, got {l}')

        l = next(f).strip()
        header = l.split(' ')
        if len(header) != 3:
            raise GmshError(f'header {l} has {len(header)} fields, expected 3')

        if header[0] != '2.2':
            raise GmshError(f'wrong file format version: got {header[0]}, expected 2.2')

        try:
            file_type = int(header[1])
        except ValueError:
            raise GmshError(f'malformed header: expected integer, got {header[1]}')

        if file_type != 0:
            raise GmshError('wrong file type: only ASCII gmsh files are supported')

        try:
            data_size = int(header[2])    # NOQA
        except ValueError:
            raise GmshError(f'malformed header: expected integer, got {header[2]}')

        l = next(f).strip()
        if l != '$EndMeshFormat':
            raise GmshError(f'expected $EndMeshFormat, got {l}')

    except StopIteration:
        raise GmshError('unexcpected end of file')

    in_section = False
    sections = defaultdict(list)

    for l in f:
        l = l.strip()
        if l == '':
            continue
        if not in_section:
            if not l.startswith('$'):
                raise GmshError(f'expected section name, got {l}')
            section = l[1:]
            if section not in allowed_sections:
                raise GmshError(f'unknown section type: {section}')
            if section not in supported_sections:
                raise GmshError(f'unsupported section type: {section}')
            if section in sections:
                raise GmshError(f'only one {section} section allowed')
            in_section = True
        elif l.startswith('$'):
            if l != '$End' + section:
                raise GmshError(f'expected $End{section}, got {l}')
            in_section = False
        else:
            sections[section].append(l)

    if in_section:
        raise GmshError(f'file ended while in section {section}')

    # now we parse each section ...

    def parse_nodes(nodes):
        try:
            num_nodes = int(nodes[0])
        except ValueError:
            raise GmshError(f'first line of nodes sections is not a number: {nodes[0]}')
        if len(nodes) != num_nodes + 1:
            raise GmshError('number-of-nodes field does not match number of lines in nodes section')

        nodes = [n.split(' ') for n in nodes[1:]]
        if not all(len(n) == 4 for n in nodes):
            raise GmshError('malformed nodes section')

        try:
            nodes = [(int(a), (float(b), float(c), float(d))) for a, b, c, d in nodes]
        except ValueError:
            raise GmshError('malformed nodes section')

        return nodes

    def parse_elements(elements):
        try:
            num_elements = int(elements[0])
        except ValueError:
            raise GmshError(f'first line of elements sections is not a number: {elements[0]}')
        if len(elements) != num_elements + 1:
            raise GmshError('number-of-elements field does not match number of lines in elements section')

        elements = [e.split(' ') for e in elements[1:]]
        try:
            elements = [tuple(int(f) for f in e) for e in elements]
        except ValueError:
            raise GmshError('malformed elements section')

        element_types = {1: 'line', 2: 'triangle'}
        element_nodes = {'line': 2, 'triangle': 3}

        def parse_line(fields):
            if fields[1] not in element_types:
                raise GmshError(f'element type {fields[0]} not supported')
            element_type = element_types[fields[1]]
            num_nodes = element_nodes[element_type]
            num_tags = fields[2]
            if len(fields) != num_nodes + num_tags + 3:
                raise GmshError('malformed elements section')
            return element_type, (fields[0], tuple(fields[3:3 + num_tags]), fields[3 + num_tags:])

        elements_by_type = defaultdict(list)
        for e in elements:
            t, l = parse_line(e)
            elements_by_type[t].append(l)

        return elements_by_type

    def parse_names(physical_names):
        try:
            num_elements = int(physical_names[0])
        except ValueError:
            raise GmshError(f'first line of physical names sections is not a number: {physical_names[0]}')
        if len(physical_names) != num_elements + 1:
            raise GmshError('number-of-names field does not match number of lines in physical names section')

        physical_names = [pn.split(' ') for pn in physical_names[1:]]
        if not all(len(pn) == 3 for pn in physical_names):
            raise GmshError('malformed physical names section')

        try:
            physical_names = [(int(b), int(a), str(c).replace('"', '')) for a, b, c in physical_names]
        except ValueError:
            raise GmshError('malformed physical names section')

        return physical_names

    parser_map = {'Nodes': parse_nodes, 'Elements': parse_elements, 'PhysicalNames': parse_names}

    for k, v in sections.items():
        sections[k] = parser_map[k](v)

    return sections
コード例 #6
0
def discretize_gmsh(domain_description=None,
                    geo_file=None,
                    geo_file_path=None,
                    msh_file_path=None,
                    mesh_algorithm='del2d',
                    clscale=1.,
                    options='',
                    refinement_steps=0):
    """Mesh a |DomainDescription| or an already existing Gmsh GEO-file using the Gmsh mesher.

    Parameters
    ----------
    domain_description
        A |DomainDescription| of the |PolygonalDomain| or |RectDomain| to discretize. Has to be `None`
        when `geo_file` is given.
    geo_file
        File handle of the Gmsh Geo-file to discretize. Has to be `None` when
        `domain_description` is given.
    geo_file_path
        Path of the created Gmsh GEO-file. When meshing a |PolygonalDomain| or |RectDomain| and
        `geo_file_path` is `None`, a temporary file will be created. If `geo_file` is specified, this
        is ignored and the path to `geo_file` will be used.
    msh_file_path
        Path of the created Gmsh MSH-file. If `None`, a temporary file will be created.
    mesh_algorithm
        The mesh generation algorithm to use (meshadapt, del2d, front2d).
    clscale
        Mesh element size scaling factor.
    options
        Other options to control the meshing procedure of Gmsh. See
        http://geuz.org/gmsh/doc/texinfo/gmsh.html#Command_002dline-options for all available options.
    refinement_steps
        Number of refinement steps to do after the initial meshing.

    Returns
    -------
    grid
        The generated :class:`~pymor.grids.gmsh.GmshGrid`.
    boundary_info
        The generated :class:`~pymor.grids.gmsh.GmshBoundaryInfo`.
    """
    assert domain_description is None or geo_file is None
    logger = getLogger('pymor.domaindiscretizers.gmsh.discretize_gmsh')

    # run Gmsh; initial meshing
    logger.info('Checking for Gmsh ...')

    # when we are running MPI parallel and Gmsh is compiled with MPI support,
    # we have to make sure Gmsh does not notice the MPI environment or it will fail.
    env = {k: v for k, v in os.environ.items() if 'MPI' not in k.upper()}
    try:
        version = subprocess.check_output(['gmsh', '--version'],
                                          stderr=subprocess.STDOUT,
                                          env=env).decode()
    except (subprocess.CalledProcessError, OSError):
        raise GmshError(
            'Could not find Gmsh.'
            ' Please ensure that the gmsh binary (http://geuz.org/gmsh/) is in your PATH.'
        )

    logger.info('Found version ' + version.strip())

    def discretize_PolygonalDomain():
        # combine points and holes, since holes are points, too, and have to be stored as such.
        points = [domain_description.points]
        points.extend(domain_description.holes)

        return points, domain_description.boundary_types

    def discretize_RectDomain():
        points = [[
            domain_description.domain[0].tolist(),
            [domain_description.domain[1][0], domain_description.domain[0][1]],
            domain_description.domain[1].tolist(),
            [domain_description.domain[0][0], domain_description.domain[1][1]]
        ]]
        boundary_types = {domain_description.bottom: [1]}
        if domain_description.right not in boundary_types:
            boundary_types[domain_description.right] = [2]
        else:
            boundary_types[domain_description.right].append(2)
        if domain_description.top not in boundary_types:
            boundary_types[domain_description.top] = [3]
        else:
            boundary_types[domain_description.top].append(3)
        if domain_description.left not in boundary_types:
            boundary_types[domain_description.left] = [4]
        else:
            boundary_types[domain_description.left].append(4)

        if None in boundary_types:
            del boundary_types[None]

        return points, boundary_types

    # these two are referenced in a finally block, but were left undefined in some paths
    geo_file, msh_file = None, None
    try:
        # When a |PolygonalDomain| or |RectDomain| has to be discretized create a Gmsh GE0-file and write all data.
        if domain_description is not None:
            logger.info('Writing Gmsh geometry file ...')
            # Create a temporary GEO-file if None is specified
            if geo_file_path is None:
                geo_file = tempfile.NamedTemporaryFile(mode='wt',
                                                       delete=False,
                                                       suffix='.geo')
                geo_file_path = geo_file.name
            else:
                geo_file = open(geo_file_path, 'w')

            if isinstance(domain_description, PolygonalDomain):
                points, boundary_types = discretize_PolygonalDomain()
            elif isinstance(domain_description, RectDomain):
                points, boundary_types = discretize_RectDomain()
            else:
                raise NotImplementedError(
                    f'I do not know how to discretize {domain_description}')

            # assign ids to all points and write them to the GEO-file.
            for id, p in enumerate([p for ps in points for p in ps]):
                assert len(p) == 2
                geo_file.write(
                    'Point(' + str(id + 1) + ') = ' +
                    str(p + [0, 0]).replace('[', '{').replace(']', '}') +
                    ';\n')

            # store points and their ids
            point_ids = dict(
                zip([str(p) for ps in points for p in ps],
                    range(1,
                          len([p for ps in points for p in ps]) + 1)))
            # shift points 1 entry to the left.
            points_deque = [collections.deque(ps) for ps in points]
            for ps_d in points_deque:
                ps_d.rotate(-1)
            # create lines by connecting the points with shifted points, such that they form a polygonal chains.
            lines = [[point_ids[str(p0)], point_ids[str(p1)]]
                     for ps, ps_d in zip(points, points_deque)
                     for p0, p1 in zip(ps, ps_d)]
            # assign ids to all lines and write them to the GEO-file.
            for l_id, l in enumerate(lines):
                geo_file.write('Line(' + str(l_id + 1) + ')' + ' = ' +
                               str(l).replace('[', '{').replace(']', '}') +
                               ';\n')

            # form line_loops (polygonal chains), create ids and write them to file.
            line_loops = [[point_ids[str(p)] for p in ps] for ps in points]
            line_loop_ids = range(
                len(lines) + 1,
                len(lines) + len(line_loops) + 1)
            for ll_id, ll in zip(line_loop_ids, line_loops):
                geo_file.write('Line Loop(' + str(ll_id) + ')' + ' = ' +
                               str(ll).replace('[', '{').replace(']', '}') +
                               ';\n')

            # set this here explicitly for string conversion to make sense
            line_loop_ids = list(line_loop_ids)
            # create the surface defined by line loops, starting with the exterior and then the holes.
            geo_file.write(
                'Plane Surface(' + str(line_loop_ids[0] + 1) + ')' + ' = ' +
                str(line_loop_ids).replace('[', '{').replace(']', '}') + ';\n')
            geo_file.write('Physical Surface("boundary") = {' +
                           str(line_loop_ids[0] + 1) + '};\n')

            # write boundaries.
            for boundary_type, bs in boundary_types.items():
                geo_file.write(
                    'Physical Line' + '("' + str(boundary_type) + '")' +
                    ' = ' +
                    str([l_id
                         for l_id in bs]).replace('[', '{').replace(']', '}') +
                    ';\n')

            geo_file.close()
        # When a GEO-File is provided just get the corresponding file path.
        else:
            geo_file_path = geo_file.name
        # Create a temporary MSH-file if no path is specified.
        if msh_file_path is None:
            msh_file = tempfile.NamedTemporaryFile(mode='wt',
                                                   delete=False,
                                                   suffix='.msh')
            msh_file_path = msh_file.name
            msh_file.close()

        tic = time.time()

        # run Gmsh; initial meshing
        logger.info('Calling Gmsh ...')
        cmd = [
            'gmsh', geo_file_path, '-2', '-algo', mesh_algorithm, '-clscale',
            str(clscale), options, '-o', msh_file_path
        ]
        subprocess.check_call(cmd, env=env)

        # run gmsh; perform mesh refinement
        cmd = ['gmsh', msh_file_path, '-refine', '-o', msh_file_path]
        for i in range(refinement_steps):
            logger.info(f'Performing Gmsh refinement step {i+1}')
            subprocess.check_call(cmd, env=env)

        toc = time.time()
        t_gmsh = toc - tic
        logger.info(f'Gmsh took {t_gmsh} s')

        # Create |GmshGrid| and |GmshBoundaryInfo| form the just created MSH-file.
        grid, bi = load_gmsh(open(msh_file_path))
    finally:
        # delete tempfiles if they were created beforehand.
        if isinstance(geo_file, tempfile._TemporaryFileWrapper):
            os.remove(geo_file_path)
        if isinstance(msh_file, tempfile._TemporaryFileWrapper):
            os.remove(msh_file_path)

    return grid, bi