diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..609ef1425d0a0df37ace94b784f8ba346efe7b15 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,36 @@ +Python 2.7 AMD CPU: + script: + - export PY_EXE=python2.7 + - export PYOPENCL_TEST=amd:pu + - export EXTRA_INSTALL="numpy mako" + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh + - ". ./build-and-test-py-project.sh" + tags: + - python2.7 + - amd-cl-cpu + except: + - tags +Python 2.7 POCL: + script: + - export PY_EXE=python2.7 + - export PYOPENCL_TEST=portable + - export EXTRA_INSTALL="numpy mako" + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh + - ". ./build-and-test-py-project.sh" + tags: + - python2.7 + - pocl + except: + - tags +Python 3.4 POCL: + script: + - export PY_EXE=python3.4 + - export PYOPENCL_TEST=portable + - export EXTRA_INSTALL="numpy mako" + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh + - ". ./build-and-test-py-project.sh" + tags: + - python3.4 + - pocl + except: + - tags diff --git a/README.rst b/README.rst index 929ac20b35e2b6103772839755b69b76f44f1e3f..3ca2d6c34f44e78a074468ac94b3547b3c603742 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,7 @@ meshmode ======== +* `Source code on Github <https://github.com/inducer/meshmode>`_ +* `Documentation <https://documen.tician.de/meshmode>`_ + .. TODO diff --git a/doc/_static/akdoc.css b/doc/_static/akdoc.css deleted file mode 100644 index d8b61e3ff7a358e5d5c0f132b5040ec7c3f43e42..0000000000000000000000000000000000000000 --- a/doc/_static/akdoc.css +++ /dev/null @@ -1,59 +0,0 @@ -pre { - line-height: 110%; -} - -.footer { - background-color: #eee; -} - -body > div.container { - margin-top:10px; -} - -dd { - margin-left: 40px; -} - -tt.descname { - font-size: 100%; -} - -code { - color: rgb(51,51,51); -} - -h1 { - padding-bottom:7px; - border-bottom: 1px solid #ccc; -} - -h2 { - padding-bottom:5px; - border-bottom: 1px solid #ccc; -} - -h3 { - padding-bottom:5px; - border-bottom: 1px solid #ccc; -} - -.rubric { - font-size: 120%; - padding-bottom:1px; - border-bottom: 1px solid #ccc; -} - -.headerlink { - padding-left: 1ex; - padding-right: 1ex; -} - -a.headerlink:hover { - text-decoration: none; -} - -blockquote p { - font-size: 100%; - font-weight: normal; - line-height: normal; -}; diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html deleted file mode 100644 index 400e7ec1d49677537aff6bf744e2803ef5c01e9e..0000000000000000000000000000000000000000 --- a/doc/_templates/layout.html +++ /dev/null @@ -1,2 +0,0 @@ -{% extends "!layout.html" %} -{% set bootswatch_css_custom = ['_static/akdoc.css']%} diff --git a/doc/conf.py b/doc/conf.py index f43c6d3596d715838132b0dfc6bfbe6d29eaa386..1e5a0227f1671b4097dfd3b92417b3c7729ac195 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -103,26 +103,24 @@ pygments_style = 'sphinx' # -- Options for HTML output ---------------------------------------------- -try: - import sphinx_bootstrap_theme -except: - from warnings import warn - warn("I would like to use the sphinx bootstrap theme, but can't find it.\n" - "'pip install sphinx_bootstrap_theme' to fix.") -else: - # Activate the theme. - html_theme = 'bootstrap' - html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() - - # Theme options are theme-specific and customize the look and feel of a theme - # further. For a list of options available for each theme, see the - # documentation. - html_theme_options = { - "navbar_fixed_top": "true", - "navbar_site_name": "Contents", - 'bootstrap_version': '3', - 'source_link_position': 'footer', +html_theme = "alabaster" + +html_theme_options = { + "extra_nav_links": { + "🚀 Github": "https://github.com/inducer/meshmode", + "💾 Download Releases": "https://pypi.python.org/pypi/meshmode", } + } + +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', + 'searchbox.html', + ] +} + # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] diff --git a/examples/multiple-meshes.py b/examples/multiple-meshes.py new file mode 100644 index 0000000000000000000000000000000000000000..84918e1779a34932ed6269a8884e30e63bb846d0 --- /dev/null +++ b/examples/multiple-meshes.py @@ -0,0 +1,26 @@ +from __future__ import division + +import numpy as np # noqa + + +order = 4 + + +def main(): + from meshmode.mesh.generation import ( # noqa + make_curve_mesh, starfish) + mesh1 = make_curve_mesh(starfish, np.linspace(0, 1, 20), 4) + + from meshmode.mesh.processing import affine_map, merge_disjoint_meshes + mesh2 = affine_map(mesh1, b=np.array([2, 3])) + + mesh = merge_disjoint_meshes((mesh1, mesh2)) + + from meshmode.mesh.visualization import draw_2d_mesh + draw_2d_mesh(mesh, set_bounding_box=True) + + import matplotlib.pyplot as pt + pt.show() + +if __name__ == "__main__": + main() diff --git a/examples/plot-connectivity.py b/examples/plot-connectivity.py index 09d795418a05f16c033f3b969862a1eda3ab07e0..068983ca81672295af65cdf3c82112478edac1a5 100644 --- a/examples/plot-connectivity.py +++ b/examples/plot-connectivity.py @@ -2,47 +2,12 @@ from __future__ import division import numpy as np # noqa import pyopencl as cl -import random -import os -order = 4 - -def main(): - cl_ctx = cl.create_some_context() - queue = cl.CommandQueue(cl_ctx) - from meshmode.mesh.generation import ( # noqa - generate_icosphere, generate_icosahedron, - generate_torus) - #mesh = generate_icosphere(1, order=order) - #mesh = generate_icosahedron(1, order=order) - mesh = generate_torus(3, 1, order=order) - from meshmode.mesh.refinement import Refiner - r = Refiner(mesh) - #mesh = r.refine(0) - from meshmode.discretization import Discretization - from meshmode.discretization.poly_element import \ - PolynomialWarpAndBlendGroupFactory - - discr = Discretization( - cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order)) - - from meshmode.discretization.visualization import make_visualizer - vis = make_visualizer(queue, discr, order=1) - os.remove("geometry.vtu") - os.remove("connectivity.vtu") - vis.write_vtk_file("geometry.vtu", [ - ("f", discr.nodes()[0]), - ]) - - from meshmode.discretization.visualization import \ - write_mesh_connectivity_vtk_file - - write_mesh_connectivity_vtk_file("connectivity.vtu", - mesh) +order = 4 -def main2(): +def main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) @@ -50,17 +15,8 @@ def main2(): generate_icosphere, generate_icosahedron, generate_torus) #mesh = generate_icosphere(1, order=order) - #mesh = generate_icosahedron(1, order=order) - mesh = generate_torus(3, 1, order=order) - from meshmode.mesh.refinement import Refiner - r = Refiner(mesh) - - times = random.randint(1, 1) - for time in xrange(times): - flags = np.zeros(len(mesh.groups[0].vertex_indices)) - for i in xrange(0, len(flags)): - flags[i] = random.randint(0, 1) - mesh = r.refine(flags) + mesh = generate_icosahedron(1, order=order) + #mesh = generate_torus(3, 1, order=order) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ @@ -71,18 +27,17 @@ def main2(): from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(queue, discr, order) - os.remove("geometry2.vtu") - os.remove("connectivity2.vtu") - vis.write_vtk_file("geometry2.vtu", [ + + vis.write_vtk_file("geometry.vtu", [ ("f", discr.nodes()[0]), ]) from meshmode.discretization.visualization import \ - write_mesh_connectivity_vtk_file + write_nodal_adjacency_vtk_file - write_mesh_connectivity_vtk_file("connectivity2.vtu", + write_nodal_adjacency_vtk_file("adjacency.vtu", mesh) + if __name__ == "__main__": main() - main2() diff --git a/examples/refinement-playground.py b/examples/refinement-playground.py new file mode 100644 index 0000000000000000000000000000000000000000..9927d6b53a647c043ee706e948d9b3de44f77141 --- /dev/null +++ b/examples/refinement-playground.py @@ -0,0 +1,497 @@ +from __future__ import division, print_function + +import numpy as np # noqa +import pyopencl as cl +import random +import os +order = 1 + + +def main(): + cl_ctx = cl.create_some_context() + queue = cl.CommandQueue(cl_ctx) + + from meshmode.mesh.generation import ( # noqa + generate_icosphere, generate_icosahedron, + generate_torus, generate_box_mesh) + #mesh = generate_icosphere(1, order=order) + #mesh = generate_icosahedron(1, order=order) + #mesh = generate_torus(3, 1, order=order) + mesh = generate_box_mesh(3*(np.linspace(0, 1, 5),)) + from meshmode.mesh.refinement import Refiner + r = Refiner(mesh) + #mesh = r.refine(0) + from meshmode.discretization.poly_element import \ + PolynomialWarpAndBlendGroupFactory + + discr = Discretization(cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order)) + from meshmode.discretization.visualization import make_visualizer + vis = make_visualizer(queue, discr, order) + os.remove("geometry.vtu") + os.remove("connectivity.vtu") + vis.write_vtk_file("geometry.vtu", [ + ("f", discr.nodes()[0]), + ]) + + from meshmode.discretization.visualization import \ + write_mesh_connectivity_vtk_file + + write_mesh_connectivity_vtk_file("connectivity.vtu", + mesh) + + +#construct vertex vertex_index +def get_vertex(mesh, vertex_index): + vertex = np.empty([len(mesh.vertices)]) + for i_cur_dim, cur_dim_coords in enumerate(mesh.vertices): + vertex[i_cur_dim] = cur_dim_coords[vertex_index] + return vertex + + +def test_refiner_connectivity(mesh): + def group_and_iel_to_global_iel(igrp, iel): + return mesh.groups[igrp].element_nr_base + iel + + from meshmode.mesh.tools import make_element_lookup_tree + tree = make_element_lookup_tree(mesh) + + from meshmode.mesh.processing import find_bounding_box + bbox_min, bbox_max = find_bounding_box(mesh) + + extent = bbox_max-bbox_min + cnx = mesh.element_connectivity + nvertices_per_element = len(mesh.groups[0].vertex_indices[0]) + #stores connectivity obtained from geometry using barycentric coordinates + connected_to_element_geometry = np.zeros( + (mesh.nelements, mesh.nelements), dtype=bool) + #store connectivity obtained from mesh's connectivity + connected_to_element_connectivity = np.zeros( + (mesh.nelements, mesh.nelements), dtype=bool) + + import six + for igrp, grp in enumerate(mesh.groups): + iel_base = grp.element_nr_base + for iel_grp in six.moves.range(grp.nelements): + + iel_g = group_and_iel_to_global_iel(igrp, iel_grp) + nb_starts = cnx.neighbors_starts + for nb_iel_g in cnx.neighbors[nb_starts[iel_g]:nb_starts[iel_g+1]]: + connected_to_element_connectivity[nb_iel_g, iel_g] = True + connected_to_element_connectivity[iel_g, nb_iel_g] = True + + for vertex_index in grp.vertex_indices[iel_grp]: + vertex = get_vertex(mesh, vertex_index) + #check which elements touch this vertex + for bounding_igrp, bounding_iel in tree.generate_matches(vertex): + if bounding_igrp == igrp and bounding_iel == iel_grp: + continue + bounding_grp = mesh.groups[bounding_igrp] + + last_bounding_vertex = get_vertex(mesh, + bounding_grp.vertex_indices[bounding_iel][nvertices_per_element-1]) + transformation = np.empty([len(mesh.vertices), nvertices_per_element-1]) + vertex_transformed = vertex - last_bounding_vertex + for ibounding_vertex_index, bounding_vertex_index in enumerate( + bounding_grp.vertex_indices[bounding_iel][:nvertices_per_element-1]): + bounding_vertex = get_vertex(mesh, bounding_vertex_index) + transformation[:,ibounding_vertex_index] = bounding_vertex - last_bounding_vertex + barycentric_coordinates = np.linalg.solve(transformation, vertex_transformed) + is_connected = True + sum_of_coords = 0.0 + for coord in barycentric_coordinates: + if coord < 0: + is_connected = False + sum_of_coords += coord + if sum_of_coords > 1: + is_connected = False + if is_connected: + connected_to_element_geometry[group_and_iel_to_global_iel(bounding_igrp, bounding_iel), + group_and_iel_to_global_iel(igrp, iel_grp)] = True + connected_to_element_geometry[group_and_iel_to_global_iel(igrp, iel_grp), + group_and_iel_to_global_iel(bounding_igrp, bounding_iel)] = True + print("GEOMETRY: ") + print(connected_to_element_geometry) + print("CONNECTIVITY: ") + print(connected_to_element_connectivity) + cmpmatrix = (connected_to_element_geometry == connected_to_element_connectivity) + print(cmpmatrix) + print(np.where(cmpmatrix == False)) # noqa + if not (connected_to_element_geometry == connected_to_element_connectivity).all(): + cmpmatrix = connected_to_element_connectivity == connected_to_element_geometry + for ii, i in enumerate(cmpmatrix): + for ij, j in enumerate(i): + if not j: + print( + ii, ij, + "GEOMETRY: ", + connected_to_element_geometry[ii][ij], + "CONNECTIVITY: ", + connected_to_element_connectivity[ii][ij]) + assert (connected_to_element_geometry == connected_to_element_connectivity).all() + + +def test_refiner_connectivity_efficient(mesh): + def group_and_iel_to_global_iel(igrp, iel): + return mesh.groups[igrp].element_nr_base + iel + + from meshmode.mesh.tools import make_element_lookup_tree + tree = make_element_lookup_tree(mesh) + + from meshmode.mesh.processing import find_bounding_box + bbox_min, bbox_max = find_bounding_box(mesh) + + extent = bbox_max-bbox_min + cnx = mesh.element_connectivity + nvertices_per_element = len(mesh.groups[0].vertex_indices[0]) + #stores connectivity obtained from geometry using barycentric coordinates + #connected_to_element_geometry = np.zeros([mesh.nelements, mesh.nelements], dtype=bool) + #store connectivity obtained from mesh's connectivity + #connected_to_element_connectivity = np.zeros([mesh.nelements, mesh.nelements], dtype=bool) + connected_to_element_geometry = [] + connected_to_element_connectivity = [] + import six + for i in six.moves.range(mesh.nelements): + connected_to_element_geometry.append([]) + connected_to_element_connectivity.append([]) + + for igrp, grp in enumerate(mesh.groups): + iel_base = grp.element_nr_base + for iel_grp in six.moves.range(grp.nelements): + + iel_g = group_and_iel_to_global_iel(igrp, iel_grp) + nb_starts = cnx.neighbors_starts + for nb_iel_g in cnx.neighbors[nb_starts[iel_g]:nb_starts[iel_g+1]]: + if iel_g not in connected_to_element_connectivity[nb_iel_g]: + connected_to_element_connectivity[nb_iel_g].append(iel_g) + if nb_iel_g not in connected_to_element_connectivity[iel_g]: + connected_to_element_connectivity[iel_g].append(nb_iel_g) + #connected_to_element_connectivity[nb_iel_g, iel_g] = True + #connected_to_element_connectivity[iel_g, nb_iel_g] = True + + for vertex_index in grp.vertex_indices[iel_grp]: + vertex = get_vertex(mesh, vertex_index) + #check which elements touch this vertex + for bounding_igrp, bounding_iel in tree.generate_matches(vertex): + if bounding_igrp == igrp and bounding_iel == iel_grp: + continue + bounding_grp = mesh.groups[bounding_igrp] + + last_bounding_vertex = get_vertex(mesh, \ + bounding_grp.vertex_indices[bounding_iel][nvertices_per_element-1]) + transformation = np.empty([len(mesh.vertices), nvertices_per_element-1]) + vertex_transformed = vertex - last_bounding_vertex + for ibounding_vertex_index, bounding_vertex_index in \ + enumerate(bounding_grp.vertex_indices[bounding_iel][:nvertices_per_element-1]): + bounding_vertex = get_vertex(mesh, bounding_vertex_index) + transformation[:,ibounding_vertex_index] = bounding_vertex - last_bounding_vertex + barycentric_coordinates = np.linalg.solve(transformation, vertex_transformed) + is_connected = True + sum_of_coords = 0.0 + for coord in barycentric_coordinates: + if coord < 0: + is_connected = False + sum_of_coords += coord + if sum_of_coords > 1: + is_connected = False + if is_connected: + el1 = group_and_iel_to_global_iel(bounding_igrp, bounding_iel) + el2 = group_and_iel_to_global_iel(igrp, iel_grp) + if el2 not in connected_to_element_geometry[el1]: + connected_to_element_geometry[el1].append(el2) + if el1 not in connected_to_element_geometry[el2]: + connected_to_element_geometry[el2].append(el1) + + from collections import Counter + for i in six.moves.range(mesh.nelements): + assert Counter(connected_to_element_connectivity[i]) \ + == Counter(connected_to_element_geometry[i]) + + +def linear_func(vert): + csum = 0 + for i in vert: + csum += i + #print csum + return csum + + +def sine_func(vert): + #print vert + import math + res = 1 + for i in vert: + res *= math.sin(i*2.0*math.pi) + #print res + return abs(res) * 0.2 + 0.2 + + +def quadratic_func(vert): + csum = 0 + for i in vert: + csum += i * i + return csum * 1.5 + + +def get_function_flags(mesh, function): + import six + from math import sqrt + flags = np.zeros(len(mesh.groups[0].vertex_indices)) + for grp in mesh.groups: + iel_base = grp.element_nr_base + for iel_grp in six.moves.range(grp.nelements): + vertex_indices = grp.vertex_indices[iel_grp] + max_edge_len = 0 + for i in six.moves.range(len(vertex_indices)): + for j in six.moves.range(i+1, len(vertex_indices)): + edge_len = 0 + for k in six.moves.range(len(mesh.vertices)): + edge_len += ( + (mesh.vertices[k, vertex_indices[i]] + - mesh.vertices[k, vertex_indices[j]]) + * (mesh.vertices[k, vertex_indices[i]] + - mesh.vertices[k, vertex_indices[j]])) + edge_len = sqrt(edge_len) + max_edge_len = max(max_edge_len, edge_len) + #print(edge_lens[0], mesh.vertices[0, vertex_indices[i]], mesh.vertices[1, vertex_indices[i]], mesh.vertices[2, vertex_indices[i]]) # noqa + centroid = [0] * len(mesh.vertices) + for j in six.moves.range(len(mesh.vertices)): + centroid[j] += mesh.vertices[j, vertex_indices[i]] + for i in six.moves.range(len(mesh.vertices)): + centroid[i] /= len(vertex_indices) + yes = False + val = function(centroid) + if max_edge_len > val: + flags[iel_grp] = True + return flags + + +def get_corner_flags(mesh): + import six + flags = np.zeros(len(mesh.groups[0].vertex_indices)) + for grp in mesh.groups: + iel_base = grp.element_nr_base + for iel_grp in six.moves.range(grp.nelements): + is_corner_el = False + vertex_indices = grp.vertex_indices[iel_grp] + for i in six.moves.range(len(vertex_indices)): + cur_vertex_corner = True + for j in six.moves.range(len(mesh.vertices)): + print(iel_grp, i, mesh.vertices[j, vertex_indices[i]]) + if mesh.vertices[j, vertex_indices[i]] != 0.0: + cur_vertex_corner = False + if cur_vertex_corner: + is_corner_el = True + break + if is_corner_el: + print(iel_grp) + flags[iel_grp] = True + return flags + + +def get_random_flags(mesh): + flags = np.zeros(len(mesh.groups[0].vertex_indices)) + for i in xrange(0, len(flags)): + flags[i] = random.randint(0, 1) + return flags + + +def refine_and_generate_chart_function(mesh, filename, function): + import six + from time import clock + cl_ctx = cl.create_some_context() + queue = cl.CommandQueue(cl_ctx) + print("NELEMENTS: ", mesh.nelements) + #print mesh + for i in six.moves.range(len(mesh.groups[0].vertex_indices[0])): + for k in six.moves.range(len(mesh.vertices)): + print(mesh.vertices[k, i]) + + #test_refiner_connectivity(mesh); + from meshmode.mesh.refinement import Refiner + r = Refiner(mesh) + #random.seed(0) + poss_flags = np.ones(len(mesh.groups[0].vertex_indices)) + #times = 3 + num_elements = [] + time_t = [] + #nelements = mesh.nelements + while True: + print("NELS:", mesh.nelements) + #flags = get_corner_flags(mesh) + flags = get_function_flags(mesh, function) + nels = 0 + for i in flags: + if i: + nels += 1 + if nels == 0: + break + print("LKJASLFKJALKASF:", nels) + num_elements.append(nels) + #flags = get_corner_flags(mesh) + beg = clock() + mesh = r.refine(flags) + end = clock() + time_taken = end - beg + time_t.append(time_taken) + #if nelements == mesh.nelements: + #break + #nelements = mesh.nelements + #from meshmode.mesh.visualization import draw_2d_mesh + #draw_2d_mesh(mesh, True, True, True, fill=None) + #import matplotlib.pyplot as pt + #pt.show() + + #poss_flags = np.zeros(len(mesh.groups[0].vertex_indices)) + #for i in xrange(0, len(flags)): + # poss_flags[i] = flags[i] + #for i in xrange(len(flags), len(poss_flags)): + # poss_flags[i] = 1 + + import matplotlib.pyplot as plt + import matplotlib.pyplot as pt + pt.xlabel('Number of elements being refined') + pt.ylabel('Time taken') + pt.plot(num_elements, time_t, "o") + pt.savefig(filename, format='pdf') + pt.clf() + print('DONE REFINING') + ''' + flags = np.zeros(len(mesh.groups[0].vertex_indices)) + flags[0] = 1 + flags[1] = 1 + mesh = r.refine(flags) + flags = np.zeros(len(mesh.groups[0].vertex_indices)) + flags[0] = 1 + flags[1] = 1 + flags[2] = 1 + mesh = r.refine(flags) + ''' + #test_refiner_connectivity_efficient(mesh) + #r.print_rays(70) + #r.print_rays(117) + #r.print_hanging_elements(10) + #r.print_hanging_elements(117) + #r.print_hanging_elements(757) + #from meshmode.mesh.visualization import draw_2d_mesh + #draw_2d_mesh(mesh, False, False, False, fill=None) + #import matplotlib.pyplot as pt + #pt.show() + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + PolynomialWarpAndBlendGroupFactory + discr = Discretization( + cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order)) + from meshmode.discretization.visualization import make_visualizer + vis = make_visualizer(queue, discr, order) + os.remove("connectivity2.vtu") + os.remove("geometry2.vtu") + vis.write_vtk_file("geometry2.vtu", [ + ("f", discr.nodes()[0]), + ]) + + from meshmode.discretization.visualization import \ + write_mesh_connectivity_vtk_file + + write_mesh_connectivity_vtk_file("connectivity2.vtu", + mesh) + + +def main2(): + from meshmode.mesh.generation import ( # noqa + generate_icosphere, generate_icosahedron, + generate_torus, generate_regular_rect_mesh, + generate_box_mesh) +# mesh = generate_icosphere(1, order=order) + #mesh = generate_icosahedron(1, order=order) + #mesh = generate_torus(3, 1, order=order) + #mesh = generate_regular_rect_mesh() + #mesh = generate_box_mesh(3*(np.linspace(0, 3, 5),)) + #mesh = generate_box_mesh(3*(np.linspace(0, 1, 3),)) + mesh = generate_box_mesh(3*(np.linspace(0, 1, 5),)) + refine_and_generate_chart_function(mesh, "plot.pdf", sine_func) + + +def all_refine(num_mesh, depth, fname): + import six + from meshmode.mesh.generation import ( # noqa + generate_icosphere, generate_icosahedron, + generate_torus, generate_regular_rect_mesh, + generate_box_mesh) + import timeit + nelements = [] + runtimes = [] + for el_fact in six.moves.range(2, num_mesh+2): + mesh = generate_box_mesh(3*(np.linspace(0, 1, el_fact),)) + from meshmode.mesh.refinement import Refiner + r = Refiner(mesh) + for time in xrange(depth): + flags = np.ones(len(mesh.groups[0].vertex_indices)) + if time < depth-1: + mesh = r.refine(flags) + else: + start = timeit.default_timer() + mesh = r.refine(flags) + stop = timeit.default_timer() + nelements.append(mesh.nelements) + runtimes.append(stop-start) + #test_refiner_connectivity_efficient(mesh) + import matplotlib.pyplot as pt + pt.plot(nelements, runtimes, "o") + pt.savefig(fname) + pt.clf() + #pt.show() + + +def uniform_refine(num_mesh, fract, depth, fname): + import six + from meshmode.mesh.generation import ( # noqa + generate_icosphere, generate_icosahedron, + generate_torus, generate_regular_rect_mesh, + generate_box_mesh) + import timeit + nelements = [] + runtimes = [] + for el_fact in six.moves.range(2, num_mesh+2): + mesh = generate_box_mesh(3*(np.linspace(0, 1, el_fact),)) + from meshmode.mesh.refinement import Refiner + r = Refiner(mesh) + all_els = range(mesh.nelements) + for time in xrange(depth): + flags = np.zeros(mesh.nelements) + from random import shuffle + shuffle(all_els) + nels_this_round = 0 + for i in six.moves.range(len(all_els)): + if i / len(flags) > fract: + break + flags[all_els[i]] = 1 + nels_this_round += 1 + + if time < depth-1: + mesh = r.refine(flags) + else: + start = timeit.default_timer() + mesh = r.refine(flags) + stop = timeit.default_timer() + nelements.append(mesh.nelements) + runtimes.append(stop-start) + all_els = [] + for i in six.moves.range(len(flags)): + if flags[i]: + all_els.append(i) + for i in six.moves.range(len(flags), mesh.nelements): + all_els.append(i) + test_refiner_connectivity(mesh) + import matplotlib.pyplot as pt + pt.plot(nelements, runtimes, "o") + pt.savefig(fname) + pt.clf() + + +if __name__ == "__main__": + print("HEREERERE") + #all_refine(3, 2, 'all_a.pdf') + #all_refine(3, 3, 'all_b.pdf') + #uniform_refine(3, 0.2, 3, 'uniform_a.pdf') + main2() diff --git a/meshmode/__init__.py b/meshmode/__init__.py index e1a4c5e6aedbffd76ceed3f426ee347380d13cce..1c38d87894841834007489ac2a4eea5175ba1428 100644 --- a/meshmode/__init__.py +++ b/meshmode/__init__.py @@ -25,7 +25,7 @@ THE SOFTWARE. __doc__ = """ .. exception:: Error -.. exception:: ConnectivityUnavailable +.. exception:: DataUnavailable """ @@ -33,5 +33,5 @@ class Error(RuntimeError): pass -class ConnectivityUnavailable(Error): +class DataUnavailable(Error): pass diff --git a/meshmode/discretization/__init__.py b/meshmode/discretization/__init__.py index 6088753222fcdfde70e51b0cca237f2af2dd9f78..e1ed150f132a516d409757567d4b5219db669235 100644 --- a/meshmode/discretization/__init__.py +++ b/meshmode/discretization/__init__.py @@ -23,9 +23,10 @@ THE SOFTWARE. """ import numpy as np -from pytools import memoize_method, memoize_method_nested +from pytools import memoize_method, memoize_in import loopy as lp import pyopencl as cl +import pyopencl.array # noqa __doc__ = """ .. autoclass:: ElementGroupBase @@ -37,11 +38,50 @@ __doc__ = """ # {{{ element group base class ElementGroupBase(object): - """Container for the :class:`QBXGrid` data corresponding to + """Container for the :class:`Discretization` data corresponding to one :class:`meshmode.mesh.MeshElementGroup`. .. attribute :: mesh_el_group + .. attribute :: order .. attribute :: node_nr_base + + .. autoattribute:: nelements + .. autoattribute:: nunit_nodes + .. autoattribute:: nnodes + .. autoattribute:: dim + .. automethod:: view + + .. method:: unit_nodes() + + Returns a :class:`numpy.ndarray` of shape ``(dim, nunit_nodes)`` + of reference coordinates of interpolation nodes. + + .. method:: basis() + + Returns a :class:`list` of basis functions that take arrays + of shape ``(dim, n)`` and return an array of shape (n,)`` + (which performs evaluation of the basis function). + + .. method:: grad_basis() + + :returns: a :class:`tuple` of functions, each of which + accepts arrays of shape *(dims, npts)* + and returns a :class:`tuple` of length *dims* containing + the derivatives along each axis as an array of size *npts*. + 'Scalar' evaluation, by passing just one vector of length *dims*, + is also supported. + + .. method:: diff_matrices() + + Return a :attr:`dim`-long :class:`tuple` of matrices of + shape ``(nunit_nodes, nunit_nodes)``, each of which, + when applied to an array of nodal values, take derivatives + in the reference (r,s,t) directions. + + .. method:: weights() + + Returns an array of length :attr:`nunit_nodes` containing + quadrature weights. """ def __init__(self, mesh_el_group, order, node_nr_base): @@ -79,6 +119,11 @@ class ElementGroupBase(object): (-1, -1)) def view(self, global_array): + """Return a view of *global_array* of shape ``(..., nelements, + nunit_nodes)`` where *global_array* is of shape ``(..., nnodes)``, + where *nnodes* is the global (per-discretization) node count. + """ + return global_array[ ..., self.node_nr_base:self.node_nr_base + self.nnodes] \ .reshape( @@ -127,7 +172,9 @@ class Discretization(object): .. attribute :: groups - .. method:: empty(dtype, queue=None, extra_dims=None) + .. automethod:: empty + + .. automethod:: zeros .. method:: nodes() @@ -141,11 +188,6 @@ class Discretization(object): """ def __init__(self, cl_ctx, mesh, group_factory, real_dtype=np.float64): - """ - :arg order: A polynomial-order-like parameter passed unmodified to - :attr:`group_class`. See subclasses for more precise definition. - """ - self.cl_context = cl_ctx self.mesh = mesh @@ -170,7 +212,20 @@ class Discretization(object): def ambient_dim(self): return self.mesh.ambient_dim - def empty(self, dtype, queue=None, extra_dims=None): + def empty(self, queue=None, dtype=None, extra_dims=None, allocator=None): + """Return an empty DOF vector. + + :arg dtype: type special value 'c' will result in a + vector of dtype :attr:`self.complex_dtype`. If + *None* (the default), a real vector will be returned. + """ + if dtype is None: + dtype = self.real_dtype + elif dtype == "c": + dtype = self.complex_dtype + else: + dtype = np.dtype(dtype) + if queue is None: first_arg = self.cl_context else: @@ -180,11 +235,14 @@ class Discretization(object): if extra_dims is not None: shape = extra_dims + shape - return cl.array.empty(first_arg, shape, dtype=dtype) + return cl.array.empty(first_arg, shape, dtype=dtype, allocator=allocator) + + def zeros(self, queue, dtype=None, extra_dims=None, allocator=None): + return self.empty(queue, dtype=dtype, extra_dims=extra_dims, + allocator=allocator).fill(0) - def num_reference_derivative( - self, queue, ref_axes, vec): - @memoize_method_nested + def num_reference_derivative(self, queue, ref_axes, vec): + @memoize_in(self, "reference_derivative_knl") def knl(): knl = lp.make_kernel( """{[k,i,j]: @@ -196,9 +254,12 @@ class Discretization(object): knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") return lp.tag_inames(knl, dict(k="g.0")) - result = self.empty(vec.dtype) + result = self.empty(dtype=vec.dtype) for grp in self.groups: + if grp.nelements == 0: + continue + mat = None for ref_axis in ref_axes: next_mat = grp.diff_matrices()[ref_axis] @@ -212,24 +273,28 @@ class Discretization(object): return result def quad_weights(self, queue): - @memoize_method_nested + @memoize_in(self, "quad_weights_knl") def knl(): knl = lp.make_kernel( "{[k,i]: 0<=k<nelements and 0<=i<ndiscr_nodes}", "result[k,i] = weights[i]", - name="quad_weights") + name="quad_weights", + default_offset=lp.auto) knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") return lp.tag_inames(knl, dict(k="g.0")) - result = self.empty(self.real_dtype) + result = self.empty(dtype=self.real_dtype) for grp in self.groups: + if grp.nelements == 0: + continue + knl()(queue, result=grp.view(result), weights=grp.weights) return result @memoize_method def nodes(self): - @memoize_method_nested + @memoize_in(self, "nodes_knl") def knl(): knl = lp.make_kernel( """{[d,k,i,j]: @@ -250,13 +315,16 @@ class Discretization(object): "stride:auto,stride:auto,stride:auto") return knl - result = self.empty(self.real_dtype, extra_dims=(self.ambient_dim,)) + result = self.empty(dtype=self.real_dtype, extra_dims=(self.ambient_dim,)) with cl.CommandQueue(self.cl_context) as queue: for grp in self.groups: + if grp.nelements == 0: + continue + meg = grp.mesh_el_group knl()(queue, - resampling_mat=grp.resampling_matrix(), + resampling_mat=grp.from_mesh_interp_matrix(), result=grp.view(result), nodes=meg.nodes) return result diff --git a/meshmode/discretization/connection.py b/meshmode/discretization/connection.py deleted file mode 100644 index 3f1614170e1deddb8e2aeca626c590114cae34c5..0000000000000000000000000000000000000000 --- a/meshmode/discretization/connection.py +++ /dev/null @@ -1,435 +0,0 @@ -from __future__ import division -from __future__ import absolute_import -import six -from six.moves import range -from six.moves import zip - -__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" - -__license__ = """ -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. -""" - -import numpy as np -import modepy as mp -import pyopencl as cl -import pyopencl.array # noqa -from pytools import memoize_method, memoize_method_nested, Record - -import logging -logger = logging.getLogger(__name__) - - -__doc__ = """ -.. autoclass:: DiscretizationConnection - -.. autofunction:: make_same_mesh_connection - -.. autofunction:: make_boundary_restriction - -Implementation details -^^^^^^^^^^^^^^^^^^^^^^ - -.. autoclass:: InterpolationBatch - -.. autoclass:: DiscretizationConnectionElementGroup -""" - - -class InterpolationBatch(object): - """One interpolation batch captures how a batch of elements *within* an - element group should be an interpolated. Note that while it's possible that - an interpolation batch takes care of interpolating an entire element group - from source to target, that's not *necessarily* the case. Consider the case - of extracting boundary values of a discretization. For, say, a triangle, at - least three different interpolation batches are needed to cover boundary - edges that fall onto each of the three edges of the unit triangle. - - .. attribute:: source_element_indices - - A :class:`pyopencl.array.Arrays` of length ``nelements``, containing the - element index from which this "*to*" element's data will be - interpolated. - - .. attribute:: target_element_indices - - A :class:`pyopencl.array.Arrays` of length ``nelements``, containing the - element index to which this "*to*" element's data will be - interpolated. - - .. attribute:: result_unit_nodes - - A :class:`numpy.ndarray` of shape - ``(from_group.dim,to_group.nelements,to_group.nunit_nodes)`` - storing the coordinates of the nodes (in unit coordinates - of the *from* reference element) from which the node - locations of this element should be interpolated. - """ - - def __init__(self, source_element_indices, - target_element_indices, result_unit_nodes): - self.source_element_indices = source_element_indices - self.target_element_indices = target_element_indices - self.result_unit_nodes = result_unit_nodes - - @property - def nelements(self): - return len(self.source_element_indices) - - -class DiscretizationConnectionElementGroup(object): - """ - .. attribute:: batches - - A list of :class:`InterpolationBatch` instances. - """ - def __init__(self, batches): - self.batches = batches - - -class DiscretizationConnection(object): - """ - .. attribute:: from_discr - - .. attribute:: to_discr - - .. attribute:: groups - - a list of :class:`MeshConnectionGroup` instances, with - a one-to-one correspondence to the groups in - :attr:`from_discr` and :attr:`to_discr`. - """ - - def __init__(self, from_discr, to_discr, groups): - if from_discr.cl_context != to_discr.cl_context: - raise ValueError("from_discr and to_discr must live in the " - "same OpenCL context") - - self.cl_context = from_discr.cl_context - - self.from_discr = from_discr - self.to_discr = to_discr - self.groups = groups - - @memoize_method - def _resample_matrix(self, elgroup_index, ibatch_index): - import modepy as mp - ibatch = self.groups[elgroup_index].batches[ibatch_index] - from_grp = self.from_discr.groups[elgroup_index] - - return mp.resampling_matrix( - mp.simplex_onb(self.from_discr.dim, from_grp.order), - ibatch.result_unit_nodes, from_grp.unit_nodes) - - def __call__(self, queue, vec): - @memoize_method_nested - def knl(): - import loopy as lp - knl = lp.make_kernel( - """{[k,i,j]: - 0<=k<nelements and - 0<=i<n_to_nodes and - 0<=j<n_from_nodes}""", - "result[target_element_indices[k], i] \ - = sum(j, resample_mat[i, j] \ - * vec[source_element_indices[k], j])", - [ - lp.GlobalArg("result", None, - shape="nelements_result, n_to_nodes"), - lp.GlobalArg("vec", None, - shape="nelements_vec, n_from_nodes"), - lp.ValueArg("nelements_result", np.int32), - lp.ValueArg("nelements_vec", np.int32), - "...", - ], - name="oversample") - - knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") - return lp.tag_inames(knl, dict(k="g.0")) - - if not isinstance(vec, cl.array.Array): - return vec - - result = self.to_discr.empty(vec.dtype) - - if vec.shape != (self.from_discr.nnodes,): - raise ValueError("invalid shape of incoming resampling data") - - for i_grp, (sgrp, tgrp, cgrp) in enumerate( - zip(self.to_discr.groups, self.from_discr.groups, self.groups)): - for i_batch, batch in enumerate(cgrp.batches): - if len(batch.source_element_indices): - knl()(queue, - resample_mat=self._resample_matrix(i_grp, i_batch), - result=sgrp.view(result), vec=tgrp.view(vec), - source_element_indices=batch.source_element_indices, - target_element_indices=batch.target_element_indices) - - return result - - # }}} - - -# {{{ same-mesh constructor - -def make_same_mesh_connection(queue, to_discr, from_discr): - if from_discr.mesh is not to_discr.mesh: - raise ValueError("from_discr and to_discr must be based on " - "the same mesh") - - assert queue.context == from_discr.cl_context - assert queue.context == to_discr.cl_context - - groups = [] - for fgrp, tgrp in zip(from_discr.groups, to_discr.groups): - all_elements = cl.array.arange(queue, - fgrp.nelements, - dtype=np.intp).with_queue(None) - ibatch = InterpolationBatch( - source_element_indices=all_elements, - target_element_indices=all_elements, - result_unit_nodes=tgrp.unit_nodes) - - groups.append( - DiscretizationConnectionElementGroup([ibatch])) - - return DiscretizationConnection( - from_discr, to_discr, groups) - -# }}} - - -# {{{ boundary restriction constructor - -class _ConnectionBatchData(Record): - pass - - -def _build_boundary_connection(queue, vol_discr, bdry_discr, connection_data): - connection_groups = [] - for igrp, (vol_grp, bdry_grp) in enumerate( - zip(vol_discr.groups, bdry_discr.groups)): - connection_batches = [] - mgrp = vol_grp.mesh_el_group - - for face_id in range(len(mgrp.face_vertex_indices())): - data = connection_data[igrp, face_id] - - bdry_unit_nodes_01 = (bdry_grp.unit_nodes + 1)*0.5 - result_unit_nodes = (np.dot(data.A, bdry_unit_nodes_01).T + data.b).T - - connection_batches.append( - InterpolationBatch( - source_element_indices=cl.array.to_device( - queue, - vol_grp.mesh_el_group.element_nr_base - + data.group_source_element_indices) - .with_queue(None), - target_element_indices=cl.array.to_device( - queue, - bdry_grp.mesh_el_group.element_nr_base - + data.group_target_element_indices) - .with_queue(None), - result_unit_nodes=result_unit_nodes, - )) - - connection_groups.append( - DiscretizationConnectionElementGroup( - connection_batches)) - - return DiscretizationConnection( - vol_discr, bdry_discr, connection_groups) - - -def make_boundary_restriction(queue, discr, group_factory): - """ - :return: a tuple ``(bdry_mesh, bdry_discr, connection)`` - """ - - logger.info("building boundary connection: start") - - # {{{ build face_map - - # maps (igrp, el_grp, face_id) to a frozenset of vertex IDs - face_map = {} - - for igrp, mgrp in enumerate(discr.mesh.groups): - grp_face_vertex_indices = mgrp.face_vertex_indices() - - for iel_grp in range(mgrp.nelements): - for fid, loc_face_vertices in enumerate(grp_face_vertex_indices): - face_vertices = frozenset( - mgrp.vertex_indices[iel_grp, fvi] - for fvi in loc_face_vertices - ) - face_map.setdefault(face_vertices, []).append( - (igrp, iel_grp, fid)) - - del face_vertices - - # }}} - - boundary_faces = [ - face_ids[0] - for face_vertices, face_ids in six.iteritems(face_map) - if len(face_ids) == 1] - - from pytools import flatten - bdry_vertex_vol_nrs = sorted(set(flatten(six.iterkeys(face_map)))) - - vol_to_bdry_vertices = np.empty( - discr.mesh.vertices.shape[-1], - discr.mesh.vertices.dtype) - vol_to_bdry_vertices.fill(-1) - vol_to_bdry_vertices[bdry_vertex_vol_nrs] = np.arange( - len(bdry_vertex_vol_nrs)) - - bdry_vertices = discr.mesh.vertices[:, bdry_vertex_vol_nrs] - - from meshmode.mesh import Mesh, SimplexElementGroup - bdry_mesh_groups = [] - connection_data = {} - - for igrp, grp in enumerate(discr.groups): - mgrp = grp.mesh_el_group - group_boundary_faces = [ - (ibface_el, ibface_face) - for ibface_group, ibface_el, ibface_face in boundary_faces - if ibface_group == igrp] - - if not isinstance(mgrp, SimplexElementGroup): - raise NotImplementedError("can only take boundary of " - "SimplexElementGroup-based meshes") - - # {{{ Preallocate arrays for mesh group - - ngroup_bdry_elements = len(group_boundary_faces) - vertex_indices = np.empty( - (ngroup_bdry_elements, mgrp.dim+1-1), - mgrp.vertex_indices.dtype) - - bdry_unit_nodes = mp.warp_and_blend_nodes(mgrp.dim-1, mgrp.order) - bdry_unit_nodes_01 = (bdry_unit_nodes + 1)*0.5 - - vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order) - nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1] - nodes = np.empty( - (discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes), - dtype=np.float64) - - # }}} - - grp_face_vertex_indices = mgrp.face_vertex_indices() - grp_vertex_unit_coordinates = mgrp.vertex_unit_coordinates() - - # batch by face_id - - batch_base = 0 - - for face_id in range(len(grp_face_vertex_indices)): - batch_boundary_el_numbers_in_grp = np.array( - [ - ibface_el - for ibface_el, ibface_face in group_boundary_faces - if ibface_face == face_id], - dtype=np.intp) - - new_el_numbers = np.arange( - batch_base, - batch_base + len(batch_boundary_el_numbers_in_grp)) - - # {{{ no per-element axes in these computations - - # Find boundary vertex indices - loc_face_vertices = list(grp_face_vertex_indices[face_id]) - - # Find unit nodes for boundary element - face_vertex_unit_coordinates = \ - grp_vertex_unit_coordinates[loc_face_vertices] - - # Find A, b such that A [e_1 e_2] + b = [r_1 r_2] - # (Notation assumes that the volume is 3D and the face is 2D. - # Code does not.) - - b = face_vertex_unit_coordinates[0] - A = ( - face_vertex_unit_coordinates[1:] - - face_vertex_unit_coordinates[0]).T - - face_unit_nodes = (np.dot(A, bdry_unit_nodes_01).T + b).T - - resampling_mat = mp.resampling_matrix( - vol_basis, - face_unit_nodes, mgrp.unit_nodes) - - # }}} - - # {{{ build information for mesh element group - - # Find vertex_indices - glob_face_vertices = mgrp.vertex_indices[ - batch_boundary_el_numbers_in_grp][:, loc_face_vertices] - vertex_indices[new_el_numbers] = \ - vol_to_bdry_vertices[glob_face_vertices] - - # Find nodes - nodes[:, new_el_numbers, :] = np.einsum( - "ij,dej->dei", - resampling_mat, - mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :]) - - # }}} - - connection_data[igrp, face_id] = _ConnectionBatchData( - group_source_element_indices=batch_boundary_el_numbers_in_grp, - group_target_element_indices=new_el_numbers, - A=A, - b=b, - ) - - batch_base += len(batch_boundary_el_numbers_in_grp) - - bdry_mesh_group = SimplexElementGroup( - mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes) - bdry_mesh_groups.append(bdry_mesh_group) - - bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups) - - from meshmode.discretization import Discretization - bdry_discr = Discretization( - discr.cl_context, bdry_mesh, group_factory) - - connection = _build_boundary_connection( - queue, discr, bdry_discr, connection_data) - - logger.info("building boundary connection: done") - - return bdry_mesh, bdry_discr, connection - -# }}} - - -# {{{ refinement connection - -def make_refinement_connection(refiner, coarse_discr): - pass - -# }}} - -# vim: foldmethod=marker diff --git a/meshmode/discretization/connection/__init__.py b/meshmode/discretization/connection/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..269ed5d14c61e3b33ef3d7e6d4354b105e9b5f50 --- /dev/null +++ b/meshmode/discretization/connection/__init__.py @@ -0,0 +1,444 @@ +from __future__ import division, print_function, absolute_import + +__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" + +__license__ = """ +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. +""" + +from six.moves import range, zip + +import numpy as np +import pyopencl as cl +import pyopencl.array # noqa +from pytools import memoize_method, memoize_in + +from meshmode.discretization.connection.same_mesh import \ + make_same_mesh_connection +from meshmode.discretization.connection.face import ( + FRESTR_INTERIOR_FACES, FRESTR_ALL_FACES, + make_face_restriction, make_face_to_all_faces_embedding) +from meshmode.discretization.connection.opposite_face import \ + make_opposite_face_connection + +import logging +logger = logging.getLogger(__name__) + + +__all__ = [ + "DiscretizationConnection", + "make_same_mesh_connection", + "FRESTR_INTERIOR_FACES", "FRESTR_ALL_FACES", + "make_face_restriction", + "make_face_to_all_faces_embedding", + "make_opposite_face_connection" + ] + +__doc__ = """ +.. autoclass:: DiscretizationConnection + +.. autofunction:: make_same_mesh_connection + +.. autofunction:: FRESTR_INTERIOR_FACES +.. autofunction:: FRESTR_ALL_FACES +.. autofunction:: make_face_restriction +.. autofunction:: make_face_to_all_faces_embedding + +.. autofunction:: make_opposite_face_connection + +Implementation details +^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: InterpolationBatch + +.. autoclass:: DiscretizationConnectionElementGroup +""" + + +# {{{ interpolation batch + +class InterpolationBatch(object): + """One interpolation batch captures how a batch of elements *within* an + element group should be an interpolated. Note that while it's possible that + an interpolation batch takes care of interpolating an entire element group + from source to target, that's not *necessarily* the case. Consider the case + of extracting boundary values of a discretization. For, say, a triangle, at + least three different interpolation batches are needed to cover boundary + edges that fall onto each of the three edges of the unit triangle. + + .. attribute:: from_group_index + + An integer indicating from which element group in the *from* discretization + the data should be interpolated. + + .. attribute:: from_element_indices + + ``element_id_t [nelements]``. (a :class:`pyopencl.array.Array`) + This contains the (group-local) element index (relative to + :attr:`from_group_index` from which this "*to*" element's data will be + interpolated. + + .. attribute:: to_element_indices + + ``element_id_t [nelements]``. (a :class:`pyopencl.array.Array`) + This contains the (group-local) element index to which this "*to*" + element's data will be interpolated. + + .. attribute:: result_unit_nodes + + A :class:`numpy.ndarray` of shape + ``(from_group.dim,to_group.nelements,to_group.nunit_nodes)`` + storing the coordinates of the nodes (in unit coordinates + of the *from* reference element) from which the node + locations of this element should be interpolated. + + .. autoattribute:: nelements + + .. attribute:: to_element_face + + *int* or *None*. (a :class:`pyopencl.array.Array` if existent) If this + interpolation batch targets interpolation *to* a face, then this number + captures the face number (on all elements referenced by + :attr:`from_element_indices` to which this batch interpolates. (Since + there is a fixed set of "from" unit nodes per batch, one batch will + always go to a single face index.) + """ + + def __init__(self, from_group_index, from_element_indices, + to_element_indices, result_unit_nodes, to_element_face): + self.from_group_index = from_group_index + self.from_element_indices = from_element_indices + self.to_element_indices = to_element_indices + self.result_unit_nodes = result_unit_nodes + self.to_element_face = to_element_face + + @property + def nelements(self): + return len(self.from_element_indices) + +# }}} + + +# {{{ connection element group + +class DiscretizationConnectionElementGroup(object): + """ + .. attribute:: batches + + A list of :class:`InterpolationBatch` instances. + """ + def __init__(self, batches): + self.batches = batches + +# }}} + + +# {{{ connection class + +class DiscretizationConnection(object): + """Data supporting an interpolation-like operation that takes in data on + one discretization and returns it on another. Implemented applications + include: + + * upsampling/downsampling on the same mesh + * restricition to the boundary + * interpolation to a refined/coarsened mesh + * interpolation onto opposing faces + + .. attribute:: from_discr + + .. attribute:: to_discr + + .. attribute:: groups + + a list of :class:`DiscretizationConnectionElementGroup` + instances, with a one-to-one correspondence to the groups in + :attr:`to_discr`. + + .. attribute:: is_surjective + + A :class:`bool` indicating whether every output degree + of freedom is set by the connection. + + .. automethod:: __call__ + + .. automethod:: full_resample_matrix + + """ + + def __init__(self, from_discr, to_discr, groups, is_surjective): + if from_discr.cl_context != to_discr.cl_context: + raise ValueError("from_discr and to_discr must live in the " + "same OpenCL context") + + self.cl_context = from_discr.cl_context + + if from_discr.mesh.vertex_id_dtype != to_discr.mesh.vertex_id_dtype: + raise ValueError("from_discr and to_discr must agree on the " + "vertex_id_dtype") + + if from_discr.mesh.element_id_dtype != to_discr.mesh.element_id_dtype: + raise ValueError("from_discr and to_discr must agree on the " + "element_id_dtype") + + self.cl_context = from_discr.cl_context + + self.from_discr = from_discr + self.to_discr = to_discr + self.groups = groups + + self.is_surjective = is_surjective + + @memoize_method + def _resample_matrix(self, to_group_index, ibatch_index): + import modepy as mp + ibatch = self.groups[to_group_index].batches[ibatch_index] + from_grp = self.from_discr.groups[ibatch.from_group_index] + + result = mp.resampling_matrix( + from_grp.basis(), + ibatch.result_unit_nodes, from_grp.unit_nodes) + + with cl.CommandQueue(self.cl_context) as queue: + return cl.array.to_device(queue, result).with_queue(None) + + @memoize_method + def _resample_point_pick_indices(self, to_group_index, ibatch_index, + tol_multiplier=None): + """If :meth:`_resample_matrix` *R* is a row subset of a permutation matrix *P*, + return the index subset I so that, loosely, ``x[I] == R @ x``. + + Will return *None* if no such index array exists, or a + :class:`pyopencl.array.Array` containing the index subset. + """ + + with cl.CommandQueue(self.cl_context) as queue: + mat = self._resample_matrix(to_group_index, ibatch_index).get( + queue=queue) + + nrows, ncols = mat.shape + result = np.zeros(nrows, dtype=self.to_discr.mesh.element_id_dtype) + + if tol_multiplier is None: + tol_multiplier = 50 + + tol = np.finfo(mat.dtype).eps * tol_multiplier + + for irow in range(nrows): + one_indices, = np.where(np.abs(mat[irow] - 1) < tol) + zero_indices, = np.where(np.abs(mat[irow]) < tol) + + if len(one_indices) != 1: + return None + if len(zero_indices) != ncols - 1: + return None + + one_index, = one_indices + result[irow] = one_index + + with cl.CommandQueue(self.cl_context) as queue: + return cl.array.to_device(queue, result).with_queue(None) + + def full_resample_matrix(self, queue): + """Build a dense matrix representing this discretization connection. + + .. warning:: + + On average, this will be exceedingly expensive (:math:`O(N^2)` in + the number *N* of discretization points) in terms of memory usage + and thus not what you'd typically want. + """ + + @memoize_in(self, "oversample_mat_knl") + def knl(): + import loopy as lp + knl = lp.make_kernel( + """{[k,i,j]: + 0<=k<nelements and + 0<=i<n_to_nodes and + 0<=j<n_from_nodes}""", + "result[itgt_base + to_element_indices[k]*n_to_nodes + i, \ + isrc_base + from_element_indices[k]*n_from_nodes + j] \ + = resample_mat[i, j]", + [ + lp.GlobalArg("result", None, + shape="nnodes_tgt, nnodes_src", + offset=lp.auto), + lp.ValueArg("itgt_base,isrc_base", np.int32), + lp.ValueArg("nnodes_tgt,nnodes_src", np.int32), + "...", + ], + name="oversample_mat") + + knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") + return lp.tag_inames(knl, dict(k="g.0")) + + result = cl.array.zeros( + queue, + (self.to_discr.nnodes, self.from_discr.nnodes), + dtype=self.to_discr.real_dtype) + + for i_tgrp, (tgrp, cgrp) in enumerate( + zip(self.to_discr.groups, self.groups)): + for i_batch, batch in enumerate(cgrp.batches): + if len(batch.from_element_indices): + if not len(batch.from_element_indices): + continue + + sgrp = self.from_discr.groups[batch.from_group_index] + + knl()(queue, + resample_mat=self._resample_matrix(i_tgrp, i_batch), + result=result, + itgt_base=tgrp.node_nr_base, + isrc_base=sgrp.node_nr_base, + from_element_indices=batch.from_element_indices, + to_element_indices=batch.to_element_indices) + + return result + + def __call__(self, queue, vec): + @memoize_in(self, "resample_by_mat_knl") + def mat_knl(): + import loopy as lp + knl = lp.make_kernel( + """{[k,i,j]: + 0<=k<nelements and + 0<=i<n_to_nodes and + 0<=j<n_from_nodes}""", + "result[to_element_indices[k], i] \ + = sum(j, resample_mat[i, j] \ + * vec[from_element_indices[k], j])", + [ + lp.GlobalArg("result", None, + shape="nelements_result, n_to_nodes", + offset=lp.auto), + lp.GlobalArg("vec", None, + shape="nelements_vec, n_from_nodes", + offset=lp.auto), + lp.ValueArg("nelements_result", np.int32), + lp.ValueArg("nelements_vec", np.int32), + "...", + ], + name="resample_by_mat") + + knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") + return lp.tag_inames(knl, dict(k="g.0")) + + @memoize_in(self, "resample_by_picking_knl") + def pick_knl(): + import loopy as lp + knl = lp.make_kernel( + """{[k,i,j]: + 0<=k<nelements and + 0<=i<n_to_nodes}""", + "result[to_element_indices[k], i] \ + = vec[from_element_indices[k], pick_list[i]]", + [ + lp.GlobalArg("result", None, + shape="nelements_result, n_to_nodes", + offset=lp.auto), + lp.GlobalArg("vec", None, + shape="nelements_vec, n_from_nodes", + offset=lp.auto), + lp.ValueArg("nelements_result", np.int32), + lp.ValueArg("nelements_vec", np.int32), + lp.ValueArg("n_from_nodes", np.int32), + "...", + ], + name="resample_by_picking") + + knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") + return lp.tag_inames(knl, dict(k="g.0")) + + if not isinstance(vec, cl.array.Array): + return vec + + if self.is_surjective: + result = self.to_discr.empty(dtype=vec.dtype) + else: + result = self.to_discr.zeros(queue, dtype=vec.dtype) + + if vec.shape != (self.from_discr.nnodes,): + raise ValueError("invalid shape of incoming resampling data") + + for i_tgrp, (tgrp, cgrp) in enumerate( + zip(self.to_discr.groups, self.groups)): + for i_batch, batch in enumerate(cgrp.batches): + sgrp = self.from_discr.groups[batch.from_group_index] + + if not len(batch.from_element_indices): + continue + + point_pick_indices = self._resample_point_pick_indices( + i_tgrp, i_batch) + + if point_pick_indices is None: + mat_knl()(queue, + resample_mat=self._resample_matrix(i_tgrp, i_batch), + result=tgrp.view(result), vec=sgrp.view(vec), + from_element_indices=batch.from_element_indices, + to_element_indices=batch.to_element_indices) + + else: + pick_knl()(queue, + pick_list=point_pick_indices, + result=tgrp.view(result), vec=sgrp.view(vec), + from_element_indices=batch.from_element_indices, + to_element_indices=batch.to_element_indices) + + return result + +# }}} + + +# {{{ check connection + +def check_connection(connection): + from_discr = connection.from_discr + to_discr = connection.to_discr + + assert len(connection.groups) == len(to_discr.groups) + + with cl.CommandQueue(to_discr.cl_context) as queue: + for cgrp, tgrp in zip(connection.groups, to_discr.groups): + for batch in cgrp.batches: + fgrp = from_discr.groups[batch.from_group_index] + + from_element_indices = batch.from_element_indices.get(queue) + to_element_indices = batch.to_element_indices.get(queue) + + assert (0 <= from_element_indices).all() + assert (0 <= to_element_indices).all() + assert (from_element_indices < fgrp.nelements).all() + assert (to_element_indices < tgrp.nelements).all() + if batch.to_element_face is not None: + assert 0 <= batch.to_element_face < fgrp.mesh_el_group.nfaces + +# }}} + + +# {{{ refinement connection + +def make_refinement_connection(refiner, coarse_discr): + pass + +# }}} + +# vim: foldmethod=marker diff --git a/meshmode/discretization/connection/face.py b/meshmode/discretization/connection/face.py new file mode 100644 index 0000000000000000000000000000000000000000..52d7575d6dd83df9ed9868d063c6fa3adbe985cf --- /dev/null +++ b/meshmode/discretization/connection/face.py @@ -0,0 +1,488 @@ +from __future__ import division, print_function, absolute_import + +__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" + +__license__ = """ +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. +""" + +import six +from six.moves import range, zip + +from pytools import Record + +import numpy as np +import pyopencl as cl +import pyopencl.array # noqa +import modepy as mp + +import logging +logger = logging.getLogger(__name__) + + +class FRESTR_INTERIOR_FACES: + """A special value to pass to + :func:`meshmode.discretization.connection.make_face_restriction` + to produce a discretization consisting of all interior faces + in a discretization. + """ + + +class FRESTR_ALL_FACES: + """A special value to pass to + :func:`meshmode.discretization.connection.make_face_restriction` + to produce a discretization consisting of all faces (interior and boundary) + faces in a discretization. + """ + + +# {{{ boundary connection + +class _ConnectionBatchData(Record): + pass + + +def _build_boundary_connection(queue, vol_discr, bdry_discr, connection_data, + per_face_groups): + from meshmode.discretization.connection import ( + InterpolationBatch, DiscretizationConnectionElementGroup, + DiscretizationConnection) + + ibdry_grp = 0 + batches = [] + + connection_groups = [] + for igrp, vol_grp in enumerate(vol_discr.groups): + mgrp = vol_grp.mesh_el_group + + for face_id in range(mgrp.nfaces): + bdry_grp = bdry_discr.groups[ibdry_grp] + data = connection_data[igrp, face_id] + + bdry_unit_nodes_01 = (bdry_grp.unit_nodes + 1)*0.5 + result_unit_nodes = (np.dot(data.A, bdry_unit_nodes_01).T + data.b).T + + batches.append( + InterpolationBatch( + from_group_index=igrp, + from_element_indices=cl.array.to_device( + queue, + vol_grp.mesh_el_group.element_nr_base + + data.group_source_element_indices) + .with_queue(None), + to_element_indices=cl.array.to_device( + queue, + data.group_target_element_indices) + .with_queue(None), + result_unit_nodes=result_unit_nodes, + to_element_face=face_id + )) + + is_last_face = face_id + 1 == mgrp.nfaces + + if per_face_groups or is_last_face: + connection_groups.append( + DiscretizationConnectionElementGroup(batches)) + batches = [] + + ibdry_grp += 1 + + assert ibdry_grp == len(bdry_discr.groups) + + return DiscretizationConnection( + vol_discr, bdry_discr, connection_groups, + is_surjective=True) + +# }}} + + +# {{{ pull together boundary vertices + +def _get_face_vertices(mesh, boundary_tag): + # a set of volume vertex numbers + bdry_vertex_vol_nrs = set() + + if boundary_tag not in [FRESTR_INTERIOR_FACES, FRESTR_ALL_FACES]: + # {{{ boundary faces + + btag_bit = mesh.boundary_tag_bit(boundary_tag) + + for fagrp_map in mesh.facial_adjacency_groups: + bdry_grp = fagrp_map.get(None) + if bdry_grp is None: + continue + + assert (bdry_grp.neighbors < 0).all() + + grp = mesh.groups[bdry_grp.igroup] + + nb_el_bits = -bdry_grp.neighbors + face_relevant_flags = (nb_el_bits & btag_bit) != 0 + + for iface, fvi in enumerate(grp.face_vertex_indices()): + bdry_vertex_vol_nrs.update( + grp.vertex_indices + [bdry_grp.elements[face_relevant_flags]] + [:, np.array(fvi, dtype=np.intp)] + .flat) + + return np.array(sorted(bdry_vertex_vol_nrs), dtype=np.intp) + + # }}} + else: + # For INTERIOR_FACES, this is likely every vertex in the book. + # Don't ever bother trying to cut the list down. + # For ALL_FACES, it literally is every single vertex. + + return np.arange(mesh.nvertices, dtype=np.intp) + +# }}} + + +def make_face_restriction(discr, group_factory, boundary_tag, + per_face_groups=False): + """Create a mesh, a discretization and a connection to restrict + a function on *discr* to its values on the edges of element faces + denoted by *boundary_tag*. + + :arg boundary_tag: The boundary tag for which to create a face + restriction. May be + :class:`meshmode.discretization.connection.INTERIOR_FACES` + to indicate interior faces, or + :class:`meshmode.discretization.connection.ALL_FACES` + to make a discretization consisting of all (interior and + boundary) faces. + + :arg per_face_groups: If *True*, the resulting discretization is + guaranteed to have groups organized as:: + + (grp0, face0), (grp0, face1), ... (grp0, faceN), + (grp1, face0), (grp1, face1), ... (grp1, faceN), ... + + each with the elements in the same order as the originating + group. If *False*, volume and boundary groups correspond with + each other one-to-one, and an interpolation batch is created + per face. + + :return: a :class:`meshmode.discretization.connection.DiscretizationConnection` + representing the new connection. The new boundary discretization can be + obtained from the + :attr:`meshmode.discretization.connection.DiscretizationConnection.to_discr` + attribute of the return value, and the corresponding new boundary mesh + from that. + + """ + + if boundary_tag is None: + boundary_tag = FRESTR_INTERIOR_FACES + from warnings import warn + warn("passing *None* for boundary_tag is deprecated--pass " + "INTERIOR_FACES instead", + DeprecationWarning, stacklevel=2) + + logger.info("building face restriction: start") + + # {{{ gather boundary vertices + + bdry_vertex_vol_nrs = _get_face_vertices(discr.mesh, boundary_tag) + + vol_to_bdry_vertices = np.empty( + discr.mesh.vertices.shape[-1], + discr.mesh.vertices.dtype) + vol_to_bdry_vertices.fill(-1) + vol_to_bdry_vertices[bdry_vertex_vol_nrs] = np.arange( + len(bdry_vertex_vol_nrs), dtype=np.intp) + + bdry_vertices = discr.mesh.vertices[:, bdry_vertex_vol_nrs] + + # }}} + + from meshmode.mesh import Mesh, SimplexElementGroup + bdry_mesh_groups = [] + connection_data = {} + + btag_bit = discr.mesh.boundary_tag_bit(boundary_tag) + + for igrp, (grp, fagrp_map) in enumerate( + zip(discr.groups, discr.mesh.facial_adjacency_groups)): + + mgrp = grp.mesh_el_group + + if not isinstance(mgrp, SimplexElementGroup): + raise NotImplementedError("can only take boundary of " + "SimplexElementGroup-based meshes") + + # {{{ pull together per-group face lists + + group_boundary_faces = [] + + if boundary_tag is FRESTR_INTERIOR_FACES: + for fagrp in six.itervalues(fagrp_map): + if fagrp.ineighbor_group is None: + # boundary faces -> not looking for those + continue + + group_boundary_faces.extend( + zip(fagrp.elements, fagrp.element_faces)) + + elif boundary_tag is FRESTR_ALL_FACES: + group_boundary_faces.extend( + (iel, iface) + for iface in range(grp.mesh_el_group.nfaces) + for iel in range(grp.nelements) + ) + + else: + bdry_grp = fagrp_map.get(None) + if bdry_grp is not None: + nb_el_bits = -bdry_grp.neighbors + face_relevant_flags = (nb_el_bits & btag_bit) != 0 + + group_boundary_faces.extend( + zip( + bdry_grp.elements[face_relevant_flags], + bdry_grp.element_faces[face_relevant_flags])) + + # }}} + + grp_face_vertex_indices = mgrp.face_vertex_indices() + grp_vertex_unit_coordinates = mgrp.vertex_unit_coordinates() + + batch_base = 0 + + # group by face_id + + for face_id in range(mgrp.nfaces): + batch_boundary_el_numbers_in_grp = np.array( + [ + ibface_el + for ibface_el, ibface_face in group_boundary_faces + if ibface_face == face_id], + dtype=np.intp) + + # {{{ Preallocate arrays for mesh group + + nbatch_elements = len(batch_boundary_el_numbers_in_grp) + + if per_face_groups or face_id == 0: + if per_face_groups: + ngroup_bdry_elements = nbatch_elements + else: + ngroup_bdry_elements = len(group_boundary_faces) + + vertex_indices = np.empty( + (ngroup_bdry_elements, mgrp.dim+1-1), + mgrp.vertex_indices.dtype) + + bdry_unit_nodes = mp.warp_and_blend_nodes(mgrp.dim-1, mgrp.order) + bdry_unit_nodes_01 = (bdry_unit_nodes + 1)*0.5 + + vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order) + nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1] + nodes = np.empty( + (discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes), + dtype=np.float64) + + # }}} + + new_el_numbers = batch_base + np.arange(nbatch_elements) + if not per_face_groups: + batch_base += nbatch_elements + + # {{{ no per-element axes in these computations + + # Find boundary vertex indices + loc_face_vertices = list(grp_face_vertex_indices[face_id]) + + # Find unit nodes for boundary element + face_vertex_unit_coordinates = \ + grp_vertex_unit_coordinates[loc_face_vertices] + + # Find A, b such that A [e_1 e_2] + b = [r_1 r_2] + # (Notation assumes that the volume is 3D and the face is 2D. + # Code does not.) + + b = face_vertex_unit_coordinates[0] + A = ( # noqa + face_vertex_unit_coordinates[1:] + - face_vertex_unit_coordinates[0]).T + + face_unit_nodes = (np.dot(A, bdry_unit_nodes_01).T + b).T + + resampling_mat = mp.resampling_matrix( + vol_basis, + face_unit_nodes, mgrp.unit_nodes) + + # }}} + + # {{{ build information for mesh element group + + # Find vertex_indices + glob_face_vertices = mgrp.vertex_indices[ + batch_boundary_el_numbers_in_grp][:, loc_face_vertices] + vertex_indices[new_el_numbers] = \ + vol_to_bdry_vertices[glob_face_vertices] + + # Find nodes + nodes[:, new_el_numbers, :] = np.einsum( + "ij,dej->dei", + resampling_mat, + mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :]) + + # }}} + + connection_data[igrp, face_id] = _ConnectionBatchData( + group_source_element_indices=batch_boundary_el_numbers_in_grp, + group_target_element_indices=new_el_numbers, + A=A, + b=b, + ) + + is_last_face = face_id + 1 == mgrp.nfaces + + if per_face_groups or is_last_face: + bdry_mesh_group = SimplexElementGroup( + mgrp.order, vertex_indices, nodes, + unit_nodes=bdry_unit_nodes) + bdry_mesh_groups.append(bdry_mesh_group) + + bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups) + + from meshmode.discretization import Discretization + bdry_discr = Discretization( + discr.cl_context, bdry_mesh, group_factory) + + with cl.CommandQueue(discr.cl_context) as queue: + connection = _build_boundary_connection( + queue, discr, bdry_discr, connection_data, + per_face_groups) + + logger.info("building face restriction: done") + + return connection + +# }}} + + +# {{{ face -> all_faces connection + +def make_face_to_all_faces_embedding(faces_connection, all_faces_discr): + """Return a + :class:`meshmode.discretization.connection.DiscretizationConnection` + connecting a discretization containing some faces of a discretization + to one containing all faces. + + :arg faces_connection: must be the (connection) result of calling + :func:`meshmode.discretization.connection.make_face_restriction` + with + :class:`meshmode.discretization.connection.FRESTR_INTERIOR_FACES` + or a boundary tag. + :arg all_faces_discr: must be the (discretization) result of calling + :func:`meshmode.discretization.connection.make_face_restriction` + with + :class:`meshmode.discretization.connection.FRESTR_ALL_FACES` + for the same volume discretization as the one from which + *faces_discr* was obtained. + """ + + vol_discr = faces_connection.from_discr + faces_discr = faces_connection.to_discr + + per_face_groups = ( + len(vol_discr.groups) != len(faces_discr.groups)) + + if len(faces_discr.groups) != len(all_faces_discr.groups): + raise ValueError("faces_discr and all_faces_discr must have the " + "same number of groups") + if len(faces_connection.groups) != len(all_faces_discr.groups): + raise ValueError("faces_connection and all_faces_discr must have the " + "same number of groups") + + from meshmode.discretization.connection import ( + DiscretizationConnection, + DiscretizationConnectionElementGroup, + InterpolationBatch) + + i_faces_grp = 0 + + with cl.CommandQueue(vol_discr.cl_context) as queue: + groups = [] + for ivol_grp, vol_grp in enumerate(vol_discr.groups): + batches = [] + + nfaces = vol_grp.mesh_el_group.nfaces + for iface in range(nfaces): + faces_grp = faces_discr.groups[i_faces_grp] + all_faces_grp = all_faces_discr.groups[i_faces_grp] + + if per_face_groups: + assert len(faces_connection.groups[i_faces_grp].batches) == 1 + else: + assert (len(faces_connection.groups[i_faces_grp].batches) + == nfaces) + + assert np.array_equal( + faces_grp.unit_nodes, all_faces_grp.unit_nodes) + + # {{{ find src_batch + + src_batches = faces_connection.groups[i_faces_grp].batches + if per_face_groups: + src_batch, = src_batches + else: + src_batch = src_batches[iface] + del src_batches + + # }}} + + if per_face_groups: + to_element_indices = src_batch.from_element_indices + else: + assert all_faces_grp.nelements == nfaces * vol_grp.nelements + + to_element_indices = ( + vol_grp.nelements*iface + + src_batch.from_element_indices.with_queue(queue) + ).with_queue(None) + + batches.append( + InterpolationBatch( + from_group_index=i_faces_grp, + from_element_indices=src_batch.to_element_indices, + to_element_indices=to_element_indices, + result_unit_nodes=all_faces_grp.unit_nodes, + to_element_face=None)) + + is_last_face = iface + 1 == nfaces + if per_face_groups or is_last_face: + groups.append( + DiscretizationConnectionElementGroup(batches=batches)) + batches = [] + + i_faces_grp += 1 + + return DiscretizationConnection( + faces_connection.to_discr, + all_faces_discr, + groups, + is_surjective=False) + +# }}} + +# vim: foldmethod=marker diff --git a/meshmode/discretization/connection/opposite_face.py b/meshmode/discretization/connection/opposite_face.py new file mode 100644 index 0000000000000000000000000000000000000000..70728522b2c06f119a5b20598616e2c464eea249 --- /dev/null +++ b/meshmode/discretization/connection/opposite_face.py @@ -0,0 +1,395 @@ +from __future__ import division, print_function, absolute_import + +__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" + +__license__ = """ +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. +""" + +from six.moves import range + +import numpy as np +import numpy.linalg as la +import pyopencl as cl +import pyopencl.array # noqa + +import logging +logger = logging.getLogger(__name__) + + +# {{{ opposite-face connection + +def _make_cross_face_batches( + queue, vol_discr, bdry_discr, + i_tgt_grp, i_src_grp, + i_face_tgt, + adj_grp, + vbc_tgt_grp_face_batch, src_grp_el_lookup): + + # {{{ index wrangling + + # Assert that the adjacency group and the restriction + # interpolation batch and the adjacency group have the same + # element ordering. + + adj_grp_tgt_flags = adj_grp.element_faces == i_face_tgt + + assert ( + np.array_equal( + adj_grp.elements[adj_grp_tgt_flags], + vbc_tgt_grp_face_batch.from_element_indices + .get(queue=queue))) + + # find to_element_indices + + to_bdry_element_indices = ( + vbc_tgt_grp_face_batch.to_element_indices + .get(queue=queue)) + + # find from_element_indices + + from_vol_element_indices = adj_grp.neighbors[adj_grp_tgt_flags] + from_element_faces = adj_grp.neighbor_faces[adj_grp_tgt_flags] + + from_bdry_element_indices = src_grp_el_lookup[ + from_vol_element_indices, from_element_faces] + + # }}} + + # {{{ visualization (for debugging) + + if 0: + print("TVE", adj_grp.elements[adj_grp_tgt_flags]) + print("TBE", to_bdry_element_indices) + print("FVE", from_vol_element_indices) + from meshmode.mesh.visualization import draw_2d_mesh + import matplotlib.pyplot as pt + draw_2d_mesh(vol_discr.mesh, draw_element_numbers=True, + set_bounding_box=True, + draw_vertex_numbers=False, + draw_face_numbers=True, + fill=None) + pt.figure() + + draw_2d_mesh(bdry_discr.mesh, draw_element_numbers=True, + set_bounding_box=True, + draw_vertex_numbers=False, + draw_face_numbers=True, + fill=None) + + pt.show() + # }}} + + # {{{ invert face map (using Gauss-Newton) + + to_bdry_nodes = ( + # FIXME: This should view-then-transfer (but PyOpenCL doesn't do + # non-contiguous transfers for now). + bdry_discr.groups[i_tgt_grp].view( + bdry_discr.nodes().get(queue=queue)) + [:, to_bdry_element_indices]) + + tol = 1e4 * np.finfo(to_bdry_nodes.dtype).eps + + from_mesh_grp = bdry_discr.mesh.groups[i_src_grp] + from_grp = bdry_discr.groups[i_src_grp] + + dim = from_grp.dim + ambient_dim, nelements, nto_unit_nodes = to_bdry_nodes.shape + + initial_guess = np.mean(from_mesh_grp.vertex_unit_coordinates(), axis=0) + from_unit_nodes = np.empty((dim, nelements, nto_unit_nodes)) + from_unit_nodes[:] = initial_guess.reshape(-1, 1, 1) + + import modepy as mp + from_vdm = mp.vandermonde(from_grp.basis(), from_grp.unit_nodes) + from_inv_t_vdm = la.inv(from_vdm.T) + from_nfuncs = len(from_grp.basis()) + + # (ambient_dim, nelements, nfrom_unit_nodes) + from_bdry_nodes = ( + # FIXME: This should view-then-transfer (but PyOpenCL doesn't do + # non-contiguous transfers for now). + bdry_discr.groups[i_src_grp].view( + bdry_discr.nodes().get(queue=queue)) + [:, from_bdry_element_indices]) + + def apply_map(unit_nodes): + # unit_nodes: (dim, nelements, nto_unit_nodes) + + # basis_at_unit_nodes + basis_at_unit_nodes = np.empty((from_nfuncs, nelements, nto_unit_nodes)) + + for i, f in enumerate(from_grp.basis()): + basis_at_unit_nodes[i] = ( + f(unit_nodes.reshape(dim, -1)) + .reshape(nelements, nto_unit_nodes)) + + intp_coeffs = np.einsum("fj,jet->fet", from_inv_t_vdm, basis_at_unit_nodes) + + # If we're interpolating 1, we had better get 1 back. + one_deviation = np.abs(np.sum(intp_coeffs, axis=0) - 1) + assert (one_deviation < tol).all(), np.max(one_deviation) + + return np.einsum("fet,aef->aet", intp_coeffs, from_bdry_nodes) + + def get_map_jacobian(unit_nodes): + # unit_nodes: (dim, nelements, nto_unit_nodes) + + # basis_at_unit_nodes + dbasis_at_unit_nodes = np.empty( + (dim, from_nfuncs, nelements, nto_unit_nodes)) + + for i, df in enumerate(from_grp.grad_basis()): + df_result = df(unit_nodes.reshape(dim, -1)) + + for rst_axis, df_r in enumerate(df_result): + dbasis_at_unit_nodes[rst_axis, i] = ( + df_r.reshape(nelements, nto_unit_nodes)) + + dintp_coeffs = np.einsum( + "fj,rjet->rfet", from_inv_t_vdm, dbasis_at_unit_nodes) + + return np.einsum("rfet,aef->raet", dintp_coeffs, from_bdry_nodes) + + # {{{ test map applier and jacobian + + if 0: + u = from_unit_nodes + f = apply_map(u) + for h in [1e-1, 1e-2]: + du = h*np.random.randn(*u.shape) + + f_2 = apply_map(u+du) + + jf = get_map_jacobian(u) + + f2_2 = f + np.einsum("raet,ret->aet", jf, du) + + print(h, la.norm((f_2-f2_2).ravel())) + + # }}} + + # {{{ visualize initial guess + + if 0: + import matplotlib.pyplot as pt + guess = apply_map(from_unit_nodes) + goals = to_bdry_nodes + + from meshmode.discretization.visualization import draw_curve + draw_curve(bdry_discr) + + pt.plot(guess[0].reshape(-1), guess[1].reshape(-1), "or") + pt.plot(goals[0].reshape(-1), goals[1].reshape(-1), "og") + pt.plot(from_bdry_nodes[0].reshape(-1), from_bdry_nodes[1].reshape(-1), "o", + color="purple") + pt.show() + + # }}} + + logger.info("make_opposite_face_connection: begin gauss-newton") + + niter = 0 + while True: + resid = apply_map(from_unit_nodes) - to_bdry_nodes + + df = get_map_jacobian(from_unit_nodes) + df_inv_resid = np.empty_like(from_unit_nodes) + + # For the 1D/2D accelerated versions, we'll use the normal + # equations and Cramer's rule. If you're looking for high-end + # numerics, look no further than meshmode. + + if dim == 1: + # A is df.T + ata = np.einsum("iket,jket->ijet", df, df) + atb = np.einsum("iket,ket->iet", df, resid) + + df_inv_resid = atb / ata[0, 0] + + elif dim == 2: + # A is df.T + ata = np.einsum("iket,jket->ijet", df, df) + atb = np.einsum("iket,ket->iet", df, resid) + + det = ata[0, 0]*ata[1, 1] - ata[0, 1]*ata[1, 0] + + df_inv_resid = np.empty_like(from_unit_nodes) + df_inv_resid[0] = 1/det * (ata[1, 1] * atb[0] - ata[1, 0]*atb[1]) + df_inv_resid[1] = 1/det * (-ata[0, 1] * atb[0] + ata[0, 0]*atb[1]) + + else: + # The boundary of a 3D mesh is 2D, so that's the + # highest-dimensional case we genuinely care about. + # + # This stinks, performance-wise, because it's not vectorized. + # But we'll only hit it for boundaries of 4+D meshes, in which + # case... good luck. :) + for e in range(nelements): + for t in range(nto_unit_nodes): + df_inv_resid[:, e, t], _, _, _ = \ + la.lstsq(df[:, :, e, t].T, resid[:, e, t]) + + from_unit_nodes = from_unit_nodes - df_inv_resid + + max_resid = np.max(np.abs(resid)) + logger.debug("gauss-newton residual: %g" % max_resid) + + if max_resid < tol: + logger.info("make_opposite_face_connection: gauss-newton: done, " + "final residual: %g" % max_resid) + break + + niter += 1 + if niter > 10: + raise RuntimeError("Gauss-Newton (for finding opposite-face reference " + "coordinates) did not converge") + + # }}} + + # {{{ find groups of from_unit_nodes + + def to_dev(ary): + return cl.array.to_device(queue, ary, array_queue=None) + + done_elements = np.zeros(nelements, dtype=np.bool) + while True: + todo_elements, = np.where(~done_elements) + if not len(todo_elements): + return + + template_unit_nodes = from_unit_nodes[:, todo_elements[0], :] + + unit_node_dist = np.max(np.max(np.abs( + from_unit_nodes[:, todo_elements, :] + - + template_unit_nodes.reshape(dim, 1, -1)), + axis=2), axis=0) + + close_els = todo_elements[unit_node_dist < tol] + done_elements[close_els] = True + + unit_node_dist = np.max(np.max(np.abs( + from_unit_nodes[:, todo_elements, :] + - + template_unit_nodes.reshape(dim, 1, -1)), + axis=2), axis=0) + + from meshmode.discretization.connection import InterpolationBatch + yield InterpolationBatch( + from_group_index=i_src_grp, + from_element_indices=to_dev(from_bdry_element_indices[close_els]), + to_element_indices=to_dev(to_bdry_element_indices[close_els]), + result_unit_nodes=template_unit_nodes, + to_element_face=None) + + # }}} + + +def _find_ibatch_for_face(vbc_tgt_grp_batches, iface): + vbc_tgt_grp_face_batches = [ + batch + for batch in vbc_tgt_grp_batches + if batch.to_element_face == iface] + + assert len(vbc_tgt_grp_face_batches) == 1 + + vbc_tgt_grp_face_batch, = vbc_tgt_grp_face_batches + + return vbc_tgt_grp_face_batch + + +def _make_el_lookup_table(queue, connection, igrp): + from_nelements = connection.from_discr.groups[igrp].nelements + from_nfaces = connection.from_discr.mesh.groups[igrp].nfaces + + iel_lookup = np.empty((from_nelements, from_nfaces), + dtype=connection.from_discr.mesh.element_id_dtype) + iel_lookup.fill(-1) + + for ibatch, batch in enumerate(connection.groups[igrp].batches): + from_element_indices = batch.from_element_indices.get(queue=queue) + iel_lookup[from_element_indices, batch.to_element_face] = \ + batch.to_element_indices.get(queue=queue) + + return iel_lookup + + +def make_opposite_face_connection(volume_to_bdry_conn): + """Given a boundary restriction connection *volume_to_bdry_conn*, + return a :class:`DiscretizationConnection` that performs data + exchange across opposite faces. + """ + + vol_discr = volume_to_bdry_conn.from_discr + vol_mesh = vol_discr.mesh + bdry_discr = volume_to_bdry_conn.to_discr + + # make sure we were handed a volume-to-boundary connection + for i_tgrp, conn_grp in enumerate(volume_to_bdry_conn.groups): + for batch in conn_grp.batches: + assert batch.from_group_index == i_tgrp + assert batch.to_element_face is not None + + ngrps = len(volume_to_bdry_conn.groups) + assert ngrps == len(vol_discr.groups) + assert ngrps == len(bdry_discr.groups) + + # One interpolation batch in this connection corresponds + # to a key (i_tgt_grp,) (i_src_grp, i_face_tgt,) + + with cl.CommandQueue(vol_discr.cl_context) as queue: + # a list of batches for each group + groups = [[] for i_tgt_grp in range(ngrps)] + + for i_src_grp in range(ngrps): + src_grp_el_lookup = _make_el_lookup_table( + queue, volume_to_bdry_conn, i_src_grp) + + for i_tgt_grp in range(ngrps): + vbc_tgt_grp_batches = volume_to_bdry_conn.groups[i_tgt_grp].batches + + adj_grp = vol_mesh.facial_adjacency_groups[i_tgt_grp][i_src_grp] + + for i_face_tgt in range(vol_mesh.groups[i_tgt_grp].nfaces): + vbc_tgt_grp_face_batch = _find_ibatch_for_face( + vbc_tgt_grp_batches, i_face_tgt) + + groups[i_tgt_grp].extend( + _make_cross_face_batches( + queue, vol_discr, bdry_discr, + i_tgt_grp, i_src_grp, + i_face_tgt, + adj_grp, + vbc_tgt_grp_face_batch, src_grp_el_lookup)) + + from meshmode.discretization.connection import ( + DiscretizationConnection, DiscretizationConnectionElementGroup) + return DiscretizationConnection( + from_discr=bdry_discr, + to_discr=bdry_discr, + groups=[ + DiscretizationConnectionElementGroup(batches=batches) + for batches in groups], + is_surjective=True) + +# }}} + +# vim: foldmethod=marker diff --git a/meshmode/discretization/connection/same_mesh.py b/meshmode/discretization/connection/same_mesh.py new file mode 100644 index 0000000000000000000000000000000000000000..3b73e34fefd5115e01b97728ab414cc8c77fd178 --- /dev/null +++ b/meshmode/discretization/connection/same_mesh.py @@ -0,0 +1,65 @@ +from __future__ import division, print_function, absolute_import + +__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" + +__license__ = """ +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. +""" + +import numpy as np +import pyopencl as cl +import pyopencl.array # noqa + + +# {{{ same-mesh constructor + +def make_same_mesh_connection(to_discr, from_discr): + from meshmode.discretization.connection import ( + InterpolationBatch, DiscretizationConnectionElementGroup, + DiscretizationConnection) + + if from_discr.mesh is not to_discr.mesh: + raise ValueError("from_discr and to_discr must be based on " + "the same mesh") + + assert to_discr.cl_context == from_discr.cl_context + + with cl.CommandQueue(to_discr.cl_context) as queue: + groups = [] + for igrp, (fgrp, tgrp) in enumerate(zip(from_discr.groups, to_discr.groups)): + all_elements = cl.array.arange(queue, + fgrp.nelements, + dtype=np.intp).with_queue(None) + ibatch = InterpolationBatch( + from_group_index=igrp, + from_element_indices=all_elements, + to_element_indices=all_elements, + result_unit_nodes=tgrp.unit_nodes, + to_element_face=None) + + groups.append( + DiscretizationConnectionElementGroup([ibatch])) + + return DiscretizationConnection( + from_discr, to_discr, groups, + is_surjective=True) + +# }}} + +# vim: foldmethod=marker diff --git a/meshmode/discretization/poly_element.py b/meshmode/discretization/poly_element.py index d59346691c93f7e967a1174b0a4622fdfff05426..a5319bc84f47ab6689f502629ff6009893033832 100644 --- a/meshmode/discretization/poly_element.py +++ b/meshmode/discretization/poly_element.py @@ -38,6 +38,7 @@ Group types .. autoclass:: InterpolatoryQuadratureSimplexElementGroup .. autoclass:: QuadratureSimplexElementGroup .. autoclass:: PolynomialWarpAndBlendElementGroup +.. autoclass:: PolynomialEquidistantElementGroup Group factories ^^^^^^^^^^^^^^^ @@ -45,6 +46,7 @@ Group factories .. autoclass:: InterpolatoryQuadratureSimplexGroupFactory .. autoclass:: QuadratureSimplexGroupFactory .. autoclass:: PolynomialWarpAndBlendGroupFactory +.. autoclass:: PolynomialEquidistantGroupFactory """ # FIXME Most of the loopy kernels will break as soon as we start using multiple @@ -63,13 +65,16 @@ from meshmode.discretization import ElementGroupBase class PolynomialSimplexElementGroupBase(ElementGroupBase): def basis(self): - return mp.simplex_onb(self.dim, self.order) + return mp.simplex_best_available_basis(self.dim, self.order) + + def grad_basis(self): + return mp.grad_simplex_best_available_basis(self.dim, self.order) @memoize_method def from_mesh_interp_matrix(self): meg = self.mesh_el_group return mp.resampling_matrix( - mp.simplex_onb(meg.dim, meg.order), + mp.simplex_best_available_basis(meg.dim, meg.order), self.unit_nodes, meg.unit_nodes) @@ -77,7 +82,7 @@ class PolynomialSimplexElementGroupBase(ElementGroupBase): def diff_matrices(self): result = mp.differentiation_matrices( self.basis(), - mp.grad_simplex_onb(self.dim, self.order), + self.grad_basis(), self.unit_nodes) if not isinstance(result, tuple): @@ -85,13 +90,6 @@ class PolynomialSimplexElementGroupBase(ElementGroupBase): else: return result - @memoize_method - def resampling_matrix(self): - meg = self.mesh_el_group - return mp.resampling_matrix( - mp.simplex_onb(self.dim, meg.order), - self.unit_nodes, meg.unit_nodes) - class InterpolatoryQuadratureSimplexElementGroup(PolynomialSimplexElementGroupBase): """Elemental discretization supplying a high-order quadrature rule @@ -154,11 +152,20 @@ class QuadratureSimplexElementGroup(PolynomialSimplexElementGroupBase): return self._quadrature_rule().weights -class PolynomialWarpAndBlendElementGroup(PolynomialSimplexElementGroupBase): +class _MassMatrixQuadratureElementGroup(PolynomialSimplexElementGroupBase): + @property + @memoize_method + def weights(self): + return np.dot( + mp.mass_matrix(self.basis(), self.unit_nodes), + np.ones(len(self.basis()))) + + +class PolynomialWarpAndBlendElementGroup(_MassMatrixQuadratureElementGroup): """Elemental discretization with a number of nodes matching the number of polynomials in :math:`P^k`, hence usable for differentiation and - interpolation. Interpolation nodes are present on the boundary of the - simplex. + interpolation. Interpolation nodes edge-clustered for avoidance of Runge + phenomena. Nodes are present on the boundary of the simplex. """ @property @memoize_method @@ -170,12 +177,24 @@ class PolynomialWarpAndBlendElementGroup(PolynomialSimplexElementGroupBase): assert dim2 == dim return result + +class PolynomialEquidistantElementGroup(_MassMatrixQuadratureElementGroup): + """Elemental discretization with a number of nodes matching the number of + polynomials in :math:`P^k`, hence usable for differentiation and + interpolation. Interpolation nodes are present on the boundary of the + simplex. + + .. versionadded:: 2016.1 + """ @property @memoize_method - def weights(self): - return np.dot( - mp.mass_matrix(self.basis(), self.unit_nodes), - np.ones(len(self.basis()))) + def unit_nodes(self): + dim = self.mesh_el_group.dim + result = mp.equidistant_nodes(dim, self.order) + + dim2, nunit_nodes = result.shape + assert dim2 == dim + return result # }}} @@ -212,6 +231,15 @@ class PolynomialWarpAndBlendGroupFactory(OrderBasedGroupFactory): mesh_group_class = _MeshSimplexElementGroup group_class = PolynomialWarpAndBlendElementGroup + +class PolynomialEquidistantGroupFactory(OrderBasedGroupFactory): + """ + .. versionadded:: 2016.1 + """ + + mesh_group_class = _MeshSimplexElementGroup + group_class = PolynomialEquidistantElementGroup + # }}} diff --git a/meshmode/discretization/visualization.py b/meshmode/discretization/visualization.py index 3c2565fdb5a7ac206d76b0c2fb5ed44853939e54..d68ec0c58078e68417721c8205fc3f24021d0bad 100644 --- a/meshmode/discretization/visualization.py +++ b/meshmode/discretization/visualization.py @@ -34,7 +34,7 @@ __doc__ = """ .. autoclass:: Visualizer -.. autofunction:: write_mesh_connectivity_vtk_file +.. autofunction:: write_nodal_adjacency_vtk_file """ @@ -77,10 +77,10 @@ class Visualizer(object): .. automethod:: write_vtk_file """ - def __init__(self, discr, vis_discr, connection): - self.discr = discr - self.vis_discr = vis_discr + def __init__(self, connection): self.connection = connection + self.discr = connection.from_discr + self.vis_discr = connection.to_discr def _resample_and_get(self, queue, vec): from pytools.obj_array import with_object_array_or_scalar @@ -240,16 +240,36 @@ def make_visualizer(queue, discr, vis_order): real_dtype=discr.real_dtype) from meshmode.discretization.connection import \ make_same_mesh_connection - cnx = make_same_mesh_connection(queue, vis_discr, discr) - return Visualizer(discr, vis_discr, cnx) + return Visualizer(make_same_mesh_connection(vis_discr, discr)) # }}} -# {{{ connectivity +# {{{ draw_curve -def write_mesh_connectivity_vtk_file(file_name, mesh, compressor=None,): +def draw_curve(discr): + mesh = discr.mesh + + import matplotlib.pyplot as pt + pt.plot(mesh.vertices[0], mesh.vertices[1], "o") + + color = pt.cm.rainbow(np.linspace(0, 1, len(discr.groups))) + with cl.CommandQueue(discr.cl_context) as queue: + for i, group in enumerate(discr.groups): + group_nodes = group.view(discr.nodes()).get(queue=queue) + pt.plot( + group_nodes[0].T, + group_nodes[1].T, "-x", + label="Group %d" % i, + color=color[i]) + +# }}} + + +# {{{ adjacency + +def write_nodal_adjacency_vtk_file(file_name, mesh, compressor=None,): from pyvisfile.vtk import ( UnstructuredGrid, DataArray, AppendedDataXMLGenerator, @@ -266,16 +286,16 @@ def write_mesh_connectivity_vtk_file(file_name, mesh, compressor=None,): np.sum(mesh.vertices[:, grp.vertex_indices], axis=-1) / grp.vertex_indices.shape[-1]) - cnx = mesh.element_connectivity + adj = mesh.nodal_adjacency - nconnections = len(cnx.neighbors) + nconnections = len(adj.neighbors) connections = np.empty((nconnections, 2), dtype=np.int32) - nb_starts = cnx.neighbors_starts + nb_starts = adj.neighbors_starts for iel in range(mesh.nelements): connections[nb_starts[iel]:nb_starts[iel+1], 0] = iel - connections[:, 1] = cnx.neighbors + connections[:, 1] = adj.neighbors grid = UnstructuredGrid( (mesh.nelements, diff --git a/meshmode/mesh/__init__.py b/meshmode/mesh/__init__.py index 6bd4b92492de942de1ca0c388207d63d44257973..5c2b5c1cb6ec7be972ed1ca15e0eb2db1c4b852c 100644 --- a/meshmode/mesh/__init__.py +++ b/meshmode/mesh/__init__.py @@ -24,6 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import six import numpy as np import modepy as mp import numpy.linalg as la @@ -39,11 +40,59 @@ __doc__ = """ :members: :undoc-members: -.. autoclass:: ElementConnectivity +.. autoclass:: NodalAdjacency +.. autoclass:: FacialAdjacencyGroup +.. autofunction:: as_python +.. autofunction:: check_bc_coverage +.. autofunction:: is_boundary_tag_empty + +Predefined Boundary tags +------------------------ + +.. autoclass:: BTAG_NONE +.. autoclass:: BTAG_ALL +.. autoclass:: BTAG_REALLY_ALL +.. autoclass:: BTAG_NO_BOUNDARY """ +# {{{ element tags + +class BTAG_NONE(object): + """A boundary tag representing an empty boundary or volume.""" + pass + + +class BTAG_ALL(object): + """A boundary tag representing the entire boundary or volume. + + In the case of the boundary, TAG_ALL does not include rank boundaries, + or, more generally, anything tagged with TAG_NO_BOUNDARY.""" + pass + + +class BTAG_REALLY_ALL(object): + """A boundary tag representing the entire boundary. + + Unlike :class:`TAG_ALL`, this includes rank boundaries, + or, more generally, everything tagged with :class:`TAG_NO_BOUNDARY`.""" + pass + + +class BTAG_NO_BOUNDARY(object): + """A boundary tag indicating that this edge should not fall under + :class:`TAG_ALL`. Among other things, this is used to keep rank boundaries + out of :class:`BTAG_ALL`. + """ + pass + + +SYSTEM_TAGS = set([BTAG_NONE, BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY]) + +# }}} + + # {{{ element group class MeshElementGroup(Record): @@ -78,6 +127,14 @@ class MeshElementGroup(Record): The number of dimensions spanned by the element. *Not* the ambient dimension, see :attr:`Mesh.ambient_dim` for that. + + .. automethod:: face_vertex_indices + .. automethod:: vertex_unit_coordinates + + .. attribute:: nfaces + + .. automethod:: __eq__ + .. automethod:: __ne__ """ def __init__(self, order, vertex_indices, nodes, @@ -134,6 +191,36 @@ class MeshElementGroup(Record): def nunit_nodes(self): return self.unit_nodes.shape[-1] + def face_vertex_indices(self): + """Return a tuple of tuples indicating which vertices + (in mathematically positive ordering) make up each face + of an element in this group. + """ + raise NotImplementedError() + + def vertex_unit_coordinates(self): + """Return an array of shape ``(nfaces, dim)`` with the unit + coordinates of each vertex. + """ + raise NotImplementedError() + + @property + def nfaces(self): + return len(self.face_vertex_indices()) + + def __eq__(self, other): + return ( + type(self) == type(other) + and self.order == other.order + and np.array_equal(self.vertex_indices, other.vertex_indices) + and np.array_equal(self.nodes, other.nodes) + and np.array_equal(self.unit_nodes, other.unit_nodes) + and self.element_nr_base == other.element_nr_base + and self.node_nr_base == other.node_nr_base) + + def __ne__(self, other): + return not self.__eq__(other) + class SimplexElementGroup(MeshElementGroup): def __init__(self, order, vertex_indices, nodes, @@ -164,7 +251,10 @@ class SimplexElementGroup(MeshElementGroup): raise TypeError("'dim' must be passed " "if 'unit_nodes' is not passed") - unit_nodes = mp.warp_and_blend_nodes(dim, order) + if dim <= 3: + unit_nodes = mp.warp_and_blend_nodes(dim, order) + else: + unit_nodes = mp.equidistant_nodes(dim, order) dims = unit_nodes.shape[0] @@ -173,12 +263,15 @@ class SimplexElementGroup(MeshElementGroup): "element. expected: %d, got: %d" % (dims+1, vertex_indices.shape[-1])) - MeshElementGroup.__init__(self, order, vertex_indices, nodes, + super(SimplexElementGroup, self).__init__(order, vertex_indices, nodes, element_nr_base, node_nr_base, unit_nodes, dim) def face_vertex_indices(self): if self.dim == 1: - return ((0, 1),) + return ( + (0,), + (1,), + ) elif self.dim == 2: return ( (0, 1), @@ -196,36 +289,21 @@ class SimplexElementGroup(MeshElementGroup): raise NotImplementedError("dim=%d" % self.dim) def vertex_unit_coordinates(self): - if self.dim == 1: - return np.array([ - [-1], [1] - ], dtype=np.float64) - elif self.dim == 2: - return np.array([ - [-1, -1], - [1, -1], - [-1, 1], - ], dtype=np.float64) - elif self.dim == 3: - return np.array([ - [-1, -1, -1], - [1, -1, -1], - [-1, 1, -1], - [-1, -1, 1], - ], dtype=np.float64) - else: - raise NotImplementedError("dim=%d" % self.dim) + from modepy.tools import unit_vertices + return unit_vertices(self.dim) # }}} -# {{{ mesh +# {{{ nodal adjacency + +class NodalAdjacency(Record): + """Describes nodal element adjacency information, i.e. information about + elements that touch in at least one point. -class ElementConnectivity(Record): - """ .. attribute:: neighbors_starts - ``element_id_t [nelments+1]`` + ``element_id_t [nelements+1]`` Use together with :attr:`neighbors`. ``neighbors_starts[iel]`` and ``neighbors_starts[iel+1]`` together indicate a ranges of element indices @@ -236,8 +314,92 @@ class ElementConnectivity(Record): ``element_id_t []`` See :attr:`neighbors_starts`. + + .. automethod:: __eq__ + .. automethod:: __ne__ """ + def __eq__(self, other): + return ( + type(self) == type(other) + and np.array_equal(self.neighbors_starts, + other.neighbors_starts) + and np.array_equal(self.neighbors, other.neighbors)) + + def __ne__(self, other): + return not self.__eq__(other) + +# }}} + + +# {{{ facial adjacency + +class FacialAdjacencyGroup(Record): + """Describes facial element adjacency information for one + :class:`MeshElementGroup`, i.e. information about elements that share (part + of) a face. + + .. attribute:: igroup + + .. attribute:: ineighbor_group + + ID of neighboring group, or *None* for boundary faces. If identical + to :attr:`igroup`, then this contains the self-connectivity in this + group. + + .. attribute:: elements + + ``element_id_t [nfagrp_elements]``. ``elements[i]`` + Group-local element numbers. + + .. attribute:: element_faces + + ``face_id_t [nfagrp_elements]``. ``element_faces[i]`` + indicate what face index of the opposite element indicated in + ``neighbors[iel_grp][iface]`` touches face number *iface* of element + number *iel_grp* in this element group. + + .. attribute:: neighbors + + ``element_id_t [nfagrp_elements]``. ``neighbors[i]`` + gives the element number within :attr:`ineighbor_group` of the element + opposite ``elements[i]``. + + If this number is negative, then this indicates that this is a + boundary face, and the bits set in ``-neighbors[i]`` + should be interpreted according to :attr:`Mesh.boundary_tags`. + + .. attribute:: neighbor_faces + + ``face_id_t [nfagrp_elements]``. ``neighbor_faces[i]`` indicate what + face index of the opposite element indicated in ``neighbors[i]`` has + facial contact with face number ``element_faces[i]`` of element number + ``elements[i]`` in element group *igroup*. + + Zero if ``neighbors[i]`` is negative. + + .. automethod:: __eq__ + .. automethod:: __ne__ + """ + + def __eq__(self, other): + return ( + type(self) == type(other) + and self.igroup == other.igroup + and self.ineighbor_group == other.ineighbor_group + and np.array_equal(self.elements, other.elements) + and np.array_equal(self.element_faces, other.element_faces) + and np.array_equal(self.neighbors, other.neighbors) + and np.array_equal(self.neighbor_faces, other.neighbor_faces) + ) + + def __ne__(self, other): + return not self.__eq__(other) + +# }}} + + +# {{{ mesh class Mesh(Record): """ @@ -250,20 +412,51 @@ class Mesh(Record): A list of :class:`MeshElementGroup` instances. - .. attribute:: element_connectivity + .. attribute:: nodal_adjacency - An instance of :class:`ElementConnectivity`. + An instance of :class:`NodalAdjacency`. Referencing this attribute may raise - :exc:`meshmode.ConnectivityUnavailable`. + :exc:`meshmode.DataUnavailable`. + + .. attribute:: facial_adjacency_groups + + A list of mappings from neighbor group IDs to instances of + :class:`FacialAdjacencyGroup`. + + ``facial_adjacency_groups[igrp][ineighbor_group]`` gives + the set of facial adjacency relations between group *igrp* + and *ineighbor_group*. *ineighbor_group* and *igrp* may be + identical, or *ineighbor_group* may be *None*, in which case + a group containing boundary faces is returned. + + Referencing this attribute may raise + :exc:`meshmode.DataUnavailable`. + + .. attribute:: boundary_tags + + A tuple of boundary tag identifiers. :class:`BTAG_ALL` and + :class:`BTAG_REALLY_ALL` are guranateed to exist. + + .. attribute:: btag_to_index + + A mapping that maps boundary tag identifiers to their + corresponding index. .. attribute:: vertex_id_dtype .. attribute:: element_id_dtype + + .. automethod:: __eq__ + .. automethod:: __ne__ """ + face_id_dtype = np.int8 + def __init__(self, vertices, groups, skip_tests=False, - element_connectivity=False, + nodal_adjacency=False, + facial_adjacency_groups=False, + boundary_tags=None, vertex_id_dtype=np.int32, element_id_dtype=np.int32): """ @@ -271,7 +464,7 @@ 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 element_connectivity: One of three options: + :arg nodal_adjacency: One of three options: *None*, in which case this information will be deduced from vertex adjacency. *False*, in which case this information will be marked unavailable (such as if there are @@ -279,8 +472,16 @@ class Mesh(Record): the full picture), and references to :attr:`element_neighbors_starts` and :attr:`element_neighbors` will result in exceptions. Lastly, a tuple - *(element_neighbors_starts, element_neighbors)*, representing the - correspondingly-named attributes. + :class:`NodalAdjacency` object. + :arg facial_adjacency_groups: One of three options: + *None*, in which case this information + will be deduced from vertex adjacency. *False*, in which case + this information will be marked unavailable (such as if there are + hanging nodes in the geometry, so that vertex adjacency does not convey + the full picture), and references to + :attr:`element_neighbors_starts` and :attr:`element_neighbors` + will result in exceptions. Lastly, a data structure as described in + :attr:`facial_adjacency_groups` may be passed. """ el_nr = 0 node_nr = 0 @@ -292,18 +493,48 @@ class Mesh(Record): el_nr += ng.nelements node_nr += ng.nnodes - if element_connectivity is not False and element_connectivity is not None: - nb_starts, nbs = element_connectivity - element_connectivity = ElementConnectivity( - neighbors_starts=nb_starts, - neighbors=nbs) + # {{{ boundary tags + + if boundary_tags is None: + boundary_tags = [] + else: + boundary_tags = boundary_tags[:] + + if BTAG_NONE in boundary_tags: + raise ValueError("BTAG_NONE is not allowed to be part of " + "boundary_tags") + if BTAG_ALL not in boundary_tags: + boundary_tags.append(BTAG_ALL) + if BTAG_REALLY_ALL not in boundary_tags: + boundary_tags.append(BTAG_REALLY_ALL) + + max_boundary_tag_count = int( + np.log(np.iinfo(element_id_dtype).max)/np.log(2)) + if len(boundary_tags) > max_boundary_tag_count: + raise ValueError("too few bits in element_id_dtype to represent all " + "boundary tags") + + btag_to_index = dict( + (btag, i) for i, btag in enumerate(boundary_tags)) + + # }}} + + if nodal_adjacency is not False and nodal_adjacency is not None: + if not isinstance(nodal_adjacency, NodalAdjacency): + nb_starts, nbs = nodal_adjacency + nodal_adjacency = NodalAdjacency( + neighbors_starts=nb_starts, + neighbors=nbs) - del nb_starts - del nbs + del nb_starts + del nbs Record.__init__( self, vertices=vertices, groups=new_groups, - _element_connectivity=element_connectivity, + _nodal_adjacency=nodal_adjacency, + _facial_adjacency_groups=facial_adjacency_groups, + boundary_tags=boundary_tags, + btag_to_index=btag_to_index, vertex_id_dtype=np.dtype(vertex_id_dtype), element_id_dtype=np.dtype(element_id_dtype), ) @@ -313,6 +544,32 @@ class Mesh(Record): 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 len(nodal_adjacency.neighbors.shape) == 1 + + assert nodal_adjacency.neighbor_starts.dtype == self.element_id_dtype + assert nodal_adjacency.neighbors.dtype == self.element_id_dtype + + if facial_adjacency_groups: + assert len(facial_adjacency_groups) == len(self.groups) + for fagrp_map in facial_adjacency_groups: + for fagrp in six.itervalues(fagrp_map): + grp = self.groups[fagrp.igroup] + + fvi = grp.face_vertex_indices() + assert fagrp.neighbors.dtype == self.element_id_dtype + assert fagrp.neighbors.shape == ( + grp.nelements, len(fvi)) + assert fagrp.neighbors.dtype == self.face_id_dtype + assert fagrp.neighbor_faces.shape == ( + grp.nelements, len(fvi)) + + is_bdry = fagrp.neighbors < 0 + assert ((1 << btag_to_index[BTAG_REALLY_ALL]) + & fagrp.neighbors[is_bdry]).all(), \ + "boundary faces without BTAG_REALLY_ALL found" + from meshmode.mesh.processing import \ test_volume_mesh_element_orientations @@ -330,19 +587,63 @@ class Mesh(Record): from pytools import single_valued return single_valued(grp.dim for grp in self.groups) + @property + def nvertices(self): + return self.vertices.shape[-1] + @property def nelements(self): return sum(grp.nelements for grp in self.groups) @property - def element_connectivity(self): - if self._element_connectivity is False: - from meshmode import ConnectivityUnavailable - raise ConnectivityUnavailable() - elif self._element_connectivity is None: - self._element_connectivity = _compute_connectivity_from_vertices(self) + def nodal_adjacency(self): + if self._nodal_adjacency is False: + from meshmode import DataUnavailable + raise DataUnavailable("nodal_adjacency") + elif self._nodal_adjacency is None: + self._nodal_adjacency = _compute_nodal_adjacency_from_vertices(self) + + return self._nodal_adjacency + + def nodal_adjacency_init_arg(self): + """Returns an 'nodal_adjacency' argument that can be + passed to a Mesh constructor. + """ - return self._element_connectivity + return self._nodal_adjacency + + @property + def facial_adjacency_groups(self): + if self._facial_adjacency_groups is False: + from meshmode import DataUnavailable + raise DataUnavailable("facial_adjacency_groups") + elif self._facial_adjacency_groups is None: + self._facial_adjacency_groups = \ + _compute_facial_adjacency_from_vertices(self) + + return self._facial_adjacency_groups + + def boundary_tag_bit(self, boundary_tag): + try: + return 1 << self.btag_to_index[boundary_tag] + except KeyError: + return 0 + + def __eq__(self, other): + return ( + type(self) == type(other) + and np.array_equal(self.vertices, other.vertices) + and self.groups == other.groups + and self.vertex_id_dtype == other.vertex_id_dtype + and self.element_id_dtype == other.element_id_dtype + and (self._nodal_adjacency + == other._nodal_adjacency) + and (self._facial_adjacency_groups + == other._facial_adjacency_groups) + and self.boundary_tags == other.boundary_tags) + + def __ne__(self, other): + return not self.__eq__(other) # Design experience: Try not to add too many global data structures to the # mesh. Let the element groups be responsible for that at the mesh level. @@ -355,10 +656,13 @@ class Mesh(Record): # {{{ node-vertex consistency test def _test_node_vertex_consistency_simplex(mesh, mgrp): - from modepy.tools import UNIT_VERTICES + if mgrp.nelements == 0: + return True + resampling_mat = mp.resampling_matrix( - mp.simplex_onb(mgrp.dim, mgrp.order), - UNIT_VERTICES[mgrp.dim].T.copy(), mgrp.unit_nodes) + mp.simplex_best_available_basis(mgrp.dim, mgrp.order), + mgrp.vertex_unit_coordinates().T, + mgrp.unit_nodes) # dim, nelments, nnvertices map_vertices = np.einsum( @@ -401,9 +705,9 @@ def _test_node_vertex_consistency(mesh): # }}} -# {{{ vertex-based connectivity +# {{{ vertex-based nodal adjacency -def _compute_connectivity_from_vertices(mesh): +def _compute_nodal_adjacency_from_vertices(mesh): # FIXME Native code would make this faster _, nvertices = mesh.vertices.shape @@ -436,10 +740,296 @@ def _compute_connectivity_from_vertices(mesh): assert neighbors_starts[-1] == len(neighbors) - return ElementConnectivity( + return NodalAdjacency( neighbors_starts=neighbors_starts, neighbors=neighbors) # }}} + +# {{{ vertex-based facial adjacency + +def _compute_facial_adjacency_from_vertices(mesh): + # FIXME Native code would make this faster + + # create face_map, which is a mapping of + # (vertices on a face) -> + # [(igrp, iel_grp, face_idx) for elements bordering that face] + face_map = {} + for igrp, grp in enumerate(mesh.groups): + for fid, face_vertex_indices in enumerate(grp.face_vertex_indices()): + all_fvi = grp.vertex_indices[:, face_vertex_indices] + + for iel_grp, fvi in enumerate(all_fvi): + face_map.setdefault( + frozenset(fvi), []).append((igrp, iel_grp, fid)) + + # maps tuples (igrp, ineighbor_group) to number of elements + group_count = {} + for face_tuples in six.itervalues(face_map): + if len(face_tuples) == 2: + (igrp, _, _), (inb_grp, _, _) = face_tuples + group_count[igrp, inb_grp] = group_count.get((igrp, inb_grp), 0) + 1 + group_count[inb_grp, igrp] = group_count.get((inb_grp, igrp), 0) + 1 + elif len(face_tuples) == 1: + (igrp, _, _), = face_tuples + group_count[igrp, None] = group_count.get((igrp, None), 0) + 1 + else: + raise RuntimeError("unexpected number of adjacent faces") + + # {{{ build facial_adjacency_groups data structure, still empty + + facial_adjacency_groups = [] + for igroup in range(len(mesh.groups)): + grp_map = {} + facial_adjacency_groups.append(grp_map) + + bdry_count = group_count.get((igroup, None)) + if bdry_count is not None: + elements = np.empty(bdry_count, dtype=mesh.element_id_dtype) + element_faces = np.empty(bdry_count, dtype=mesh.face_id_dtype) + neighbors = np.empty(bdry_count, dtype=mesh.element_id_dtype) + neighbor_faces = np.zeros(bdry_count, dtype=mesh.face_id_dtype) + + neighbors.fill(-( + mesh.boundary_tag_bit(BTAG_ALL) + | mesh.boundary_tag_bit(BTAG_REALLY_ALL))) + + grp_map[None] = FacialAdjacencyGroup( + igroup=igroup, + ineighbor_group=None, + elements=elements, + element_faces=element_faces, + neighbors=neighbors, + neighbor_faces=neighbor_faces) + + for ineighbor_group in range(len(mesh.groups)): + nb_count = group_count.get((igroup, ineighbor_group)) + if nb_count is not None: + elements = np.empty(nb_count, dtype=mesh.element_id_dtype) + element_faces = np.empty(nb_count, dtype=mesh.face_id_dtype) + neighbors = np.empty(nb_count, dtype=mesh.element_id_dtype) + neighbor_faces = np.empty(nb_count, dtype=mesh.face_id_dtype) + + grp_map[ineighbor_group] = FacialAdjacencyGroup( + igroup=igroup, + ineighbor_group=ineighbor_group, + elements=elements, + element_faces=element_faces, + neighbors=neighbors, + neighbor_faces=neighbor_faces) + + # }}} + + # maps tuples (igrp, ineighbor_group) to number of elements filled in group + fill_count = {} + for face_tuples in six.itervalues(face_map): + if len(face_tuples) == 2: + for (igroup, iel, iface), (inb_group, inb_el, inb_face) in [ + (face_tuples[0], face_tuples[1]), + (face_tuples[1], face_tuples[0]), + ]: + idx = fill_count.get((igrp, inb_grp), 0) + fill_count[igrp, inb_grp] = idx + 1 + + fagrp = facial_adjacency_groups[igroup][inb_grp] + fagrp.elements[idx] = iel + fagrp.element_faces[idx] = iface + fagrp.neighbors[idx] = inb_el + fagrp.neighbor_faces[idx] = inb_face + + elif len(face_tuples) == 1: + (igroup, iel, iface), = face_tuples + + idx = fill_count.get((igrp, None), 0) + fill_count[igrp, None] = idx + 1 + + fagrp = facial_adjacency_groups[igroup][None] + fagrp.elements[idx] = iel + fagrp.element_faces[idx] = iface + + else: + raise RuntimeError("unexpected number of adjacent faces") + + return facial_adjacency_groups + +# }}} + + +# {{{ as_python + +def _numpy_array_as_python(array): + return "np.array(%s, dtype=np.%s)" % ( + repr(array.tolist()), + array.dtype.name) + + +def as_python(mesh, function_name="make_mesh"): + """Return a snippet of Python code (as a string) that will + recreate the mesh given as an input parameter. + """ + + from pytools.py_codegen import PythonCodeGenerator, Indentation + cg = PythonCodeGenerator() + cg(""" + # generated by meshmode.mesh.as_python + + import numpy as np + from meshmode.mesh import ( + Mesh, MeshElementGroup, FacialAdjacencyGroup, + BTAG_ALL, BTAG_REALLY_ALL) + + """) + + cg("def %s():" % function_name) + with Indentation(cg): + cg("vertices = " + _numpy_array_as_python(mesh.vertices)) + cg("") + cg("groups = []") + cg("") + for group in mesh.groups: + cg("import %s" % type(group).__module__) + cg("groups.append(%s.%s(" % ( + type(group).__module__, + type(group).__name__)) + cg(" order=%s," % group.order) + cg(" vertex_indices=%s," + % _numpy_array_as_python(group.vertex_indices)) + cg(" nodes=%s," + % _numpy_array_as_python(group.nodes)) + cg(" unit_nodes=%s))" + % _numpy_array_as_python(group.unit_nodes)) + + # {{{ facial adjacency groups + + def fagrp_params_str(fagrp): + params = { + "igroup": fagrp.igroup, + "ineighbor_group": repr(fagrp.ineighbor_group), + "elements": _numpy_array_as_python(fagrp.elements), + "element_faces": _numpy_array_as_python(fagrp.element_faces), + "neighbors": _numpy_array_as_python(fagrp.neighbors), + "neighbor_faces": _numpy_array_as_python(fagrp.neighbor_faces), + } + return ",\n ".join("%s=%s" % (k, v) for k, v in six.iteritems(params)) + + if mesh._facial_adjacency_groups: + cg("facial_adjacency_groups = []") + + for igrp, fagrp_map in enumerate(mesh.facial_adjacency_groups): + cg("facial_adjacency_groups.append({%s})" % ",\n ".join( + "%r: FacialAdjacencyGroup(%s)" % ( + inb_grp, fagrp_params_str(fagrp)) + for inb_grp, fagrp in six.iteritems(fagrp_map))) + + else: + cg("facial_adjacency_groups = %r" % mesh._facial_adjacency_groups) + + # }}} + + # {{{ boundary tags + + def strify_boundary_tag(btag): + if isinstance(btag, type): + return btag.__name__ + else: + return repr(btag) + + btags_str = ", ".join( + strify_boundary_tag(btag) for btag in mesh.boundary_tags) + + # }}} + + cg("return Mesh(vertices, groups, skip_tests=True,") + cg(" vertex_id_dtype=np.%s," % mesh.vertex_id_dtype.name) + cg(" element_id_dtype=np.%s," % mesh.element_id_dtype.name) + + if isinstance(mesh._nodal_adjacency, NodalAdjacency): + el_con_str = "(%s, %s)" % ( + _numpy_array_as_python( + mesh._nodal_adjacency.neighbors_starts), + _numpy_array_as_python( + mesh._nodal_adjacency.neighbors), + ) + else: + el_con_str = repr(mesh._nodal_adjacency) + + cg(" nodal_adjacency=%s," % el_con_str) + cg(" facial_adjacency_groups=facial_adjacency_groups,") + cg(" boundary_tags=[%s])" % btags_str) + + # FIXME: Handle facial adjacency, boundary tags + + return cg.get() + +# }}} + + +# {{{ check_bc_coverage + +def check_bc_coverage(mesh, boundary_tags, incomplete_ok=False): + """Verify boundary condition coverage. + + Given a list of boundary tags as *boundary_tags*, this function verifies + that + + 1. the union of all these boundaries gives the complete boundary, + 1. all these boundaries are disjoint. + + :arg incomplete_ok: Do not report an error if some faces are not covered + by the boundary conditions. + """ + + for igrp, fagrp_map in enumerate(mesh.facial_adjacency_groups): + bdry_grp = fagrp_map.get(None) + if bdry_grp is None: + continue + + nb_elements = bdry_grp.neighbors + assert (nb_elements < 0).all() + + nb_el_bits = -nb_elements + + seen = np.zeros_like(nb_el_bits, dtype=np.bool) + + for btag in boundary_tags: + tag_bit = mesh.boundary_tag_bit(btag) + tag_set = (nb_el_bits & tag_bit) != 0 + + if (seen & tag_set).any(): + raise RuntimeError("faces with multiple boundary conditions found") + + seen = seen | tag_set + + if not incomplete_ok and not seen.all(): + raise RuntimeError("found faces without boundary conditions") + +# }}} + + +# {{{ is_boundary_tag_empty + +def is_boundary_tag_empty(mesh, boundary_tag): + """Return *True* if the corresponding boundary tag does not occur as part of + *mesh*. + """ + + btag_bit = mesh.boundary_tag_bit(boundary_tag) + if not btag_bit: + return True + + for igrp in range(len(mesh.groups)): + bdry_fagrp = mesh.facial_adjacency_groups[igrp].get(None, None) + if bdry_fagrp is None: + continue + + neg = bdry_fagrp.neighbors < 0 + if (-bdry_fagrp.neighbors[neg] & btag_bit).any(): + return False + + return True + +# }}} + + # vim: foldmethod=marker diff --git a/meshmode/mesh/generation.py b/meshmode/mesh/generation.py index 5b925dcb62dfacc73b90b1180800a737b1ba0a4d..65761586e01c8b0aa60f82d6f8b4bc00941eeed7 100644 --- a/meshmode/mesh/generation.py +++ b/meshmode/mesh/generation.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) 2013 Andreas Kloeckner" @@ -24,6 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from six.moves import range import numpy as np import numpy.linalg as la @@ -53,7 +52,13 @@ Surfaces .. autofunction:: generate_icosahedron .. autofunction:: generate_icosphere .. autofunction:: generate_torus + +Volumes +------- + +.. autofunction:: generate_box_mesh .. autofunction:: generate_regular_rect_mesh +.. autofunction:: generate_warped_rect_mesh """ @@ -207,8 +212,10 @@ def make_curve_mesh(curve_f, element_boundaries, order): nodes=nodes, unit_nodes=unodes) - return Mesh(vertices=vertices, groups=[egroup], - element_connectivity=None) + return Mesh( + vertices=vertices, groups=[egroup], + nodal_adjacency=None, + facial_adjacency_groups=None) # }}} @@ -227,7 +234,11 @@ def make_group_from_vertices(vertices, vertex_indices, order): dim = nspan_vectors # dim, nunit_nodes - unit_nodes = mp.warp_and_blend_nodes(dim, order) + if dim <= 3: + unit_nodes = mp.warp_and_blend_nodes(dim, order) + else: + unit_nodes = mp.equidistant_nodes(dim, order) + unit_nodes_01 = 0.5 + 0.5*unit_nodes nodes = np.einsum( @@ -280,8 +291,10 @@ def generate_icosahedron(r, order): grp = make_group_from_vertices(vertices, vertex_indices, order) from meshmode.mesh import Mesh - return Mesh(vertices, [grp], - element_connectivity=None) + return Mesh( + vertices, [grp], + nodal_adjacency=None, + facial_adjacency_groups=None) # }}} @@ -297,8 +310,10 @@ def generate_icosphere(r, order): nodes=grp.nodes * r / np.sqrt(np.sum(grp.nodes**2, axis=0))) from meshmode.mesh import Mesh - return Mesh(mesh.vertices, [grp], - element_connectivity=None) + return Mesh( + mesh.vertices, [grp], + nodal_adjacency=None, + facial_adjacency_groups=None) # }}} @@ -360,7 +375,11 @@ def generate_torus_and_cycle_vertices(r_outer, r_inner, nodes[2] = b*np.sin(minor_theta) from meshmode.mesh import Mesh - return (Mesh(vertices, [grp.copy(nodes=nodes)], element_connectivity=None), + return ( + Mesh( + vertices, [grp.copy(nodes=nodes)], + nodal_adjacency=None, + facial_adjacency_groups=None), [idx(i, 0) for i in range(n_outer)], [idx(0, j) for j in range(n_inner)]) @@ -373,6 +392,104 @@ def generate_torus(r_outer, r_inner, n_outer=20, n_inner=10, order=1): return mesh +# {{{ generate_box_mesh + +def generate_box_mesh(axis_coords, order=1, coord_dtype=np.float64): + """Create a semi-structured mesh. + + :param axis_coords: a tuple with a number of entries corresponding + to the number of dimensions, with each entry a numpy array + specifying the coordinates to be used along that axis. + """ + + for iaxis, axc in enumerate(axis_coords): + if len(axc) < 2: + raise ValueError("need at least two points along axis %d" + % (iaxis+1)) + + dim = len(axis_coords) + + shape = tuple(len(axc) for axc in axis_coords) + + from pytools import product + nvertices = product(shape) + + vertex_indices = np.arange(nvertices).reshape(*shape, order="F") + + vertices = np.empty((dim,)+shape, dtype=coord_dtype) + for idim in range(dim): + vshape = (shape[idim],) + (1,)*idim + vertices[idim] = axis_coords[idim].reshape(*vshape) + + vertices = vertices.reshape(dim, -1) + + el_vertices = [] + + if dim == 1: + for i in range(shape[0]-1): + # a--b + + a = vertex_indices[i] + b = vertex_indices[i+1] + + el_vertices.append((a, b,)) + + elif dim == 2: + for i in range(shape[0]-1): + for j in range(shape[1]-1): + + # c--d + # | | + # a--b + + a = vertex_indices[i, j] + b = vertex_indices[i+1, j] + c = vertex_indices[i, j+1] + d = vertex_indices[i+1, j+1] + + el_vertices.append((a, b, c)) + el_vertices.append((d, c, b)) + + elif dim == 3: + for i in range(shape[0]-1): + for j in range(shape[1]-1): + for k in range(shape[2]-1): + + a000 = vertex_indices[i, j, k] + a001 = vertex_indices[i, j, k+1] + a010 = vertex_indices[i, j+1, k] + a011 = vertex_indices[i, j+1, k+1] + + a100 = vertex_indices[i+1, j, k] + a101 = vertex_indices[i+1, j, k+1] + a110 = vertex_indices[i+1, j+1, k] + a111 = vertex_indices[i+1, j+1, k+1] + + el_vertices.append((a000, a100, a010, a001)) + el_vertices.append((a101, a100, a001, a010)) + el_vertices.append((a101, a011, a010, a001)) + + el_vertices.append((a100, a010, a101, a110)) + el_vertices.append((a011, a010, a110, a101)) + el_vertices.append((a011, a111, a101, a110)) + + else: + raise NotImplementedError("box meshes of dimension %d" + % dim) + + el_vertices = np.array(el_vertices, dtype=np.int32) + + grp = make_group_from_vertices( + vertices.reshape(dim, -1), el_vertices, order) + + from meshmode.mesh import Mesh + return Mesh(vertices, [grp], + nodal_adjacency=None, + facial_adjacency_groups=None) + +# }}} + + # {{{ generate_regular_rect_mesh def generate_regular_rect_mesh(a=(0, 0), b=(1, 1), n=(5, 5), order=1): @@ -386,42 +503,40 @@ def generate_regular_rect_mesh(a=(0, 0), b=(1, 1), n=(5, 5), order=1): if min(n) < 2: raise ValueError("need at least two points in each direction") - node_dict = {} - vertices = [] - points_1d = [np.linspace(a_i, b_i, n_i) + axis_coords = [np.linspace(a_i, b_i, n_i) for a_i, b_i, n_i in zip(a, b, n)] - for j in range(n[1]): - for i in range(n[0]): - node_dict[i, j] = len(vertices) - vertices.append(np.array([points_1d[0][i], points_1d[1][j]])) - - vertices = np.array(vertices).T.copy() - - vertex_indices = [] - - for i in range(n[0]-1): - for j in range(n[1]-1): - - # c--d - # | | - # a--b + return generate_box_mesh(axis_coords, order=order) - a = node_dict[i, j] - b = node_dict[i+1, j] - c = node_dict[i, j+1] - d = node_dict[i+1, j+1] +# }}} - vertex_indices.append((a, b, c)) - vertex_indices.append((d, c, b)) - vertex_indices = np.array(vertex_indices, dtype=np.int32) +# {{{ generate_warped_rect_mesh - grp = make_group_from_vertices(vertices, vertex_indices, order) +def generate_warped_rect_mesh(dim, order, n): + """Generate a mesh of a warped line/square/cube. Mainly useful for testing + functionality with curvilinear meshes. + """ - from meshmode.mesh import Mesh - return Mesh(vertices, [grp], - element_connectivity=None) + assert dim in [1, 2, 3] + mesh = generate_regular_rect_mesh( + a=(-0.5,)*dim, b=(0.5,)*dim, + n=(n,)*dim, order=order) + + def m(x): + result = np.empty_like(x) + result[0] = ( + 1.5*x[0] + np.cos(x[0]) + + 0.1*np.sin(10*x[1])) + result[1] = ( + 0.05*np.cos(10*x[0]) + + 1.3*x[1] + np.sin(x[1])) + if len(x) == 3: + result[2] = x[2] + np.sin(x[0]) + return result + + from meshmode.mesh.processing import map_mesh + return map_mesh(mesh, m) # }}} diff --git a/meshmode/mesh/io.py b/meshmode/mesh/io.py index 292ee55077a178c70694bf8fd719b2fe42eef143..e27f8de352016e6f70c86be905fd4e247213d72b 100644 --- a/meshmode/mesh/io.py +++ b/meshmode/mesh/io.py @@ -1,8 +1,4 @@ -from __future__ import division -from __future__ import absolute_import -import six -from six.moves import range -from six.moves import zip +from __future__ import division, absolute_import __copyright__ = "Copyright (C) 2014 Andreas Kloeckner" @@ -26,6 +22,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import six +from six.moves import range, zip import numpy as np from meshpy.gmsh_reader import ( # noqa @@ -39,6 +37,8 @@ __doc__ = """ .. autofunction:: read_gmsh .. autofunction:: generate_gmsh +.. autofunction:: from_meshpy +.. autofunction:: from_vertices_and_simplices """ @@ -171,11 +171,16 @@ class GmshMeshReceiver(GmshMeshReceiverBase): groups.append(group) - return Mesh(vertices, groups, element_connectivity=None) + return Mesh( + vertices, groups, + nodal_adjacency=None, + facial_adjacency_groups=None) # }}} +# {{{ gmsh + def read_gmsh(filename, force_ambient_dim=None): """Read a gmsh mesh file from *filename* and return a :class:`meshmode.mesh.Mesh`. @@ -213,7 +218,7 @@ def generate_gmsh(source, dimensions, order=None, other_options=[], mesh = recv.get_mesh() if force_ambient_dim is None: - AXIS_NAMES = "xyz" + AXIS_NAMES = "xyz" # noqa dim = mesh.vertices.shape[0] for idim in range(dim): @@ -226,3 +231,66 @@ def generate_gmsh(source, dimensions, order=None, other_options=[], break return mesh + +# }}} + + +# {{{ meshpy + +def from_meshpy(mesh_info, order=1): + """Imports a mesh from a :mod:`meshpy` *mesh_info* data structure, + which may be generated by either :mod:`meshpy.triangle` or + :mod:`meshpy_tet`. + """ + from meshmode.mesh import Mesh + from meshmode.mesh.generation import make_group_from_vertices + + vertices = np.array(mesh_info.points).T + elements = np.array(mesh_info.elements, np.int32) + + grp = make_group_from_vertices(vertices, elements, order) + + # FIXME: Should transfer boundary/volume markers + + return Mesh( + vertices=vertices, groups=[grp], + nodal_adjacency=None, + facial_adjacency_groups=None) + +# }}} + + +# {{{ from_vertices_and_simplices + +def from_vertices_and_simplices(vertices, simplices, order=1, fix_orientation=False): + """Imports a mesh from a numpy array of vertices and an array + of simplices. + + :arg vertices: + An array of vertex coordinates with shape + *(ambient_dim, nvertices)* + :arg simplices: + An array *(nelements, nvertices)* of (mesh-wide) + vertex indices. + """ + from meshmode.mesh import Mesh + from meshmode.mesh.generation import make_group_from_vertices + + grp = make_group_from_vertices(vertices, simplices, order) + + if fix_orientation: + from meshmode.mesh.processing import ( + find_volume_mesh_element_group_orientation, + flip_simplex_element_group) + orient = find_volume_mesh_element_group_orientation(vertices, grp) + grp = flip_simplex_element_group(vertices, grp, orient < 0) + + return Mesh( + vertices=vertices, groups=[grp], + nodal_adjacency=None, + facial_adjacency_groups=None) + +# }}} + + +# vim: foldmethod=marker diff --git a/meshmode/mesh/processing.py b/meshmode/mesh/processing.py index 94cd8b0951f5eb937c556bc239a2ebd7110bfb04..06631f9dedd6fdb1bcd3112a8c8157d4c72cb7f7 100644 --- a/meshmode/mesh/processing.py +++ b/meshmode/mesh/processing.py @@ -1,7 +1,4 @@ -from __future__ import division -from __future__ import absolute_import -from six.moves import range -from functools import reduce +from __future__ import division, absolute_import, print_function __copyright__ = "Copyright (C) 2014 Andreas Kloeckner" @@ -25,6 +22,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from six.moves import range +from functools import reduce + import numpy as np import numpy.linalg as la import modepy as mp @@ -34,14 +34,15 @@ __doc__ = """ .. autofunction:: find_volume_mesh_element_orientations .. autofunction:: perform_flips .. autofunction:: find_bounding_box -.. autofunction:: merge_dijsoint_meshes +.. autofunction:: merge_disjoint_meshes +.. autofunction:: map_mesh .. autofunction:: affine_map """ # {{{ orientations -def find_volume_mesh_element_group_orientation(mesh, grp): +def find_volume_mesh_element_group_orientation(vertices, grp): """Return a positive floating point number for each positively oriented element, and a negative floating point number for each negatively oriented element. @@ -56,11 +57,11 @@ def find_volume_mesh_element_group_orientation(mesh, grp): "exclusively SimplexElementGroup-based meshes") # (ambient_dim, nelements, nvertices) - vertices = mesh.vertices[:, grp.vertex_indices] + my_vertices = vertices[:, grp.vertex_indices] # (ambient_dim, nelements, nspan_vectors) spanning_vectors = ( - vertices[:, :, 1:] - vertices[:, :, 0][:, :, np.newaxis]) + my_vertices[:, :, 1:] - my_vertices[:, :, 0][:, :, np.newaxis]) ambient_dim = spanning_vectors.shape[0] nspan_vectors = spanning_vectors.shape[-1] @@ -81,6 +82,10 @@ def find_volume_mesh_element_group_orientation(mesh, grp): from operator import xor outer_prod = -reduce(xor, mvs) + if grp.dim == 1: + # FIXME: This is a little weird. + outer_prod = -outer_prod + return (outer_prod.I | outer_prod).as_scalar() @@ -102,7 +107,8 @@ def find_volume_mesh_element_orientations(mesh, tolerate_unimplemented_checks=Fa if tolerate_unimplemented_checks: try: signed_area_elements = \ - find_volume_mesh_element_group_orientation(mesh, grp) + find_volume_mesh_element_group_orientation( + mesh.vertices, grp) except NotImplementedError: result_grp_view[:] = float("nan") else: @@ -110,7 +116,8 @@ def find_volume_mesh_element_orientations(mesh, tolerate_unimplemented_checks=Fa result_grp_view[:] = signed_area_elements else: signed_area_elements = \ - find_volume_mesh_element_group_orientation(mesh, grp) + find_volume_mesh_element_group_orientation( + mesh.vertices, grp) assert not np.isnan(signed_area_elements).any() result_grp_view[:] = signed_area_elements @@ -158,7 +165,7 @@ def flip_simplex_element_group(vertices, grp, grp_flip_flags): flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) flip_matrix = mp.resampling_matrix( - mp.simplex_onb(grp.dim, grp.order), + mp.simplex_best_available_basis(grp.dim, grp.order), flipped_unit_nodes, grp.unit_nodes) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 @@ -220,7 +227,7 @@ def find_bounding_box(mesh): # {{{ merging -def merge_dijsoint_meshes(meshes, skip_tests=False): +def merge_disjoint_meshes(meshes, skip_tests=False): if not meshes: raise ValueError("must pass at least one mesh") @@ -271,27 +278,48 @@ def merge_dijsoint_meshes(meshes, skip_tests=False): # }}} -# {{{ affine map +# {{{ map -def affine_map(mesh, A=None, b=None): - """Apply the affine map *f(x)=Ax+b* to the geometry of *mesh*.""" +def map_mesh(mesh, f): # noqa + """Apply the map *f* to the mesh. *f* needs to accept and return arrays of + shape ``(ambient_dim, npoints)``.""" - vertices = np.einsum("ij,jv->iv", A, mesh.vertices) + b[:, np.newaxis] + vertices = f(mesh.vertices) # {{{ assemble new groups list new_groups = [] for group in mesh.groups: - nodes = ( - np.einsum("ij,jen->ien", A, group.nodes) - + b[:, np.newaxis, np.newaxis]) - new_groups.append(group.copy(nodes=nodes)) + mapped_nodes = f(group.nodes.reshape(mesh.ambient_dim, -1)) + new_groups.append(group.copy( + nodes=mapped_nodes.reshape(*group.nodes.shape))) # }}} from meshmode.mesh import Mesh - return Mesh(vertices, new_groups, skip_tests=True) + return Mesh(vertices, new_groups, skip_tests=True, + nodal_adjacency=mesh.nodal_adjacency_init_arg(), + facial_adjacency_groups=mesh._facial_adjacency_groups) + +# }}} + + +# {{{ affine map + +def affine_map(mesh, A=None, b=None): # noqa + """Apply the affine map *f(x)=Ax+b* to the geometry of *mesh*.""" + + if A is None: + A = np.eye(mesh.ambient_dim) # noqa + + if b is None: + b = np.zeros(A.shape[0]) + + def f(x): + return np.einsum("ij,jv->iv", A, x) + b[:, np.newaxis] + + return map_mesh(mesh, f) # }}} diff --git a/meshmode/mesh/refinement.py b/meshmode/mesh/refinement.py index cd2ce945c7277274d0138ecff8bfedc8d12736a2..0a6518f0ec040c3b1a0adc8f18900a65ff197caf 100644 --- a/meshmode/mesh/refinement.py +++ b/meshmode/mesh/refinement.py @@ -1,4 +1,4 @@ -from __future__ import division +from __future__ import division, print_function __copyright__ = "Copyright (C) 2014 Andreas Kloeckner" @@ -9,10 +9,8 @@ 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 @@ -22,153 +20,128 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import numpy as np - -class VertexRay: - def __init__(self, ray, pos): - self.ray = ray - self.pos = pos - -class _SplitFaceRecord(object): - """ - .. attribute:: neighboring_elements - .. attribute:: new_vertex_nr - - integer or None - """ -class Adj: - """One vertex-associated entry of a ray. - - .. attribute:: elements - - A list of numbers of elements adjacent to - the edge following :attr:`vertex` - along the ray. - - .. attribute:: velements - - A list of numbers of elements adjacent to - :attr:`vertex`. - - """ - def __init__(self, vertex=None, elements=[None, None], velements - =[None, None]): - self.vertex = vertex - self.elements = elements - self.velements = velements - def __str__(self): - return 'vertex: ' + str(self.vertex) + ' ' + 'elements: ' +\ - str(self.elements) + ' velements: ' + str(self.velements) -#map pair of vertices to ray and midpoint -class PairMap: - """Describes a segment of a ray between two vertices. - - .. attribute:: ray - - Index of the ray in the *rays* list. - - .. attribute:: d - - A :class:`bool` denoting direction in the ray, - with *True* representing "positive" and *False* - representing "negative". +import numpy as np +import itertools +from six.moves import range + + +class TreeRayNode(object): + """Describes a ray as a tree, this class represents each node in this tree + .. attribute:: left + Left child. + *None* if ray segment hasn't been split yet. + .. attribute:: right + Right child. + *None* if ray segment hasn't been split yet. .. attribute:: midpoint - - Vertex index of the midpoint of this segment. - + Vertex index of midpoint of this ray segment. *None* if no midpoint has been assigned yet. + .. attribute:: adjacent_elements + List containing elements indices of elements adjacent + to this ray segment. """ + def __init__(self, left_vertex, right_vertex, adjacent_elements=[]): + import copy + self.left = None + self.right = None + self.parent = None + self.left_vertex = left_vertex + self.right_vertex = right_vertex + self.midpoint = None + self.adjacent_elements = copy.deepcopy(adjacent_elements) + self.adjacent_add_diff = [] - def __init__(self, ray=None, d = True, midpoint=None): - self.ray = ray - #direction in ray, True means that second node (bigger index - #) is after first in ray - self.d = d - self.midpoint = midpoint - def __str__(self): - return 'ray: ' + str(self.ray) + ' d: ' + str(self.d) + \ - ' midpoint: ' + str(self.midpoint) class Refiner(object): def __init__(self, mesh): - from llist import dllist, dllistnode - from meshmode.mesh.tesselate import tesselatetri - self.tri_node_tuples, self.tri_result = tesselatetri() - print self.tri_node_tuples - print self.tri_result + from meshmode.mesh.tesselate import tesselatetet, tesselatetri + self.lazy = False + self.seen_tuple = {} + tri_node_tuples, tri_result = tesselatetri() + tet_node_tuples, tet_result = tesselatetet() + print(tri_node_tuples, tri_result) + #quadrilateral_node_tuples = [ + #print tri_result, tet_result + self.simplex_node_tuples = [None, None, tri_node_tuples, tet_node_tuples] + self.simplex_result = [None, None, tri_result, tet_result] + #print tri_node_tuples, tri_result + #self.simplex_node_tuples, self.simplex_result = tesselatetet() self.last_mesh = mesh - # Indices in last_mesh that were split in the last round of - # refinement. Only these elements may be split this time - # around. - self.last_split_elements = None - # {{{ initialization - # a list of dllist instances containing Adj objects - self.rays = [] - - # mapping: (vertex_1, vertex_2) -> PairMap + # mapping: (vertex_1, vertex_2) -> TreeRayNode # where vertex_i represents a vertex number # # Assumption: vertex_1 < vertex_2 self.pair_map = {} nvertices = len(mesh.vertices[0]) - - # list of dictionaries, with each entry corresponding to - # one vertex. - # - # Each dictionary maps - # ray number -> dllist node containing a :class:`Adj`, - # (part of *rays*) - self.vertex_to_ray = [] - import six - for i in six.moves.range(nvertices): - self.vertex_to_ray.append({}) + #array containing element whose edge lies on corresponding vertex + self.hanging_vertex_element = [] + for i in range(nvertices): + self.hanging_vertex_element.append([]) + for grp in mesh.groups: iel_base = grp.element_nr_base - for iel_grp in six.moves.range(grp.nelements): - #use six.moves.range - + for iel_grp in range(grp.nelements): vert_indices = grp.vertex_indices[iel_grp] + for i in range(len(vert_indices)): + for j in range(i+1, len(vert_indices)): - for i in six.moves.range(len(vert_indices)): - for j in six.moves.range(i+1, len(vert_indices)): - - #minimum and maximum of the two indices for storing + #minimum and maximum of the two indices for storing #data in vertex_pair - mn_idx = min(vert_indices[i], vert_indices[j]) - mx_idx = max(vert_indices[i], vert_indices[j]) + min_index = min(vert_indices[i], vert_indices[j]) + max_index = max(vert_indices[i], vert_indices[j]) - vertex_pair = (mn_idx, mx_idx) + vertex_pair = (min_index, max_index) + #print(vertex_pair) if vertex_pair not in self.pair_map: - els = [] - els.append(iel_base+iel_grp) - fr = Adj(mn_idx, els) - to = Adj(mx_idx, [None, None]) - self.rays.append(dllist([fr, to])) - self.pair_map[vertex_pair] = PairMap(len(self.rays) - 1) - self.vertex_to_ray[mn_idx][len(self.rays)-1]\ - = self.rays[len(self.rays)-1].nodeat(0) - self.vertex_to_ray[mx_idx][len(self.rays)-1]\ - = self.rays[len(self.rays)-1].nodeat(1) - else: - self.rays[self.pair_map[vertex_pair].ray].nodeat(0).value.elements.append(iel_base+iel_grp) + self.pair_map[vertex_pair] = TreeRayNode(min_index, max_index) + self.pair_map[vertex_pair].adjacent_elements.append(iel_base+iel_grp) + elif (iel_base+iel_grp) not in self.pair_map[vertex_pair].adjacent_elements: + (self.pair_map[vertex_pair]. + adjacent_elements.append(iel_base+iel_grp)) # }}} - #print mesh.groups[0].vertex_indices + #print(vert_indices) + #generate reference tuples + self.index_to_node_tuple = [] + self.index_to_midpoint_tuple = [] + for d in range(len(vert_indices)): + dim = d + 1 + cur_index_to_node_tuple = [()] * dim + for i in range(0, dim-1): + cur_index_to_node_tuple[0] = cur_index_to_node_tuple[0] + (0,) + for i in range(1, dim): + for j in range(1, dim): + if i == j: + cur_index_to_node_tuple[i] = cur_index_to_node_tuple[i] + (2,) + else: + cur_index_to_node_tuple[i] = cur_index_to_node_tuple[i] + (0,) + cur_index_to_midpoint_tuple = [()] * (int((dim * (dim - 1)) / 2)) + curind = 0 + for ind1 in range(0, len(cur_index_to_node_tuple)): + for ind2 in range(ind1+1, len(cur_index_to_node_tuple)): + i = cur_index_to_node_tuple[ind1] + j = cur_index_to_node_tuple[ind2] + #print(i, j) + for k in range(0, dim-1): + cur = int((i[k] + j[k]) / 2) + cur_index_to_midpoint_tuple[curind] = cur_index_to_midpoint_tuple[curind] + (cur,) + curind += 1 + self.index_to_node_tuple.append(cur_index_to_node_tuple) + self.index_to_midpoint_tuple.append(cur_index_to_midpoint_tuple) ''' - self.ray_vertices = np.empty([len(mesh.groups[0].vertex_indices) * - len(mesh.groups[0].vertex_indices[0]) * (len(mesh.groups[0].vertex_indices[0]) - 1) / 2, 2], + self.ray_vertices = np.empty([len(mesh.groups[0].vertex_indices) * + len(mesh.groups[0].vertex_indices[0]) * (len(mesh.groups[0].vertex_indices[0]) - 1) / 2, 2], dtype=np.int32) - self.ray_elements = np.zeros([len(mesh.groups[0].vertex_indices) * + self.ray_elements = np.zeros([len(mesh.groups[0].vertex_indices) * len(mesh.groups[0].vertex_indices[0]) * (len(mesh.groups[0].vertex_indices[0]) - 1) / 2, 1, 2], dtype=np.int32) self.vertex_to_ray = [] - for i in mesh.vertices[0]: self.vertex_to_ray.append([]); count = 0 @@ -184,7 +157,7 @@ class Refiner(object): self.vertex_to_ray[grp.vertex_indices[i][k]].append(temp2) count += 1 ind = 0 - #print self.ray_vertices + #print(self.ray_vertices) for i in self.ray_vertices: which = 0 for grp in mesh.groups: @@ -214,16 +187,16 @@ class Refiner(object): np.bool) def get_current_mesh(self): - + from meshmode.mesh import Mesh #return Mesh(vertices, [grp], element_connectivity=self.generate_connectivity(len(self.last_mesh.groups[group].vertex_indices) \ # + count*3)) - groups = [] + groups = [] grpn = 0 for grp in self.last_mesh.groups: groups.append(np.empty([len(grp.vertex_indices), len(self.last_mesh.groups[grpn].vertex_indices[0])], dtype=np.int32)) - for iel_grp in xrange(grp.nelements): + for iel_grp in range(grp.nelements): for i in range(0, len(grp.vertex_indices[iel_grp])): groups[grpn][iel_grp][i] = grp.vertex_indices[iel_grp][i] grpn += 1 @@ -235,459 +208,552 @@ class Refiner(object): self.last_mesh = Mesh(self.last_mesh.vertices, grp,\ element_connectivity=self.generate_connectivity(len(self.last_mesh.groups[0].vertex_indices),\ len(self.last_mesh.vertices[0]))) - + return self.last_mesh + + def get_leaves(self, cur_node): + queue = [cur_node] + res = [] + while queue: + vertex = queue.pop(0) + #if leaf node + if vertex.left is None and vertex.right is None: + res.append(vertex) + else: + queue.append(vertex.left) + queue.append(vertex.right) + return res + + def get_subtree(self, cur_node): + queue = [cur_node] + res = [] + while queue: + vertex = queue.pop(0) + res.append(vertex) + if not (vertex.left is None and vertex.right is None): + queue.append(vertex.left) + queue.append(vertex.right) + return res + + def apply_diff(self, cur_node, new_hanging_vertex_elements): + for el in cur_node.adjacent_add_diff: + if el not in cur_node.adjacent_elements: + cur_node.adjacent_elements.append(el) + if el not in new_hanging_vertex_elements[cur_node.left_vertex]: + new_hanging_vertex_elements[cur_node.left_vertex].append(el) + if el not in new_hanging_vertex_elements[cur_node.right_vertex]: + new_hanging_vertex_elements[cur_node.right_vertex].append(el) + if cur_node.left is not None and cur_node.right is not None: + if el not in cur_node.left.adjacent_add_diff: + cur_node.left.adjacent_add_diff.append(el) + if el not in cur_node.right.adjacent_add_diff: + cur_node.right.adjacent_add_diff.append(el) + cur_node.adjacent_add_diff = [] + +# def propagate(self, cur_node, new_hanging_vertex_elements): +# if cur_node.parent is not None: +# parent_node = cur_node.parent +# self.propagate(parent_node, new_hanging_vertex_elements) +# self.apply_diff(parent_node, new_hanging_vertex_elements) + + def get_root(self, cur_node): + while(cur_node.parent is not None): + cur_node = cur_node.parent + return cur_node + + def propagate_tree(self, cur_node, new_hanging_vertex_elements, element_to_element): + vertex_tuple = (min(cur_node.left_vertex, cur_node.right_vertex), max(cur_node.left_vertex, cur_node.right_vertex)) + self.seen_tuple[vertex_tuple] = True + self.apply_diff(cur_node, new_hanging_vertex_elements) + if cur_node.left is not None and cur_node.right is not None: + self.propagate_tree(cur_node.left, new_hanging_vertex_elements, element_to_element) + self.propagate_tree(cur_node.right, new_hanging_vertex_elements, element_to_element) + else: + for el in cur_node.adjacent_elements: + element_to_element[el].update(cur_node.adjacent_elements) + for el in new_hanging_vertex_elements[cur_node.left_vertex]: + element_to_element[el].update(new_hanging_vertex_elements[cur_node.left_vertex]) + for el in new_hanging_vertex_elements[cur_node.right_vertex]: + element_to_element[el].update(new_hanging_vertex_elements[cur_node.right_vertex]) + +# def remove_from_subtree(self, cur_node, new_hanging_vertex_elements, to_remove): +# if self.lazy: +# self.propagate(cur_node, new_hanging_vertex_elements) +# if to_remove in cur_node.adjacent_add_diff: +# cur_node.adjacent_add_diff.remove(to_remove) +# if to_remove not in cur_node.adjacent_remove_diff: +# cur_node.adjacent_remove_diff.append(to_remove) +# else: +# subtree = self.get_subtree(cur_node) +# for node in subtree: +# if to_remove in node.adjacent_elements: +# node.adjacent_elements.remove(to_remove) +# if to_remove in new_hanging_vertex_elements[node.left_vertex]: +# new_hanging_vertex_elements[node.left_vertex].remove(to_remove) +# if to_remove in new_hanging_vertex_elements[node.right_vertex]: +# new_hanging_vertex_elements[node.right_vertex].remove(to_remove) + + def add_to_subtree(self, cur_node, new_hanging_vertex_elements, to_add): + if self.lazy: + if to_add not in cur_node.adjacent_add_diff: + cur_node.adjacent_add_diff.append(to_add) + else: + subtree = self.get_subtree(cur_node) + for node in subtree: + if to_add not in node.adjacent_elements: + node.adjacent_elements.append(to_add) + if to_add not in new_hanging_vertex_elements[node.left_vertex]: + new_hanging_vertex_elements[node.left_vertex].append(to_add) + if to_add not in new_hanging_vertex_elements[node.right_vertex]: + new_hanging_vertex_elements[node.right_vertex].append(to_add) + #refine_flag tells you which elements to split as a numpy array of bools def refine(self, refine_flags): - """ - :return: a refined mesh - """ - - # multi-level mapping: - # { - # dimension of intersecting entity (0=vertex,1=edge,2=face,...) - # : - # { frozenset of end vertices : _SplitFaceRecord } - # } - ''' - import numpy as np - count = 0 - for i in refine_flags: - if i: - count += 1 - - le = len(self.last_mesh.vertices[0]) - - vertices = np.empty([len(self.last_mesh.vertices), len(self.last_mesh.groups[0].vertex_indices[0]) - * count + le]) - vertex_indices = np.empty([len(self.last_mesh.groups[0].vertex_indices) - + count*3, - len(self.last_mesh.groups[0].vertex_indices[0])], dtype=np.int32) - indices_it = len(self.last_mesh.groups[0].vertex_indices) - for i in range(0, len(self.last_mesh.vertices)): - for j in range(0, len(self.last_mesh.vertices[i])): - vertices[i][j] = self.last_mesh.vertices[i][j] - for i in range(0, len(self.last_mesh.groups[0].vertex_indices)): - for j in range(0, len(self.last_mesh.groups[0].vertex_indices[i])): - vertex_indices[i][j] = self.last_mesh.groups[0].vertex_indices[i][j] - - - import itertools - for i in range(0, len(refine_flags)): - if refine_flags[i]: - for subset in itertools.combinations(self.last_mesh.groups[0].vertex_indices[i], - len(self.last_mesh.groups[0].vertex_indices[i]) - 1): - for j in range(0, len(self.last_mesh.vertices)): - avg = 0 - for k in subset: - avg += self.last_mesh.vertices[j][k] - avg /= len(self.last_mesh.vertices) - self.last_mesh.vertices[j][le] = avg - le++ - vertex_indices[indices_it][0] = self.last_mesh.groups[0].vertex_indices[i][0] - ''' - ''' import numpy as np - count = 0 - for i in refine_flags: - if i: - count += 1 - #print count - #print self.last_mesh.vertices - #print vertices - if(len(self.last_mesh.groups[0].vertex_indices[0]) == 3): - for group in range(0, len(self.last_mesh.groups)): - le = len(self.last_mesh.vertices[0]) - vertices = np.empty([len(self.last_mesh.vertices), - len(self.last_mesh.groups[group].vertex_indices[0]) - * count + le]) - vertex_indices = np.empty([len(self.last_mesh.groups[group].vertex_indices) - + count*3, - len(self.last_mesh.groups[group].vertex_indices[0])], dtype=np.int32) - indices_i = 0 - for i in range(0, len(self.last_mesh.vertices)): - for j in range(0, len(self.last_mesh.vertices[i])): - vertices[i][j] = self.last_mesh.vertices[i][j] - #for i in range(0, len(self.last_mesh.groups[group].vertex_indices)): - #for j in range(0, len(self.last_mesh.groups[group].vertex_indices[i])): - #vertex_indices[i][j] = self.last_mesh.groups[group].vertex_indices[i][j] - for fl in range(0, len(refine_flags)): - if(refine_flags[fl]): - i = self.last_mesh.groups[group].vertex_indices[fl] - for j in range(0, len(i)): - for k in range(j + 1, len(i)): - for l in range(0, 3): - #print self.last_mesh.vertices[l][i[j]], ' ', self.last_mesh.vertices[l][i[k]], '=', ((self.last_mesh.vertices[l][i[j]] + self.last_mesh.vertices[l][i[k]]) / 2) - vertices[l][le]=((self.last_mesh.vertices[l][i[j]] + self.last_mesh.vertices[l][i[k]]) / 2) - le += 1 - vertex_indices[indices_i][0] = i[0] - vertex_indices[indices_i][1] = le-3 - vertex_indices[indices_i][2] = le-2 - indices_i += 1 - vertex_indices[indices_i][0] = i[1] - vertex_indices[indices_i][1] = le-1 - vertex_indices[indices_i][2] = le-3 - indices_i += 1 - vertex_indices[indices_i][0] = i[2] - vertex_indices[indices_i][1] = le-2 - vertex_indices[indices_i][2] = le-1 - indices_i += 1 - vertex_indices[indices_i][0] = le-3 - vertex_indices[indices_i][1] = le-2 - vertex_indices[indices_i][2] = le-1 - indices_i += 1 - else: - for j in range(0, len(self.last_mesh.groups[group].vertex_indices[fl])): - vertex_indices[indices_i][j] = self.last_mesh.groups[group].vertex_indices[fl][j] - indices_i += 1 - ''' - import numpy as np - import six - # if (isinstance(self.last_mesh.groups[0], SimplexElementGroup) and - # self.last_mesh.groups[0].dim == 2): - print 'refining' - if(len(self.last_mesh.groups[0].vertex_indices[0]) == 3): - groups = [] - midpoint_already = {} + #vertices and groups for next generation + nvertices = len(self.last_mesh.vertices[0]) + + groups = [] + + midpoint_already = set() + grpn = 0 + totalnelements = 0 + + for grp in self.last_mesh.groups: + iel_base = grp.element_nr_base nelements = 0 - nvertices = len(self.last_mesh.vertices[0]) - grpn = 0 - totalnelements=0 - - # {{{ create new vertices array and each group's vertex_indices - - for grp in self.last_mesh.groups: - iel_base = grp.element_nr_base - nelements = 0 - #print grp.nelements - for iel_grp in six.moves.range(grp.nelements): - nelements += 1 - vert_indices = grp.vertex_indices[iel_grp] - if refine_flags[iel_base+iel_grp]: - nelements += 3 - for i in six.moves.range(0, len(vert_indices)): - for j in six.moves.range(i+1, len(vert_indices)): - mn_idx = min(vert_indices[i], vert_indices[j]) - mx_idx = max(vert_indices[i], vert_indices[j]) - vertex_pair = (mn_idx, mx_idx) - if vertex_pair not in midpoint_already and \ - self.pair_map[vertex_pair].midpoint is None: + for iel_grp in range(grp.nelements): + nelements += 1 + vertex_indices = grp.vertex_indices[iel_grp] + if refine_flags[iel_base+iel_grp]: + cur_dim = len(grp.vertex_indices[iel_grp])-1 + nelements += len(self.simplex_result[cur_dim]) - 1 + for i in range(len(vertex_indices)): + for j in range(i+1, len(vertex_indices)): + i_index = vertex_indices[i] + j_index = vertex_indices[j] + index_tuple = (i_index, j_index) if i_index < j_index else (j_index, i_index) + if index_tuple not in midpoint_already and \ + self.pair_map[index_tuple].midpoint is None: nvertices += 1 - midpoint_already[vertex_pair] = True - groups.append(np.empty([nelements, - len(grp.vertex_indices[0])], dtype=np.int32)) - grpn += 1 - totalnelements += nelements - - vertices = np.empty([len(self.last_mesh.vertices), nvertices]) - - # }}} - - # {{{ bring over vertex_indices and vertices info from previous generation - - for i in six.moves.range(0, len(self.last_mesh.vertices)): - for j in six.moves.range(0, len(self.last_mesh.vertices[i])): - # always use v[i,j] - vertices[i,j] = self.last_mesh.vertices[i,j] - grpn = 0 - for grp in self.last_mesh.groups: - for iel_grp in six.moves.range(grp.nelements): - for i in six.moves.range(0, len(grp.vertex_indices[iel_grp])): - groups[grpn][iel_grp][i] = grp.vertex_indices[iel_grp][i] - grpn += 1 - grpn = 0 - - # }}} - - vertices_idx = len(self.last_mesh.vertices[0]) - for grp in self.last_mesh.groups: - iel_base = grp.element_nr_base - nelements_in_grp = len(grp.vertex_indices) - - # np.where - for iel_grp in six.moves.range(grp.nelements): - if refine_flags[iel_base+iel_grp]: - - # {{{ split element - - # {{{ go through vertex pairs in element - - # stores indices of all midpoints for this element - # (in order of vertex pairs in elements) - verts = [] - - vert_indices = grp.vertex_indices[iel_grp] - for i in six.moves.range(0, len(vert_indices)): - for j in six.moves.range(i+1, len(vert_indices)): - mn_idx = min(vert_indices[i], vert_indices[j]) - mx_idx = max(vert_indices[i], vert_indices[j]) - vertex_pair = (mn_idx, mx_idx) - - # {{{ create midpoint if it doesn't exist already - - cur_pmap = self.pair_map[vertex_pair] - if cur_pmap.midpoint is None: - self.pair_map[vertex_pair].midpoint = vertices_idx - vertex_pair1 = (mn_idx, vertices_idx) - vertex_pair2 = (mx_idx, vertices_idx) - self.pair_map[vertex_pair1] =\ - PairMap(cur_pmap.ray, cur_pmap.d, None) - self.pair_map[vertex_pair2] =\ - PairMap(cur_pmap.ray, not cur_pmap.d, None) - self.vertex_to_ray.append({}) - - # FIXME: Check where the new Adj.elements - # gets populated. - - # try and collapse the two branches by setting up variables - # ahead of time + midpoint_already.add(index_tuple) + groups.append(np.empty([nelements, len(grp.vertex_indices[0])], dtype=np.int32)) + grpn += 1 + totalnelements += nelements + + vertices = np.empty([len(self.last_mesh.vertices), nvertices]) + + new_hanging_vertex_element = [ + [] for i in range(nvertices)] + +# def remove_element_from_connectivity(vertices, new_hanging_vertex_elements, to_remove): +# #print(vertices) +# import itertools +# if len(vertices) == 2: +# min_vertex = min(vertices[0], vertices[1]) +# max_vertex = max(vertices[0], vertices[1]) +# ray = self.pair_map[(min_vertex, max_vertex)] +# self.remove_from_subtree(ray, new_hanging_vertex_elements, to_remove) +# return +# +# cur_dim = len(vertices)-1 +# element_rays = [] +# midpoints = [] +# split_possible = True +# for i in range(len(vertices)): +# for j in range(i+1, len(vertices)): +# min_vertex = min(vertices[i], vertices[j]) +# max_vertex = max(vertices[i], vertices[j]) +# element_rays.append(self.pair_map[(min_vertex, max_vertex)]) +# if element_rays[len(element_rays)-1].midpoint is not None: +# midpoints.append(element_rays[len(element_rays)-1].midpoint) +# else: +# split_possible = False + + #for node in element_rays: + #self.remove_from_subtree(node, new_hanging_vertex_elements, to_remove) + #if split_possible: +# if split_possible: +# node_tuple_to_coord = {} +# for node_index, node_tuple in enumerate(self.index_to_node_tuple[cur_dim]): +# node_tuple_to_coord[node_tuple] = vertices[node_index] +# for midpoint_index, midpoint_tuple in enumerate(self.index_to_midpoint_tuple[cur_dim]): +# node_tuple_to_coord[midpoint_tuple] = midpoints[midpoint_index] +# for i in range(len(self.simplex_result[cur_dim])): +# next_vertices = [] +# for j in range(len(self.simplex_result[cur_dim][i])): +# next_vertices.append(node_tuple_to_coord[self.simplex_node_tuples[cur_dim][self.simplex_result[cur_dim][i][j]]]) +# all_rays_present = True +# for v1 in range(len(next_vertices)): +# for v2 in range(v1+1, len(next_vertices)): +# vertex_tuple = (min(next_vertices[v1], next_vertices[v2]), max(next_vertices[v1], next_vertices[v2])) +# if vertex_tuple not in self.pair_map: +# all_rays_present = False +# if all_rays_present: +# remove_element_from_connectivity(next_vertices, new_hanging_vertex_elements, to_remove) +# else: +# split_possible = False +# if not split_possible: +# next_vertices_list = list(itertools.combinations(vertices, len(vertices)-1)) +# for next_vertices in next_vertices_list: +# remove_element_from_connectivity(next_vertices, new_hanging_vertex_elements, to_remove) + + def add_element_to_connectivity(vertices, new_hanging_vertex_elements, to_add): + if len(vertices) == 2: + min_vertex = min(vertices[0], vertices[1]) + max_vertex = max(vertices[0], vertices[1]) + ray = self.pair_map[(min_vertex, max_vertex)] + self.add_to_subtree(ray, new_hanging_vertex_elements, to_add) + return + + cur_dim = len(vertices)-1 + element_rays = [] + midpoints = [] + split_possible = True + for i in range(len(vertices)): + for j in range(i+1, len(vertices)): + min_vertex = min(vertices[i], vertices[j]) + max_vertex = max(vertices[i], vertices[j]) + element_rays.append(self.pair_map[(min_vertex, max_vertex)]) + if element_rays[len(element_rays)-1].midpoint is not None: + midpoints.append(element_rays[len(element_rays)-1].midpoint) + else: + split_possible = False + #for node in element_rays: + #self.add_to_subtree(node, new_hanging_vertex_elements, to_add) + if split_possible: + node_tuple_to_coord = {} + for node_index, node_tuple in enumerate(self.index_to_node_tuple[cur_dim]): + node_tuple_to_coord[node_tuple] = vertices[node_index] + for midpoint_index, midpoint_tuple in enumerate(self.index_to_midpoint_tuple[cur_dim]): + node_tuple_to_coord[midpoint_tuple] = midpoints[midpoint_index] + for i in range(len(self.simplex_result[cur_dim])): + next_vertices = [] + for j in range(len(self.simplex_result[cur_dim][i])): + next_vertices.append(node_tuple_to_coord[self.simplex_node_tuples[cur_dim][self.simplex_result[cur_dim][i][j]]]) + all_rays_present = True + for v1 in range(len(next_vertices)): + for v2 in range(v1+1, len(next_vertices)): + vertex_tuple = (min(next_vertices[v1], next_vertices[v2]), max(next_vertices[v1], next_vertices[v2])) + if vertex_tuple not in self.pair_map: + all_rays_present = False + if all_rays_present: + add_element_to_connectivity(next_vertices, new_hanging_vertex_elements, to_add) + else: + split_possible = False + if not split_possible: + next_vertices_list = list(itertools.combinations(vertices, len(vertices)-1)) + for next_vertices in next_vertices_list: + add_element_to_connectivity(next_vertices, new_hanging_vertex_elements, to_add) +# for node in element_rays: +# self.add_element_to_connectivity(node, new_hanging_vertex_elements, to_add) + # leaves = self.get_subtree(node) + # for leaf in leaves: + # if to_add not in leaf.adjacent_elements: + # leaf.adjacent_elements.append(to_add) + # if to_add not in new_hanging_vertex_elements[leaf.left_vertex]: + # new_hanging_vertex_elements[leaf.left_vertex].append(to_add) + # if to_add not in new_hanging_vertex_elements[leaf.right_vertex]: + # new_hanging_vertex_elements[leaf.right_vertex].append(to_add) + +# next_element_rays = [] +# for i in range(len(element_rays)): +# for j in range(i+1, len(element_rays)): +# if element_rays[i].midpoint is not None and element_rays[j].midpoint is not None: +# min_midpoint = min(element_rays[i].midpoint, element_rays[j].midpoint) +# max_midpoint = max(element_rays[i].midpoint, element_rays[j].midpoint) +# vertex_pair = (min_midpoint, max_midpoint) +# if vertex_pair in self.pair_map: +# next_element_rays.append(self.pair_map[vertex_pair]) +# cur_next_rays = [] +# if element_rays[i].left_vertex == element_rays[j].left_vertex: +# cur_next_rays = [element_rays[i].left, element_rays[j].left, self.pair_map[vertex_pair]] +# if element_rays[i].right_vertex == element_rays[j].right_vertex: +# cur_next_rays = [element_rays[i].right, element_rays[j].right, self.pair_map[vertex_pair]] +# if element_rays[i].left_vertex == element_rays[j].right_vertex: +# cur_next_rays = [element_rays[i].left, element_rays[j].right, self.pair_map[vertex_pair]] +# if element_rays[i].right_vertex == element_rays[j].left_vertex: +# cur_next_rays = [element_rays[i].right, element_rays[j].left, self.pair_map[vertex_pair]] +# assert (cur_next_rays != []) +# #print cur_next_rays +# add_element_to_connectivity(cur_next_rays, new_hanging_vertex_elements, to_add) +# else: +# return +# else: +# return +# add_element_to_connectivity(next_element_rays, new_hanging_vertex_elements, to_add) + + def add_hanging_vertex_el(v_index, el): + assert not (v_index == 37 and el == 48) + + new_hanging_vertex_element[v_index].append(el) + +# def remove_ray_el(ray, el): +# ray.remove(el) + + def check_adjacent_elements(groups, new_hanging_vertex_elements, nelements_in_grp): + for grp in groups: + iel_base = 0 + for iel_grp in range(nelements_in_grp): + vertex_indices = grp[iel_grp] + for i in range(len(vertex_indices)): + for j in range(i+1, len(vertex_indices)): + min_index = min(vertex_indices[i], vertex_indices[j]) + max_index = max(vertex_indices[i], vertex_indices[j]) + cur_node = self.pair_map[(min_index, max_index)] + #print iel_base+iel_grp, cur_node.left_vertex, cur_node.right_vertex + #if (iel_base + iel_grp) not in cur_node.adjacent_elements: + #print min_index, max_index + #print iel_base + iel_grp, cur_node.left_vertex, cur_node.right_vertex, cur_node.adjacent_elements + #assert (4 in new_hanging_vertex_elements[cur_node.left_vertex] or 4 in new_hanging_vertex_elements[cur_node.right_vertex]) + assert ((iel_base+iel_grp) in cur_node.adjacent_elements) + assert((iel_base+iel_grp) in new_hanging_vertex_elements[cur_node.left_vertex]) + assert((iel_base+iel_grp) in new_hanging_vertex_elements[cur_node.right_vertex]) + + for i in range(len(self.last_mesh.vertices)): + for j in range(len(self.last_mesh.vertices[i])): + vertices[i,j] = self.last_mesh.vertices[i,j] + import copy + if i == 0: + new_hanging_vertex_element[j] = copy.deepcopy(self.hanging_vertex_element[j]) + grpn = 0 + for grp in self.last_mesh.groups: + for iel_grp in range(grp.nelements): + for i in range(len(grp.vertex_indices[iel_grp])): + groups[grpn][iel_grp][i] = grp.vertex_indices[iel_grp][i] + grpn += 1 + + grpn = 0 + vertices_index = len(self.last_mesh.vertices[0]) + nelements_in_grp = grp.nelements + for grp in self.last_mesh.groups: + iel_base = grp.element_nr_base + for iel_grp in range(grp.nelements): + if refine_flags[iel_base+iel_grp]: + midpoint_vertices = [] + midpoint_tuples = [] + vertex_indices = grp.vertex_indices[iel_grp] + #if simplex + if len(grp.vertex_indices[iel_grp]) == len(self.last_mesh.vertices)+1: + for i in range(len(vertex_indices)): + for j in range(i+1, len(vertex_indices)): + min_index = min(vertex_indices[i], vertex_indices[j]) + max_index = max(vertex_indices[i], vertex_indices[j]) + cur_node = self.pair_map[(min_index, max_index)] + if cur_node.midpoint is None: + cur_node.midpoint = vertices_index import copy - if self.pair_map[vertex_pair].d: - velements = self.vertex_to_ray[mn_idx][cur_pmap.ray].value.elements - self.rays[cur_pmap.ray].insert(Adj(vertices_idx, copy.deepcopy(velements), - copy.deepcopy(velements)), \ - self.vertex_to_ray[mx_idx][cur_pmap.ray]) - self.vertex_to_ray[vertices_idx][cur_pmap.ray] = \ - self.vertex_to_ray[mx_idx][cur_pmap.ray].prev - else: - velements = self.vertex_to_ray[mx_idx][cur_pmap.ray].value.elements - self.rays[cur_pmap.ray].insert(Adj(vertices_idx, copy.deepcopy(velements), - copy.deepcopy(velements)), \ - self.vertex_to_ray[mn_idx][cur_pmap.ray]) - self.vertex_to_ray[vertices_idx][cur_pmap.ray] = \ - self.vertex_to_ray[mn_idx][cur_pmap.ray].prev - - # compute location of midpoint - for k in six.moves.range(len(self.last_mesh.vertices)): - vertices[k,vertices_idx] =\ - (self.last_mesh.vertices[k,vert_indices[i]]\ - +self.last_mesh.vertices[k,vert_indices[j]]) / 2.0 - - verts.append(vertices_idx) - vertices_idx += 1 + cur_node.left = TreeRayNode(min_index, vertices_index, + copy.deepcopy(cur_node.adjacent_elements)) + cur_node.left.parent = cur_node + cur_node.right = TreeRayNode(max_index, vertices_index, + copy.deepcopy(cur_node.adjacent_elements)) + cur_node.right.parent = cur_node + vertex_pair1 = (min_index, vertices_index) + vertex_pair2 = (max_index, vertices_index) + self.pair_map[vertex_pair1] = cur_node.left + self.pair_map[vertex_pair2] = cur_node.right + for k in range(len(self.last_mesh.vertices)): + vertices[k, vertices_index] = \ + (self.last_mesh.vertices[k, vertex_indices[i]] + + self.last_mesh.vertices[k, vertex_indices[j]]) / 2.0 + midpoint_vertices.append(vertices_index) + vertices_index += 1 else: - verts.append(self.pair_map[vertex_pair].midpoint) - - # }}} - - # }}} - - # {{{ fix connectivity - - # new elements will be nels+0 .. nels+2 - # (While those don't exist yet, we generate connectivity for them - # anyhow.) - - nnew_elements = 0 - - unique_vertex_pairs = [ - (i,j) for i in range(3) for j in range(i+1, 3)] - for i, j in unique_vertex_pairs: - mn_idx = min(grp.vertex_indices[iel_grp][i], grp.vertex_indices[iel_grp][j]) - mx_idx = max(grp.vertex_indices[iel_grp][i], grp.vertex_indices[iel_grp][j]) - if mn_idx == grp.vertex_indices[iel_grp][i]: - min_element_index = i - max_element_index = j - else: - min_element_index = j - max_element_index = i - vertex_pair = (mn_idx, mx_idx) - cur_pmap = self.pair_map[vertex_pair] - if cur_pmap.d: - start_node =\ - self.vertex_to_ray[mn_idx][cur_pmap.ray] - end_node = self.vertex_to_ray[mx_idx][cur_pmap.ray] - first_element_index = min_element_index - second_element_index = max_element_index - else: - start_node =\ - self.vertex_to_ray[mx_idx][cur_pmap.ray] - end_node = self.vertex_to_ray[mn_idx][cur_pmap.ray] - first_element_index = max_element_index - second_element_index = min_element_index - midpoint_node=\ - self.vertex_to_ray[cur_pmap.midpoint][cur_pmap.ray] - # hop along ray from start node to midpoint node - while start_node != midpoint_node: - # replace old (big) element with new - # (refined) element - node_els = start_node.value.elements - #print node_els - iold_el = node_els.index(iel_base+iel_grp) - node_els[iold_el] = iel_base+nelements_in_grp+first_element_index - start_node = start_node.next - # hop along ray from midpoint node to end node - while start_node != end_node: - #replace old (big) element with new - # (refined element - node_els = start_node.value.elements - iold_el = node_els.index(iel_base+iel_grp) - node_els[iold_el] = iel_base+nelements_in_grp+second_element_index - start_node = start_node.next - - # }}} + cur_midpoint = cur_node.midpoint + midpoint_vertices.append(cur_midpoint) + #generate new rays - from llist import dllist, dllistnode - ind = 0 - - for i in six.moves.range(0, len(verts)): - for j in six.moves.range(i+1, len(verts)): - mn_vert = min(verts[i], verts[j]) - mx_vert = max(verts[i], verts[j]) - vertex_pair = (mn_vert, mx_vert) - els = [] - els.append(iel_base+iel_grp) - els.append(iel_base+nelements_in_grp+ind) - - fr = Adj(mn_vert, els) - - # We're creating a new ray, and this is the end node - # of it. - to = Adj(mx_vert, [None, None]) - - self.rays.append(dllist([fr, to])) - self.pair_map[vertex_pair] = PairMap(len(self.rays) - 1) - self.vertex_to_ray[mn_vert][len(self.rays)-1]\ - = self.rays[len(self.rays)-1].nodeat(0) - self.vertex_to_ray[mx_vert][len(self.rays)-1]\ - = self.rays[len(self.rays)-1].nodeat(1) - ind += 1 - ind = 0 - - #map vertex indices to coordinates + cur_dim = len(grp.vertex_indices[0])-1 + for i in range(len(midpoint_vertices)): + for j in range(i+1, len(midpoint_vertices)): + min_index = min(midpoint_vertices[i], midpoint_vertices[j]) + max_index = max(midpoint_vertices[i], midpoint_vertices[j]) + vertex_pair = (min_index, max_index) + if vertex_pair in self.pair_map: + continue + self.pair_map[vertex_pair] = TreeRayNode(min_index, max_index, []) node_tuple_to_coord = {} - node_tuple_to_coord[(0, 0)] = grp.vertex_indices[iel_grp][0] - node_tuple_to_coord[(2, 0)] = grp.vertex_indices[iel_grp][1] - node_tuple_to_coord[(0, 2)] = grp.vertex_indices[iel_grp][2] - vertex_pair = (min(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][1]), \ - max(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][1])) - node_tuple_to_coord[(1, 0)] = self.pair_map[vertex_pair].midpoint - vertex_pair = (min(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][2]), \ - max(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][2])) - node_tuple_to_coord[(0, 1)] = self.pair_map[vertex_pair].midpoint - vertex_pair = (min(grp.vertex_indices[iel_grp][1], grp.vertex_indices[iel_grp][2]), \ - max(grp.vertex_indices[iel_grp][1], grp.vertex_indices[iel_grp][2])) - node_tuple_to_coord[(1, 1)] = self.pair_map[vertex_pair].midpoint - #generate actual elements - #middle element - for i in six.moves.range(0, len(self.tri_result[1])): - groups[grpn][iel_grp][i] = \ - node_tuple_to_coord[self.tri_node_tuples[self.tri_result[1][i]]] - for i in six.moves.range(0, 4): - if i == 1: - continue - for j in six.moves.range(0, len(self.tri_result[i])): - groups[grpn][nelements_in_grp][j] = \ - node_tuple_to_coord[self.tri_node_tuples[self.tri_result[i][j]]] - nelements_in_grp += 1 - ''' - #middle element - vertex_pair = (min(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][1]), \ - max(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][1])) - groups[grpn][iel_grp][0] = self.pair_map[vertex_pair].midpoint - vertex_pair = (min(grp.vertex_indices[iel_grp][1], grp.vertex_indices[iel_grp][2]), \ - max(grp.vertex_indices[iel_grp][1], grp.vertex_indices[iel_grp][2])) - groups[grpn][iel_grp][1] = self.pair_map[vertex_pair].midpoint - vertex_pair = (min(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][2]), \ - max(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][2])) - groups[grpn][iel_grp][2] = self.pair_map[vertex_pair].midpoint - #element 0 - groups[grpn][nelements_in_grp][0] = grp.vertex_indices[iel_grp][0] - vertex_pair = (min(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][1]), \ - max(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][1])) - groups[grpn][nelements_in_grp][1] = self.pair_map[vertex_pair].midpoint - vertex_pair = (min(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][2]), \ - max(grp.vertex_indices[iel_grp][0], grp.vertex_indices[iel_grp][2])) - groups[grpn][nelements_in_grp][2] = self.pair_map[vertex_pair].midpoint - nelements_in_grp += 1 - #element 1 - groups[grpn][nelements_in_grp][0] = grp.vertex_indices[iel_grp][1] - vertex_pair = (min(grp.vertex_indices[iel_grp][1], grp.vertex_indices[iel_grp][0]), \ - max(grp.vertex_indices[iel_grp][1], grp.vertex_indices[iel_grp][0])) - groups[grpn][nelements_in_grp][1] = self.pair_map[vertex_pair].midpoint - vertex_pair = (min(grp.vertex_indices[iel_grp][1], grp.vertex_indices[iel_grp][2]), \ - max(grp.vertex_indices[iel_grp][1], grp.vertex_indices[iel_grp][2])) - groups[grpn][nelements_in_grp][2] = self.pair_map[vertex_pair].midpoint - nelements_in_grp += 1 - #element 2 - groups[grpn][nelements_in_grp][0] = grp.vertex_indices[iel_grp][2] - vertex_pair = (min(grp.vertex_indices[iel_grp][2], grp.vertex_indices[iel_grp][0]), \ - max(grp.vertex_indices[iel_grp][2], grp.vertex_indices[iel_grp][0])) - groups[grpn][nelements_in_grp][1] = self.pair_map[vertex_pair].midpoint - vertex_pair = (min(grp.vertex_indices[iel_grp][2], grp.vertex_indices[iel_grp][1]), \ - max(grp.vertex_indices[iel_grp][2], grp.vertex_indices[iel_grp][1])) - groups[grpn][nelements_in_grp][2] = self.pair_map[vertex_pair].midpoint - nelements_in_grp += 1 - ''' - # }}} + for node_index, node_tuple in enumerate(self.index_to_node_tuple[cur_dim]): + node_tuple_to_coord[node_tuple] = grp.vertex_indices[iel_grp][node_index] + for midpoint_index, midpoint_tuple in enumerate(self.index_to_midpoint_tuple[cur_dim]): + node_tuple_to_coord[midpoint_tuple] = midpoint_vertices[midpoint_index] + for i in range(len(self.simplex_result[cur_dim])): + for j in range(len(self.simplex_result[cur_dim][i])): + if i == 0: + groups[grpn][iel_grp][j] = \ + node_tuple_to_coord[self.simplex_node_tuples[cur_dim][self.simplex_result[cur_dim][i][j]]] + else: + groups[grpn][nelements_in_grp+i-1][j] = \ + node_tuple_to_coord[self.simplex_node_tuples[cur_dim][self.simplex_result[cur_dim][i][j]]] + nelements_in_grp += len(self.simplex_result[cur_dim])-1 + #assuming quad otherwise + #else: + #quadrilateral +# node_tuple_to_coord = {} +# for node_index, node_tuple in enumerate(self.index_to_node_tuple[cur_dim]): +# node_tuple_to_coord[node_tuple] = grp.vertex_indices[iel_grp][node_index] +# def generate_all_tuples(cur_list): +# if len(cur_list[len(cur_list)-1]) + + + #clear connectivity data + for grp in self.last_mesh.groups: + iel_base = grp.element_nr_base + for iel_grp in range(grp.nelements): + for i in range(len(grp.vertex_indices[iel_grp])): + for j in range(i+1, len(grp.vertex_indices[iel_grp])): + min_vert = min(grp.vertex_indices[iel_grp][i], grp.vertex_indices[iel_grp][j]) + max_vert = max(grp.vertex_indices[iel_grp][i], grp.vertex_indices[iel_grp][j]) + vertex_pair = (min_vert, max_vert) + root_ray = self.get_root(self.pair_map[vertex_pair]) + if root_ray not in self.seen_tuple: + self.seen_tuple[root_ray] = True + cur_tree = self.get_subtree(root_ray) + for node in cur_tree: + node.adjacent_elements = [] + new_hanging_vertex_element[node.left_vertex] = [] + new_hanging_vertex_element[node.right_vertex] = [] + + self.seen_tuple.clear() + + nelements_in_grp = grp.nelements + for grp in groups: + for iel_grp in range(len(grp)): + add_verts = [] + for i in range(len(grp[iel_grp])): + add_verts.append(grp[iel_grp][i]) + add_element_to_connectivity(add_verts, new_hanging_vertex_element, iel_base+iel_grp) + #assert ray connectivity + #check_adjacent_elements(groups, new_hanging_vertex_element, nelements_in_grp) - grpn += 1 - #print vertices - #print vertex_indices + self.hanging_vertex_element = new_hanging_vertex_element from meshmode.mesh.generation import make_group_from_vertices - #grp = make_group_from_vertices(vertices, vertex_indices, 4) grp = [] - grpn = 0 for grpn in range(0, len(groups)): grp.append(make_group_from_vertices(vertices, groups[grpn], 4)) from meshmode.mesh import Mesh - #return Mesh(vertices, [grp], element_connectivity=self.generate_connectivity(len(self.last_mesh.groups[group].vertex_indices) \ - # + count*3)) - - self.last_mesh = Mesh(vertices, grp, element_connectivity=self.generate_connectivity(totalnelements, nvertices, groups)) - return self.last_mesh - split_faces = {} - - ibase = self.get_refine_base_index() - affected_group_indices = set() - - for grp in self.last_mesh.groups: - iel_base + self.last_mesh = Mesh(vertices, grp, element_connectivity=self.generate_connectivity( + totalnelements, nvertices, groups)) + return self.last_mesh + def print_rays(self, ind): + for i in range(len(self.last_mesh.groups[0].vertex_indices[ind])): + for j in range(i+1, len(self.last_mesh.groups[0].vertex_indices[ind])): + mn = min(self.last_mesh.groups[0].vertex_indices[ind][i], + self.last_mesh.groups[0].vertex_indices[ind][j]) + mx = max(self.last_mesh.groups[0].vertex_indices[ind][j], + self.last_mesh.groups[0].vertex_indices[ind][i]) + vertex_pair = (mn, mx) + print('LEFT VERTEX:', self.pair_map[vertex_pair].left_vertex) + print('RIGHT VERTEX:', self.pair_map[vertex_pair].right_vertex) + print('ADJACENT:') + rays = self.get_leaves(self.pair_map[vertex_pair]) + for k in rays: + print(k.adjacent_elements) + ''' + def print_rays(self, groups, ind): + for i in range(len(groups[0][ind])): + for j in range(i+1, len(groups[0][ind])): + mn = min(groups[0][ind][i], groups[0][ind][j]) + mx = max(groups[0][ind][i], groups[0][ind][j]) + vertex_pair = (mn, mx) + print('LEFT VERTEX:', self.pair_map[vertex_pair].left_vertex) + print('RIGHT VERTEX:', self.pair_map[vertex_pair].right_vertex) + print('ADJACENT:') + rays = self.get_leaves(self.pair_map[vertex_pair]) + for k in rays: + print(k.adjacent_elements) + ''' + + def print_hanging_elements(self, ind): + for i in self.last_mesh.groups[0].vertex_indices[ind]: + print("IND:", i, self.hanging_vertex_element[i]) def generate_connectivity(self, nelements, nvertices, groups): # medium-term FIXME: make this an incremental update # rather than build-from-scratch - vertex_to_element = [[] for i in xrange(nvertices)] - + vertex_to_element = [[] for i in range(nvertices)] element_index = 0 for grp in groups: - for iel_grp in xrange(len(grp)): + for iel_grp in range(len(grp)): for ivertex in grp[iel_grp]: vertex_to_element[ivertex].append(element_index) element_index += 1 - element_to_element = [set() for i in xrange(nelements)] + element_to_element = [set() for i in range(nelements)] element_index = 0 - for grp in groups: - for iel_grp in xrange(len(grp)): - for ivertex in grp[iel_grp]: - element_to_element[element_index].update( - vertex_to_element[ivertex]) - element_index += 1 - #print self.ray_elements + if self.lazy: + for grp in groups: + for iel_grp in range(len(grp)): + for i in range(len(grp[iel_grp])): + for j in range(i+1, len(grp[iel_grp])): + vertex_pair = (min(grp[iel_grp][i], grp[iel_grp][j]), max(grp[iel_grp][i], grp[iel_grp][j])) + #print 'iel:', iel_grp, 'pair:', vertex_pair + if vertex_pair not in self.seen_tuple: + self.propagate_tree(self.get_root(self.pair_map[vertex_pair]), self.hanging_vertex_element, element_to_element) + #print self.pair_map[vertex_pair].left_vertex, self.pair_map[vertex_pair].right_vertex, self.pair_map[vertex_pair].adjacent_elements, self.hanging_vertex_element[self.pair_map[vertex_pair].left_vertex], self.hanging_vertex_element[self.pair_map[vertex_pair].right_vertex] + + else: + for grp in groups: + for iel_grp in range(len(grp)): + for ivertex in grp[iel_grp]: + element_to_element[element_index].update( + vertex_to_element[ivertex]) + if self.hanging_vertex_element[ivertex]: + for hanging_element in self.hanging_vertex_element[ivertex]: + if element_index != hanging_element: + element_to_element[element_index].update([hanging_element]) + element_to_element[hanging_element].update([element_index]) + for i in range(len(grp[iel_grp])): + for j in range(i+1, len(grp[iel_grp])): + vertex_pair = (min(grp[iel_grp][i], grp[iel_grp][j]), + max(grp[iel_grp][i], grp[iel_grp][j])) + #element_to_element[element_index].update( + #self.pair_map[vertex_pair].adjacent_elements) + queue = [self.pair_map[vertex_pair]] + while queue: + vertex = queue.pop(0) + #if leaf node + if vertex.left is None and vertex.right is None: + assert(element_index in vertex.adjacent_elements) + element_to_element[element_index].update( + vertex.adjacent_elements) + else: + queue.append(vertex.left) + queue.append(vertex.right) + ''' + if self.hanging_vertex_element[ivertex] and element_index != self.hanging_vertex_element[ivertex][0]: + element_to_element[element_index].update([self.hanging_vertex_element[ivertex][0]]) + element_to_element[self.hanging_vertex_element[ivertex][0]].update([element_index]) + ''' + element_index += 1 + print(len(element_to_element)) + for iel, neighbors in enumerate(element_to_element): + if iel in neighbors: + neighbors.remove(iel) + #print(self.ray_elements) + ''' for ray in self.rays: curnode = ray.first while curnode is not None: - ''' if len(curnode.value.elements) >= 2: if curnode.value.elements[0] is not None: element_to_element[curnode.value.elements[0]].update(curnode.value.elements) if curnode.value.elements[1] is not None: element_to_element[curnode.value.elements[1]].update(curnode.value.elements) - ''' if len(curnode.value.velements) >= 2: if curnode.value.velements[0] is not None: element_to_element[curnode.value.velements[0]].update(curnode.value.velements) if curnode.value.velements[1] is not None: element_to_element[curnode.value.velements[1]].update(curnode.value.velements) curnode = curnode.next - + ''' ''' for i in self.ray_elements: for j in i: @@ -717,5 +783,3 @@ class Refiner(object): # vim: foldmethod=marker -# vim: shiftwidth=4 -# vim: softtabstop=4 diff --git a/meshmode/mesh/tesselate.py b/meshmode/mesh/tesselate.py new file mode 100644 index 0000000000000000000000000000000000000000..4dbfefab948decd297ad00abc303d5cf878f2054 --- /dev/null +++ b/meshmode/mesh/tesselate.py @@ -0,0 +1,73 @@ +from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ + as gnitstam + + +def add_tuples(a, b): + return tuple(ac+bc for ac, bc in zip(a, b)) + + +def tesselatetri(): + result = [] + + node_tuples = list(gnitstam(2, 2)) + node_dict = dict( + (ituple, idx) + for idx, ituple in enumerate(node_tuples)) + + def try_add_tri(current, d1, d2, d3): + try: + result.append(( + node_dict[add_tuples(current, d1)], + node_dict[add_tuples(current, d2)], + node_dict[add_tuples(current, d3)], + )) + except KeyError: + pass + + if len(result) > 0: + return [node_tuples, result] + for current in node_tuples: + # this is a tesselation of a cube into six tets. + # subtets that fall outside of the master tet are simply not added. + + # positively oriented + try_add_tri(current, (0, 0), (1, 0), (0, 1)) + try_add_tri(current, (1, 0), (1, 1), (0, 1)) + return [node_tuples, result] + + +def tesselatetet(): + node_tuples = list(gnitstam(2, 3)) + + node_dict = dict( + (ituple, idx) + for idx, ituple in enumerate(node_tuples)) + + def try_add_tet(current, d1, d2, d3, d4): + try: + result.append(( + node_dict[add_tuples(current, d1)], + node_dict[add_tuples(current, d2)], + node_dict[add_tuples(current, d3)], + node_dict[add_tuples(current, d4)], + )) + except KeyError: + pass + + result = [] + + if len(result) > 0: + return [node_tuples, result] + for current in node_tuples: + # this is a tesselation of a cube into six tets. + # subtets that fall outside of the master tet are simply not added. + + # positively oriented + try_add_tet(current, (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)) + try_add_tet(current, (1, 0, 1), (1, 0, 0), (0, 0, 1), (0, 1, 0)) + try_add_tet(current, (1, 0, 1), (0, 1, 1), (0, 1, 0), (0, 0, 1)) + + try_add_tet(current, (1, 0, 0), (0, 1, 0), (1, 0, 1), (1, 1, 0)) + try_add_tet(current, (0, 1, 1), (0, 1, 0), (1, 1, 0), (1, 0, 1)) + try_add_tet(current, (0, 1, 1), (1, 1, 1), (1, 0, 1), (1, 1, 0)) + return [node_tuples, result] diff --git a/meshmode/mesh/tools.py b/meshmode/mesh/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..4b46bb83d6022710aed5505c3c21646929de8426 --- /dev/null +++ b/meshmode/mesh/tools.py @@ -0,0 +1,95 @@ +from __future__ import division +from __future__ import absolute_import +from six.moves import range + +__copyright__ = "Copyright (C) 2010,2012,2013 Andreas Kloeckner, Michael Tom" + +__license__ = """ +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. +""" + +import numpy as np +from pytools.spatial_btree import SpatialBinaryTreeBucket + + +# {{{ make_element_lookup_tree + +def make_element_lookup_tree(mesh, eps=1e-12): + from meshmode.mesh.processing import find_bounding_box + bbox_min, bbox_max = find_bounding_box(mesh) + bbox_min -= eps + bbox_max += eps + + tree = SpatialBinaryTreeBucket(bbox_min, bbox_max) + + for igrp, grp in enumerate(mesh.groups): + for iel_grp in range(grp.nelements): + el_vertices = mesh.vertices[:, grp.vertex_indices[iel_grp]] + + el_bbox_min = np.min(el_vertices, axis=-1) - eps + el_bbox_max = np.max(el_vertices, axis=-1) + eps + + tree.insert((igrp, iel_grp), (el_bbox_min, el_bbox_max)) + + return tree + +# }}} + + +# {{{ nd_quad_submesh + +def nd_quad_submesh(node_tuples): + """Return a list of tuples of indices into the node list that + generate a tesselation of the reference element. + + :arg node_tuples: A list of tuples *(i, j, ...)* of integers + indicating node positions inside the unit element. The + returned list references indices in this list. + + :func:`pytools.generate_nonnegative_integer_tuples_below` + may be used to generate *node_tuples*. + + See also :func:`modepy.tools.simplex_submesh`. + """ + + from pytools import single_valued, add_tuples + dims = single_valued(len(nt) for nt in node_tuples) + + node_dict = dict( + (ituple, idx) + for idx, ituple in enumerate(node_tuples)) + + from pytools import generate_nonnegative_integer_tuples_below as gnitb + + result = [] + for current in node_tuples: + try: + result.append(tuple( + node_dict[add_tuples(current, offset)] + for offset in gnitb(2, dims))) + + except KeyError: + pass + + return result + +# }}} + + +# vim: foldmethod=marker diff --git a/meshmode/mesh/visualization.py b/meshmode/mesh/visualization.py index 8966861b168f5d740a099e5ec4ad80baccdba4b0..caa9c1beb3cdb7123a3c9798ce04a3c882b14f93 100644 --- a/meshmode/mesh/visualization.py +++ b/meshmode/mesh/visualization.py @@ -30,7 +30,8 @@ import numpy as np # {{{ draw_2d_mesh def draw_2d_mesh(mesh, draw_vertex_numbers=True, draw_element_numbers=True, - draw_connectivity=False, **kwargs): + draw_nodal_adjacency=False, draw_face_numbers=False, + set_bounding_box=False, **kwargs): assert mesh.ambient_dim == 2 import matplotlib.pyplot as pt @@ -74,7 +75,7 @@ def draw_2d_mesh(mesh, draw_vertex_numbers=True, draw_element_numbers=True, ha="center", va="center", color="blue", bbox=dict(facecolor='white', alpha=0.5, lw=0)) - if draw_connectivity: + if draw_nodal_adjacency: def global_iel_to_group_and_iel(global_iel): for igrp, grp in enumerate(mesh.groups): if global_iel < grp.nelements: @@ -83,7 +84,7 @@ def draw_2d_mesh(mesh, draw_vertex_numbers=True, draw_element_numbers=True, raise ValueError("invalid element nr") - cnx = mesh.element_connectivity + cnx = mesh.nodal_adjacency nb_starts = cnx.neighbors_starts for iel_g in range(mesh.nelements): @@ -110,6 +111,42 @@ def draw_2d_mesh(mesh, draw_vertex_numbers=True, draw_element_numbers=True, length_includes_head=True, color="green", head_width=1e-2, lw=1e-2) + if draw_face_numbers: + for igrp, grp in enumerate(mesh.groups): + for iel, el in enumerate(grp.vertex_indices): + elverts = mesh.vertices[:, el] + el_center = np.mean(elverts, axis=-1) + + for iface, fvi in enumerate(grp.face_vertex_indices()): + face_center = ( + 0.3*el_center + + + 0.7*np.mean(elverts[:, fvi], axis=-1)) + + pt.text(face_center[0], face_center[1], str(iface), fontsize=12, + ha="center", va="center", color="purple", + bbox=dict(facecolor='white', alpha=0.5, lw=0)) + + if set_bounding_box: + from meshmode.mesh.processing import find_bounding_box + lower, upper = find_bounding_box(mesh) + pt.xlim([lower[0], upper[0]]) + pt.ylim([lower[1], upper[1]]) + +# }}} + + +# {{{ draw_curve + +def draw_curve(mesh): + import matplotlib.pyplot as pt + pt.plot(mesh.vertices[0], mesh.vertices[1], "o") + + for i, group in enumerate(mesh.groups): + pt.plot( + group.nodes[0].ravel(), + group.nodes[1].ravel(), "-x", label="Group %d" % i) + # }}} # vim: foldmethod=marker diff --git a/meshmode/version.py b/meshmode/version.py index 92c29262fa4d3e5d7e00f6e6bef07af819a6d872..1da70a1e09bc16f4b7e677505af6cf4dd4502b86 100644 --- a/meshmode/version.py +++ b/meshmode/version.py @@ -1,2 +1,2 @@ -VERSION = (2014, 1) +VERSION = (2015, 1) VERSION_TEXT = ".".join(str(i) for i in VERSION) diff --git a/requirements.txt b/requirements.txt index a6902dd2d88736406f611ddf5d76b324987f5dcb..8e99cd7fdd25e578a5dc11c56f4c43506d18974f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ 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 # required by pytential, which is in turn needed for some tests diff --git a/setup.cfg b/setup.cfg index 6faef2e65abe138f9ab22c94452c43be0e52c075..512924240ea5f744029e9dd0395769ca66267ca1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [flake8] -ignore = E126,E127,E128,E123,E226,E241,E242,E265 +ignore = E126,E127,E128,E123,E226,E241,E242,E265,W503,E402 max-line-length=85 diff --git a/test/circle.step b/test/circle.step new file mode 100644 index 0000000000000000000000000000000000000000..78f370557b0fd9f525f484a7d6cf91ba1ea78e29 --- /dev/null +++ b/test/circle.step @@ -0,0 +1,85 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('FreeCAD Model'),'2;1'); +FILE_NAME('/home/andreas/own/graphics/freecad/circle.step', + '2015-04-28T14:41:10',('FreeCAD'),('FreeCAD'), + 'Open CASCADE STEP processor 6.7','FreeCAD','Unknown'); +FILE_SCHEMA(('AUTOMOTIVE_DESIGN_CC2 { 1 2 10303 214 -1 1 5 4 }')); +ENDSEC; +DATA; +#1 = APPLICATION_PROTOCOL_DEFINITION('committee draft', + 'automotive_design',1997,#2); +#2 = APPLICATION_CONTEXT( + 'core data for automotive mechanical design processes'); +#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10); +#4 = PRODUCT_DEFINITION_SHAPE('','',#5); +#5 = PRODUCT_DEFINITION('design','',#6,#9); +#6 = PRODUCT_DEFINITION_FORMATION('','',#7); +#7 = PRODUCT('Circle001','Circle001','',(#8)); +#8 = MECHANICAL_CONTEXT('',#2,'mechanical'); +#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design'); +#10 = MANIFOLD_SURFACE_SHAPE_REPRESENTATION('',(#11,#15),#46); +#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14); +#12 = CARTESIAN_POINT('',(0.,0.,0.)); +#13 = DIRECTION('',(0.,0.,1.)); +#14 = DIRECTION('',(1.,0.,-0.)); +#15 = SHELL_BASED_SURFACE_MODEL('',(#16)); +#16 = OPEN_SHELL('',(#17)); +#17 = ADVANCED_FACE('',(#18),#31,.T.); +#18 = FACE_BOUND('',#19,.F.); +#19 = EDGE_LOOP('',(#20)); +#20 = ORIENTED_EDGE('',*,*,#21,.T.); +#21 = EDGE_CURVE('',#22,#22,#24,.T.); +#22 = VERTEX_POINT('',#23); +#23 = CARTESIAN_POINT('',(2.E-04,0.,0.)); +#24 = SURFACE_CURVE('',#25,(#30),.PCURVE_S1.); +#25 = CIRCLE('',#26,2.E-04); +#26 = AXIS2_PLACEMENT_3D('',#27,#28,#29); +#27 = CARTESIAN_POINT('',(0.,0.,0.)); +#28 = DIRECTION('',(0.,0.,1.)); +#29 = DIRECTION('',(1.,0.,0.)); +#30 = PCURVE('',#31,#36); +#31 = PLANE('',#32); +#32 = AXIS2_PLACEMENT_3D('',#33,#34,#35); +#33 = CARTESIAN_POINT('',(2.E-04,0.,0.)); +#34 = DIRECTION('',(0.,0.,-1.)); +#35 = DIRECTION('',(-1.,0.,0.)); +#36 = DEFINITIONAL_REPRESENTATION('',(#37),#45); +#37 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#38,#39,#40,#41,#42,#43,#44), +.UNSPECIFIED.,.F.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),( + -2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718, +8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM() +RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM( + '') ); +#38 = CARTESIAN_POINT('',(0.,0.)); +#39 = CARTESIAN_POINT('',(0.,3.464101615138E-04)); +#40 = CARTESIAN_POINT('',(3.E-04,1.732050807569E-04)); +#41 = CARTESIAN_POINT('',(6.E-04,4.898587196589E-20)); +#42 = CARTESIAN_POINT('',(3.E-04,-1.732050807569E-04)); +#43 = CARTESIAN_POINT('',(2.981555974335E-19,-3.464101615138E-04)); +#44 = CARTESIAN_POINT('',(0.,0.)); +#45 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#46 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) +GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#50)) GLOBAL_UNIT_ASSIGNED_CONTEXT( +(#47,#48,#49)) REPRESENTATION_CONTEXT('Context #1', + '3D Context with UNIT and UNCERTAINTY') ); +#47 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT($,.METRE.) ); +#48 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) ); +#49 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() ); +#50 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-10),#47, + 'distance_accuracy_value','confusion accuracy'); +#51 = PRODUCT_TYPE('part',$,(#7)); +#52 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(#53), + #46); +#53 = STYLED_ITEM('color',(#54),#17); +#54 = PRESENTATION_STYLE_ASSIGNMENT((#55)); +#55 = SURFACE_STYLE_USAGE(.BOTH.,#56); +#56 = SURFACE_SIDE_STYLE('',(#57)); +#57 = SURFACE_STYLE_FILL_AREA(#58); +#58 = FILL_AREA_STYLE('',(#59)); +#59 = FILL_AREA_STYLE_COLOUR('',#60); +#60 = COLOUR_RGB('',0.800000011921,0.800000011921,0.800000011921); +ENDSEC; +END-ISO-10303-21; diff --git a/test/test_meshmode.py b/test/test_meshmode.py index 6047113c956d1f616c57fc6610b8a75393b90db0..c7c2bc488932feec285147eaf2fc7fa7358deab4 100644 --- a/test/test_meshmode.py +++ b/test/test_meshmode.py @@ -1,7 +1,4 @@ -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -from six.moves import range +from __future__ import division, absolute_import, print_function __copyright__ = "Copyright (C) 2014 Andreas Kloeckner" @@ -25,6 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from six.moves import range import numpy as np import numpy.linalg as la import pyopencl as cl @@ -35,58 +33,342 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +from meshmode.discretization.poly_element import ( + InterpolatoryQuadratureSimplexGroupFactory, + PolynomialWarpAndBlendGroupFactory, + PolynomialEquidistantGroupFactory, + ) +from meshmode.mesh import BTAG_ALL +from meshmode.discretization.connection import \ + FRESTR_ALL_FACES, FRESTR_INTERIOR_FACES + import pytest import logging logger = logging.getLogger(__name__) -def test_boundary_interpolation(ctx_getter): +# {{{ circle mesh + +def test_circle_mesh(do_plot=False): + from meshmode.mesh.io import generate_gmsh, FileSource + print("BEGIN GEN") + mesh = generate_gmsh( + FileSource("circle.step"), 2, order=2, + force_ambient_dim=2, + other_options=[ + "-string", "Mesh.CharacteristicLengthMax = 0.05;"] + ) + print("END GEN") + print(mesh.nelements) + + from meshmode.mesh.processing import affine_map + mesh = affine_map(mesh, A=3*np.eye(2)) + + if do_plot: + from meshmode.mesh.visualization import draw_2d_mesh + draw_2d_mesh(mesh, fill=None, draw_nodal_adjacency=True, + set_bounding_box=True) + import matplotlib.pyplot as pt + pt.show() + +# }}} + + +# {{{ convergence of boundary interpolation + +@pytest.mark.parametrize("group_factory", [ + InterpolatoryQuadratureSimplexGroupFactory, + PolynomialWarpAndBlendGroupFactory + ]) +@pytest.mark.parametrize("boundary_tag", [ + BTAG_ALL, + FRESTR_ALL_FACES, + FRESTR_INTERIOR_FACES, + ]) +@pytest.mark.parametrize(("mesh_name", "dim", "mesh_pars"), [ + ("blob", 2, [1e-1, 8e-2, 5e-2]), + ("warp", 2, [10, 20, 30]), + ("warp", 3, [10, 20, 30]), + ]) +@pytest.mark.parametrize("per_face_groups", [False, True]) +def test_boundary_interpolation(ctx_getter, group_factory, boundary_tag, + mesh_name, dim, mesh_pars, per_face_groups): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) - from meshmode.mesh.io import generate_gmsh, FileSource from meshmode.discretization import Discretization - from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory - from meshmode.discretization.connection import make_boundary_restriction + from meshmode.discretization.connection import ( + make_face_restriction, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 4 - for h in [1e-1, 3e-2, 1e-2]: - print("BEGIN GEN") - mesh = generate_gmsh( - FileSource("blob-2d.step"), 2, order=order, - force_ambient_dim=2, - other_options=[ - "-string", "Mesh.CharacteristicLengthMax = %s;" % h] - ) - print("END GEN") + def f(x): + return 0.1*cl.clmath.sin(30*x) + + for mesh_par in mesh_pars: + # {{{ get mesh + + if mesh_name == "blob": + assert dim == 2 + + h = mesh_par + + from meshmode.mesh.io import generate_gmsh, FileSource + print("BEGIN GEN") + mesh = generate_gmsh( + FileSource("blob-2d.step"), 2, order=order, + force_ambient_dim=2, + other_options=[ + "-string", "Mesh.CharacteristicLengthMax = %s;" % h] + ) + print("END GEN") + elif mesh_name == "warp": + from meshmode.mesh.generation import generate_warped_rect_mesh + mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) + + h = 1/mesh_par + else: + raise ValueError("mesh_name not recognized") + + # }}} vol_discr = Discretization(cl_ctx, mesh, - InterpolatoryQuadratureSimplexGroupFactory(order)) + group_factory(order)) print("h=%s -> %d elements" % ( h, sum(mgrp.nelements for mgrp in mesh.groups))) x = vol_discr.nodes()[0].with_queue(queue) - f = 0.1*cl.clmath.sin(30*x) + vol_f = f(x) - bdry_mesh, bdry_discr, bdry_connection = make_boundary_restriction( - queue, vol_discr, InterpolatoryQuadratureSimplexGroupFactory(order)) + bdry_connection = make_face_restriction( + vol_discr, group_factory(order), + boundary_tag, per_face_groups=per_face_groups) + check_connection(bdry_connection) + bdry_discr = bdry_connection.to_discr bdry_x = bdry_discr.nodes()[0].with_queue(queue) - bdry_f = 0.1*cl.clmath.sin(30*bdry_x) - bdry_f_2 = bdry_connection(queue, f) + bdry_f = f(bdry_x) + bdry_f_2 = bdry_connection(queue, vol_f) + + if mesh_name == "blob" and dim == 2: + mat = bdry_connection.full_resample_matrix(queue).get(queue) + bdry_f_2_by_mat = mat.dot(vol_f.get()) + + mat_error = la.norm(bdry_f_2.get(queue=queue) - bdry_f_2_by_mat) + assert mat_error < 1e-14, mat_error err = la.norm((bdry_f-bdry_f_2).get(), np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) - assert eoc_rec.order_estimate() >= order-0.5 + assert ( + eoc_rec.order_estimate() >= order-0.5 + or eoc_rec.max_error() < 1e-14) + +# }}} + + +# {{{ boundary-to-all-faces connecttion + +@pytest.mark.parametrize(("mesh_name", "dim", "mesh_pars"), [ + ("blob", 2, [1e-1, 8e-2, 5e-2]), + ("warp", 2, [10, 20, 30]), + ("warp", 3, [10, 20, 30]), + ]) +@pytest.mark.parametrize("per_face_groups", [False, True]) +def test_all_faces_interpolation(ctx_getter, mesh_name, dim, mesh_pars, + per_face_groups): + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + + from meshmode.discretization import Discretization + from meshmode.discretization.connection import ( + make_face_restriction, make_face_to_all_faces_embedding, + check_connection) + + from pytools.convergence import EOCRecorder + eoc_rec = EOCRecorder() + + order = 4 + + def f(x): + return 0.1*cl.clmath.sin(30*x) + + for mesh_par in mesh_pars: + # {{{ get mesh + + if mesh_name == "blob": + assert dim == 2 + + h = mesh_par + + from meshmode.mesh.io import generate_gmsh, FileSource + print("BEGIN GEN") + mesh = generate_gmsh( + FileSource("blob-2d.step"), 2, order=order, + force_ambient_dim=2, + other_options=[ + "-string", "Mesh.CharacteristicLengthMax = %s;" % h] + ) + print("END GEN") + elif mesh_name == "warp": + from meshmode.mesh.generation import generate_warped_rect_mesh + mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) + + h = 1/mesh_par + else: + raise ValueError("mesh_name not recognized") + + # }}} + + vol_discr = Discretization(cl_ctx, mesh, + PolynomialWarpAndBlendGroupFactory(order)) + print("h=%s -> %d elements" % ( + h, sum(mgrp.nelements for mgrp in mesh.groups))) + + all_face_bdry_connection = make_face_restriction( + vol_discr, PolynomialWarpAndBlendGroupFactory(order), + FRESTR_ALL_FACES, per_face_groups=per_face_groups) + all_face_bdry_discr = all_face_bdry_connection.to_discr + + for ito_grp, ceg in enumerate(all_face_bdry_connection.groups): + for ibatch, batch in enumerate(ceg.batches): + assert np.array_equal( + batch.from_element_indices.get(queue), + np.arange(vol_discr.mesh.nelements)) + + if per_face_groups: + assert ito_grp == batch.to_element_face + else: + assert ibatch == batch.to_element_face + + all_face_x = all_face_bdry_discr.nodes()[0].with_queue(queue) + all_face_f = f(all_face_x) + + all_face_f_2 = all_face_bdry_discr.zeros(queue) + + for boundary_tag in [ + BTAG_ALL, + FRESTR_INTERIOR_FACES, + ]: + bdry_connection = make_face_restriction( + vol_discr, PolynomialWarpAndBlendGroupFactory(order), + boundary_tag, per_face_groups=per_face_groups) + bdry_discr = bdry_connection.to_discr + + bdry_x = bdry_discr.nodes()[0].with_queue(queue) + bdry_f = f(bdry_x) + + all_face_embedding = make_face_to_all_faces_embedding( + bdry_connection, all_face_bdry_discr) + + check_connection(all_face_embedding) + + all_face_f_2 += all_face_embedding(queue, bdry_f) + + err = la.norm((all_face_f-all_face_f_2).get(), np.inf) + eoc_rec.add_data_point(h, err) + + print(eoc_rec) + assert ( + eoc_rec.order_estimate() >= order-0.5 + or eoc_rec.max_error() < 1e-14) + +# }}} + + +# {{{ convergence of opposite-face interpolation + +@pytest.mark.parametrize("group_factory", [ + InterpolatoryQuadratureSimplexGroupFactory, + PolynomialWarpAndBlendGroupFactory + ]) +@pytest.mark.parametrize(("mesh_name", "dim", "mesh_pars"), [ + ("blob", 2, [1e-1, 8e-2, 5e-2]), + ("warp", 2, [3, 5, 7]), + ("warp", 3, [3, 5]), + ]) +def test_opposite_face_interpolation(ctx_getter, group_factory, + mesh_name, dim, mesh_pars): + logging.basicConfig(level=logging.INFO) + + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + + from meshmode.discretization import Discretization + from meshmode.discretization.connection import ( + make_face_restriction, make_opposite_face_connection, + check_connection) + + from pytools.convergence import EOCRecorder + eoc_rec = EOCRecorder() + + order = 5 + + def f(x): + return 0.1*cl.clmath.sin(30*x) + + for mesh_par in mesh_pars: + # {{{ get mesh + + if mesh_name == "blob": + assert dim == 2 + + h = mesh_par + + from meshmode.mesh.io import generate_gmsh, FileSource + print("BEGIN GEN") + mesh = generate_gmsh( + FileSource("blob-2d.step"), 2, order=order, + force_ambient_dim=2, + other_options=[ + "-string", "Mesh.CharacteristicLengthMax = %s;" % h] + ) + print("END GEN") + elif mesh_name == "warp": + from meshmode.mesh.generation import generate_warped_rect_mesh + mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) + h = 1/mesh_par + else: + raise ValueError("mesh_name not recognized") + + # }}} + + vol_discr = Discretization(cl_ctx, mesh, + group_factory(order)) + print("h=%s -> %d elements" % ( + h, sum(mgrp.nelements for mgrp in mesh.groups))) + + bdry_connection = make_face_restriction( + vol_discr, group_factory(order), + FRESTR_INTERIOR_FACES) + bdry_discr = bdry_connection.to_discr + + opp_face = make_opposite_face_connection(bdry_connection) + check_connection(opp_face) + + bdry_x = bdry_discr.nodes()[0].with_queue(queue) + bdry_f = f(bdry_x) + + bdry_f_2 = opp_face(queue, bdry_f) + + err = la.norm((bdry_f-bdry_f_2).get(), np.inf) + eoc_rec.add_data_point(h, err) + + print(eoc_rec) + assert ( + eoc_rec.order_estimate() >= order-0.5 + or eoc_rec.max_error() < 1e-13) + +# }}} + + +# {{{ element orientation def test_element_orientation(): from meshmode.mesh.io import generate_gmsh, FileSource @@ -116,6 +398,10 @@ def test_element_orientation(): assert ((mesh_orient < 0) == (flippy > 0)).all() +# }}} + + +# {{{ merge and map def test_merge_and_map(ctx_getter, visualize=False): from meshmode.mesh.io import generate_gmsh, FileSource @@ -128,10 +414,10 @@ def test_merge_and_map(ctx_getter, visualize=False): other_options=["-string", "Mesh.CharacteristicLengthMax = 0.02;"] ) - from meshmode.mesh.processing import merge_dijsoint_meshes, affine_map + from meshmode.mesh.processing import merge_disjoint_meshes, affine_map mesh2 = affine_map(mesh, A=np.eye(2), b=np.array([5, 0])) - mesh3 = merge_dijsoint_meshes((mesh2, mesh)) + mesh3 = merge_disjoint_meshes((mesh2, mesh)) if visualize: from meshmode.discretization import Discretization @@ -147,6 +433,10 @@ def test_merge_and_map(ctx_getter, visualize=False): vis = make_visualizer(queue, discr, 1) vis.write_vtk_file("merged.vtu", []) +# }}} + + +# {{{ sanity checks: single element @pytest.mark.parametrize("dim", [2, 3]) @pytest.mark.parametrize("order", [1, 3]) @@ -156,21 +446,21 @@ def test_sanity_single_element(ctx_getter, dim, order, visualize=False): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) - from modepy.tools import UNIT_VERTICES - vertices = UNIT_VERTICES[dim].T.copy() + from modepy.tools import unit_vertices + vertices = unit_vertices(dim).T.copy() center = np.empty(dim, np.float64) center.fill(-0.5) import modepy as mp - from meshmode.mesh import SimplexElementGroup, Mesh + from meshmode.mesh import SimplexElementGroup, Mesh, BTAG_ALL mg = SimplexElementGroup( order=order, vertex_indices=np.arange(dim+1, dtype=np.int32).reshape(1, -1), nodes=mp.warp_and_blend_nodes(dim, order).reshape(dim, 1, -1), dim=dim) - mesh = Mesh(vertices, [mg]) + mesh = Mesh(vertices, [mg], nodal_adjacency=None, facial_adjacency_groups=None) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ @@ -198,9 +488,11 @@ def test_sanity_single_element(ctx_getter, dim, order, visualize=False): # {{{ boundary discretization - from meshmode.discretization.connection import make_boundary_restriction - bdry_mesh, bdry_discr, bdry_connection = make_boundary_restriction( - queue, vol_discr, PolynomialWarpAndBlendGroupFactory(order + 3)) + from meshmode.discretization.connection import make_face_restriction + bdry_connection = make_face_restriction( + vol_discr, PolynomialWarpAndBlendGroupFactory(order + 3), + BTAG_ALL) + bdry_discr = bdry_connection.to_discr # }}} @@ -229,6 +521,60 @@ def test_sanity_single_element(ctx_getter, dim, order, visualize=False): assert normal_outward_check.get().all(), normal_outward_check.get() +# }}} + + +# {{{ sanity check: volume interpolation on scipy/qhull delaunay meshes in nD + +@pytest.mark.parametrize("dim", [2, 3, 4]) +@pytest.mark.parametrize("order", [3]) +def test_sanity_qhull_nd(ctx_getter, dim, order): + pytest.importorskip("scipy") + + logging.basicConfig(level=logging.INFO) + + ctx = ctx_getter() + queue = cl.CommandQueue(ctx) + + from scipy.spatial import Delaunay + verts = np.random.rand(1000, dim) + dtri = Delaunay(verts) + + from meshmode.mesh.io import from_vertices_and_simplices + mesh = from_vertices_and_simplices(dtri.points.T, dtri.simplices, + fix_orientation=True) + + from meshmode.discretization import Discretization + low_discr = Discretization(ctx, mesh, + PolynomialEquidistantGroupFactory(order)) + high_discr = Discretization(ctx, mesh, + PolynomialEquidistantGroupFactory(order+1)) + + from meshmode.discretization.connection import make_same_mesh_connection + cnx = make_same_mesh_connection(high_discr, low_discr) + + def f(x): + return 0.1*cl.clmath.sin(x) + + x_low = low_discr.nodes()[0].with_queue(queue) + f_low = f(x_low) + + x_high = high_discr.nodes()[0].with_queue(queue) + f_high_ref = f(x_high) + + f_high_num = cnx(queue, f_low) + + err = (f_high_ref-f_high_num).get() + + err = la.norm(err, np.inf)/la.norm(f_high_ref.get(), np.inf) + + print(err) + assert err < 1e-2 + +# }}} + + +# {{{ sanity checks: ball meshes # python test_meshmode.py 'test_sanity_balls(cl._csc, "disk-radius-1.step", 2, 2, visualize=True)' # noqa @pytest.mark.parametrize(("src_file", "dim"), [ @@ -266,15 +612,15 @@ def test_sanity_balls(ctx_getter, src_file, dim, mesh_order, # {{{ discretizations and connections from meshmode.discretization import Discretization - from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory vol_discr = Discretization(ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(quad_order)) - from meshmode.discretization.connection import make_boundary_restriction - bdry_mesh, bdry_discr, bdry_connection = make_boundary_restriction( - queue, vol_discr, - InterpolatoryQuadratureSimplexGroupFactory(quad_order)) + from meshmode.discretization.connection import make_face_restriction + bdry_connection = make_face_restriction( + vol_discr, + InterpolatoryQuadratureSimplexGroupFactory(quad_order), + BTAG_ALL) + bdry_discr = bdry_connection.to_discr # }}} @@ -344,6 +690,10 @@ def test_sanity_balls(ctx_getter, src_file, dim, mesh_order, print(surf_eoc_rec) assert surf_eoc_rec.order_estimate() >= mesh_order +# }}} + + +# {{{ rect/box mesh generation def test_rect_mesh(do_plot=False): from meshmode.mesh.generation import generate_regular_rect_mesh @@ -351,11 +701,250 @@ def test_rect_mesh(do_plot=False): if do_plot: from meshmode.mesh.visualization import draw_2d_mesh - draw_2d_mesh(mesh, fill=None, draw_connectivity=True) + draw_2d_mesh(mesh, fill=None, draw_nodal_adjacency=True) import matplotlib.pyplot as pt pt.show() +def test_box_mesh(ctx_getter, visualize=False): + from meshmode.mesh.generation import generate_box_mesh + mesh = generate_box_mesh(3*(np.linspace(0, 1, 5),)) + + if visualize: + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + PolynomialWarpAndBlendGroupFactory + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + + discr = Discretization(cl_ctx, mesh, + PolynomialWarpAndBlendGroupFactory(1)) + + from meshmode.discretization.visualization import make_visualizer + vis = make_visualizer(queue, discr, 1) + vis.write_vtk_file("box.vtu", []) + +# }}} + + +# {{{ as_python stringification + +def test_as_python(): + from meshmode.mesh.generation import generate_box_mesh + mesh = generate_box_mesh(3*(np.linspace(0, 1, 5),)) + + # These implicitly compute these adjacency structures. + mesh.nodal_adjacency + mesh.facial_adjacency_groups + + from meshmode.mesh import as_python + code = as_python(mesh) + + print(code) + exec_dict = {} + exec(compile(code, "gen_code.py", "exec"), exec_dict) + + mesh_2 = exec_dict["make_mesh"]() + + assert mesh == mesh_2 + +# }}} + + +# {{{ test lookup tree for element finding + +def test_lookup_tree(do_plot=False): + from meshmode.mesh.generation import make_curve_mesh, cloverleaf + mesh = make_curve_mesh(cloverleaf, np.linspace(0, 1, 1000), order=3) + + from meshmode.mesh.tools import make_element_lookup_tree + tree = make_element_lookup_tree(mesh) + + from meshmode.mesh.processing import find_bounding_box + bbox_min, bbox_max = find_bounding_box(mesh) + + extent = bbox_max-bbox_min + + for i in range(20): + pt = bbox_min + np.random.rand(2) * extent + print(pt) + for igrp, iel in tree.generate_matches(pt): + print(igrp, iel) + + if do_plot: + with open("tree.dat", "w") as outf: + tree.visualize(outf) + +# }}} + + +# {{{ test refiner + +def _get_vertex(mesh, vertex_index): + vertex = np.empty([len(mesh.vertices)]) + for i_cur_dim, cur_dim_coords in enumerate(mesh.vertices): + vertex[i_cur_dim] = cur_dim_coords[vertex_index] + return vertex + + +def get_blobby_2d_mesh(): + from meshmode.mesh.io import generate_gmsh, FileSource + return generate_gmsh( + FileSource("blob-2d.step"), 2, order=2, + force_ambient_dim=2, + other_options=[ + "-string", "Mesh.CharacteristicLengthMax = 1e-1;"] + ) + + +def get_some_mesh(): + from meshmode.mesh.generation import ( # noqa + generate_icosphere, generate_icosahedron, + generate_torus, generate_regular_rect_mesh) + import random + #mesh = generate_icosphere(1, order=4) + #mesh = generate_icosahedron(1, order=4) + mesh = generate_torus(3, 1, order=4) + + #mesh = generate_regular_rect_mesh() + + from meshmode.mesh.refinement import Refiner + r = Refiner(mesh) + times = random.randint(1, 2) + for time in range(times): + flags = np.zeros(len(mesh.groups[0].vertex_indices)) + ''' + if time == 0: + flags[0] = 1 + if time == 1: + #flags[1] = 1 + flags[33] = 1 + if time == 2: + flags[3] = 1 + pass + #flags[8] = 1 + #flags[40] = 1 + ''' + for i in range(0, len(flags)): + flags[i] = random.randint(0, 1) + mesh = r.refine(flags) + + return mesh + + +@pytest.mark.parametrize("mesh_factory", [get_some_mesh]) +@pytest.mark.parametrize("num_rounds", [1, 2, 3]) +def test_refiner_connectivity(mesh_factory, num_rounds): + mesh = mesh_factory() + + def group_and_iel_to_global_iel(igrp, iel): + return mesh.groups[igrp].element_nr_base + iel + + from meshmode.mesh.tools import make_element_lookup_tree + tree = make_element_lookup_tree(mesh) + + from meshmode.mesh.processing import find_bounding_box + bbox_min, bbox_max = find_bounding_box(mesh) + + cnx = mesh.element_connectivity + nvertices_per_element = len(mesh.groups[0].vertex_indices[0]) + #stores connectivity obtained from geometry using barycentric coordinates + connected_to_element_geometry = np.zeros( + (mesh.nelements, mesh.nelements), dtype=bool) + #store connectivity obtained from mesh's connectivity + connected_to_element_connectivity = np.zeros( + (mesh.nelements, mesh.nelements), dtype=bool) + + import six + for igrp, grp in enumerate(mesh.groups): + for iel_grp in six.moves.range(grp.nelements): + + iel_g = group_and_iel_to_global_iel(igrp, iel_grp) + nb_starts = cnx.neighbors_starts + for nb_iel_g in cnx.neighbors[nb_starts[iel_g]:nb_starts[iel_g+1]]: + connected_to_element_connectivity[nb_iel_g, iel_g] = True + connected_to_element_connectivity[iel_g, nb_iel_g] = True + + for vertex_index in grp.vertex_indices[iel_grp]: + vertex = _get_vertex(mesh, vertex_index) + + # check which elements touch this vertex + for bounding_igrp, bounding_iel in tree.generate_matches(vertex): + if bounding_igrp == igrp and bounding_iel == iel_grp: + continue + bounding_grp = mesh.groups[bounding_igrp] + + last_bounding_vertex = _get_vertex(mesh, + bounding_grp.vertex_indices[bounding_iel][nvertices_per_element-1]) + + transformation = np.empty([len(mesh.vertices), nvertices_per_element-1]) + vertex_transformed = vertex - last_bounding_vertex + for ibounding_vertex_index, bounding_vertex_index in enumerate( + bounding_grp.vertex_indices[bounding_iel][:nvertices_per_element-1]): + bounding_vertex = _get_vertex(mesh, bounding_vertex_index) + transformation[:, ibounding_vertex_index] = \ + bounding_vertex - last_bounding_vertex + barycentric_coordinates, resid, _, _ = np.linalg.lstsq(transformation, vertex_transformed) + + bc = barycentric_coordinates + tol = 1e-12 + + if resid > tol: + continue + + if ((bc > -tol).all() and np.sum(bc) < 1+tol): + connected_to_element_geometry[ + group_and_iel_to_global_iel(bounding_igrp, bounding_iel), + group_and_iel_to_global_iel(igrp, iel_grp)] = True + connected_to_element_geometry[ + group_and_iel_to_global_iel(igrp, iel_grp), + group_and_iel_to_global_iel(bounding_igrp, bounding_iel)] = True + + ''' + print ("GEOMETRY: ") + print (connected_to_element_geometry) + print ("CONNECTIVITY: ") + print (connected_to_element_connectivity) +# cmpmatrix = (connected_to_element_geometry == connected_to_element_connectivity) + #print cmpmatrix +# print np.where(cmpmatrix == False) + if not (connected_to_element_geometry == connected_to_element_connectivity).all(): + cmpmatrix = connected_to_element_connectivity == connected_to_element_geometry + for ii, i in enumerate(cmpmatrix): + for ij, j in enumerate(i): + if j == False: + pass + print (ii, ij) + ''' + assert (connected_to_element_geometry == connected_to_element_connectivity).all() + +# }}} + + +# {{{ test_nd_quad_submesh + +@pytest.mark.parametrize("dims", [2, 3, 4]) +def test_nd_quad_submesh(dims): + from meshmode.mesh.tools import nd_quad_submesh + from pytools import generate_nonnegative_integer_tuples_below as gnitb + + node_tuples = list(gnitb(3, dims)) + + for i, nt in enumerate(node_tuples): + print(i, nt) + + assert len(node_tuples) == 3**dims + + elements = nd_quad_submesh(node_tuples) + + for e in elements: + print(e) + + assert len(elements) == 2**dims + +# }}} + + if __name__ == "__main__": import sys if len(sys.argv) > 1: