def grad_simplex_onb(dims, n): """Return the gradients of the functions returned by :func:`simplex_onb`. :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. See the following publications: * |proriol-ref| * |koornwinder-ref| * |dubiner-ref| .. versionchanged:: 2013.2 Made return value a tuple, to make bases hashable. """ from functools import partial from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam if dims == 1: return tuple(partial(grad_jacobi, 0, 0, i) for i in range(n+1)) elif dims == 2: return tuple(partial(grad_pkdo_2d, order) for order in gnitstam(n, dims)) elif dims == 3: return tuple(partial(grad_pkdo_3d, order) for order in gnitstam(n, dims)) else: raise NotImplementedError("%d-dimensional bases" % dims)
def grad_simplex_onb(dims, n): """Return the gradients of the functions returned by :func:`simplex_onb`. :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. See the following publications: * |proriol-ref| * |koornwinder-ref| * |dubiner-ref| .. versionchanged:: 2013.2 Made return value a tuple, to make bases hashable. """ from functools import partial from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam if dims == 1: return tuple(partial(grad_jacobi, 0, 0, i) for i in range(n + 1)) elif dims == 2: return tuple( partial(grad_pkdo_2d, order) for order in gnitstam(n, dims)) elif dims == 3: return tuple( partial(grad_pkdo_3d, order) for order in gnitstam(n, dims)) else: raise NotImplementedError("%d-dimensional bases" % dims)
def test_tri_face_node_distribution(): """Test whether the nodes on the faces of the triangle are distributed according to the same proportions on each face. If this is not the case, then reusing the same face mass matrix for each face would be invalid. """ n = 8 from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, 2)) from modepy.nodes import warp_and_blend_nodes unodes = warp_and_blend_nodes(2, n, node_tuples) faces = [ [i for i, nt in enumerate(node_tuples) if nt[0] == 0], [i for i, nt in enumerate(node_tuples) if nt[1] == 0], [i for i, nt in enumerate(node_tuples) if sum(nt) == n] ] projected_face_points = [] for face_i in faces: start = unodes[:, face_i[0]] end = unodes[:, face_i[-1]] direction = end-start direction /= np.dot(direction, direction) pfp = np.array([np.dot(direction, unodes[:, i]-start) for i in face_i]) projected_face_points.append(pfp) first_points = projected_face_points[0] for points in projected_face_points[1:]: error = la.norm(points-first_points, np.Inf) assert error < 1e-15
def test_simplex_quadrature(quad_class, highest_order, dim): """Check that quadratures on simplices works as advertised""" from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam from modepy.tools import Monomial order = 1 while True: try: quad = quad_class(order, dim) except mp.QuadratureRuleUnavailable: print(("UNAVAIL", quad_class, order)) break if isinstance(quad_class, mp.VioreanuRokhlinSimplexQuadrature): assert (quad.weights > 0).all() if 0: import matplotlib.pyplot as pt pt.plot(quad.nodes[0], quad.nodes[1]) pt.show() print((quad_class, order, quad.exact_to)) for comb in gnitstam(quad.exact_to, dim): f = Monomial(comb) i_f = quad(f) ref = f.simplex_integral() err = abs(i_f - ref) assert err < 6e-15, (err, comb, i_f, ref) order += 1 if highest_order is not None and order >= highest_order: break
def fit_modal_decay(coeffs, dims, n, ignored_modes=1): """Fit a curve to the modal decay on each element. :arg coeffs: a array of shape *(num_elements, num_modes)* containing modal coefficients of the functions to be analyzed :arg dims: number of dimensions :arg ignored_modes: the number of modal coefficients to ignore at the beginning. The default value of '1' ignores the constant. :returns: a tuple *(expn, constant)* of arrays of length *num_elements*, where the modal decay is fit to the curve ``constant * total_degree**exponent``. ``-exponent-1`` can be used as a rough indicator of how many continuous derivatives the underlying function possesses. """ from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam mode_order_tuples = list(gnitstam(n, dims)) coeffs_squared = skyline_pessimize(coeffs**2) mode_number_vector_int = make_mode_number_vector( mode_order_tuples, ignored_modes) mode_number_vector = mode_number_vector_int.astype(np.float64) weight_vector = np.ones_like(mode_number_vector) fit_mat = get_decay_fit_matrix(mode_number_vector, ignored_modes, weight_vector) el_norm_squared = np.sum(coeffs_squared, axis=-1) scaled_baseline = ( create_decay_baseline(mode_number_vector, n) * el_norm_squared[:, np.newaxis])**2 log_modal_coeffs = np.log(coeffs_squared[:, ignored_modes:] + scaled_baseline)/2 assert fit_mat.shape[0] == 2 # exponent and log(constant) fit_values = np.dot(fit_mat, (weight_vector*log_modal_coeffs).T).T exponent = fit_values[:, 1] const = np.exp(fit_values[:, 0]) if 0: import matplotlib.pyplot as pt pt.plot(log_modal_coeffs.flat, "o-") fit = np.log(const[:, np.newaxis] * mode_number_vector**exponent[:, np.newaxis]) pt.plot(fit.flat) # plot_expt = np.zeros_like(log_modal_coeffs) # plot_expt[:] = exponent[:, np.newaxis] # pt.plot(plot_expt.flat) pt.show() return exponent, const
def _vis_connectivity(self): """ :return: an array of shape ``(vis_discr.nelements,nsubelements,primitive_element_size)`` """ # Assume that we're using modepy's default node ordering. from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam, single_valued vis_order = single_valued( group.order for group in self.vis_discr.groups) node_tuples = list(gnitstam(vis_order, self.vis_discr.dim)) from modepy.tools import submesh el_connectivity = np.array( submesh(node_tuples), dtype=np.intp) nelements = sum(group.nelements for group in self.vis_discr.groups) vis_connectivity = np.empty( (nelements,) + el_connectivity.shape, dtype=np.intp) el_nr_base = 0 for group in self.vis_discr.groups: assert len(node_tuples) == group.nunit_nodes vis_connectivity[el_nr_base:el_nr_base+group.nelements] = ( np.arange( el_nr_base*group.nunit_nodes, (el_nr_base+group.nelements)*group.nunit_nodes, group.nunit_nodes )[:, np.newaxis, np.newaxis] + el_connectivity) el_nr_base += group.nelements return vis_connectivity
def test_hypercube_quadrature(cls, dim): from pytools import \ generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam from modepy.tools import Monomial def _check_monomial(quad, comb): f = Monomial(comb) int_approx = quad(f) int_exact = 2**dim * f.hypercube_integral() error = abs(int_approx - int_exact) / abs(int_exact) logger.info("%s: %.5e %.5e / rel error %.5e", comb, int_approx, int_exact, error) return error order = 1 while True: try: quad = cls(order, dim) except mp.QuadratureRuleUnavailable: logger.info("UNAVAILABLE at order %d", order) break assert np.all(quad.weights > 0) logger.info("quadrature: %s %d %d", cls, order, quad.exact_to) for comb in gnitstam(quad.exact_to, dim): assert _check_monomial(quad, comb) < 5.0e-15 comb = (0,) * (dim - 1) + (quad.exact_to + 1,) assert _check_monomial(quad, comb) > 5.0e-15 order += 2
def tesselatetet(): node_tuples = list(gnitstam(2, 3)) node_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
def fit_modal_decay(coeffs, dims, n, ignored_modes=1): """Fit a curve to the modal decay on each element. :arg coeffs: a array of shape *(num_elements, num_modes)* containing modal coefficients of the functions to be analyzed :arg dims: number of dimensions :arg ignored_modes: the number of modal coefficients to ignore at the beginning. The default value of '1' ignores the constant. :returns: a tuple *(expn, constant)* of arrays of length *num_elements*, where the modal decay is fit to the curve ``constant * total_degree**exponent``. ``-exponent-1`` can be used as a rough indicator of how many continuous derivatives the underlying function possesses. """ from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam mode_order_tuples = list(gnitstam(n, dims)) coeffs_squared = skyline_pessimize(coeffs**2) mode_number_vector_int = make_mode_number_vector(mode_order_tuples, ignored_modes) mode_number_vector = mode_number_vector_int.astype(np.float64) weight_vector = np.ones_like(mode_number_vector) fit_mat = get_decay_fit_matrix(mode_number_vector, ignored_modes, weight_vector) el_norm_squared = np.sum(coeffs_squared, axis=-1) scaled_baseline = (create_decay_baseline(mode_number_vector, n) * el_norm_squared[:, np.newaxis])**2 log_modal_coeffs = np.log(coeffs_squared[:, ignored_modes:] + scaled_baseline) / 2 assert fit_mat.shape[0] == 2 # exponent and log(constant) fit_values = np.dot(fit_mat, (weight_vector * log_modal_coeffs).T).T exponent = fit_values[:, 1] const = np.exp(fit_values[:, 0]) if 0: import matplotlib.pyplot as pt pt.plot(log_modal_coeffs.flat, "o-") fit = np.log(const[:, np.newaxis] * mode_number_vector**exponent[:, np.newaxis]) pt.plot(fit.flat) # plot_expt = np.zeros_like(log_modal_coeffs) # plot_expt[:] = exponent[:, np.newaxis] # pt.plot(plot_expt.flat) pt.show() return exponent, const
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 square into two triangles. # subtriangles 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 plot_element_values(n, nodes, values, resample_n=None, node_tuples=None, show_nodes=False): dims = len(nodes) orig_nodes = nodes orig_values = values if resample_n is not None: import modepy as mp basis = mp.simplex_onb(dims, n) fine_nodes = mp.equidistant_nodes(dims, resample_n) values = np.dot(mp.resampling_matrix(basis, fine_nodes, nodes), values) nodes = fine_nodes n = resample_n from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam if dims == 1: import matplotlib.pyplot as pt pt.plot(nodes[0], values) if show_nodes: pt.plot(orig_nodes[0], orig_values, "x") pt.show() elif dims == 2: import mayavi.mlab as mlab mlab.triangular_mesh( nodes[0], nodes[1], values, submesh(list(gnitstam(n, 2)))) if show_nodes: mlab.points3d(orig_nodes[0], orig_nodes[1], orig_values, scale_factor=0.05) mlab.show() else: raise RuntimeError("unsupported dimensionality %d" % dims)
def lexicographic_node_tuples(self): from pytools import \ generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam result = list(gnitstam(self.order, self.dimensions)) assert len(result) == self.node_count() return result
def test_simplex_quadrature(quad_class, highest_order, dim): """Check that quadratures on simplices works as advertised""" from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam from modepy.tools import Monomial order = 1 while True: try: quad = quad_class(order, dim) except mp.QuadratureRuleUnavailable: print(("UNAVAILABLE", quad_class, order)) break if isinstance(quad_class, mp.VioreanuRokhlinSimplexQuadrature): assert (quad.weights > 0).all() if 0: import matplotlib.pyplot as pt pt.plot(quad.nodes[0], quad.nodes[1]) pt.show() print((quad_class, order, quad.exact_to)) for comb in gnitstam(quad.exact_to, dim): f = Monomial(comb) i_f = quad(f) ref = f.simplex_integral() err = abs(i_f - ref) assert err < 6e-15, (err, comb, i_f, ref) order += 1 if highest_order is not None and order >= highest_order: break
def equidistant_nodes(dims, n, node_tuples=None): """ :arg dims: dimensionality of desired simplex (e.g. 1, 2 or 3, for interval, triangle or tetrahedron). :arg n: Desired maximum total polynomial degree to interpolate. :arg node_tuples: a list of tuples of integers indicating the node order. Use default order if *None*, see :func:`pytools.generate_nonnegative_integer_tuples_summing_to_at_most`. :returns: An array of shape *(dims, nnodes)* containing unit coordinates of the interpolation nodes. (see :ref:`tri-coords` and :ref:`tet-coords`) """ if node_tuples is None: from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, dims)) else: try: from math import comb # comb is v3.8+ node_count = comb(n + dims, dims) except ImportError: from functools import reduce from operator import mul node_count = reduce(mul, range(n + 1, n + dims + 1), 1) \ // reduce(mul, range(1, dims + 1), 1) if len(node_tuples) != node_count: raise ValueError( "'node_tuples' list does not have the correct length") # shape: (2, nnodes) return (np.array(node_tuples, dtype=np.float64) / n * 2 - 1).T
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 square into two triangles. # subtriangles 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 _vis_connectivity(self): """ :return: an array of shape ``(vis_discr.nelements,nsubelements,primitive_element_size)`` """ # Assume that we're using modepy's default node ordering. from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam, single_valued vis_order = single_valued(group.order for group in self.vis_discr.groups) node_tuples = list(gnitstam(vis_order, self.vis_discr.dim)) from modepy.tools import submesh el_connectivity = np.array(submesh(node_tuples), dtype=np.intp) nelements = sum(group.nelements for group in self.vis_discr.groups) vis_connectivity = np.empty((nelements, ) + el_connectivity.shape, dtype=np.intp) el_nr_base = 0 for group in self.vis_discr.groups: assert len(node_tuples) == group.nunit_nodes vis_connectivity[el_nr_base:el_nr_base + group.nelements] = ( np.arange(el_nr_base * group.nunit_nodes, (el_nr_base + group.nelements) * group.nunit_nodes, group.nunit_nodes)[:, np.newaxis, np.newaxis] + el_connectivity) el_nr_base += group.nelements return vis_connectivity
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 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 simplex_onb_with_mode_ids(dims, n): """Return a list of orthonormal basis functions in dimension *dims* of maximal total degree *n*. :returns: a tuple ``(mode_ids, basis)``, where *basis* is a class:`tuple` of functions, each of which accepts arrays of shape *(dims, npts)* and return the function values as an array of size *npts*. 'Scalar' evaluation, by passing just one vector of length *dims*, is also supported. *mode_ids* is a tuple of the same length as *basis*, where each entry is a tuple of integers describing the order of the mode along the coordinate axes. See the following publications: * |proriol-ref| * |koornwinder-ref| * |dubiner-ref| ... versionadded: 2018.1 """ from functools import partial from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam if dims == 0: def zerod_basis(x): if len(x.shape) == 1: return 1 else: return np.ones(x.shape[1]) return ((0, ), ), (zerod_basis, ) elif dims == 1: mode_ids = tuple(range(n + 1)) return mode_ids, tuple(partial(jacobi, 0, 0, i) for i in mode_ids) elif dims == 2: mode_ids = tuple(gnitstam(n, dims)) return mode_ids, tuple(partial(pkdo_2d, order) for order in mode_ids) elif dims == 3: mode_ids = tuple(gnitstam(n, dims)) return mode_ids, tuple(partial(pkdo_3d, order) for order in mode_ids) else: raise NotImplementedError("%d-dimensional bases" % dims)
def get_full_coefficient_identifiers(self): """ Returns identifiers for every coefficient in the complete expansion. """ from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam) res = sorted(gnitstam(self.order, self.dim), key=sum) return res
def vtk_lagrange_simplex_node_tuples_to_permutation(node_tuples): order = max([sum(i) for i in node_tuples]) dims = len(node_tuples[0]) node_to_index = dict( (node_tuple, i) for i, node_tuple in enumerate(gnitstam(order, dims))) assert len(node_tuples) == len(node_to_index) return [node_to_index[v] for v in node_tuples]
def get_full_coefficient_identifiers(self): """ Returns identifiers for every coefficient in the complete expansion. """ from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam) return sorted(gnitstam(self.order, self.kernel.dim), key=sum)
def simplex_onb_with_mode_ids(dims, n): """Return a list of orthonormal basis functions in dimension *dims* of maximal total degree *n*. :returns: a tuple ``(mode_ids, basis)``, where *basis* is a class:`tuple` of functions, each of which accepts arrays of shape *(dims, npts)* and return the function values as an array of size *npts*. 'Scalar' evaluation, by passing just one vector of length *dims*, is also supported. *mode_ids* is a tuple of the same length as *basis*, where each entry is a tuple of integers describing the order of the mode along the coordinate axes. See the following publications: * |proriol-ref| * |koornwinder-ref| * |dubiner-ref| ... versionadded: 2018.1 """ from functools import partial from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam if dims == 0: def zerod_basis(x): if len(x.shape) == 1: return 1 else: return np.ones(x.shape[1]) return ((0,),), (zerod_basis,) elif dims == 1: mode_ids = tuple(range(n+1)) return mode_ids, tuple(partial(jacobi, 0, 0, i) for i in mode_ids) elif dims == 2: mode_ids = tuple(gnitstam(n, dims)) return mode_ids, tuple(partial(pkdo_2d, order) for order in mode_ids) elif dims == 3: mode_ids = tuple(gnitstam(n, dims)) return mode_ids, tuple(partial(pkdo_3d, order) for order in mode_ids) else: raise NotImplementedError("%d-dimensional bases" % dims)
def simplex_onb(dims, n): """Return a list of orthonormal basis functions in dimension *dims* of maximal total degree *n*. :returns: a class:`tuple` of functions, each of which accepts arrays of shape *(dims, npts)* and return the function values as an array of size *npts*. 'Scalar' evaluation, by passing just one vector of length *dims*, is also supported. See the following publications: * |proriol-ref| * |koornwinder-ref| * |dubiner-ref| .. versionchanged:: 2013.2 Made return value a tuple, to make bases hashable. """ from functools import partial from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam if dims == 0: def zerod_basis(x): if len(x.shape) == 1: return 1 else: return np.ones(x.shape[1]) return (zerod_basis, ) elif dims == 1: return tuple(partial(jacobi, 0, 0, i) for i in range(n + 1)) elif dims == 2: return tuple(partial(pkdo_2d, order) for order in gnitstam(n, dims)) elif dims == 3: return tuple(partial(pkdo_3d, order) for order in gnitstam(n, dims)) else: raise NotImplementedError("%d-dimensional bases" % dims)
def estimate_lebesgue_constant(n, nodes, visualize=False): """Estimate the `Lebesgue constant <https://en.wikipedia.org/wiki/Lebesgue_constant_(interpolation)>`_ of the *nodes* at polynomial order *n*. :arg nodes: an array of shape *(dims, nnodes)* as returned by :func:`modepy.warp_and_blend_nodes`. :arg visualize: visualize the function that gives rise to the returned Lebesgue constant. (2D only for now) :return: the Lebesgue constant, a scalar .. versionadded:: 2013.2 """ from modepy.matrices import vandermonde from modepy.modes import simplex_onb dims = len(nodes) basis = simplex_onb(dims, n) vdm = vandermonde(basis, nodes) from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam huge_n = 30*n equi_node_tuples = list(gnitstam(huge_n, dims)) tons_of_equi_nodes = ( np.array(equi_node_tuples, dtype=np.float64) / huge_n * 2 - 1).T eq_vdm = vandermonde(basis, tons_of_equi_nodes) eq_to_out = la.solve(vdm.T, eq_vdm.T).T lebesgue_worst = np.sum(np.abs(eq_to_out), axis=1) lebesgue_constant = np.max(lebesgue_worst) if visualize: print("Lebesgue constant: %g" % lebesgue_constant) from modepy.tools import submesh import mayavi.mlab as mlab mlab.figure(bgcolor=(1, 1, 1)) mlab.triangular_mesh( tons_of_equi_nodes[0], tons_of_equi_nodes[1], lebesgue_worst / lebesgue_constant, submesh(equi_node_tuples)) x, y = np.mgrid[-1:1:20j, -1:1:20j] mlab.mesh(x, y, 0*x, representation="wireframe", color=(0.4, 0.4, 0.4), line_width=0.6) mlab.show() return lebesgue_constant
def connectivity_for_element_group(self, grp): from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam, generate_nonnegative_integer_tuples_below as gnitb) from meshmode.mesh import TensorProductElementGroup, SimplexElementGroup if isinstance(grp.mesh_el_group, SimplexElementGroup): node_tuples = list(gnitstam(grp.order, grp.dim)) from modepy.tools import simplex_submesh el_connectivity = np.array( simplex_submesh(node_tuples), dtype=np.intp) vtk_cell_type = self.simplex_cell_types[grp.dim] elif isinstance(grp.mesh_el_group, TensorProductElementGroup): node_tuples = list(gnitb(grp.order+1, grp.dim)) node_tuple_to_index = dict( (nt, i) for i, nt in enumerate(node_tuples)) def add_tuple(a, b): return tuple(ai+bi for ai, bi in zip(a, b)) el_offsets = { 1: [(0,), (1,)], 2: [(0, 0), (1, 0), (1, 1), (0, 1)], 3: [ (0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1), ] }[grp.dim] el_connectivity = np.array([ [ node_tuple_to_index[add_tuple(origin, offset)] for offset in el_offsets] for origin in gnitb(grp.order, grp.dim)]) vtk_cell_type = self.tensor_cell_types[grp.dim] else: raise NotImplementedError("visualization for element groups " "of type '%s'" % type(grp.mesh_el_group).__name__) assert len(node_tuples) == grp.nunit_dofs return el_connectivity, vtk_cell_type
def get_full_coefficient_identifiers(self): """ Returns identifiers for every coefficient in the complete expansion. """ from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam) res = sorted(gnitstam(self.order, self.dim), key=sum) if self.max_mi is None: return res return [ mi for mi in res if all(mi[i] <= self.max_mi[i] for i in range(self.dim)) ]
def simplex_monomial_basis(dims, n): """Return a list of monomial basis functions in dimension *dims* of maximal total degree *n*. :returns: a class:`tuple` of functions, each of which accepts arrays of shape *(dims, npts)* and return the function values as an array of size *npts*. 'Scalar' evaluation, by passing just one vector of length *dims*, is also supported. .. versionadded:: 2016.1 """ from functools import partial from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam return tuple(partial(monomial, order) for order in gnitstam(n, dims))
def grad_simplex_monomial_basis(dims, n): """Return the gradients of the functions returned by :func:`simplex_monomial_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. .. versionadded:: 2016.1 """ from functools import partial from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam return tuple(partial(grad_monomial, order) for order in gnitstam(n, dims))
def test_diff_matrix_permutation(dims): order = 5 space = mp.PN(dims, order) from pytools import \ generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam node_tuples = list(gnitstam(order, dims)) simplex_onb = mp.orthonormal_basis_for_space(space, mp.Simplex(dims)) nodes = np.array(mp.warp_and_blend_nodes(dims, order, node_tuples=node_tuples)) diff_matrices = mp.differentiation_matrices( simplex_onb.functions, simplex_onb.gradients, nodes) for iref_axis in range(dims): perm = mp.diff_matrix_permutation(node_tuples, iref_axis) assert la.norm( diff_matrices[iref_axis] - diff_matrices[0][perm][:, perm]) < 1e-10
def simplex_monomial_basis_with_mode_ids(dims, n): """Return a list of monomial basis functions in dimension *dims* of maximal total degree *n*. :returns: a tuple ``(mode_ids, basis)``, where *basis* is a class:`tuple` of functions, each of which accepts arrays of shape *(dims, npts)* and return the function values as an array of size *npts*. 'Scalar' evaluation, by passing just one vector of length *dims*, is also supported. *mode_ids* is a tuple of the same length as *basis*, where each entry is a tuple of integers describing the order of the mode along the coordinate axes. .. versionadded:: 2018.1 """ from functools import partial from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam mode_ids = tuple(gnitstam(n, dims)) return mode_ids, tuple(partial(monomial, order) for order in mode_ids)
def test_diff_matrix_permutation(dims): order = 5 from pytools import \ generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam node_tuples = list(gnitstam(order, dims)) simplex_onb = mp.simplex_onb(dims, order) grad_simplex_onb = mp.grad_simplex_onb(dims, order) nodes = np.array( mp.warp_and_blend_nodes(dims, order, node_tuples=node_tuples)) diff_matrices = mp.differentiation_matrices(simplex_onb, grad_simplex_onb, nodes) for iref_axis in range(dims): perm = mp.diff_matrix_permutation(node_tuples, iref_axis) assert la.norm(diff_matrices[iref_axis] - diff_matrices[0][perm][:, perm]) < 1e-10
def equidistant_nodes(dims, n, node_tuples=None): """ :arg dims: dimensionality of desired simplex (e.g. 1, 2 or 3, for interval, triangle or tetrahedron). :arg n: Desired maximum total polynomial degree to interpolate. :arg node_tuples: a list of tuples of integers indicating the node order. Use default order if *None*, see :func:`pytools.generate_nonnegative_integer_tuples_summing_to_at_most`. :returns: An array of shape *(dims, nnodes)* containing unit coordinates of the interpolation nodes. (see :ref:`tri-coords` and :ref:`tet-coords`) """ if node_tuples is None: from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, dims)) else: if len(node_tuples) != (n+1)*(n+2)//2: raise ValueError("node_tuples list does not have the correct length") # shape: (2, nnodes) return (np.array(node_tuples, dtype=np.float64)/n*2 - 1).T
def equidistant_nodes(dims, n, node_tuples=None): """ :arg dims: dimensionality of desired simplex (e.g. 1, 2 or 3, for interval, triangle or tetrahedron). :arg n: Desired maximum total polynomial degree to interpolate. :arg node_tuples: a list of tuples of integers indicating the node order. Use default order if *None*, see :func:`pytools.generate_nonnegative_integer_tuples_summing_to_at_most`. :returns: An array of shape *(dims, nnodes)* containing unit coordinates of the interpolation nodes. (see :ref:`tri-coords` and :ref:`tet-coords`) """ if node_tuples is None: from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, dims)) else: if len(node_tuples) != (n + 1) * (n + 2) // 2: raise ValueError( "node_tuples list does not have the correct length") # shape: (2, nnodes) return (np.array(node_tuples, dtype=np.float64) / n * 2 - 1).T
from pov import Sphere, Cylinder, File, Union, Texture, Pigment, \ Camera, LightSource, Plane, Background, Finish import numpy as np import modepy as mp import six from six.moves import range from six.moves import zip 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):
def as_scalar_pde(pde, vec_idx): r""" Returns a scalar PDE that is satisfied by the *vec_idx* component of *pde*. :arg pde: An instance of :class:`LinearPDESystemOperator` :arg vec_idx: the index of the vector-valued function that we want as a scalar PDE """ from sumpy.tools import nullspace indices = set() for eq in pde.eqs: for deriv_ident in eq.keys(): indices.add(deriv_ident.vec_idx) # this is already a scalar pde if len(indices) == 1 and list(indices)[0] == vec_idx: return pde from pytools import ProcessLogger plog = ProcessLogger(logger, "computing single PDE for multiple PDEs") from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam) dim = pde.total_dims # slowly increase the order of the derivatives that we take of the # system of PDEs. Once we reach the order of the scalar PDE, this # loop will break for order in range(2, 100): mis = sorted(gnitstam(order, dim), key=sum) pde_mat = [] coeff_ident_enumerate_dict = dict((tuple(mi), i) for (i, mi) in enumerate(mis)) offset = len(mis) # Create a matrix of equations that are derivatives of the # original system of PDEs for mi in mis: for pde_dict in pde.eqs: eq = [0]*(len(mis)*(max(indices)+1)) for ident, coeff in pde_dict.items(): c = tuple(add_mi(ident.mi, mi)) if c not in coeff_ident_enumerate_dict: break idx = offset*ident.vec_idx + coeff_ident_enumerate_dict[c] eq[idx] = coeff else: pde_mat.append(eq) if len(pde_mat) == 0: continue # Get the nullspace of the matrix and get the rows related to this # vec_idx n = nullspace(pde_mat)[offset*vec_idx:offset*(vec_idx+1), :] indep_row = find_linear_relationship(n) if len(indep_row) > 0: pde_dict = {} mult = indep_row[max(indep_row.keys())] for k, v in indep_row.items(): pde_dict[DerivativeIdentifier(mis[k], 0)] = v / mult plog.done() return LinearPDESystemOperator(pde.dim, pmap(pde_dict)) plog.done() assert False
import numpy as np from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam # prepare plot and eval nodes on triangle dims = 2 node_n = 40 node_tuples = list(gnitstam(node_n, dims)) plot_nodes = np.array(node_tuples, dtype=np.float64) / node_n eval_nodes = 2 * (plot_nodes - 0.5).T # get triangle submesh from modepy.tools import submesh tri_subtriangles = np.array(submesh(node_tuples)) # evaluate each basis function, build global tri mesh node_count = 0 all_nodes = [] all_triangles = [] all_values = [] from modepy.modes import simplex_onb p = 3 stretch_factor = 1.5 for (i, j), basis_func in zip( gnitstam(p, dims), simplex_onb(dims, p), ):
def _vis_connectivity(self): """ :return: a list of :class:`_VisConnectivityGroup` instances. """ # Assume that we're using modepy's default node ordering. from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam, generate_nonnegative_integer_tuples_below as gnitb) from meshmode.mesh import TensorProductElementGroup, SimplexElementGroup result = [] from pyvisfile.vtk import (VTK_LINE, VTK_TRIANGLE, VTK_TETRA, VTK_QUAD, VTK_HEXAHEDRON) subel_nr_base = 0 for group in self.vis_discr.groups: if isinstance(group.mesh_el_group, SimplexElementGroup): node_tuples = list(gnitstam(group.order, group.dim)) from modepy.tools import submesh el_connectivity = np.array(submesh(node_tuples), dtype=np.intp) vtk_cell_type = { 1: VTK_LINE, 2: VTK_TRIANGLE, 3: VTK_TETRA, }[group.dim] elif isinstance(group.mesh_el_group, TensorProductElementGroup): node_tuples = list(gnitb(group.order + 1, group.dim)) node_tuple_to_index = dict( (nt, i) for i, nt in enumerate(node_tuples)) def add_tuple(a, b): return tuple(ai + bi for ai, bi in zip(a, b)) el_offsets = { 1: [(0, ), (1, )], 2: [(0, 0), (1, 0), (1, 1), (0, 1)], 3: [ (0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1), ] }[group.dim] el_connectivity = np.array([[ node_tuple_to_index[add_tuple(origin, offset)] for offset in el_offsets ] for origin in gnitb(group.order, group.dim)]) vtk_cell_type = { 1: VTK_LINE, 2: VTK_QUAD, 3: VTK_HEXAHEDRON, }[group.dim] else: raise NotImplementedError("visualization for element groups " "of type '%s'" % type(group.mesh_el_group).__name__) assert len(node_tuples) == group.nunit_nodes vis_connectivity = ( group.node_nr_base + np.arange(0, group.nelements * group.nunit_nodes, group.nunit_nodes)[:, np.newaxis, np.newaxis] + el_connectivity).astype(np.intp) vgrp = _VisConnectivityGroup(vis_connectivity=vis_connectivity, vtk_cell_type=vtk_cell_type, subelement_nr_base=subel_nr_base) result.append(vgrp) subel_nr_base += vgrp.nsubelements return result
from pov import Sphere, Cylinder, File, Union, Texture, Pigment, \ Camera, LightSource, Plane, Background, Finish import numpy as np import modepy as mp import six from six.moves import range from six.moves import zip 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
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)
from __future__ import absolute_import import numpy as np from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam from six.moves import zip # prepare plot and eval nodes on triangle dims = 2 node_n = 40 node_tuples = list(gnitstam(node_n, dims)) plot_nodes = np.array(node_tuples, dtype=np.float64) / node_n eval_nodes = 2*(plot_nodes - 0.5).T # get triangle submesh from modepy.tools import submesh tri_subtriangles = np.array(submesh(node_tuples)) # evaluate each basis function, build global tri mesh node_count = 0 all_nodes = [] all_triangles = [] all_values = [] from modepy.modes import simplex_onb p = 3 stretch_factor = 1.5 for (i, j), basis_func in zip( gnitstam(p, dims), simplex_onb(dims, p),
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)
def _vis_connectivity(self): """ :return: a list of :class:`_VisConnectivityGroup` instances. """ # Assume that we're using modepy's default node ordering. from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam, generate_nonnegative_integer_tuples_below as gnitb) from meshmode.mesh import TensorProductElementGroup, SimplexElementGroup result = [] from pyvisfile.vtk import ( VTK_LINE, VTK_TRIANGLE, VTK_TETRA, VTK_QUAD, VTK_HEXAHEDRON) subel_nr_base = 0 for group in self.vis_discr.groups: if isinstance(group.mesh_el_group, SimplexElementGroup): node_tuples = list(gnitstam(group.order, group.dim)) from modepy.tools import submesh el_connectivity = np.array( submesh(node_tuples), dtype=np.intp) vtk_cell_type = { 1: VTK_LINE, 2: VTK_TRIANGLE, 3: VTK_TETRA, }[group.dim] elif isinstance(group.mesh_el_group, TensorProductElementGroup): node_tuples = list(gnitb(group.order+1, group.dim)) node_tuple_to_index = dict( (nt, i) for i, nt in enumerate(node_tuples)) def add_tuple(a, b): return tuple(ai+bi for ai, bi in zip(a, b)) el_offsets = { 1: [(0,), (1,)], 2: [(0, 0), (1, 0), (1, 1), (0, 1)], 3: [ (0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1), ] }[group.dim] el_connectivity = np.array([ [ node_tuple_to_index[add_tuple(origin, offset)] for offset in el_offsets] for origin in gnitb(group.order, group.dim)]) vtk_cell_type = { 1: VTK_LINE, 2: VTK_QUAD, 3: VTK_HEXAHEDRON, }[group.dim] else: raise NotImplementedError("visualization for element groups " "of type '%s'" % type(group.mesh_el_group).__name__) assert len(node_tuples) == group.nunit_nodes vis_connectivity = ( group.node_nr_base + np.arange( 0, group.nelements*group.nunit_nodes, group.nunit_nodes )[:, np.newaxis, np.newaxis] + el_connectivity).astype(np.intp) vgrp = _VisConnectivityGroup( vis_connectivity=vis_connectivity, vtk_cell_type=vtk_cell_type, subelement_nr_base=subel_nr_base) result.append(vgrp) subel_nr_base += vgrp.nsubelements return result