diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bc7599d4ce9d16f916e91e1bb109ac4f426023ff..7c2f2c973ec844128cbbe291a6008a1b894344a0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,3 +34,12 @@ Python 3.5 POCL: - pocl except: - tags +Documentation: + script: + - EXTRA_INSTALL="numpy" + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-docs.sh + - ". ./build-docs.sh" + tags: + - python3.5 + only: + - master diff --git a/doc/index.rst b/doc/index.rst index f652d1ba9f72dffe21584f07737ebf1a7b40c4a9..4bde9b27731468d2d65b2fe35c3ee887242debea 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -8,6 +8,7 @@ Contents: mesh discretization + misc Indices and tables ================== diff --git a/doc/misc.rst b/doc/misc.rst new file mode 100644 index 0000000000000000000000000000000000000000..488262acbc29882a93c25d4ba137d77ac37e8660 --- /dev/null +++ b/doc/misc.rst @@ -0,0 +1,79 @@ +.. _installation: + +Installation +============ + +This command should install :mod:`meshmode`:: + + pip install meshmode + +(Note the extra "."!) + +You may need to run this with :command:`sudo`. +If you don't already have `pip `_, +run this beforehand:: + + curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py + python get-pip.py + +For a more manual installation, `download the source +`_, unpack it, and say:: + + python setup.py install + +You may also clone its git repository:: + + git clone --recursive git://github.com/inducer/meshmode + git clone --recursive http://git.tiker.net/trees/meshmode.git + +User-visible Changes +==================== + +Version 2016.1 +-------------- +.. note:: + + This version is currently under development. You can get snapshots from + meshmode's `git repository `_ + +.. _license: + +Licensing +========= + +:mod:`meshmode` is licensed to you under the MIT/X Consortium license: + +Copyright (c) 2014-16 Andreas Klöckner and Contributors. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +Acknowledgments +=============== + +Andreas Klöckner's work on :mod:`meshmode` was supported in part by + +* US Navy ONR grant number N00014-14-1-0117 +* the US National Science Foundation under grant numbers DMS-1418961 and CCF-1524433. + +AK also gratefully acknowledges a hardware gift from Nvidia Corporation. The +views and opinions expressed herein do not necessarily reflect those of the +funding agencies. diff --git a/meshmode/discretization/__init__.py b/meshmode/discretization/__init__.py index e1ed150f132a516d409757567d4b5219db669235..45fdc22e4b64f93dfd8c04f34d0e5d29cbecf7a6 100644 --- a/meshmode/discretization/__init__.py +++ b/meshmode/discretization/__init__.py @@ -311,7 +311,7 @@ class Discretization(object): knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") knl = lp.tag_inames(knl, dict(k="g.0")) - knl = lp.tag_data_axes(knl, "result", + knl = lp.tag_array_axes(knl, "result", "stride:auto,stride:auto,stride:auto") return knl diff --git a/meshmode/mesh/__init__.py b/meshmode/mesh/__init__.py index 5c2b5c1cb6ec7be972ed1ca15e0eb2db1c4b852c..4e2f1f0ace6ddf7ca50ca0d95fde980414563dcb 100644 --- a/meshmode/mesh/__init__.py +++ b/meshmode/mesh/__init__.py @@ -292,6 +292,42 @@ class SimplexElementGroup(MeshElementGroup): from modepy.tools import unit_vertices return unit_vertices(self.dim) + +class TensorProductElementGroup(MeshElementGroup): + def __init__(self, order, vertex_indices, nodes, + element_nr_base=None, node_nr_base=None, + unit_nodes=None): + """ + :arg order: the mamximum total degree used for interpolation. + :arg nodes: ``[ambient_dim, nelements, nunit_nodes]`` + The nodes are assumed to be mapped versions of *unit_nodes*. + :arg unit_nodes: ``[dim, nunit_nodes]`` + The unit nodes of which *nodes* is a mapped + version. + + Do not supply *element_nr_base* and *node_nr_base*, they will be + automatically assigned. + """ + + if not issubclass(vertex_indices.dtype.type, np.integer): + raise TypeError("vertex_indices must be integral") + + dims = unit_nodes.shape[0] + + if vertex_indices.shape[-1] != 2**dims: + raise ValueError("vertex_indices has wrong number of vertices per " + "element. expected: %d, got: %d" % (dims+1, + vertex_indices.shape[-1])) + + super(TensorProductElementGroup, self).__init__(order, vertex_indices, nodes, + element_nr_base, node_nr_base, unit_nodes) + + def face_vertex_indices(self): + raise NotImplementedError() + + def vertex_unit_coordinates(self): + raise NotImplementedError() + # }}} diff --git a/meshmode/mesh/io.py b/meshmode/mesh/io.py index e27f8de352016e6f70c86be905fd4e247213d72b..f3cdaea853d0f9441d7d463f2155ba8d95004d1e 100644 --- a/meshmode/mesh/io.py +++ b/meshmode/mesh/io.py @@ -27,18 +27,23 @@ from six.moves import range, zip import numpy as np from meshpy.gmsh_reader import ( # noqa - GmshMeshReceiverBase, FileSource, LiteralSource) + GmshMeshReceiverBase, ScriptSource, FileSource, LiteralSource, + ScriptWithFilesSource, + GmshSimplexElementBase, + GmshTensorProductElementBase) __doc__ = """ +.. autoclass:: ScriptSource .. autoclass:: FileSource -.. autoclass:: LiteralSource +.. autoclass:: ScriptWithFilesSource .. autofunction:: read_gmsh .. autofunction:: generate_gmsh .. autofunction:: from_meshpy .. autofunction:: from_vertices_and_simplices +.. autofunction:: to_json """ @@ -96,6 +101,9 @@ class GmshMeshReceiver(GmshMeshReceiverBase): for el_type in self.element_types: el_type_hist[el_type] = el_type_hist.get(el_type, 0) + 1 + if not el_type_hist: + raise RuntimeError("empty mesh in gmsh input") + groups = self.groups = [] ambient_dim = self.points.shape[-1] @@ -126,7 +134,8 @@ class GmshMeshReceiver(GmshMeshReceiverBase): # }}} - from meshmode.mesh import SimplexElementGroup, Mesh + from meshmode.mesh import (Mesh, + SimplexElementGroup, TensorProductElementGroup) for group_el_type, ngroup_elements in six.iteritems(el_type_hist): if group_el_type.dimensions != mesh_bulk_dim: @@ -154,21 +163,33 @@ class GmshMeshReceiver(GmshMeshReceiverBase): unit_nodes = (np.array(group_el_type.lexicographic_node_tuples(), dtype=np.float64).T/group_el_type.order)*2 - 1 - group = SimplexElementGroup( - group_el_type.order, - vertex_indices, - nodes, - unit_nodes=unit_nodes - ) + if isinstance(group_el_type, GmshSimplexElementBase): + group = SimplexElementGroup( + group_el_type.order, + vertex_indices, + nodes, + unit_nodes=unit_nodes + ) + + if group.dim == 2: + from meshmode.mesh.processing import flip_simplex_element_group + group = flip_simplex_element_group(vertices, group, + np.ones(ngroup_elements, np.bool)) + + elif isinstance(group_el_type, GmshTensorProductElementBase): + group = TensorProductElementGroup( + group_el_type.order, + vertex_indices, + nodes, + unit_nodes=unit_nodes + ) + else: + raise NotImplementedError("gmsh element type: %s" + % type(group_el_type).__name__) # Gmsh seems to produce elements in the opposite orientation # of what we like. Flip them all. - if group.dim == 2: - from meshmode.mesh.processing import flip_simplex_element_group - group = flip_simplex_element_group(vertices, group, - np.ones(ngroup_elements, np.bool)) - groups.append(group) return Mesh( @@ -195,8 +216,9 @@ def read_gmsh(filename, force_ambient_dim=None): return recv.get_mesh() -def generate_gmsh(source, dimensions, order=None, other_options=[], - extension="geo", gmsh_executable="gmsh", force_ambient_dim=None): +def generate_gmsh(source, dimensions=None, order=None, other_options=[], + extension="geo", gmsh_executable="gmsh", force_ambient_dim=None, + output_file_name="output.msh"): """Run :command:`gmsh` on the input given by *source*, and return a :class:`meshmode.mesh.Mesh` based on the result. @@ -293,4 +315,57 @@ def from_vertices_and_simplices(vertices, simplices, order=1, fix_orientation=Fa # }}} +# {{{ to_json + +def to_json(mesh): + """Return a JSON-able Python data structure for *mesh*. The structure directly + reflects the :class:`Mesh` data structure.""" + + def btag_to_json(btag): + if isinstance(btag, str): + return btag + else: + return btag.__name__ + + def group_to_json(group): + return { + "type": type(group).__name__, + "order": group.order, + "vertex_indices": group.vertex_indices.tolist(), + "nodes": group.nodes.tolist(), + "unit_nodes": group.unit_nodes.tolist(), + "element_nr_base": group.element_nr_base, + "node_nr_base": group.node_nr_base, + "dim": group.dim, + } + + from meshmode import DataUnavailable + + def nodal_adjacency_to_json(mesh): + try: + na = mesh.nodal_adjacency + except DataUnavailable: + return None + + return { + "neighbors_starts": na.neighbors_starts.tolist(), + "neighbors": na.neighbors.tolist(), + } + + return { + "version": 0, + "vertices": mesh.vertices.tolist(), + "groups": [group_to_json(group) for group in mesh.groups], + "nodal_adjacency": nodal_adjacency_to_json(mesh), + # not yet implemented + "facial_adjacency_groups": None, + "boundary_tags": [btag_to_json(btag) for btag in mesh.boundary_tags], + "btag_to_index": dict( + (btag_to_json(btag), value) + for btag, value in six.iteritems(mesh.btag_to_index)), + } + +# }}} + + # vim: foldmethod=marker diff --git a/meshmode/version.py b/meshmode/version.py index 1da70a1e09bc16f4b7e677505af6cf4dd4502b86..d26fbc2f9341a880b1119e7e6079bd51e59e11b9 100644 --- a/meshmode/version.py +++ b/meshmode/version.py @@ -1,2 +1,2 @@ -VERSION = (2015, 1) +VERSION = (2016, 1) VERSION_TEXT = ".".join(str(i) for i in VERSION) diff --git a/requirements.txt b/requirements.txt index 8e99cd7fdd25e578a5dc11c56f4c43506d18974f..6ed102e6d1309b0e65dcfec56e15aac08448a7d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,11 @@ numpy -git+git://github.com/inducer/modepy -git+git://github.com/pyopencl/pyopencl -git+git://github.com/inducer/islpy -git+git://github.com/inducer/loopy +git+https://github.com/inducer/meshpy.git +git+https://github.com/inducer/modepy.git +git+https://github.com/pyopencl/pyopencl.git +git+https://github.com/inducer/islpy.git +git+https://github.com/inducer/loopy.git # required by pytential, which is in turn needed for some tests -git+git://github.com/inducer/boxtree -git+git://github.com/inducer/sumpy -git+git://github.com/inducer/pytential +git+https://github.com/inducer/boxtree.git +git+https://github.com/inducer/sumpy.git +git+https://github.com/inducer/pytential.git diff --git a/test/test_meshmode.py b/test/test_meshmode.py index 7624fbe93dee63e467f04c6f296726b12ace1e37..01e6c35cd95cd5d87270d43962d57bd25b605514 100644 --- a/test/test_meshmode.py +++ b/test/test_meshmode.py @@ -802,6 +802,29 @@ def test_nd_quad_submesh(dims): # }}} +# {{{ test_quad_mesh + +def test_quad_mesh(): + from meshmode.mesh.io import generate_gmsh, ScriptWithFilesSource + print("BEGIN GEN") + mesh = generate_gmsh( + ScriptWithFilesSource( + """ + Merge "blob-2d.step"; + Mesh.CharacteristicLengthMax = 0.05; + Recombine Surface "*" = 0.0001; + Mesh 2; + Save "output.msh"; + """, + ["blob-2d.step"]), + force_ambient_dim=2, + ) + print("END GEN") + print(mesh.nelements) + +# }}} + + if __name__ == "__main__": import sys if len(sys.argv) > 1: