diff --git a/.gitignore b/.gitignore index feac29bf0dc2a0c0ce9af09a2085e4cd718891ce..e090b1d87d5c4921b7bd12087cccd1b157c9cf07 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ distribute*egg distribute*tar.gz a.out *.vtu + +.cache 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 <https://pypi.python.org/pypi/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 +<http://pypi.python.org/pypi/meshmode>`_, 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 <https://github.com/inducer/meshmode>`_ + +.. _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/doc/upload-docs.sh b/doc/upload-docs.sh index 25c97f52894584dfd72f3955c20026f475136e39..2c219a8c84b8f6b1835a9fb6ffb25b2274461616 100755 --- a/doc/upload-docs.sh +++ b/doc/upload-docs.sh @@ -1,3 +1,3 @@ #! /bin/sh -rsync --progress --verbose --archive --delete _build/html/* doc-upload:doc/meshmode +rsync --verbose --archive --delete _build/html/* doc-upload:doc/meshmode diff --git a/meshmode/discretization/connection/refinement.py b/meshmode/discretization/connection/refinement.py index 75604ec05dd945cde45e0e6cdd55724f88083c3f..4477a9bfd0fdbf4e24659c059997e1e6156fb894 100644 --- a/meshmode/discretization/connection/refinement.py +++ b/meshmode/discretization/connection/refinement.py @@ -161,7 +161,9 @@ def make_refinement_connection(refiner, coarse_discr, group_factory): coarse_mesh = refiner.get_previous_mesh() fine_mesh = refiner.last_mesh - assert coarse_discr.mesh is coarse_mesh + if coarse_discr.mesh != coarse_mesh: + raise ValueError( + "course_discr must live on the same mesh given to the refiner!") from meshmode.discretization import Discretization fine_discr = Discretization( diff --git a/meshmode/discretization/visualization.py b/meshmode/discretization/visualization.py index 956e301533b3222db7d50916ed381222b26e416a..d68ec0c58078e68417721c8205fc3f24021d0bad 100644 --- a/meshmode/discretization/visualization.py +++ b/meshmode/discretization/visualization.py @@ -291,7 +291,7 @@ def write_nodal_adjacency_vtk_file(file_name, mesh, compressor=None,): nconnections = len(adj.neighbors) connections = np.empty((nconnections, 2), dtype=np.int32) - nb_starts = adj.neighbor_starts + nb_starts = adj.neighbors_starts for iel in range(mesh.nelements): connections[nb_starts[iel]:nb_starts[iel+1], 0] = iel diff --git a/meshmode/mesh/__init__.py b/meshmode/mesh/__init__.py index 4e2f1f0ace6ddf7ca50ca0d95fde980414563dcb..da0fdfe242536997dc3dc2f71c465f2eddf07560 100644 --- a/meshmode/mesh/__init__.py +++ b/meshmode/mesh/__init__.py @@ -490,6 +490,7 @@ class Mesh(Record): face_id_dtype = np.int8 def __init__(self, vertices, groups, skip_tests=False, + node_vertex_consistency_tolerance=None, nodal_adjacency=False, facial_adjacency_groups=False, boundary_tags=None, @@ -500,6 +501,9 @@ class Mesh(Record): :arg skip_tests: Skip mesh tests, in case you want to load a broken mesh anyhow and then fix it inside of this data structure. + :arg node_vertex_consistency_tolerance: If *False*, do not check + for consistency between vertex and nodal data. If *None*, use + the (small, near FP-epsilon) tolerance. :arg nodal_adjacency: One of three options: *None*, in which case this information will be deduced from vertex adjacency. *False*, in which case @@ -576,15 +580,16 @@ class Mesh(Record): ) if not skip_tests: - assert _test_node_vertex_consistency(self) + assert _test_node_vertex_consistency( + self, node_vertex_consistency_tolerance) for g in self.groups: assert g.vertex_indices.dtype == self.vertex_id_dtype if nodal_adjacency: - assert nodal_adjacency.neighbor_starts.shape == (self.nelements+1,) + assert nodal_adjacency.neighbors_starts.shape == (self.nelements+1,) assert len(nodal_adjacency.neighbors.shape) == 1 - assert nodal_adjacency.neighbor_starts.dtype == self.element_id_dtype + assert nodal_adjacency.neighbors_starts.dtype == self.element_id_dtype assert nodal_adjacency.neighbors.dtype == self.element_id_dtype if facial_adjacency_groups: @@ -691,7 +696,7 @@ class Mesh(Record): # {{{ node-vertex consistency test -def _test_node_vertex_consistency_simplex(mesh, mgrp): +def _test_node_vertex_consistency_simplex(mesh, mgrp, tol): if mgrp.nelements == 0: return True @@ -710,7 +715,8 @@ def _test_node_vertex_consistency_simplex(mesh, mgrp): np.sum((map_vertices - grp_vertices)**2, axis=0), axis=-1)) - tol = 1e3 * np.finfo(per_element_vertex_errors.dtype).eps + if tol is None: + tol = 1e3 * np.finfo(per_element_vertex_errors.dtype).eps from meshmode.mesh.processing import find_bounding_box @@ -723,14 +729,17 @@ def _test_node_vertex_consistency_simplex(mesh, mgrp): return True -def _test_node_vertex_consistency(mesh): +def _test_node_vertex_consistency(mesh, tol): """Ensure that order of by-index vertices matches that of mapped unit vertices. """ + if tol is False: + return + for mgrp in mesh.groups: if isinstance(mgrp, SimplexElementGroup): - assert _test_node_vertex_consistency_simplex(mesh, mgrp) + assert _test_node_vertex_consistency_simplex(mesh, mgrp, tol) else: from warnings import warn warn("not implemented: node-vertex consistency check for '%s'" diff --git a/meshmode/mesh/generation.py b/meshmode/mesh/generation.py index 65761586e01c8b0aa60f82d6f8b4bc00941eeed7..ce2a5f236d0ec524a411c78c87b7904abd423db6 100644 --- a/meshmode/mesh/generation.py +++ b/meshmode/mesh/generation.py @@ -175,7 +175,10 @@ def qbx_peanut(t): # {{{ make_curve_mesh -def make_curve_mesh(curve_f, element_boundaries, order): +def make_curve_mesh(curve_f, element_boundaries, order, + unit_nodes=None, + node_vertex_consistency_tolerance=None, + return_parametrization_points=False): """ :arg curve_f: A callable representing a parametrization for a curve, accepting a vector of point locations and returning @@ -183,15 +186,20 @@ def make_curve_mesh(curve_f, element_boundaries, order): :arg element_boundaries: a vector of element boundary locations in :math:`[0,1]`, in order. 0 must be the first entry, 1 the last one. - :returns: a :class:`meshmode.mesh.Mesh` + :arg unit_nodes: if given, the unit nodes to use. Must have shape + ``(dim, nnoodes)``. + :returns: a :class:`meshmode.mesh.Mesh`, or if *return_parametrization_points* + is True, a tuple ``(mesh, par_points)``, where *par_points* is an array of + parametrization points. """ assert element_boundaries[0] == 0 assert element_boundaries[-1] == 1 nelements = len(element_boundaries) - 1 - unodes = mp.warp_and_blend_nodes(1, order) - nodes_01 = 0.5*(unodes+1) + if unit_nodes is None: + unit_nodes = mp.warp_and_blend_nodes(1, order) + nodes_01 = 0.5*(unit_nodes+1) vertices = curve_f(element_boundaries) @@ -200,7 +208,8 @@ def make_curve_mesh(curve_f, element_boundaries, order): # (el_nr, node_nr) t = el_starts[:, np.newaxis] + el_lengths[:, np.newaxis]*nodes_01 - nodes = curve_f(t.ravel()).reshape(vertices.shape[0], nelements, -1) + t = t.ravel() + nodes = curve_f(t).reshape(vertices.shape[0], nelements, -1) from meshmode.mesh import Mesh, SimplexElementGroup egroup = SimplexElementGroup( @@ -210,12 +219,18 @@ def make_curve_mesh(curve_f, element_boundaries, order): np.arange(1, nelements+1, dtype=np.int32) % nelements, ]).T, nodes=nodes, - unit_nodes=unodes) + unit_nodes=unit_nodes) - return Mesh( + mesh = Mesh( vertices=vertices, groups=[egroup], nodal_adjacency=None, - facial_adjacency_groups=None) + facial_adjacency_groups=None, + node_vertex_consistency_tolerance=node_vertex_consistency_tolerance) + + if return_parametrization_points: + return mesh, t + else: + return mesh # }}} diff --git a/meshmode/mesh/io.py b/meshmode/mesh/io.py index a5bbe20492ae25d59b6cf7a4a3cf2e2e2df4d4f4..f3cdaea853d0f9441d7d463f2155ba8d95004d1e 100644 --- a/meshmode/mesh/io.py +++ b/meshmode/mesh/io.py @@ -43,6 +43,7 @@ __doc__ = """ .. autofunction:: generate_gmsh .. autofunction:: from_meshpy .. autofunction:: from_vertices_and_simplices +.. autofunction:: to_json """ @@ -314,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/mesh/refinement/__init__.py b/meshmode/mesh/refinement/__init__.py index 2154d45195412c6c012a54ace9b8fbcb07561055..cb001e3266192a9280e313ae95ef6b93ff988d7d 100644 --- a/meshmode/mesh/refinement/__init__.py +++ b/meshmode/mesh/refinement/__init__.py @@ -348,6 +348,10 @@ class Refiner(object): indicating which elements should be split. """ + if len(refine_flags) != self.last_mesh.nelements: + raise ValueError("length of refine_flags does not match " + "element count of last generated mesh") + #vertices and groups for next generation nvertices = len(self.last_mesh.vertices[0]) @@ -845,7 +849,7 @@ class Refiner(object): assert neighbors_starts[-1] == len(neighbors) from meshmode.mesh import NodalAdjacency - return NodalAdjacency(neighbor_starts=neighbors_starts, neighbors=neighbors) + return NodalAdjacency(neighbors_starts=neighbors_starts, neighbors=neighbors) # }}} diff --git a/meshmode/mesh/refinement/utils.py b/meshmode/mesh/refinement/utils.py index 9a78c31d6cd2dbe5bdb2f3729815b06630c96c7f..0d0476f3454a7079ebd4df3b1ace1b604355edde 100644 --- a/meshmode/mesh/refinement/utils.py +++ b/meshmode/mesh/refinement/utils.py @@ -64,7 +64,7 @@ def check_nodal_adj_against_geometry(mesh, tol=1e-12): for igrp, grp in enumerate(mesh.groups): for iel_grp in range(grp.nelements): iel_g = group_and_iel_to_global_iel(igrp, iel_grp) - nb_starts = nadj.neighbor_starts + nb_starts = nadj.neighbors_starts for nb_iel_g in nadj.neighbors[nb_starts[iel_g]:nb_starts[iel_g+1]]: connected_to_element_connectivity[iel_g].add(nb_iel_g) diff --git a/meshmode/mesh/visualization.py b/meshmode/mesh/visualization.py index caa9c1beb3cdb7123a3c9798ce04a3c882b14f93..601086c1570e1b2a87f57b68361010774a78aeb7 100644 --- a/meshmode/mesh/visualization.py +++ b/meshmode/mesh/visualization.py @@ -1,6 +1,4 @@ -from __future__ import division -from __future__ import absolute_import -from six.moves import range +from __future__ import division, absolute_import __copyright__ = "Copyright (C) 2014 Andreas Kloeckner" @@ -25,6 +23,7 @@ THE SOFTWARE. """ import numpy as np +from six.moves import range # {{{ draw_2d_mesh 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)