def warp_and_blend_nodes_2d(n, node_tuples=None): try: alpha = _alpha_opt_2d[n-1] except IndexError: alpha = 5/3 if node_tuples is None: from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, 2)) else: if len(node_tuples) != (n+1)*(n+2)//2: raise ValueError("node_tuples list does not have the correct length") # shape: (2, nnodes) unit_nodes = (np.array(node_tuples, dtype=np.float64)/n*2 - 1).T from modepy.tools import ( unit_to_barycentric, barycentric_to_equilateral, equilateral_to_unit) bary = unit_to_barycentric(unit_nodes) return equilateral_to_unit( barycentric_to_equilateral(bary) + _2d_equilateral_shift(n, bary, alpha))
def test_barycentric_coordinate_map(dims): from random import Random rng = Random(17) n = 5 unit = np.empty((dims, n)) from modepy.tools import ( pick_random_simplex_unit_coordinate, unit_to_barycentric, barycentric_to_unit, barycentric_to_equilateral, equilateral_to_unit,) for i in range(n): unit[:, i] = pick_random_simplex_unit_coordinate(rng, dims) bary = unit_to_barycentric(unit) assert (np.abs(np.sum(bary, axis=0) - 1) < 1e-15).all() assert (bary >= 0).all() unit2 = barycentric_to_unit(bary) assert la.norm(unit-unit2) < 1e-14 equi = barycentric_to_equilateral(bary) unit3 = equilateral_to_unit(equi) assert la.norm(unit-unit3) < 1e-14
def warp_and_blend_nodes_2d(n, node_tuples=None): try: alpha = _alpha_opt_2d[n - 1] except IndexError: alpha = 5 / 3 if node_tuples is None: from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, 2)) else: if len(node_tuples) != (n + 1) * (n + 2) // 2: raise ValueError( "node_tuples list does not have the correct length") # shape: (2, nnodes) unit_nodes = (np.array(node_tuples, dtype=np.float64) / n * 2 - 1).T from modepy.tools import (unit_to_barycentric, barycentric_to_equilateral, equilateral_to_unit) bary = unit_to_barycentric(unit_nodes) return equilateral_to_unit( barycentric_to_equilateral(bary) + _2d_equilateral_shift(n, bary, alpha))
def get_simplex_element_flip_matrix(order, unit_nodes, permutation=None): """ Generate a resampling matrix that corresponds to a permutation of the barycentric coordinates being applied. The default permutation is to swap the first two barycentric coordinates. :param order: The order of the function space on the simplex, (see second argument in :fun:`modepy.simplex_best_available_basis`) :param unit_nodes: A np array of unit nodes with shape *(dim, nunit_nodes)* :param permutation: Either *None*, or a tuple of shape storing a permutation: the *i*th barycentric coordinate gets mapped to the *permutation[i]*th coordinate. :return: A numpy array of shape *(nunit_nodes, nunit_nodes)* which, when its transpose is right-applied to the matrix of nodes (shaped *(dim, nunit_nodes)*), corresponds to the permutation being applied """ from modepy.tools import barycentric_to_unit, unit_to_barycentric bary_unit_nodes = unit_to_barycentric(unit_nodes) flipped_bary_unit_nodes = bary_unit_nodes.copy() if permutation is None: flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] else: flipped_bary_unit_nodes[permutation, :] = bary_unit_nodes flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) dim = unit_nodes.shape[0] shape = mp.Simplex(dim) space = mp.PN(dim, order) basis = mp.basis_for_space(space, shape) flip_matrix = mp.resampling_matrix(basis.functions, flipped_unit_nodes, unit_nodes) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 # Flipping twice should be the identity if permutation is None: assert la.norm( np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 return flip_matrix
def flip_simplex_element_group(vertices, grp, grp_flip_flags): from modepy.tools import barycentric_to_unit, unit_to_barycentric from meshmode.mesh import SimplexElementGroup if not isinstance(grp, SimplexElementGroup): raise NotImplementedError( "flips only supported on " "exclusively SimplexElementGroup-based meshes") # Swap the first two vertices on elements to be flipped. new_vertex_indices = grp.vertex_indices.copy() new_vertex_indices[grp_flip_flags, 0] \ = grp.vertex_indices[grp_flip_flags, 1] new_vertex_indices[grp_flip_flags, 1] \ = grp.vertex_indices[grp_flip_flags, 0] # Generate a resampling matrix that corresponds to the # first two barycentric coordinates being swapped. bary_unit_nodes = unit_to_barycentric(grp.unit_nodes) flipped_bary_unit_nodes = bary_unit_nodes.copy() flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) flip_matrix = mp.resampling_matrix( mp.simplex_best_available_basis(grp.dim, grp.order), flipped_unit_nodes, grp.unit_nodes) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 # Flipping twice should be the identity assert la.norm( np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 # Apply the flip matrix to the nodes. new_nodes = grp.nodes.copy() new_nodes[:, grp_flip_flags] = np.einsum("ij,dej->dei", flip_matrix, grp.nodes[:, grp_flip_flags]) return SimplexElementGroup(grp.order, new_vertex_indices, new_nodes, unit_nodes=grp.unit_nodes)
def flip_simplex_element_group(vertices, grp, grp_flip_flags): from modepy.tools import barycentric_to_unit, unit_to_barycentric from meshmode.mesh import SimplexElementGroup if not isinstance(grp, SimplexElementGroup): raise NotImplementedError("flips only supported on " "exclusively SimplexElementGroup-based meshes") # Swap the first two vertices on elements to be flipped. new_vertex_indices = grp.vertex_indices.copy() new_vertex_indices[grp_flip_flags, 0] \ = grp.vertex_indices[grp_flip_flags, 1] new_vertex_indices[grp_flip_flags, 1] \ = grp.vertex_indices[grp_flip_flags, 0] # Generate a resampling matrix that corresponds to the # first two barycentric coordinates being swapped. bary_unit_nodes = unit_to_barycentric(grp.unit_nodes) flipped_bary_unit_nodes = bary_unit_nodes.copy() flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) flip_matrix = mp.resampling_matrix( mp.simplex_best_available_basis(grp.dim, grp.order), flipped_unit_nodes, grp.unit_nodes) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 # Flipping twice should be the identity assert la.norm( np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 # Apply the flip matrix to the nodes. new_nodes = grp.nodes.copy() new_nodes[:, grp_flip_flags] = np.einsum( "ij,dej->dei", flip_matrix, grp.nodes[:, grp_flip_flags]) return SimplexElementGroup( grp.order, new_vertex_indices, new_nodes, unit_nodes=grp.unit_nodes)
def flip_matrix(self): """ :return: The matrix which should be applied to the *(dim, nunitnodes)*-shaped array of nodes corresponding to an element in order to change orientation - <-> +. The matrix will be *(dim, dim)* and orthogonal with *np.float64* type entries. """ if self._flip_matrix is None: # This is very similar to :mod:`meshmode` in processing.py # the function :function:`from_simplex_element_group`, but # we needed to use firedrake nodes from modepy.tools import barycentric_to_unit, unit_to_barycentric # Generate a resampling matrix that corresponds to the # first two barycentric coordinates being swapped. bary_unit_nodes = unit_to_barycentric(self.unit_nodes()) flipped_bary_unit_nodes = bary_unit_nodes.copy() flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) from modepy import resampling_matrix, simplex_best_available_basis flip_matrix = resampling_matrix( simplex_best_available_basis(self.dim(), self.analog().degree), flipped_unit_nodes, self.unit_nodes()) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 # Flipping twice should be the identity assert la.norm( np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 self._flip_matrix = flip_matrix return self._flip_matrix
def test_barycentric_coordinate_map(dims): n = 100 from modepy.tools import ( unit_to_barycentric, barycentric_to_unit, barycentric_to_equilateral, equilateral_to_unit, ) rng = np.random.Generator(np.random.PCG64(17)) unit = nd.random_nodes_for_shape(shp.Simplex(dims), n, rng=rng) bary = unit_to_barycentric(unit) assert (np.abs(np.sum(bary, axis=0) - 1) < 1e-15).all() assert (bary >= 0).all() unit2 = barycentric_to_unit(bary) assert la.norm(unit - unit2) < 1e-14 equi = barycentric_to_equilateral(bary) unit3 = equilateral_to_unit(equi) assert la.norm(unit - unit3) < 1e-14
def warp_and_blend_nodes_2d(n, node_tuples=None): try: alpha = _alpha_opt_2d[n - 1] except IndexError: alpha = 5 / 3 space = PN(2, n) if node_tuples is None: node_tuples = node_tuples_for_space(space) else: if len(node_tuples) != space.space_dim: raise ValueError( "'node_tuples' list does not have the correct length") # shape: (2, nnodes) unit_nodes = (np.array(node_tuples, dtype=np.float64) / n * 2 - 1).T from modepy.tools import (unit_to_barycentric, barycentric_to_equilateral, equilateral_to_unit) bary = unit_to_barycentric(unit_nodes) return equilateral_to_unit( barycentric_to_equilateral(bary) + _2d_equilateral_shift(n, bary, alpha))
def warp_and_blend_nodes_3d(n, node_tuples=None): try: alpha = _alpha_opt_3d[n-1] except IndexError: alpha = 1. if node_tuples is None: from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, 3)) else: if len(node_tuples) != (n+1)*(n+2)*(n+3)//6: raise ValueError("node_tuples list does not have the correct length") # shape: (3, nnodes) unit_nodes = (np.array(node_tuples, dtype=np.float64)/n*2 - 1).T from modepy.tools import ( unit_to_barycentric, barycentric_to_equilateral, equilateral_to_unit, EQUILATERAL_VERTICES) bary = unit_to_barycentric(unit_nodes) equi = barycentric_to_equilateral(bary) equi_vertices = EQUILATERAL_VERTICES[3] # total number of nodes and tolerance tol = 1e-8 shift = np.zeros_like(equi) for i1, i2, i3, i4, vertex_step in [ (0, 1, 2, 3, -1), (1, 2, 3, 0, -1), (2, 3, 0, 1, -1), (3, 0, 1, 2, -1), ]: vi2, vi3, vi4 = [(i1 + vertex_step*i) % 4 for i in range(1, 4)] # all vertices have the same distance from the origin tangent1 = equi_vertices[vi3] - equi_vertices[vi4] tangent1 /= la.norm(tangent1) tangent2 = equi_vertices[vi2] - equi_vertices[vi3] tangent2 -= np.dot(tangent1, tangent2)*tangent1 tangent2 /= la.norm(tangent2) sub_bary = bary[[i2, i3, i4]] warp1, warp2 = _2d_equilateral_shift(n, sub_bary, alpha) l1 = bary[i1] l2, l3, l4 = sub_bary blend = l2*l3*l4 denom = (l2+0.5*l1)*(l3+0.5*l1)*(l4+0.5*l1) denom_ok = denom > tol blend[denom_ok] = ( (1+(alpha*l1[denom_ok])**2) * blend[denom_ok] / denom[denom_ok]) shift = shift + (blend*warp1)[np.newaxis, :] * tangent1[:, np.newaxis] shift = shift + (blend*warp2)[np.newaxis, :] * tangent2[:, np.newaxis] is_face = (l1 < tol) & ((l2 > tol) | (l3 > tol) | (l4 > tol)) # assign face warp separately shift[:, is_face] = ( + warp1[is_face][np.newaxis, :] * tangent1[:, np.newaxis] + warp2[is_face][np.newaxis, :] * tangent2[:, np.newaxis] ) return equilateral_to_unit(equi + shift)
link_radius = 0.02 from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, 3)) faces = [ [nt for nt in node_tuples if nt[0] == 0], [nt for nt in node_tuples if nt[1] == 0], [nt for nt in node_tuples if nt[2] == 0], [nt for nt in node_tuples if sum(nt) == n] ] from modepy.tools import unit_to_barycentric, barycentric_to_equilateral nodes = [(n[0],n[2], n[1]) for n in barycentric_to_equilateral( unit_to_barycentric( mp.warp_and_blend_nodes(3, n, node_tuples))).T] id_to_node = dict(list(zip(node_tuples, nodes))) def get_ball_radius(nid): in_faces = len([f for f in faces if nid in f]) if in_faces >= 2: return ball_radius * 1.333 else: return ball_radius def get_ball_color(nid): in_faces = len([f for f in faces if nid in f]) if in_faces >= 2: return (1,0,1) else: return (0,0,1)
import matplotlib.pyplot as pt import numpy as np import modepy as mp dims = 3 n = 10 unit = mp.warp_and_blend_nodes(dims, n) if 0: from modepy.tools import estimate_lebesgue_constant lebesgue = estimate_lebesgue_constant(n, unit, visualize=True) from modepy.tools import unit_to_barycentric, barycentric_to_equilateral equi = barycentric_to_equilateral(unit_to_barycentric(unit)) if dims == 2: pt.plot(equi[0], equi[1], "o") from modepy.tools import EQUILATERAL_VERTICES uv = list(EQUILATERAL_VERTICES[2]) uv.append(uv[0]) uv = np.array(uv) pt.plot(uv[:, 0], uv[:, 1], "") pt.gca().set_aspect("equal") pt.show() elif dims == 3: import mayavi.mlab as mlab mlab.points3d(equi[0], equi[1], equi[2]) mlab.orientation_axes()
def warp_and_blend_nodes_3d(n, node_tuples=None): try: alpha = _alpha_opt_3d[n - 1] except IndexError: alpha = 1. if node_tuples is None: from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, 3)) else: if len(node_tuples) != (n + 1) * (n + 2) * (n + 3) // 6: raise ValueError( "node_tuples list does not have the correct length") # shape: (3, nnodes) unit_nodes = (np.array(node_tuples, dtype=np.float64) / n * 2 - 1).T from modepy.tools import (unit_to_barycentric, barycentric_to_equilateral, equilateral_to_unit, EQUILATERAL_VERTICES) bary = unit_to_barycentric(unit_nodes) equi = barycentric_to_equilateral(bary) equi_vertices = EQUILATERAL_VERTICES[3] # total number of nodes and tolerance tol = 1e-8 shift = np.zeros_like(equi) for i1, i2, i3, i4, vertex_step in [ (0, 1, 2, 3, -1), (1, 2, 3, 0, -1), (2, 3, 0, 1, -1), (3, 0, 1, 2, -1), ]: vi2, vi3, vi4 = [(i1 + vertex_step * i) % 4 for i in range(1, 4)] # all vertices have the same distance from the origin tangent1 = equi_vertices[vi3] - equi_vertices[vi4] tangent1 /= la.norm(tangent1) tangent2 = equi_vertices[vi2] - equi_vertices[vi3] tangent2 -= np.dot(tangent1, tangent2) * tangent1 tangent2 /= la.norm(tangent2) sub_bary = bary[[i2, i3, i4]] warp1, warp2 = _2d_equilateral_shift(n, sub_bary, alpha) l1 = bary[i1] l2, l3, l4 = sub_bary blend = l2 * l3 * l4 denom = (l2 + 0.5 * l1) * (l3 + 0.5 * l1) * (l4 + 0.5 * l1) denom_ok = denom > tol blend[denom_ok] = ((1 + (alpha * l1[denom_ok])**2) * blend[denom_ok] / denom[denom_ok]) shift = shift + (blend * warp1)[np.newaxis, :] * tangent1[:, np.newaxis] shift = shift + (blend * warp2)[np.newaxis, :] * tangent2[:, np.newaxis] is_face = (l1 < tol) & ((l2 > tol) | (l3 > tol) | (l4 > tol)) # assign face warp separately shift[:, is_face] = ( +warp1[is_face][np.newaxis, :] * tangent1[:, np.newaxis] + warp2[is_face][np.newaxis, :] * tangent2[:, np.newaxis]) return equilateral_to_unit(equi + shift)
n = 8 ball_radius = 0.05 link_radius = 0.02 from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, 3)) faces = [[nt for nt in node_tuples if nt[0] == 0], [nt for nt in node_tuples if nt[1] == 0], [nt for nt in node_tuples if nt[2] == 0], [nt for nt in node_tuples if sum(nt) == n]] from modepy.tools import unit_to_barycentric, barycentric_to_equilateral nodes = [(n[0], n[2], n[1]) for n in barycentric_to_equilateral( unit_to_barycentric(mp.warp_and_blend_nodes(3, n, node_tuples))).T] id_to_node = dict(list(zip(node_tuples, nodes))) def get_ball_radius(nid): in_faces = len([f for f in faces if nid in f]) if in_faces >= 2: return ball_radius * 1.333 else: return ball_radius def get_ball_color(nid): in_faces = len([f for f in faces if nid in f]) if in_faces >= 2: return (1, 0, 1)
from __future__ import absolute_import import matplotlib.pyplot as pt import numpy as np import modepy as mp dims = 3 n = 10 unit = mp.warp_and_blend_nodes(dims, n) if 0: from modepy.tools import estimate_lebesgue_constant lebesgue = estimate_lebesgue_constant(n, unit, visualize=True) from modepy.tools import unit_to_barycentric, barycentric_to_equilateral equi = barycentric_to_equilateral(unit_to_barycentric(unit)) if dims == 2: pt.plot(equi[0], equi[1], "o") from modepy.tools import EQUILATERAL_VERTICES uv = list(EQUILATERAL_VERTICES[2]) uv.append(uv[0]) uv = np.array(uv) pt.plot(uv[:, 0], uv[:, 1], "") pt.gca().set_aspect("equal") pt.show() elif dims == 3: import mayavi.mlab as mlab mlab.points3d(