From 385abdd5dad3a322f753e2d179d53beed17afc78 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 1 Feb 2016 14:10:14 +0000 Subject: [PATCH 001/809] Bare tsfc repo --- tsfc/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tsfc/__init__.py diff --git a/tsfc/__init__.py b/tsfc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 33524abba5c59a6b84bda40f2832e89cb97717bc Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 1 Feb 2016 14:13:57 +0000 Subject: [PATCH 002/809] Import fiatinterface and mixedelement --- tsfc/fiatinterface.py | 248 ++++++++++++++++++++++++++++++++++++++++++ tsfc/mixedelement.py | 112 +++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 tsfc/fiatinterface.py create mode 100644 tsfc/mixedelement.py diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py new file mode 100644 index 0000000000..8569169797 --- /dev/null +++ b/tsfc/fiatinterface.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +# +# This file was modified from FFC +# (http://bitbucket.org/fenics-project/ffc), copyright notice +# reproduced below. +# +# Copyright (C) 2009-2013 Kristian B. Oelgaard and Anders Logg +# +# This file is part of FFC. +# +# FFC is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# FFC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with FFC. If not, see . + +from __future__ import absolute_import +from __future__ import print_function + +from singledispatch import singledispatch +from functools import partial +import weakref + +import FIAT +from FIAT.reference_element import FiredrakeQuadrilateral +from FIAT.dual_set import DualSet + +import ufl +from ufl.algorithms.elementtransformations import reconstruct_element + +from .mixedelement import MixedElement + + +__all__ = ("create_element", "supported_elements", "as_fiat_cell") + + +supported_elements = { + # These all map directly to FIAT elements + "Brezzi-Douglas-Marini": FIAT.BrezziDouglasMarini, + "Brezzi-Douglas-Fortin-Marini": FIAT.BrezziDouglasFortinMarini, + "BrokenElement": FIAT.DiscontinuousElement, + "Bubble": FIAT.Bubble, + "Crouzeix-Raviart": FIAT.CrouzeixRaviart, + "Discontinuous Lagrange": FIAT.DiscontinuousLagrange, + "Discontinuous Taylor": FIAT.DiscontinuousTaylor, + "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, + "Discontinuous Lagrange Trace": FIAT.DiscontinuousLagrangeTrace, + "EnrichedElement": FIAT.EnrichedElement, + "Lagrange": FIAT.Lagrange, + "Nedelec 1st kind H(curl)": FIAT.Nedelec, + "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, + "OuterProductElement": FIAT.TensorFiniteElement, + "Raviart-Thomas": FIAT.RaviartThomas, + "TraceElement": FIAT.HDivTrace, + "Regge": FIAT.Regge, + # These require special treatment below + "DQ": None, + "FacetElement": None, + "InteriorElement": None, + "Q": None, + "Real": None, + "RTCE": None, + "RTCF": None, +} +"""A :class:`.dict` mapping UFL element family names to their +FIAT-equivalent constructors. If the value is ``None``, the UFL +element is supported, but must be handled specially because it doesn't +have a direct FIAT equivalent.""" + + +def as_fiat_cell(cell): + """Convert a ufl cell to a FIAT cell. + + :arg cell: the :class:`ufl.Cell` to convert.""" + if not isinstance(cell, ufl.AbstractCell): + raise ValueError("Expecting a UFL Cell") + return FIAT.ufc_cell(cell) + + +@singledispatch +def convert(element, vector_is_mixed): + """Handler for converting UFL elements to FIAT elements. + + :arg element: The UFL element to convert. + :arg vector_is_mixed: Should Vector and Tensor elements be treated + as Mixed? If ``False``, then just look at the sub-element. + + Do not use this function directly, instead call + :func:`create_element`.""" + if element.family() in supported_elements: + raise ValueError("Element %s supported, but no handler provided" % element) + raise ValueError("Unsupported element type %s" % type(element)) + + +# Base finite elements first +@convert.register(ufl.FiniteElement) # noqa +def _(element, vector_is_mixed): + if element.family() == "Real": + # Real element is just DG0 + cell = element.cell() + return create_element(ufl.FiniteElement("DG", cell, 0), vector_is_mixed) + cell = as_fiat_cell(element.cell()) + lmbda = supported_elements[element.family()] + if lmbda is None: + if element.cell().cellname() != "quadrilateral": + raise ValueError("%s is supported, but handled incorrectly" % + element.family()) + # Handle quadrilateral short names like RTCF and RTCE. + element = reconstruct_element(element, + element.family(), + quad_opc, + element.degree()) + # Can't use create_element here because we're going to modify + # it, so if we pull it from the cache, that's bad. + element = convert(element, vector_is_mixed) + # Splat quadrilateral elements that are on TFEs back into + # something with the correct entity dofs. + nodes = element.dual.nodes + ref_el = FiredrakeQuadrilateral() + + entity_ids = element.dual.entity_ids + flat_entity_ids = {} + flat_entity_ids[0] = entity_ids[(0, 0)] + flat_entity_ids[1] = dict(enumerate(entity_ids[(0, 1)].values() + + entity_ids[(1, 0)].values())) + flat_entity_ids[2] = entity_ids[(1, 1)] + + element.dual = DualSet(nodes, ref_el, flat_entity_ids) + element.ref_el = ref_el + return element + return lmbda(cell, element.degree()) + + +# Element modifiers +@convert.register(ufl.FacetElement) # noqa +def _(element, vector_is_mixed): + return FIAT.RestrictedElement(create_element(element._element, vector_is_mixed), + restriction_domain="facet") + + +@convert.register(ufl.InteriorElement) # noqa +def _(element, vector_is_mixed): + return FIAT.RestrictedElement(create_element(element._element, vector_is_mixed), + restriction_domain="interior") + + +@convert.register(ufl.EnrichedElement) # noqa +def _(element, vector_is_mixed): + if len(element._elements) != 2: + raise ValueError("Enriched elements with more than two components not handled") + A, B = element._elements + return FIAT.EnrichedElement(create_element(A, vector_is_mixed), + create_element(B, vector_is_mixed)) + + +@convert.register(ufl.TraceElement) # noqa +@convert.register(ufl.BrokenElement) +def _(element, vector_is_mixed): + return supported_elements[element.family()](create_element(element._element, + vector_is_mixed)) + + +# Now for the OPE-specific stuff +@convert.register(ufl.OuterProductElement) # noqa +def _(element, vector_is_mixed): + cell = element.cell() + if type(cell) is not ufl.OuterProductCell: + raise ValueError("OPE not on OPC?") + A = element._A + B = element._B + return FIAT.TensorFiniteElement(create_element(A, vector_is_mixed), + create_element(B, vector_is_mixed)) + + +@convert.register(ufl.HDivElement) # noqa +def _(element, vector_is_mixed): + return FIAT.Hdiv(create_element(element._element, vector_is_mixed)) + + +@convert.register(ufl.HCurlElement) # noqa +def _(element, vector_is_mixed): + return FIAT.Hcurl(create_element(element._element, vector_is_mixed)) + + +# Finally the MixedElement case +@convert.register(ufl.MixedElement) # noqa +def _(element, vector_is_mixed): + # If we're just trying to get the scalar part of a vector element? + if not vector_is_mixed: + assert isinstance(element, (ufl.VectorElement, + ufl.TensorElement, + ufl.OuterProductVectorElement, + ufl.OuterProductTensorElement)) + return create_element(element.sub_elements()[0], vector_is_mixed) + + elements = [] + + def rec(eles): + for ele in eles: + if ele.num_sub_elements() > 0: + rec(ele.sub_elements()) + else: + elements.append(ele) + + rec(element.sub_elements()) + fiat_elements = map(partial(create_element, vector_is_mixed=vector_is_mixed), + elements) + return MixedElement(fiat_elements) + + +quad_opc = ufl.OuterProductCell(ufl.Cell("interval"), ufl.Cell("interval")) +_cache = weakref.WeakKeyDictionary() + + +def create_element(element, vector_is_mixed=True): + """Create a FIAT element (suitable for tabulating with) given a UFL element. + + :arg element: The UFL element to create a FIAT element from. + + :arg vector_is_mixed: indicate whether VectorElement (or + TensorElement) should be treated as a MixedElement. Maybe + useful if you want a FIAT element that tells you how many + "nodes" the finite element has. + """ + try: + cache = _cache[element] + except KeyError: + _cache[element] = {} + cache = _cache[element] + + try: + return cache[vector_is_mixed] + except KeyError: + pass + + if element.cell() is None: + raise ValueError("Don't know how to build element when cell is not given") + + fiat_element = convert(element, vector_is_mixed) + cache[vector_is_mixed] = fiat_element + return fiat_element diff --git a/tsfc/mixedelement.py b/tsfc/mixedelement.py new file mode 100644 index 0000000000..2ac4a05fa5 --- /dev/null +++ b/tsfc/mixedelement.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# +# This file was modified from FFC +# (http://bitbucket.org/fenics-project/ffc), copyright notice +# reproduced below. +# +# Copyright (C) 2005-2010 Anders Logg +# +# This file is part of FFC. +# +# FFC is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# FFC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with FFC. If not, see . + +from __future__ import absolute_import +from __future__ import print_function + +import numpy + +from collections import defaultdict +from operator import add +from functools import partial + + +class MixedElement(object): + """A FIAT-like representation of a mixed element. + + :arg elements: An iterable of FIAT elements. + + This object offers tabulation of the concatenated basis function + tables along with an entity_dofs dict.""" + def __init__(self, elements): + self._elements = tuple(elements) + self._entity_dofs = None + + def get_reference_element(self): + return self.elements()[0].get_reference_element() + + def elements(self): + return self._elements + + def space_dimension(self): + return sum(e.space_dimension() for e in self.elements()) + + def value_shape(self): + return (sum(numpy.prod(e.value_shape(), dtype=int) for e in self.elements()), ) + + def entity_dofs(self): + if self._entity_dofs is not None: + return self._entity_dofs + + ret = defaultdict(partial(defaultdict, list)) + + dicts = (e.entity_dofs() for e in self.elements()) + + offsets = numpy.cumsum([0] + list(e.space_dimension() + for e in self.elements()), + dtype=int) + for i, d in enumerate(dicts): + for dim, dofs in d.items(): + for ent, off in dofs.items(): + ret[dim][ent] += map(partial(add, offsets[i]), + off) + self._entity_dofs = ret + return self._entity_dofs + + def num_components(self): + return self.value_shape()[0] + + def tabulate(self, order, points): + """Tabulate a mixed element by appropriately splatting + together the tabulation of the individual elements. + """ + # FIXME: Could we reorder the basis functions so that indexing + # in the form compiler for mixed interior facets becomes + # easier? + # Would probably need to redo entity_dofs as well. + shape = (self.space_dimension(), self.num_components(), len(points)) + + output = {} + + sub_dims = [0] + list(e.space_dimension() for e in self.elements()) + sub_cmps = [0] + list(numpy.prod(e.value_shape(), dtype=int) + for e in self.elements()) + irange = numpy.cumsum(sub_dims) + crange = numpy.cumsum(sub_cmps) + + for i, e in enumerate(self.elements()): + table = e.tabulate(order, points) + + for d, tab in table.items(): + try: + arr = output[d] + except KeyError: + arr = numpy.zeros(shape) + output[d] = arr + + ir = irange[i:i+2] + cr = crange[i:i+2] + tab = tab.reshape(ir[1] - ir[0], cr[1] - cr[0], -1) + arr[slice(*ir), slice(*cr)] = tab + + return output From 3ab03cd232b772ca5f1454a462985180f6076da4 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 1 Feb 2016 14:18:41 +0000 Subject: [PATCH 003/809] Import quadrature --- tsfc/quadrature.py | 422 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 tsfc/quadrature.py diff --git a/tsfc/quadrature.py b/tsfc/quadrature.py new file mode 100644 index 0000000000..a5bce62e3c --- /dev/null +++ b/tsfc/quadrature.py @@ -0,0 +1,422 @@ +# -*- coding: utf-8 -*- +# +# This file was modified from FFC +# (http://bitbucket.org/fenics-project/ffc), copyright notice +# reproduced below. +# +# Copyright (C) 2011 Garth N. Wells +# +# This file is part of FFC. +# +# FFC is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# FFC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with FFC. If not, see . + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import division +from singledispatch import singledispatch +import numpy + +import FIAT +from FIAT.reference_element import UFCInterval, UFCTriangle, UFCTetrahedron, \ + FiredrakeQuadrilateral, two_product_cell +import ufl + +from tsfc.fiatinterface import as_fiat_cell + + +__all__ = ("create_quadrature", "QuadratureRule") + + +class QuadratureRule(object): + __slots__ = ("points", "weights") + + def __init__(self, points, weights): + """A representation of a quadrature rule. + + :arg points: The quadrature points. + :arg weights: The quadrature point weights.""" + points = numpy.asarray(points, dtype=numpy.float64) + weights = numpy.asarray(weights, dtype=numpy.float64) + if weights.shape != points.shape[:1]: + raise ValueError("Have %d weights, but %d points" % (weights.shape[0], + points.shape[0])) + self.points = points + self.weights = weights + + +def fiat_scheme(cell, degree): + """Create a quadrature rule using FIAT. + + On simplexes, this is a collapsed Guass scheme, on tensor-product + cells, it is a tensor-product quadrature rule of the subcells. + + :arg cell: The FIAT cell to create the quadrature for. + :arg degree: The degree of polynomial that the rule should + integrate exactly.""" + try: + points = tuple((d + 2) // 2 for d in degree) + except TypeError: + points = (degree + 2) // 2 + + if numpy.prod(points) < 0: + raise ValueError("Requested a quadrature rule with a negative number of points") + if numpy.prod(points) > 500: + raise RuntimeError("Requested a quadrature rule with more than 500") + quad = FIAT.make_quadrature(cell, points) + return QuadratureRule(quad.get_points(), quad.get_weights()) + + +@singledispatch +def default_scheme(cell, degree): + """Create a quadrature rule. + + For low-degree (<=6) polynomials on triangles and tetrahedra, this + uses hard-coded rules, otherwise it falls back to the schemes that + FIAT provides (see :func:`fiat_scheme`). + + :arg cell: The FIAT cell to create the quadrature for. + :arg degree: The degree of polynomial that the rule should + integrate exactly.""" + raise ValueError("No scheme handler defined for %s" % cell) + + +@default_scheme.register(two_product_cell) # noqa +@default_scheme.register(FiredrakeQuadrilateral) +@default_scheme.register(UFCInterval) +def _(cell, degree): + return fiat_scheme(cell, degree) + + +@default_scheme.register(UFCTriangle) # noqa +def _(cell, degree): + if degree < 0: + raise ValueError("Need positive degree, not %d" % degree) + if degree > 6: + return fiat_scheme(cell, degree) + if degree == 0 or degree == 1: + # Scheme from Zienkiewicz and Taylor, 1 point, degree of precision 1 + x = numpy.array([[1.0/3.0, 1.0/3.0]], dtype=numpy.float64) + w = numpy.array([0.5], dtype=numpy.float64) + if degree == 2: + # Scheme from Strang and Fix, 3 points, degree of precision 2 + x = numpy.array([[1.0/6.0, 1.0/6.0], + [1.0/6.0, 2.0/3.0], + [2.0/3.0, 1.0/6.0]], + dtype=numpy.float64) + w = numpy.full(3, 1.0/6.0, dtype=numpy.float64) + if degree == 3: + # Scheme from Strang and Fix, 6 points, degree of precision 3 + x = numpy.array([[0.659027622374092, 0.231933368553031], + [0.659027622374092, 0.109039009072877], + [0.231933368553031, 0.659027622374092], + [0.231933368553031, 0.109039009072877], + [0.109039009072877, 0.659027622374092], + [0.109039009072877, 0.231933368553031]], + dtype=numpy.float64) + w = numpy.full(6, 1.0/12.0, dtype=numpy.float64) + if degree == 4: + # Scheme from Strang and Fix, 6 points, degree of precision 4 + x = numpy.array([[0.816847572980459, 0.091576213509771], + [0.091576213509771, 0.816847572980459], + [0.091576213509771, 0.091576213509771], + [0.108103018168070, 0.445948490915965], + [0.445948490915965, 0.108103018168070], + [0.445948490915965, 0.445948490915965]], + dtype=numpy.float64) + w = numpy.empty(6, dtype=numpy.float64) + w[0:3] = 0.109951743655322 + w[3:6] = 0.223381589678011 + w /= 2.0 + if degree == 5: + # Scheme from Strang and Fix, 7 points, degree of precision 5 + x = numpy.array([[0.33333333333333333, 0.33333333333333333], + [0.79742698535308720, 0.10128650732345633], + [0.10128650732345633, 0.79742698535308720], + [0.10128650732345633, 0.10128650732345633], + [0.05971587178976981, 0.47014206410511505], + [0.47014206410511505, 0.05971587178976981], + [0.47014206410511505, 0.47014206410511505]], + dtype=numpy.float64) + w = numpy.empty(7, dtype=numpy.float64) + w[0] = 0.22500000000000000 + w[1:4] = 0.12593918054482717 + w[4:7] = 0.13239415278850616 + w = w/2.0 + if degree == 6: + # Scheme from Strang and Fix, 12 points, degree of precision 6 + x = numpy.array([[0.873821971016996, 0.063089014491502], + [0.063089014491502, 0.873821971016996], + [0.063089014491502, 0.063089014491502], + [0.501426509658179, 0.249286745170910], + [0.249286745170910, 0.501426509658179], + [0.249286745170910, 0.249286745170910], + [0.636502499121399, 0.310352451033785], + [0.636502499121399, 0.053145049844816], + [0.310352451033785, 0.636502499121399], + [0.310352451033785, 0.053145049844816], + [0.053145049844816, 0.636502499121399], + [0.053145049844816, 0.310352451033785]], + dtype=numpy.float64) + w = numpy.empty(12, dtype=numpy.float64) + w[0:3] = 0.050844906370207 + w[3:6] = 0.116786275726379 + w[6:12] = 0.082851075618374 + w = w/2.0 + + return QuadratureRule(x, w) + + +@default_scheme.register(UFCTetrahedron) # noqa +def _(cell, degree): + if degree < 0: + raise ValueError("Need positive degree, not %d" % degree) + if degree > 6: + return fiat_scheme(cell, degree) + if degree == 0 or degree == 1: + # Scheme from Zienkiewicz and Taylor, 1 point, degree of precision 1 + x = numpy.array([[1.0/4.0, 1.0/4.0, 1.0/4.0]], dtype=numpy.float64) + w = numpy.array([1.0/6.0], dtype=numpy.float64) + elif degree == 2: + # Scheme from Zienkiewicz and Taylor, 4 points, degree of precision 2 + a, b = 0.585410196624969, 0.138196601125011 + x = numpy.array([[a, b, b], + [b, a, b], + [b, b, a], + [b, b, b]], + dtype=numpy.float64) + w = numpy.full(4, 1.0/24.0, dtype=numpy.float64) + elif degree == 3: + # Scheme from Zienkiewicz and Taylor, 5 points, degree of precision 3 + # Note: this scheme has a negative weight + x = numpy.array([[0.2500000000000000, 0.2500000000000000, 0.2500000000000000], + [0.5000000000000000, 0.1666666666666666, 0.1666666666666666], + [0.1666666666666666, 0.5000000000000000, 0.1666666666666666], + [0.1666666666666666, 0.1666666666666666, 0.5000000000000000], + [0.1666666666666666, 0.1666666666666666, 0.1666666666666666]], + dtype=numpy.float64) + w = numpy.empty(5, dtype=numpy.float64) + w[0] = -0.8 + w[1:5] = 0.45 + w = w/6.0 + elif degree == 4: + # Keast rule, 14 points, degree of precision 4 + # Values taken from http://people.sc.fsu.edu/~jburkardt/datasets/quadrature_rules_tet/quadrature_rules_tet.html + # (KEAST5) + x = numpy.array([[0.0000000000000000, 0.5000000000000000, 0.5000000000000000], + [0.5000000000000000, 0.0000000000000000, 0.5000000000000000], + [0.5000000000000000, 0.5000000000000000, 0.0000000000000000], + [0.5000000000000000, 0.0000000000000000, 0.0000000000000000], + [0.0000000000000000, 0.5000000000000000, 0.0000000000000000], + [0.0000000000000000, 0.0000000000000000, 0.5000000000000000], + [0.6984197043243866, 0.1005267652252045, 0.1005267652252045], + [0.1005267652252045, 0.1005267652252045, 0.1005267652252045], + [0.1005267652252045, 0.1005267652252045, 0.6984197043243866], + [0.1005267652252045, 0.6984197043243866, 0.1005267652252045], + [0.0568813795204234, 0.3143728734931922, 0.3143728734931922], + [0.3143728734931922, 0.3143728734931922, 0.3143728734931922], + [0.3143728734931922, 0.3143728734931922, 0.0568813795204234], + [0.3143728734931922, 0.0568813795204234, 0.3143728734931922]], + dtype=numpy.float64) + w = numpy.empty(14, dtype=numpy.float64) + w[0:6] = 0.0190476190476190 + w[6:10] = 0.0885898247429807 + w[10:14] = 0.1328387466855907 + w = w/6.0 + elif degree == 5: + # Keast rule, 15 points, degree of precision 5 + # Values taken from http://people.sc.fsu.edu/~jburkardt/datasets/quadrature_rules_tet/quadrature_rules_tet.html + # (KEAST6) + x = numpy.array([[0.2500000000000000, 0.2500000000000000, 0.2500000000000000], + [0.0000000000000000, 0.3333333333333333, 0.3333333333333333], + [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], + [0.3333333333333333, 0.3333333333333333, 0.0000000000000000], + [0.3333333333333333, 0.0000000000000000, 0.3333333333333333], + [0.7272727272727273, 0.0909090909090909, 0.0909090909090909], + [0.0909090909090909, 0.0909090909090909, 0.0909090909090909], + [0.0909090909090909, 0.0909090909090909, 0.7272727272727273], + [0.0909090909090909, 0.7272727272727273, 0.0909090909090909], + [0.4334498464263357, 0.0665501535736643, 0.0665501535736643], + [0.0665501535736643, 0.4334498464263357, 0.0665501535736643], + [0.0665501535736643, 0.0665501535736643, 0.4334498464263357], + [0.0665501535736643, 0.4334498464263357, 0.4334498464263357], + [0.4334498464263357, 0.0665501535736643, 0.4334498464263357], + [0.4334498464263357, 0.4334498464263357, 0.0665501535736643]], + dtype=numpy.float64) + w = numpy.empty(15, dtype=numpy.float64) + w[0] = 0.1817020685825351 + w[1:5] = 0.0361607142857143 + w[5:9] = 0.0698714945161738 + w[9:15] = 0.0656948493683187 + w = w/6.0 + elif degree == 6: + # Keast rule, 24 points, degree of precision 6 + # Values taken from http://people.sc.fsu.edu/~jburkardt/datasets/quadrature_rules_tet/quadrature_rules_tet.html + # (KEAST7) + x = numpy.array([[0.3561913862225449, 0.2146028712591517, 0.2146028712591517], + [0.2146028712591517, 0.2146028712591517, 0.2146028712591517], + [0.2146028712591517, 0.2146028712591517, 0.3561913862225449], + [0.2146028712591517, 0.3561913862225449, 0.2146028712591517], + [0.8779781243961660, 0.0406739585346113, 0.0406739585346113], + [0.0406739585346113, 0.0406739585346113, 0.0406739585346113], + [0.0406739585346113, 0.0406739585346113, 0.8779781243961660], + [0.0406739585346113, 0.8779781243961660, 0.0406739585346113], + [0.0329863295731731, 0.3223378901422757, 0.3223378901422757], + [0.3223378901422757, 0.3223378901422757, 0.3223378901422757], + [0.3223378901422757, 0.3223378901422757, 0.0329863295731731], + [0.3223378901422757, 0.0329863295731731, 0.3223378901422757], + [0.2696723314583159, 0.0636610018750175, 0.0636610018750175], + [0.0636610018750175, 0.2696723314583159, 0.0636610018750175], + [0.0636610018750175, 0.0636610018750175, 0.2696723314583159], + [0.6030056647916491, 0.0636610018750175, 0.0636610018750175], + [0.0636610018750175, 0.6030056647916491, 0.0636610018750175], + [0.0636610018750175, 0.0636610018750175, 0.6030056647916491], + [0.0636610018750175, 0.2696723314583159, 0.6030056647916491], + [0.2696723314583159, 0.6030056647916491, 0.0636610018750175], + [0.6030056647916491, 0.0636610018750175, 0.2696723314583159], + [0.0636610018750175, 0.6030056647916491, 0.2696723314583159], + [0.2696723314583159, 0.0636610018750175, 0.6030056647916491], + [0.6030056647916491, 0.2696723314583159, 0.0636610018750175]], + dtype=numpy.float64) + w = numpy.empty(24, dtype=numpy.float64) + w[0:4] = 0.0399227502581679 + w[4:8] = 0.0100772110553207 + w[8:12] = 0.0553571815436544 + w[12:24] = 0.0482142857142857 + w = w/6.0 + + return QuadratureRule(x, w) + + +def create_quadrature_rule(cell, degree, scheme="default"): + """Create a quadrature rule. + + :arg cell: The UFL cell to create the rule for. + :arg degree: The degree of polynomial that should be integrated + exactly by the rule. + :kwarg scheme: optional scheme to use (either ``"default"``, or + ``"canonical"``). These correspond to + :func:`default_scheme` and :func:`fiat_scheme` respectively. + + .. note :: + + If the cell is a tensor product cell, the degree should be a + tuple, indicating the degree in each direction of the tensor + product. + """ + if scheme not in ("default", "canonical"): + raise ValueError("Unknown quadrature scheme '%s'" % scheme) + + cellname = cell.cellname() + + try: + degree = tuple(degree) + if cellname != "OuterProductCell": + raise ValueError("Not expecting tuple of degrees") + except TypeError: + if cellname == "OuterProductCell": + # We got a single degree, assume we meant that degree in + # each direction. + degree = (degree, degree) + + if cellname == "vertex": + return QuadratureRule(numpy.zeros((1, 0), dtype=numpy.float64), + numpy.ones(1, dtype=numpy.float64)) + cell = as_fiat_cell(cell) + + if scheme == "canonical": + return fiat_scheme(cell, degree) + + return default_scheme(cell, degree) + + +def integration_cell(cell, integral_type): + """Return the integration cell for a given integral type. + + :arg cell: The "base" cell (that cell integrals are performed + over). + :arg integral_type: The integration type. + """ + if integral_type == "cell": + return cell + if integral_type in ("exterior_facet", "interior_facet"): + return {"interval": ufl.vertex, + "triangle": ufl.interval, + "quadrilateral": ufl.interval, + "tetrahedron": ufl.triangle, + "hexahedron": ufl.quadrilateral}[cell.cellname()] + if integral_type in ("exterior_facet_top", "exterior_facet_bottom", + "interior_facet_horiz"): + return cell.facet_horiz + if integral_type in ("exterior_facet_vert", "interior_facet_vert"): + return cell.facet_vert + raise ValueError("Don't know how to find an integration cell") + + +def select_degree(degree, cell, integral_type): + """Select correct part of degree given an integral type. + + :arg degree: The degree on the cell. + :arg cell: The "base" integration cell (that cell integrals are + performed over). + :arg integral_type: The integration type. + + For non-tensor-product cells, this always just returns the + degree. For tensor-product cells it returns the degree on the + appropriate sub-entity. + """ + if integral_type == "cell": + return degree + if integral_type in ("exterior_facet", "interior_facet"): + if cell.cellname() == "quadrilateral": + try: + d1, d2 = degree + if len(degree) != 2: + raise ValueError("Expected tuple degree of length 2") + if d1 != d2: + raise ValueError("tuple degree must have matching values") + return d1 + except TypeError: + return degree + return degree + if integral_type in ("exterior_facet_top", "exterior_facet_bottom", + "interior_facet_horiz"): + return degree[0] + if integral_type in ("exterior_facet_vert", "interior_facet_vert"): + if cell.topological_dimension() == 2: + return degree[1] + return degree + + +def create_quadrature(cell, integral_type, degree, scheme="default"): + """Create a quadrature rule. + + :arg cell: The UFL cell. + :arg integral_type: The integral being performed. + :arg degree: The degree of polynomial that should be integrated + exactly by the rule. + :kwarg scheme: optional scheme to use (either ``"default"``, or + ``"canonical"``). These correspond to + :func:`default_scheme` and :func:`fiat_scheme` respectively. + + .. note :: + + If the cell is a tensor product cell, the degree should be a + tuple, indicating the degree in each direction of the tensor + product. + """ + # Pick out correct part of degree for non-cell integrals. + degree = select_degree(degree, cell, integral_type) + # Pick cell to be integrated over for non-cell integrals. + cell = integration_cell(cell, integral_type) + return create_quadrature_rule(cell, degree, scheme=scheme) From 34a63352fbbc58a6ccc6959a4745ef8d677d5f5d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 1 Feb 2016 14:18:04 +0000 Subject: [PATCH 004/809] import files --- tsfc/__init__.py | 3 + tsfc/coffee.py | 344 ++++++++++++++++ tsfc/constants.py | 23 ++ tsfc/driver.py | 393 ++++++++++++++++++ tsfc/einstein.py | 532 +++++++++++++++++++++++++ tsfc/fem.py | 797 +++++++++++++++++++++++++++++++++++++ tsfc/impero.py | 62 +++ tsfc/modified_terminals.py | 163 ++++++++ tsfc/node.py | 67 ++++ tsfc/scheduling.py | 174 ++++++++ 10 files changed, 2558 insertions(+) create mode 100644 tsfc/coffee.py create mode 100644 tsfc/constants.py create mode 100644 tsfc/driver.py create mode 100644 tsfc/einstein.py create mode 100644 tsfc/fem.py create mode 100644 tsfc/impero.py create mode 100644 tsfc/modified_terminals.py create mode 100644 tsfc/node.py create mode 100644 tsfc/scheduling.py diff --git a/tsfc/__init__.py b/tsfc/__init__.py index e69de29bb2..ca26644e98 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -0,0 +1,3 @@ +from __future__ import absolute_import + +from tsfc.driver import compile_form # noqa diff --git a/tsfc/coffee.py b/tsfc/coffee.py new file mode 100644 index 0000000000..e66c5ab474 --- /dev/null +++ b/tsfc/coffee.py @@ -0,0 +1,344 @@ +from __future__ import absolute_import + +import collections +import itertools +from math import isnan + +import numpy +from singledispatch import singledispatch + +import coffee.base as coffee + +from tsfc import einstein as ein, impero as imp +from tsfc.constants import NUMPY_TYPE, SCALAR_TYPE, PRECISION + + +class Bunch(object): + pass + + +class OrderedCounter(collections.Counter, collections.OrderedDict): + """A Counter object that has deterministic iteration order.""" + pass + + +def generate(indexed_ops, temporaries, shape_map, apply_ordering, index_extents, index_names): + temporaries_set = set(temporaries) + ops = [op for indices, op in indexed_ops] + + code = make_loop_tree(indexed_ops) + + reference_count = collections.Counter() + for op in ops: + reference_count.update(count_references(temporaries_set, op)) + assert temporaries_set == set(reference_count) + + indices, declare = place_declarations(code, reference_count, shape_map, apply_ordering, ops) + + parameters = Bunch() + parameters.index_extents = index_extents + parameters.declare = declare + parameters.indices = indices + parameters.names = {} + + for i, temp in enumerate(temporaries): + parameters.names[temp] = "t%d" % i + + for index, name in index_names: + parameters.names[index] = name + + index_counter = 0 + for index in index_extents: + if index not in parameters.names: + index_counter += 1 + parameters.names[index] = "i_%d" % index_counter + + return arabica(code, parameters) + + +def make_loop_tree(indexed_ops, level=0): + keyfunc = lambda (indices, op): indices[level:level+1] + statements = [] + for first_index, op_group in itertools.groupby(indexed_ops, keyfunc): + if first_index: + inner_block = make_loop_tree(op_group, level+1) + statements.append(imp.For(first_index[0], inner_block)) + else: + statements.extend(op for indices, op in op_group) + return imp.Block(statements) + + +def count_references(temporaries, op): + counter = collections.Counter() + + def recurse(o, top=False): + if o in temporaries: + counter[o] += 1 + + if top or o not in temporaries: + if isinstance(o, (ein.Literal, ein.Variable)): + pass + elif isinstance(o, ein.Indexed): + recurse(o.children[0]) + else: + for c in o.children: + recurse(c) + + if isinstance(op, imp.Evaluate): + recurse(op.expression, top=True) + elif isinstance(op, imp.Initialise): + counter[op.indexsum] += 1 + elif isinstance(op, imp.Return): + recurse(op.expression) + elif isinstance(op, imp.Accumulate): + counter[op.indexsum] += 1 + recurse(op.indexsum.children[0]) + else: + raise AssertionError("unhandled operation: %s" % type(op)) + + return counter + + +def place_declarations(tree, reference_count, shape_map, apply_ordering, operations): + temporaries = set(reference_count) + indices = {} + # We later iterate over declare keys, so need this to be ordered + declare = collections.OrderedDict() + + def recurse(expr, loop_indices): + if isinstance(expr, imp.Block): + declare[expr] = [] + # Need to iterate over counter in given order + counter = OrderedCounter() + for statement in expr.children: + counter.update(recurse(statement, loop_indices)) + for e, count in counter.items(): + if count == reference_count[e]: + indices[e] = apply_ordering(set(shape_map(e)) - loop_indices) + if indices[e]: + declare[expr].append(e) + del counter[e] + return counter + elif isinstance(expr, imp.For): + return recurse(expr.children[0], loop_indices | {expr.index}) + else: + return count_references(temporaries, expr) + + remainder = recurse(tree, set()) + assert not remainder + + for op in operations: + declare[op] = False + if isinstance(op, imp.Evaluate): + e = op.expression + elif isinstance(op, imp.Initialise): + e = op.indexsum + else: + continue + + if len(indices[e]) == 0: + declare[op] = True + + return indices, declare + + +def _decl_symbol(expr, parameters): + multiindex = parameters.indices[expr] + rank = tuple(parameters.index_extents[index] for index in multiindex) + if hasattr(expr, 'shape'): + rank += expr.shape + return coffee.Symbol(parameters.names[expr], rank=rank) + + +def _ref_symbol(expr, parameters): + multiindex = parameters.indices[expr] + # TODO: Shall I make a COFFEE Symbol here? + rank = tuple(parameters.names[index] for index in multiindex) + return coffee.Symbol(parameters.names[expr], rank=tuple(rank)) + + +@singledispatch +def arabica(tree, parameters): + raise AssertionError("cannot generate COFFEE from %s" % type(tree)) + + +@arabica.register(imp.Block) # noqa: Not actually redefinition +def _(tree, parameters): + statements = [arabica(child, parameters) for child in tree.children] + declares = [] + for expr in parameters.declare[tree]: + declares.append(coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters))) + return coffee.Block(declares + statements, open_scope=True) + + +@arabica.register(imp.For) # noqa: Not actually redefinition +def _(tree, parameters): + extent = tree.index.extent + assert extent + i = coffee.Symbol(parameters.names[tree.index]) + # TODO: symbolic constant for "int" + return coffee.For(coffee.Decl("int", i, init=0), + coffee.Less(i, extent), + coffee.Incr(i, 1), + arabica(tree.children[0], parameters)) + + +@arabica.register(imp.Initialise) # noqa: Not actually redefinition +def _(leaf, parameters): + if parameters.declare[leaf]: + return coffee.Decl(SCALAR_TYPE, _decl_symbol(leaf.indexsum, parameters), 0.0) + else: + return coffee.Assign(_ref_symbol(leaf.indexsum, parameters), 0.0) + + +@arabica.register(imp.Accumulate) # noqa: Not actually redefinition +def _(leaf, parameters): + return coffee.Incr(_ref_symbol(leaf.indexsum, parameters), + expression(leaf.indexsum.children[0], parameters)) + + +@arabica.register(imp.Return) # noqa: Not actually redefinition +def _(leaf, parameters): + return coffee.Incr(expression(leaf.variable, parameters), + expression(leaf.expression, parameters)) + + +@arabica.register(imp.Evaluate) # noqa: Not actually redefinition +def _(leaf, parameters): + expr = leaf.expression + if isinstance(expr, ein.ListTensor): + # TODO: remove constant float branch. + if parameters.declare[leaf]: + values = numpy.array([expression(v, parameters) for v in expr.array.flat], dtype=object) + if all(isinstance(value, float) for value in values): + qualifiers = ["static", "const"] + values = numpy.array(values, dtype=NUMPY_TYPE) + else: + qualifiers = [] + values = values.reshape(expr.shape) + return coffee.Decl(SCALAR_TYPE, + _decl_symbol(expr, parameters), + coffee.ArrayInit(values, precision=PRECISION), + qualifiers=qualifiers) + else: + ops = [] + for multiindex, value in numpy.ndenumerate(expr.array): + coffee_sym = coffee.Symbol(_ref_symbol(expr, parameters), rank=multiindex) + ops.append(coffee.Assign(coffee_sym, expression(value, parameters))) + return coffee.Block(ops, open_scope=False) + elif isinstance(expr, ein.Literal): + assert parameters.declare[leaf] + return coffee.Decl(SCALAR_TYPE, + _decl_symbol(expr, parameters), + coffee.ArrayInit(expr.array, precision=PRECISION), + qualifiers=["static", "const"]) + else: + code = expression(expr, parameters, top=True) + if parameters.declare[leaf]: + return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), code) + else: + return coffee.Assign(_ref_symbol(expr, parameters), code) + + +def expression(expr, parameters, top=False): + if not top and expr in parameters.names: + return _ref_symbol(expr, parameters) + else: + return handle(expr, parameters) + + +@singledispatch +def handle(expr, parameters): + raise AssertionError("cannot generate COFFEE from %s" % type(expr)) + + +@handle.register(ein.Product) # noqa: Not actually redefinition +def _(expr, parameters): + return coffee.Prod(*[expression(c, parameters) + for c in expr.children]) + + +@handle.register(ein.Sum) # noqa: Not actually redefinition +def _(expr, parameters): + return coffee.Sum(*[expression(c, parameters) + for c in expr.children]) + + +@handle.register(ein.Division) # noqa: Not actually redefinition +def _(expr, parameters): + return coffee.Div(*[expression(c, parameters) + for c in expr.children]) + + +@handle.register(ein.Power) # noqa: Not actually redefinition +def _(expr, parameters): + base, exponent = expr.children + return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) + + +@handle.register(ein.MathFunction) # noqa: Not actually redefinition +def _(expr, parameters): + name_map = {'abs': 'fabs', 'ln': 'log'} + name = name_map.get(expr.name, expr.name) + return coffee.FunCall(name, expression(expr.children[0], parameters)) + + +@handle.register(ein.Comparison) # noqa: Not actually redefinition +def _(expr, parameters): + type_map = {">": coffee.Greater, + ">=": coffee.GreaterEq, + "==": coffee.Eq, + "!=": coffee.NEq, + "<": coffee.Less, + "<=": coffee.LessEq} + return type_map[expr.operator](*[expression(c, parameters) for c in expr.children]) + + +@handle.register(ein.LogicalNot) # noqa: Not actually redefinition +def _(expr, parameters): + return coffee.Not(*[expression(c, parameters) for c in expr.children]) + + +@handle.register(ein.LogicalAnd) # noqa: Not actually redefinition +def _(expr, parameters): + return coffee.And(*[expression(c, parameters) for c in expr.children]) + + +@handle.register(ein.LogicalOr) # noqa: Not actually redefinition +def _(expr, parameters): + return coffee.Or(*[expression(c, parameters) for c in expr.children]) + + +@handle.register(ein.Conditional) # noqa: Not actually redefinition +def _(expr, parameters): + return coffee.Ternary(*[expression(c, parameters) for c in expr.children]) + + +@handle.register(ein.Literal) # noqa: Not actually redefinition +@handle.register(ein.Zero) +def _(expr, parameters): + assert not expr.shape + if isnan(expr.value): + return coffee.Symbol("NAN") + else: + # Formatting? Symbol? + return expr.value + + +@handle.register(ein.Variable) # noqa: Not actually redefinition +def _(expr, parameters): + return coffee.Symbol(expr.name) + + +@handle.register(ein.Indexed) # noqa: Not actually redefinition +def _(expr, parameters): + rank = [] + for index in expr.multiindex: + if isinstance(index, ein.Index): + rank.append(parameters.names[index]) + elif isinstance(index, ein.VariableIndex): + rank.append(index.name) + else: + rank.append(index) + return coffee.Symbol(expression(expr.children[0], parameters), + rank=tuple(rank)) diff --git a/tsfc/constants.py b/tsfc/constants.py new file mode 100644 index 0000000000..e86915cb7d --- /dev/null +++ b/tsfc/constants.py @@ -0,0 +1,23 @@ +from __future__ import absolute_import + +import numpy + + +NUMPY_TYPE = numpy.dtype("double") + +PRECISION = numpy.finfo(NUMPY_TYPE).precision + +SCALAR_TYPE = {numpy.dtype("double"): "double", + numpy.dtype("float32"): "float"}[NUMPY_TYPE] + + +RESTRICTION_MAP = {"+": 0, "-": 1} + +PARAMETERS = { + "quadrature_rule": "auto", + "quadrature_degree": "auto" +} + + +def default_parameters(): + return PARAMETERS.copy() diff --git a/tsfc/driver.py b/tsfc/driver.py new file mode 100644 index 0000000000..b04634a9b5 --- /dev/null +++ b/tsfc/driver.py @@ -0,0 +1,393 @@ +from __future__ import absolute_import + +import numpy +import time +import collections + +from ufl.algorithms import compute_form_data + +from ffc.log import info_green +from tsfc.fiatinterface import create_element +from tsfc.mixedelement import MixedElement as ffc_MixedElement +from tsfc.quadrature import create_quadrature, QuadratureRule + +import coffee.base as coffee + +from tsfc import fem, einstein as ein, impero as imp, scheduling as sch +from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee +from tsfc.constants import default_parameters + + +def compile_form(form, prefix="form", parameters=None): + assert not isinstance(form, (list, tuple)) + + if parameters is None: + parameters = default_parameters() + else: + _ = default_parameters() + _.update(parameters) + parameters = _ + + fd = compute_form_data(form, + do_apply_function_pullbacks=True, + do_apply_integral_scaling=True, + do_apply_geometry_lowering=True, + do_apply_restrictions=True, + do_estimate_degrees=True) + + kernels = [] + for idata in fd.integral_data: + if len(idata.integrals) != 1: + raise NotImplementedError("Don't support IntegralData with more than one integral") + for integral in idata.integrals: + kernel = compile_integral(integral, idata, fd, prefix, parameters) + if kernel is not None: + kernels.append(kernel) + return kernels + + +class Kernel(object): + __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + "coefficient_numbers", "__weakref__") + """A compiled Kernel object. + + :kwarg ast: The COFFEE ast for the kernel. + :kwarg integral_type: The type of integral. + :kwarg oriented: Does the kernel require cell_orientations. + :kwarg subdomain_id: What is the subdomain id for this kernel. + :kwarg coefficient_numbers: A list of which coefficients from the + form the kernel needs. + """ + def __init__(self, ast=None, integral_type=None, oriented=False, + subdomain_id=None, coefficient_numbers=()): + # Defaults + self.ast = ast + self.integral_type = integral_type + self.oriented = oriented + self.subdomain_id = subdomain_id + self.coefficient_numbers = coefficient_numbers + super(Kernel, self).__init__() + + +def compile_integral(integral, idata, fd, prefix, parameters): + cpu_time = time.time() + + _ = {} + # Record per-integral parameters + _.update(integral.metadata()) + # parameters override per-integral metadata + _.update(parameters) + parameters = _ + + if parameters.get("quadrature_degree") == "auto": + del parameters["quadrature_degree"] + if parameters.get("quadrature_rule") == "auto": + del parameters["quadrature_rule"] + # Check if the integral has a quad degree attached, otherwise use + # the estimated polynomial degree attached by compute_form_data + quadrature_degree = parameters.get("quadrature_degree", + parameters["estimated_polynomial_degree"]) + integral_type = integral.integral_type() + integrand = integral.integrand() + kernel = Kernel(integral_type=integral_type, subdomain_id=integral.subdomain_id()) + + arglist = [] + prepare = [] + coefficient_map = {} + + funarg, prepare_, expressions, finalise = prepare_arguments(integral_type, fd.preprocessed_form.arguments()) + + arglist.append(funarg) + prepare += prepare_ + argument_indices = tuple(index for index in expressions[0].multiindex if isinstance(index, ein.Index)) + + mesh = idata.domain + coordinates = fem.coordinate_coefficient(mesh) + funarg, prepare_, expression = prepare_coefficient(integral_type, coordinates, "coords") + + arglist.append(funarg) + prepare += prepare_ + coefficient_map[coordinates] = expression + + coefficient_numbers = [] + # enabled_coefficients is a boolean array that indicates which of + # reduced_coefficients the integral requires. + for i, on in enumerate(idata.enabled_coefficients): + if not on: + continue + coefficient = fd.reduced_coefficients[i] + # This is which coefficient in the original form the current + # coefficient is. + # Consider f*v*dx + g*v*ds, the full form contains two + # coefficients, but each integral only requires one. + coefficient_numbers.append(fd.original_coefficient_positions[i]) + funarg, prepare_, expression = prepare_coefficient(integral_type, coefficient, "w_%d" % i) + + arglist.append(funarg) + prepare += prepare_ + coefficient_map[coefficient] = expression + + kernel.coefficient_numbers = tuple(coefficient_numbers) + + if integral_type in ["exterior_facet", "exterior_facet_vert"]: + decl = coffee.Decl("const unsigned int", coffee.Symbol("facet", rank=(1,))) + arglist.append(decl) + elif integral_type in ["interior_facet", "interior_facet_vert"]: + decl = coffee.Decl("const unsigned int", coffee.Symbol("facet", rank=(2,))) + arglist.append(decl) + + cell = integrand.ufl_domain().ufl_cell() + + quad_rule = parameters.get("quadrature_rule", + create_quadrature(cell, integral_type, + quadrature_degree)) + + if not isinstance(quad_rule, QuadratureRule): + raise ValueError("Expected to find a QuadratureRule object, not a %s" % + type(quad_rule)) + + tabulation_manager = fem.TabulationManager(integral_type, cell, quad_rule.points) + integrand = fem.replace_coordinates(integrand, coordinates) + quadrature_index, nonfem, cell_orientations = \ + fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, argument_indices, coefficient_map) + nonfem = [ein.IndexSum(e, quadrature_index) for e in nonfem] + simplified = [ein.inline_indices(e) for e in nonfem] + + if cell_orientations: + decl = coffee.Decl("const int *restrict *restrict", coffee.Symbol("cell_orientations")) + arglist.insert(2, decl) + kernel.oriented = True + + # Need a deterministic ordering for these + index_extents = collections.OrderedDict() + for e in simplified: + index_extents.update(ein.collect_index_extents(e)) + index_ordering = apply_prefix_ordering(index_extents.keys(), + (quadrature_index,) + argument_indices) + apply_ordering = make_index_orderer(index_ordering) + + shape_map = lambda expr: expr.free_indices + ordered_shape_map = lambda expr: apply_ordering(shape_map(expr)) + + indexed_ops = sch.make_ordering(zip(expressions, simplified), ordered_shape_map) + # Zero-simplification occurred + if len(indexed_ops) == 0: + return None + temporaries = make_temporaries(op for indices, op in indexed_ops) + + index_names = zip((quadrature_index,) + argument_indices, ['ip', 'j', 'k']) + body = generate_coffee(indexed_ops, temporaries, shape_map, + apply_ordering, index_extents, index_names) + body.open_scope = False + + funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) + ast = coffee.FunDecl("void", funname, arglist, coffee.Block(prepare + [body] + + finalise), + pred=["static", "inline"]) + kernel.ast = ast + + info_green("TSFC finished in %g seconds." % (time.time() - cpu_time)) + return kernel + + +def prepare_coefficient(integral_type, coefficient, name): + + if coefficient.ufl_element().family() == 'Real': + # Constant + + shape = coefficient.ufl_shape or (1,) + + funarg = coffee.Decl("const %s" % SCALAR_TYPE, coffee.Symbol(name, rank=shape)) + expression = ein.Variable(name, shape) + if coefficient.ufl_shape == (): + expression = ein.Indexed(expression, (0,)) + + return funarg, [], expression + + fiat_element = create_element(coefficient.ufl_element()) + + if not integral_type.startswith("interior_facet"): + # Simple case + + shape = (fiat_element.space_dimension(),) + funarg = coffee.Decl("const %s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape)) + + i = ein.Index() + expression = ein.ComponentTensor( + ein.Indexed(ein.Variable(name, shape + (1,)), + (i, 0)), + (i,)) + + return funarg, [], expression + + if not isinstance(fiat_element, ffc_MixedElement): + # Interior facet integral + + shape = (2, fiat_element.space_dimension()) + + funarg = coffee.Decl("const %s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape)) + expression = ein.Variable(name, shape + (1,)) + + f, i = ein.Index(), ein.Index() + expression = ein.ComponentTensor( + ein.Indexed(ein.Variable(name, shape + (1,)), + (f, i, 0)), + (f, i,)) + + return funarg, [], expression + + # Interior facet integral + mixed / vector element + name_ = name + "_" + shape = (2, fiat_element.space_dimension()) + + funarg = coffee.Decl("const %s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_)) + prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] + expression = ein.Variable(name, shape) + + offset = 0 + i = coffee.Symbol("i") + for element in fiat_element.elements(): + space_dim = element.space_dimension() + + loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, coffee.Sum(offset, i))), + coffee.Symbol(name_, rank=(coffee.Sum(2 * offset, i), 0))) + prepare.append(coffee_for(i, space_dim, loop_body)) + + loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, coffee.Sum(offset, i))), + coffee.Symbol(name_, rank=(coffee.Sum(2 * offset + space_dim, i), 0))) + prepare.append(coffee_for(i, space_dim, loop_body)) + + offset += space_dim + + return funarg, prepare, expression + + +def prepare_arguments(integral_type, arguments): + from itertools import chain, product + + if len(arguments) == 0: + # No arguments + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=(1,))) + expression = ein.Indexed(ein.Variable("A", (1,)), (0,)) + + return funarg, [], [expression], [] + + elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + indices = tuple(ein.Index() for i in xrange(len(arguments))) + + if not integral_type.startswith("interior_facet"): + # Not an interior facet integral + shape = tuple(element.space_dimension() for element in elements) + + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) + expression = ein.Indexed(ein.Variable("A", shape), indices) + + return funarg, [], [expression], [] + + if not any(isinstance(element, ffc_MixedElement) for element in elements): + # Interior facet integral, but no vector (mixed) arguments + shape = [] + for element in elements: + shape += [2, element.space_dimension()] + shape = tuple(shape) + + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) + varexp = ein.Variable("A", shape) + + expressions = [] + for restrictions in product((0, 1), repeat=len(arguments)): + is_ = tuple(chain(*zip(restrictions, indices))) + expressions.append(ein.Indexed(varexp, is_)) + + return funarg, [], expressions, [] + + # Interior facet integral + vector (mixed) argument(s) + shape = tuple(element.space_dimension() for element in elements) + funarg_shape = tuple(s * 2 for s in shape) + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=funarg_shape)) + + prepare = [] + expressions = [] + + references = [] + for restrictions in product((0, 1), repeat=len(arguments)): + name = "A" + "".join(map(str, restrictions)) + + prepare.append(coffee.Decl(SCALAR_TYPE, + coffee.Symbol(name, rank=shape), + init=coffee.ArrayInit(numpy.zeros(1)))) + expressions.append(ein.Indexed(ein.Variable(name, shape), indices)) + + for multiindex in numpy.ndindex(shape): + references.append(coffee.Symbol(name, multiindex)) + + restriction_shape = [] + for e in elements: + if isinstance(e, ffc_MixedElement): + restriction_shape += [len(e.elements()), + e.elements()[0].space_dimension()] + else: + restriction_shape += [1, e.space_dimension()] + restriction_shape = tuple(restriction_shape) + + references = numpy.array(references) + if len(arguments) == 1: + references = references.reshape((2,) + restriction_shape) + references = references.transpose(1, 0, 2) + elif len(arguments) == 2: + references = references.reshape((2, 2) + restriction_shape) + references = references.transpose(2, 0, 3, 4, 1, 5) + references = references.reshape(funarg_shape) + + finalise = [] + for multiindex in numpy.ndindex(funarg_shape): + finalise.append(coffee.Assign(coffee.Symbol("A", rank=multiindex), + references[multiindex])) + + return funarg, prepare, expressions, finalise + + +def coffee_for(index, extent, body): + return coffee.For(coffee.Decl("int", index, init=0), + coffee.Less(index, extent), + coffee.Incr(index, 1), + body) + + +def make_index_orderer(index_ordering): + idx2pos = {idx: pos for pos, idx in enumerate(index_ordering)} + + def apply_ordering(shape): + return tuple(sorted(shape, key=lambda i: idx2pos[i])) + return apply_ordering + + +def apply_prefix_ordering(indices, prefix_ordering): + rest = set(indices) - set(prefix_ordering) + # Need to return deterministically ordered indices + return tuple(prefix_ordering) + tuple(k for k in indices if k in rest) + + +def make_temporaries(operations): + # For fast look up + set_ = set() + + # For ordering + list = [] + + def make_temporary(o): + if o not in set_: + set_.add(o) + list.append(o) + + for op in operations: + if isinstance(op, (imp.Initialise, imp.Return)): + pass + elif isinstance(op, imp.Accumulate): + make_temporary(op.indexsum) + elif isinstance(op, imp.Evaluate): + make_temporary(op.expression) + else: + raise AssertionError("unhandled operation: %s" % type(op)) + + return list diff --git a/tsfc/einstein.py b/tsfc/einstein.py new file mode 100644 index 0000000000..93fcf2b1e3 --- /dev/null +++ b/tsfc/einstein.py @@ -0,0 +1,532 @@ +from __future__ import absolute_import + +import collections +import numpy + +from singledispatch import singledispatch + +import ufl + +from tsfc.node import Node as node_Node, traversal + + +class NodeMeta(type): + def __call__(self, *args, **kwargs): + # Create and initialise object + obj = super(NodeMeta, self).__call__(*args, **kwargs) + + # Set free_indices if not set already + if not hasattr(obj, 'free_indices'): + free_indices = set() + for child in obj.children: + free_indices |= set(child.free_indices) + obj.free_indices = tuple(free_indices) + + return obj + + +class Node(node_Node): + __metaclass__ = NodeMeta + + __slots__ = ('free_indices') + + +class Scalar(Node): + __slots__ = () + + shape = () + + +class Zero(Node): + __slots__ = ('shape',) + __front__ = ('shape',) + + def __init__(self, shape=()): + self.shape = shape + + children = () + + @property + def value(self): + assert not self.shape + return 0.0 + + +class Literal(Node): + __slots__ = ('array',) + __front__ = ('array',) + + def __new__(cls, array): + array = numpy.asarray(array) + if (array == 0).all(): + return Zero(array.shape) + else: + return super(Literal, cls).__new__(cls) + + def __init__(self, array): + self.array = numpy.asarray(array, dtype=float) + + children = () + + def is_equal(self, other): + if type(self) != type(other): + return False + if self.shape != other.shape: + return False + return tuple(self.array.flat) == tuple(other.array.flat) + + def get_hash(self): + return hash((type(self), self.shape, tuple(self.array.flat))) + + @property + def value(self): + return float(self.array) + + @property + def shape(self): + return self.array.shape + + +class Variable(Node): + __slots__ = ('name', 'shape') + __front__ = ('name', 'shape') + + def __init__(self, name, shape): + self.name = name + self.shape = shape + + children = () + + +class Sum(Scalar): + __slots__ = ('children',) + + def __new__(cls, a, b): + assert not a.shape + assert not b.shape + + if isinstance(a, Zero): + return b + elif isinstance(b, Zero): + return a + + self = super(Sum, cls).__new__(cls) + self.children = a, b + return self + + +class Product(Scalar): + __slots__ = ('children',) + + def __new__(cls, a, b): + assert not a.shape + assert not b.shape + + if isinstance(a, Zero) or isinstance(b, Zero): + return Zero() + + self = super(Product, cls).__new__(cls) + self.children = a, b + return self + + +class Division(Scalar): + __slots__ = ('children',) + + def __new__(cls, a, b): + assert not a.shape + assert not b.shape + + if isinstance(b, Zero): + raise ValueError("division by zero") + if isinstance(a, Zero): + return Zero() + + self = super(Division, cls).__new__(cls) + self.children = a, b + return self + + +class Power(Scalar): + __slots__ = ('children',) + + def __new__(cls, base, exponent): + assert not base.shape + assert not exponent.shape + + if isinstance(base, Zero): + if isinstance(exponent, Zero): + raise ValueError("cannot solve 0^0") + return Zero() + elif isinstance(exponent, Zero): + return Literal(1) + + self = super(Power, cls).__new__(cls) + self.children = base, exponent + return self + + +class MathFunction(Scalar): + __slots__ = ('name', 'children') + __front__ = ('name',) + + def __init__(self, name, argument): + assert isinstance(name, str) + assert not argument.shape + + self.name = name + self.children = argument, + + +class MinValue(Scalar): + __slots__ = ('children',) + + def __init__(self, a, b): + assert not a.shape + assert not b.shape + + self.children = a, b + + +class MaxValue(Scalar): + __slots__ = ('children',) + + def __init__(self, a, b): + assert not a.shape + assert not b.shape + + self.children = a, b + + +class Comparison(Scalar): + __slots__ = ('operator', 'children') + __front__ = ('operator',) + + def __init__(self, op, a, b): + assert not a.shape + assert not b.shape + + if op not in [">", ">=", "==", "!=", "<", "<="]: + raise ValueError("invalid operator") + + self.operator = op + self.children = a, b + + +class LogicalNot(Scalar): + __slots__ = ('children',) + + def __init__(self, expression): + assert not expression.shape + + self.children = expression, + + +class LogicalAnd(Scalar): + __slots__ = ('children',) + + def __init__(self, a, b): + assert not a.shape + assert not b.shape + + self.children = a, b + + +class LogicalOr(Scalar): + __slots__ = ('children',) + + def __init__(self, a, b): + assert not a.shape + assert not b.shape + + self.children = a, b + + +class Conditional(Node): + __slots__ = ('children', 'shape') + + def __init__(self, condition, then, else_): + assert not condition.shape + assert then.shape == else_.shape + + self.children = condition, then, else_ + self.shape = then.shape + + +class Index(object): + __slots__ = ('extent') + + def __init__(self): + self.extent = None + + def set_extent(self, value): + if self.extent is None: + self.extent = value + elif self.extent != value: + raise ValueError("Inconsistent index extents!") + + +class VariableIndex(object): + def __init__(self, name): + self.name = name + + +class Indexed(Scalar): + __slots__ = ('children', 'multiindex') + __back__ = ('multiindex',) + + def __new__(cls, aggregate, multiindex): + assert len(aggregate.shape) == len(multiindex) + for index, extent in zip(multiindex, aggregate.shape): + if isinstance(index, Index): + index.set_extent(extent) + + if isinstance(aggregate, Zero): + return Zero() + else: + return super(Indexed, cls).__new__(cls) + + def __init__(self, aggregate, multiindex): + self.children = (aggregate,) + self.multiindex = multiindex + + new_indices = set(i for i in multiindex if isinstance(i, Index)) + self.free_indices = tuple(set(aggregate.free_indices) | new_indices) + + +class ComponentTensor(Node): + __slots__ = ('children', 'multiindex', 'shape') + __back__ = ('multiindex',) + + def __init__(self, expression, multiindex): + assert not expression.shape + # assert set(multiindex) <= set(expression.free_indices) + assert all(index.extent for index in multiindex) + + self.children = (expression,) + self.multiindex = multiindex + + self.free_indices = tuple(set(expression.free_indices) - set(multiindex)) + self.shape = tuple(index.extent for index in multiindex) + + +class IndexSum(Scalar): + __slots__ = ('children', 'index') + __back__ = ('index',) + + def __new__(cls, summand, index): + assert not summand.shape + if isinstance(summand, Zero): + return summand + + self = super(IndexSum, cls).__new__(cls) + self.children = (summand,) + self.index = index + + assert index in summand.free_indices + self.free_indices = tuple(set(summand.free_indices) - {index}) + + return self + + +class ListTensor(Node): + __slots__ = ('array',) + + def __new__(cls, array): + array = numpy.asarray(array) + + if all(isinstance(elem, Zero) for elem in array.flat): + assert all(elem.shape == () for elem in array.flat) + return Zero(array.shape) + + self = super(ListTensor, cls).__new__(cls) + self.array = array + return self + + @property + def children(self): + return tuple(self.array.flat) + + @property + def shape(self): + return self.array.shape + + def reconstruct(self, *args): + return ListTensor(numpy.asarray(args).reshape(self.array.shape)) + + def __repr__(self): + return "ListTensor(%r)" % self.array.tolist() + + def is_equal(self, other): + if type(self) != type(other): + return False + if self.shape != other.shape: + return False + return self.children == other.children + + def get_hash(self): + return hash((type(self), self.shape, self.children)) + + +class FromUFLMixin(object): + def __init__(self): + self.index_map = collections.defaultdict(Index) + + def scalar_value(self, o): + return Literal(o.value()) + + def identity(self, o): + return Literal(numpy.eye(*o.ufl_shape)) + + def zero(self, o): + return Zero(o.ufl_shape) + + def sum(self, o, *ops): + if o.ufl_shape: + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(Sum(*[Indexed(op, indices) for op in ops]), indices) + else: + return Sum(*ops) + + def product(self, o, *ops): + assert o.ufl_shape == () + return Product(*ops) + + def division(self, o, numerator, denominator): + return Division(numerator, denominator) + + def abs(self, o, expr): + if o.ufl_shape: + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(MathFunction('abs', Indexed(expr, indices)), indices) + else: + return MathFunction('abs', expr) + + def power(self, o, base, exponent): + return Power(base, exponent) + + def math_function(self, o, expr): + return MathFunction(o._name, expr) + + def min_value(self, o, *ops): + return MinValue(*ops) + + def max_value(self, o, *ops): + return MaxValue(*ops) + + def binary_condition(self, o, left, right): + return Comparison(o._name, left, right) + + def not_condition(self, o, expr): + return LogicalNot(expr) + + def and_condition(self, o, *ops): + return LogicalAnd(*ops) + + def or_condition(self, o, *ops): + return LogicalOr(*ops) + + def conditional(self, o, condition, then, else_): + assert o.ufl_shape == () # TODO + return Conditional(condition, then, else_) + + def multi_index(self, o): + indices = [] + for i in o: + if isinstance(i, ufl.classes.FixedIndex): + indices.append(int(i)) + elif isinstance(i, ufl.classes.Index): + indices.append(self.index_map[i.count()]) + return tuple(indices) + + def indexed(self, o, aggregate, index): + return Indexed(aggregate, index) + + def list_tensor(self, o, *ops): + nesting = [isinstance(op, ListTensor) for op in ops] + if all(nesting): + return ListTensor(numpy.array([op.array for op in ops])) + elif len(o.ufl_shape) > 1: + children = [] + for op in ops: + child = numpy.zeros(o.ufl_shape[1:], dtype=object) + for multiindex in numpy.ndindex(child.shape): + child[multiindex] = Indexed(op, multiindex) + children.append(child) + return ListTensor(numpy.array(children)) + else: + return ListTensor(numpy.array(ops)) + + def component_tensor(self, o, expression, index): + return ComponentTensor(expression, index) + + def index_sum(self, o, summand, indices): + index, = indices + + if o.ufl_shape: + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(IndexSum(Indexed(summand, indices), index), indices) + else: + return IndexSum(summand, index) + + +def inline_indices(expression): + result_cache = {} + + def cached_handle(node, subst): + cache_key = (node, tuple(sorted(subst.items()))) + try: + return result_cache[cache_key] + except KeyError: + result = handle(node, subst) + result_cache[cache_key] = result + return result + + @singledispatch + def handle(node, subst): + raise AssertionError("Cannot handle foreign type: %s" % type(node)) + + @handle.register(Node) # noqa: Not actually redefinition + def _(node, subst): + new_children = [cached_handle(child, subst) for child in node.children] + if all(nc == c for nc, c in zip(new_children, node.children)): + return node + else: + return node.reconstruct(*new_children) + + @handle.register(Indexed) # noqa: Not actually redefinition + def _(node, subst): + child, = node.children + multiindex = tuple(subst.get(i, i) for i in node.multiindex) + if isinstance(child, ComponentTensor): + new_subst = dict(zip(child.multiindex, multiindex)) + composed_subst = {k: new_subst.get(v, v) for k, v in subst.items()} + composed_subst.update(new_subst) + filtered_subst = {k: v for k, v in composed_subst.items() if k in child.children[0].free_indices} + return cached_handle(child.children[0], filtered_subst) + elif isinstance(child, ListTensor) and all(isinstance(i, int) for i in multiindex): + return cached_handle(child.array[multiindex], subst) + else: + new_child = cached_handle(child, subst) + if new_child == child and multiindex == node.multiindex: + return node + else: + return Indexed(new_child, multiindex) + + return cached_handle(expression, {}) + + +def collect_index_extents(expression): + result = collections.OrderedDict() + + for node in traversal([expression]): + if isinstance(node, Indexed): + assert len(node.multiindex) == len(node.children[0].shape) + for index, extent in zip(node.multiindex, node.children[0].shape): + if isinstance(index, Index): + if index not in result: + result[index] = extent + elif result[index] != extent: + raise AssertionError("Inconsistent index extents!") + + return result diff --git a/tsfc/fem.py b/tsfc/fem.py new file mode 100644 index 0000000000..b9e2699268 --- /dev/null +++ b/tsfc/fem.py @@ -0,0 +1,797 @@ +from __future__ import absolute_import + +import collections +import itertools + +import numpy +from singledispatch import singledispatch + +import ufl +from ufl.corealg.map_dag import map_expr_dag, map_expr_dags +from ufl.corealg.multifunction import MultiFunction +from ufl.classes import (Argument, CellEdgeVectors, CellFacetJacobian, + CellOrientation, Coefficient, FormArgument, + QuadratureWeight, ReferenceCellVolume, + ReferenceNormal, ReferenceValue, ScalarValue, + Zero) +from ufl.domain import find_geometric_dimension + +from tsfc.fiatinterface import create_element, as_fiat_cell + +from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal +from tsfc.constants import NUMPY_TYPE, PRECISION +from tsfc import einstein as ein +from tsfc.einstein import FromUFLMixin + + +# FFC uses one less digits for rounding than for printing +epsilon = eval("1e-%d" % (PRECISION - 1)) + + +class ReplaceSpatialCoordinates(MultiFunction): + + def __init__(self, coordinates): + self.coordinates = coordinates + MultiFunction.__init__(self) + + expr = MultiFunction.reuse_if_untouched + + def terminal(self, t): + return t + + def spatial_coordinate(self, o): + return ReferenceValue(self.coordinates) + + +class ModifiedTerminalMixin(object): + + def unexpected(self, o): + assert False, "Not expected %r at this stage." % o + + # global derivates should have been pulled back + grad = unexpected + div = unexpected + curl = unexpected + + # div and curl should have been algebraically lowered + reference_div = unexpected + reference_curl = unexpected + + def _modified_terminal(self, o): + assert is_modified_terminal(o) + return self.modified_terminal(o) + + # Unlike UFL, we do not regard Indexed as a terminal modifier. + # indexed = _modified_terminal + + positive_restricted = _modified_terminal + negative_restricted = _modified_terminal + + cell_avg = _modified_terminal + facet_avg = _modified_terminal + + reference_grad = _modified_terminal + reference_value = _modified_terminal + + terminal = _modified_terminal + + +class CollectModifiedTerminals(MultiFunction, ModifiedTerminalMixin): + + def __init__(self, return_list): + MultiFunction.__init__(self) + self.return_list = return_list + + def expr(self, o, *ops): + pass # operands visited + + def indexed(self, o, *ops): + pass # not a terminal modifier + + def multi_index(self, o): + pass # ignore + + def modified_terminal(self, o): + self.return_list.append(o) + + +class PickRestriction(MultiFunction, ModifiedTerminalMixin): + """Pick out parts of an expression with specified restrictions on + the arguments. + + :arg test: The restriction on the test function. + :arg trial: The restriction on the trial function. + + Returns those parts of the expression that have the requested + restrictions, or else :class:`ufl.classes.Zero` if no such part + exists. + """ + def __init__(self, test=None, trial=None): + self.restrictions = {0: test, 1: trial} + MultiFunction.__init__(self) + + expr = MultiFunction.reuse_if_untouched + + def multi_index(self, o): + return o + + def modified_terminal(self, o): + mt = analyse_modified_terminal(o) + t = mt.terminal + r = mt.restriction + if isinstance(t, Argument) and r != self.restrictions[t.number()]: + return Zero(o.ufl_shape, o.ufl_free_indices, o.ufl_index_dimensions) + else: + return o + + +class FindPolynomialDegree(MultiFunction): + + """Simple-minded degree estimator. + + Attempt to estimate the polynomial degree of an expression. Used + to determine whether something we're taking a gradient of is + cellwise constant. Returns either the degree of the expression, + or else ``None`` if the degree could not be determined. + + To do this properly, we'd need to carry around a tensor-valued + degree object such that we can determine when (say) d^2/dx^2 is + zero but d^2/dxdy is not. + + """ + def _spanning_degree(self, element): + """Determine the degree of the polynomial space spanning an element. + + :arg element: The element to determine the degree of. + + .. warning:: + + For non-simplex elements, this assumes a tensor-product + space. + """ + cell = element.cell() + if cell is None: + return element.degree() + if cell.cellname() in ("interval", "triangle", "tetrahedron"): + return element.degree() + elif cell.cellname() == "quadrilateral": + # TODO: Tensor-product space assumed + return 2*element.degree() + elif cell.cellname() == "OuterProductCell": + try: + return sum(element.degree()) + except TypeError: + assert element.degree() == 0 + return 0 + else: + raise ValueError("Unknown cell %s" % cell.cellname()) + + def quadrature_weight(self, o): + return 0 + + def multi_index(self, o): + return 0 + + # Default handler, no estimation. + def expr(self, o): + return None + + # Coefficient-like things, compute degree of spanning polynomial space + def spatial_coordinate(self, o): + return self._spanning_degree(o.ufl_domain().ufl_coordinate_element()) + + def form_argument(self, o): + return self._spanning_degree(o.ufl_element()) + + # Index-like operations, return degree of operand + def component_tensor(self, o, op, idx): + return op + + def indexed(self, o, op, idx): + return op + + def index_sum(self, o, op, idx): + return op + + def list_tensor(self, o, *ops): + if any(ops is None for op in ops): + return None + return max(*ops) + + # No change + def reference_value(self, o, op): + return op + + def restricted(self, o, op): + return op + + # Constants are constant + def constant_value(self, o): + return 0 + + # Multiplication adds degrees + def product(self, o, a, b): + if a is None or b is None: + return None + return a + b + + # If the degree of the exponent is zero, use degree of operand, + # otherwise don't guess. + def power(self, o, a, b): + if b == 0: + return a + return None + + # Pick maximal degree + def conditional(self, o, test, a, b): + if a is None or b is None: + return None + return max(a, b) + + def min_value(self, o, a, b): + if a is None or b is None: + return None + return max(a, b) + + def max_value(self, o, a, b): + if a is None or b is None: + return None + return max(a, b) + + def sum(self, o, a, b): + if a is None or b is None: + return None + return max(a, b) + + # If denominator is constant, use degree of numerator, otherwise + # don't guess + def division(self, o, a, b): + if b == 0: + return a + return None + + def abs(self, o, a): + if a == 0: + return a + return None + + # If operand is constant, return 0, otherwise don't guess. + def math_function(self, o, op): + if op == 0: + return 0 + return None + + # Reduce degrees! + def reference_grad(self, o, degree): + if degree is None: + return None + return max(degree - 1, 0) + + +class SimplifyExpr(MultiFunction): + """Apply some simplification passes to an expression.""" + + def __init__(self): + MultiFunction.__init__(self) + self.mapper = FindPolynomialDegree() + + expr = MultiFunction.reuse_if_untouched + + def reference_grad(self, o): + """Try and zero-simplify ``RGrad(expr)`` where the degree of + ``expr`` can be determined. + + Uses :class:`FindPolynomialDegree` to determine the degree of + ``expr``.""" + # Find degree of operand + degree = map_expr_dag(self.mapper, o.ufl_operands[0]) + # Either we have non-constant, or we didn't know, in which + # case return ourselves. + if degree is None or degree > 0: + return o + # We are RGrad(constant-function), return Zero of appropriate shape + op = o.ufl_operands[0] + gdim = find_geometric_dimension(op) + return ufl.classes.Zero(op.ufl_shape + (gdim, ), + op.ufl_free_indices, + op.ufl_index_dimensions) + + def abs(self, o, op): + """Convert Abs(CellOrientation * ...) -> Abs(...)""" + if isinstance(op, ufl.classes.CellOrientation): + # Cell orientation is +-1 + return ufl.classes.FloatValue(1) + if isinstance(op, ufl.classes.ScalarValue): + # Inline abs(constant) + return self.expr(op, abs(op._value)) + if isinstance(op, (ufl.classes.Division, ufl.classes.Product)): + # Visit children, distributing Abs + ops = tuple(map_expr_dag(self, ufl.classes.Abs(_)) + for _ in op.ufl_operands) + new_ops = [] + # Strip Abs off again (we'll put it outside the product now) + for _ in ops: + if isinstance(_, ufl.classes.Abs): + new_ops.append(_.ufl_operands[0]) + else: + new_ops.append(_) + # Rebuild product + new_prod = self.expr(op, *new_ops) + # Rebuild Abs + return self.expr(o, new_prod) + return self.expr(o, op) + + +class NumericTabulator(object): + + def __init__(self, points): + self.points = points + self.tables = {} + + def tabulate(self, ufl_element, max_deriv): + element = create_element(ufl_element) + phi = element.space_dimension() + C = ufl_element.reference_value_size() - len(ufl_element.symmetry()) + q = len(self.points) + for D, fiat_table in element.tabulate(max_deriv, self.points).iteritems(): + reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) + for c, table in enumerate(reordered_table): + # Copied from FFC (ffc/quadrature/quadratureutils.py) + table[abs(table) < epsilon] = 0 + table[abs(table - 1.0) < epsilon] = 1.0 + table[abs(table + 1.0) < epsilon] = -1.0 + table[abs(table - 0.5) < epsilon] = 0.5 + table[abs(table + 0.5) < epsilon] = -0.5 + self.tables[(ufl_element, c, D)] = table + + def __getitem__(self, key): + return self.tables[key] + + +class TabulationManager(object): + + def __init__(self, integral_type, cell, points): + self.integral_type = integral_type + self.cell = cell + self.points = points + + self.tabulators = [] + self.tables = {} + + if integral_type == 'cell': + self.tabulators.append(NumericTabulator(points)) + + elif integral_type in ['exterior_facet', 'interior_facet']: + # TODO: handle and test integration on facets of intervals + + for entity in range(cell.num_facets()): + t = as_fiat_cell(cell).get_facet_transform(entity) + self.tabulators.append(NumericTabulator(numpy.asarray(map(t, points)))) + + elif integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: + for entity in range(2): # top and bottom + t = as_fiat_cell(cell).get_horiz_facet_transform(entity) + self.tabulators.append(NumericTabulator(numpy.asarray(map(t, points)))) + + elif integral_type in ['exterior_facet_vert', 'interior_facet_vert']: + for entity in range(cell._A.num_facets()): # "base cell" facets + t = as_fiat_cell(cell).get_vert_facet_transform(entity) + self.tabulators.append(NumericTabulator(numpy.asarray(map(t, points)))) + + else: + raise NotImplementedError("integral type %s not supported" % integral_type) + + if integral_type in ['exterior_facet', 'exterior_facet_vert']: + self.facet = {None: ein.VariableIndex('facet[0]')} + elif integral_type in ['interior_facet', 'interior_facet_vert']: + self.facet = {'+': ein.VariableIndex('facet[0]'), + '-': ein.VariableIndex('facet[1]')} + elif integral_type == 'exterior_facet_bottom': + self.facet = {None: 0} + elif integral_type == 'exterior_facet_top': + self.facet = {None: 1} + elif integral_type == 'interior_facet_horiz': + self.facet = {'+': 1, '-': 0} + else: + self.facet = None + + def tabulate(self, ufl_element, max_deriv): + for tabulator in self.tabulators: + tabulator.tabulate(ufl_element, max_deriv) + + def get(self, key, restriction, cellwise_constant=False): + try: + table = self.tables[(key, cellwise_constant)] + except KeyError: + tables = [tabulator[key] for tabulator in self.tabulators] + if cellwise_constant: + tables = [table[0] for table in tables] + + if self.integral_type == 'cell': + table, = tables + else: + table = numpy.array(tables) + + self.tables[(key, cellwise_constant)] = table + + if self.integral_type == 'cell': + return ein.Literal(table) + else: + f = self.facet[restriction] + + indices = tuple(ein.Index() for i in range(len(table.shape)-1)) + return ein.ComponentTensor( + ein.Indexed( + ein.Literal(table), + (f,) + indices), + indices) + + +class Translator(MultiFunction, ModifiedTerminalMixin, FromUFLMixin): + + def __init__(self, weights, quadrature_index, argument_indices, tabulation_manager, coefficient_map): + MultiFunction.__init__(self) + FromUFLMixin.__init__(self) + self.weights = ein.Literal(weights) + self.quadrature_index = quadrature_index + self.argument_indices = argument_indices + self.tabulation_manager = tabulation_manager + self.coefficient_map = coefficient_map + self.cell_orientations = False + self.facet = tabulation_manager.facet + + def modified_terminal(self, o): + mt = analyse_modified_terminal(o) + return translate(mt.terminal, o, mt, self) + + +def table_keys(ufl_element, local_derivatives): + # TODO: + # Consider potential duplicate calculation due to second + # derivatives and symmetries. + + size = ufl_element.reference_value_size() + dim = ufl_element.cell().topological_dimension() + + def flat_index(ordered_deriv): + result = [0] * dim + for i in ordered_deriv: + result[i] += 1 + return tuple(result) + + ordered_derivs = itertools.product(range(dim), repeat=local_derivatives) + flat_derivs = map(flat_index, ordered_derivs) + + return [(ufl_element, c, flat_deriv) + for c in xrange(size) + for flat_deriv in flat_derivs] + + +@singledispatch +def translate(terminal, e, mt, params): + raise AssertionError("Cannot handle terminal type: %s" % type(terminal)) + + +@translate.register(Zero) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + assert False + + +@translate.register(ScalarValue) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + assert False + + +@translate.register(QuadratureWeight) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + return ein.Indexed(params.weights, (params.quadrature_index,)) + + +@translate.register(Argument) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + argument_index = params.argument_indices[terminal.number()] + + result = numpy.zeros(e.ufl_shape, dtype=object) + for multiindex, key in zip(numpy.ndindex(e.ufl_shape), + table_keys(terminal.ufl_element(), + mt.local_derivatives)): + table = params.tabulation_manager.get(key, mt.restriction) + result[multiindex] = ein.Indexed(table, (params.quadrature_index, argument_index)) + + if result.shape: + return ein.ListTensor(result) + else: + return result[()] + + +@translate.register(Coefficient) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + degree = map_expr_dag(FindPolynomialDegree(), e) + cellwise_constant = not (degree is None or degree > 0) + + def evaluate_at(params, key): + table = params.tabulation_manager.get(key, mt.restriction, cellwise_constant) + kernel_argument = params.coefficient_map[terminal] + + q = ein.Index() + r = ein.Index() + + if mt.restriction is None: + kar = ein.Indexed(kernel_argument, (r,)) + elif mt.restriction is '+': + kar = ein.Indexed(kernel_argument, (0, r)) + elif mt.restriction is '-': + kar = ein.Indexed(kernel_argument, (1, r)) + else: + assert False + + if cellwise_constant: + return ein.IndexSum(ein.Product(ein.Indexed(table, (r,)), kar), r) + else: + return ein.Indexed( + ein.ComponentTensor( + ein.IndexSum( + ein.Product(ein.Indexed(table, (q, r)), + kar), + r), + (q,)), + (params.quadrature_index,)) + + if terminal.ufl_element().family() == 'Real': + assert mt.local_derivatives == 0 + return params.coefficient_map[terminal] + + result = numpy.zeros(e.ufl_shape, dtype=object) + for multiindex, key in zip(numpy.ndindex(e.ufl_shape), + table_keys(terminal.ufl_element(), + mt.local_derivatives)): + result[multiindex] = evaluate_at(params, key) + + if result.shape: + return ein.ListTensor(result) + else: + return result[()] + + +@translate.register(CellFacetJacobian) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + i = ein.Index() + j = ein.Index() + f = params.facet[mt.restriction] + table = make_cell_facet_jacobian(terminal) + if params.tabulation_manager.integral_type in ["exterior_facet_bottom", + "exterior_facet_top", + "interior_facet_horiz"]: + table = table[:2] + elif params.tabulation_manager.integral_type in ["exterior_facet_vert", "interior_facet_vert"]: + table = table[2:] + return ein.ComponentTensor( + ein.Indexed( + ein.Literal(table), + (f, i, j)), + (i, j)) + + +@translate.register(ReferenceNormal) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + i = ein.Index() + f = params.facet[mt.restriction] + table = make_reference_normal(terminal) + if params.tabulation_manager.integral_type in ["exterior_facet_bottom", + "exterior_facet_top", + "interior_facet_horiz"]: + table = table[:2] + elif params.tabulation_manager.integral_type in ["exterior_facet_vert", "interior_facet_vert"]: + table = table[2:] + return ein.ComponentTensor( + ein.Indexed( + ein.Literal(table), + (f, i,)), + (i,)) + + +@translate.register(CellOrientation) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + if mt.restriction == '+' or mt.restriction is None: + f = 0 + elif mt.restriction == '-': + f = 1 + else: + assert False + params.cell_orientations = True + raw = ein.Indexed(ein.Variable("cell_orientations", (2, 1)), (f, 0)) # TODO: (2, 1) and (1, 1) + return ein.Conditional(ein.Comparison("==", raw, ein.Literal(1)), + ein.Literal(-1), + ein.Conditional(ein.Comparison("==", raw, ein.Zero()), + ein.Literal(1), + ein.Literal(numpy.nan))) + + +@translate.register(ReferenceCellVolume) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + cell = terminal.ufl_domain().ufl_cell() + volume = {ufl.Cell("interval"): 1.0, + ufl.Cell("triangle"): 1.0/2.0, + ufl.Cell("quadrilateral"): 1.0, + ufl.Cell("tetrahedron"): 1.0/6.0, + ufl.OuterProductCell(ufl.Cell("interval"), ufl.Cell("interval")): 1.0, + ufl.OuterProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): 1.0/2.0, + ufl.OuterProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): 1.0} + return ein.Literal(volume[cell]) + + +@translate.register(CellEdgeVectors) # noqa: Not actually redefinition +def _(terminal, e, mt, params): + return ein.Literal(make_cell_edge_vectors(terminal)) + + +def coordinate_coefficient(domain): + return ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) + + +def replace_coordinates(integrand, coordinate_coefficient): + # Replace SpatialCoordinate nodes with Coefficients + return map_expr_dag(ReplaceSpatialCoordinates(coordinate_coefficient), integrand) + + +def process(integral_type, integrand, tabulation_manager, quadrature_weights, argument_indices, coefficient_map): + # Abs-simplification + integrand = map_expr_dag(SimplifyExpr(), integrand) + + # Collect modified terminals + modified_terminals = [] + map_expr_dag(CollectModifiedTerminals(modified_terminals), integrand) + + # Collect maximal derivatives that needs tabulation + max_derivs = collections.defaultdict(int) + + for mt in map(analyse_modified_terminal, modified_terminals): + if isinstance(mt.terminal, FormArgument): + ufl_element = mt.terminal.ufl_element() + max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) + + # Collect tabulations for all components and derivatives + for ufl_element, max_deriv in max_derivs.items(): + if ufl_element.family() != 'Real': + tabulation_manager.tabulate(ufl_element, max_deriv) + + if integral_type.startswith("interior_facet"): + expressions = [] + for rs in itertools.product(("+", "-"), repeat=len(argument_indices)): + expressions.append(map_expr_dag(PickRestriction(*rs), integrand)) + else: + expressions = [integrand] + + # Translate UFL to Einstein's notation, + # lowering finite element specific nodes + quadrature_index = ein.Index() + + translator = Translator(quadrature_weights, quadrature_index, + argument_indices, tabulation_manager, + coefficient_map) + return quadrature_index, map_expr_dags(translator, expressions), translator.cell_orientations + + +def make_cell_facet_jacobian(terminal): + + interval = numpy.array([[1.0], + [1.0]], dtype=NUMPY_TYPE) + + triangle = numpy.array([[-1.0, 1.0], + [0.0, 1.0], + [1.0, 0.0]], dtype=NUMPY_TYPE) + + tetrahedron = numpy.array([[-1.0, -1.0, 1.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]], dtype=NUMPY_TYPE) + + quadrilateral = numpy.array([[0.0, 1.0], + [0.0, 1.0], + [1.0, 0.0], + [1.0, 0.0]], dtype=NUMPY_TYPE) + + # Outer product cells + # Convention is: + # Bottom facet, top facet, then the extruded facets in the order + # of the base cell + interval_x_interval = numpy.array([[1.0, 0.0], + [1.0, 0.0], + [0.0, 1.0], + [0.0, 1.0]], dtype=NUMPY_TYPE) + + triangle_x_interval = numpy.array([[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [-1.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0]], dtype=NUMPY_TYPE) + + quadrilateral_x_interval = numpy.array([[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0]], + dtype=NUMPY_TYPE) + + cell = terminal.ufl_domain().ufl_cell() + cell = cell.reconstruct(geometric_dimension=cell.topological_dimension()) + + cell_to_table = {ufl.Cell("interval"): interval, + ufl.Cell("triangle"): triangle, + ufl.Cell("quadrilateral"): quadrilateral, + ufl.Cell("tetrahedron"): tetrahedron, + ufl.OuterProductCell(ufl.Cell("interval"), ufl.Cell("interval")): interval_x_interval, + ufl.OuterProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): triangle_x_interval, + ufl.OuterProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): quadrilateral_x_interval} + + table = cell_to_table[cell] + + shape = table.shape[:1] + terminal.ufl_shape + return table.reshape(shape) + + +def make_reference_normal(terminal): + interval = numpy.array([[-1.0], + [1.0]], dtype=NUMPY_TYPE) + + triangle = numpy.array([[1.0, 1.0], + [-1.0, 0.0], + [0.0, -1.]], dtype=NUMPY_TYPE) + + tetrahedron = numpy.array([[1.0, 1.0, 1.0], + [-1.0, 0.0, 0.0], + [0.0, -1.0, 0.0], + [0.0, 0.0, -1.0]], dtype=NUMPY_TYPE) + + quadrilateral = numpy.array([[-1.0, 0.0], + [1.0, 0.0], + [0.0, -1.0], + [0.0, 1.0]], dtype=NUMPY_TYPE) + + interval_x_interval = numpy.array([[0.0, -1.0], + [0.0, 1.0], + [-1.0, 0.0], + [1.0, 0.0]], dtype=NUMPY_TYPE) + + triangle_x_interval = numpy.array([[0.0, 0.0, -1.0], + [0.0, 0.0, 1.0], + [1.0, 1.0, 0.0], + [-1.0, 0.0, 0.0], + [0.0, -1.0, 0.0]], dtype=NUMPY_TYPE) + + quadrilateral_x_interval = numpy.array([[0.0, 0.0, -1.0], + [0.0, 0.0, 1.0], + [-1.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, -1.0, 0.0], + [0.0, 1.0, 0.0]], dtype=NUMPY_TYPE) + + cell = terminal.ufl_domain().ufl_cell() + cell = cell.reconstruct(geometric_dimension=cell.topological_dimension()) + + cell_to_table = {ufl.Cell("interval"): interval, + ufl.Cell("triangle"): triangle, + ufl.Cell("quadrilateral"): quadrilateral, + ufl.Cell("tetrahedron"): tetrahedron, + ufl.OuterProductCell(ufl.Cell("interval"), ufl.Cell("interval")): interval_x_interval, + ufl.OuterProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): triangle_x_interval, + ufl.OuterProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): quadrilateral_x_interval} + + table = cell_to_table[cell] + + shape = table.shape[:1] + terminal.ufl_shape + return table.reshape(shape) + + +def make_cell_edge_vectors(terminal): + from FIAT.reference_element import two_product_cell + shape = terminal.ufl_shape + cell = as_fiat_cell(terminal.ufl_domain().ufl_cell()) + if isinstance(cell, two_product_cell): + raise NotImplementedError("CEV not implemented on OPEs yet") + nedge = len(cell.get_topology()[1]) + vecs = numpy.vstack(tuple(cell.compute_edge_tangent(i) for i in range(nedge))).astype(NUMPY_TYPE) + + assert vecs.shape == shape + return vecs diff --git a/tsfc/impero.py b/tsfc/impero.py new file mode 100644 index 0000000000..210afc7522 --- /dev/null +++ b/tsfc/impero.py @@ -0,0 +1,62 @@ +from __future__ import absolute_import + +from tsfc.node import Node as node_Node + + +class Node(node_Node): + __slots__ = () + + +class Terminal(Node): + __slots__ = () + + children = () + + +class Evaluate(Terminal): + __slots__ = ('expression',) + __front__ = ('expression',) + + def __init__(self, expression): + self.expression = expression + + +class Initialise(Terminal): + __slots__ = ('indexsum',) + __front__ = ('indexsum',) + + def __init__(self, indexsum): + self.indexsum = indexsum + + +class Accumulate(Terminal): + __slots__ = ('indexsum',) + __front__ = ('indexsum',) + + def __init__(self, indexsum): + self.indexsum = indexsum + + +class Return(Terminal): + __slots__ = ('variable', 'expression') + __front__ = ('variable', 'expression') + + def __init__(self, variable, expression): + self.variable = variable + self.expression = expression + + +class Block(Node): + __slots__ = ('children',) + + def __init__(self, statements): + self.children = tuple(statements) + + +class For(Node): + __slots__ = ('index', 'children') + __front__ = ('index',) + + def __init__(self, index, statement): + self.index = index + self.children = (statement,) diff --git a/tsfc/modified_terminals.py b/tsfc/modified_terminals.py new file mode 100644 index 0000000000..6fda005675 --- /dev/null +++ b/tsfc/modified_terminals.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2011-2015 Martin Sandve Alnæs +# +# This file is part of UFLACS. +# +# UFLACS is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# UFLACS is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with UFLACS. If not, see . +# +# Modified by Miklós Homolya, 2016. + +"""Definitions of 'modified terminals', a core concept in uflacs.""" + +from __future__ import print_function # used in some debugging + +from ufl.classes import (ReferenceValue, ReferenceGrad, + Restricted, FacetAvg, CellAvg) + + +class ModifiedTerminal(object): + + """A modified terminal expression is an object of a Terminal subtype, wrapped in terminal modifier types. + + The variables of this class are: + + expr - The original UFL expression + + terminal - the underlying Terminal object + local_derivatives - tuple of ints, each meaning derivative in that local direction + reference_value - bool, whether this is represented in reference frame + averaged - None, 'facet' or 'cell' + restriction - None, '+' or '-' + """ + + def __init__(self, expr, terminal, local_derivatives, averaged, restriction, reference_value): + # The original expression + self.expr = expr + + # The underlying terminal expression + self.terminal = terminal + + # Components + self.reference_value = reference_value + self.restriction = restriction + + # Derivatives + self.local_derivatives = local_derivatives + + # Evaluation method (alternative: { None, 'facet_midpoint', 'cell_midpoint', 'facet_avg', 'cell_avg' }) + self.averaged = averaged + + def as_tuple(self): + t = self.terminal + rv = self.reference_value + ld = self.local_derivatives + a = self.averaged + r = self.restriction + return (t, rv, ld, a, r) + + def __hash__(self): + return hash(self.as_tuple()) + + def __eq__(self, other): + return isinstance(other, ModifiedTerminal) and self.as_tuple() == other.as_tuple() + + def __lt__(self, other): + return self.as_tuple() < other.as_tuple() + + def __str__(self): + s = [] + s += ["terminal: {0}".format(self.terminal)] + s += ["local_derivatives: {0}".format(self.local_derivatives)] + s += ["averaged: {0}".format(self.averaged)] + s += ["restriction: {0}".format(self.restriction)] + return '\n'.join(s) + + +def is_modified_terminal(v): + "Check if v is a terminal or a terminal wrapped in terminal modifier types." + while not v._ufl_is_terminal_: + if v._ufl_is_terminal_modifier_: + v = v.ufl_operands[0] + else: + return False + return True + + +def strip_modified_terminal(v): + "Extract core Terminal from a modified terminal or return None." + while not v._ufl_is_terminal_: + if v._ufl_is_terminal_modifier_: + v = v.ufl_operands[0] + else: + return None + return v + + +def analyse_modified_terminal(expr): + """Analyse a so-called 'modified terminal' expression and return its properties in more compact form. + + A modified terminal expression is an object of a Terminal subtype, wrapped in terminal modifier types. + + The wrapper types can include 0-* Grad or ReferenceGrad objects, + and 0-1 ReferenceValue, 0-1 Restricted, 0-1 Indexed, and 0-1 FacetAvg or CellAvg objects. + """ + # Data to determine + local_derivatives = 0 + reference_value = None + restriction = None + averaged = None + + # Start with expr and strip away layers of modifiers + t = expr + while not t._ufl_is_terminal_: + if isinstance(t, ReferenceValue): + assert reference_value is None, "Got twice pulled back terminal!" + reference_value = True + t, = t.ufl_operands + + elif isinstance(t, ReferenceGrad): + local_derivatives += 1 + t, = t.ufl_operands + + elif isinstance(t, Restricted): + assert restriction is None, "Got twice restricted terminal!" + restriction = t._side + t, = t.ufl_operands + + elif isinstance(t, CellAvg): + assert averaged is None, "Got twice averaged terminal!" + averaged = "cell" + t, = t.ufl_operands + + elif isinstance(t, FacetAvg): + assert averaged is None, "Got twice averaged terminal!" + averaged = "facet" + t, = t.ufl_operands + + elif t._ufl_terminal_modifiers_: + raise ValueError("Missing handler for terminal modifier type %s, object is %s." % (type(t), repr(t))) + + else: + raise ValueError("Unexpected type %s object %s." % (type(t), repr(t))) + + # Make reference_value true or false + if reference_value is None: + reference_value = False + + mt = ModifiedTerminal(expr, t, local_derivatives, averaged, restriction, reference_value) + + if local_derivatives and not reference_value: + raise ValueError("Local derivatives of non-local value?") + + return mt diff --git a/tsfc/node.py b/tsfc/node.py new file mode 100644 index 0000000000..3e1f4c8675 --- /dev/null +++ b/tsfc/node.py @@ -0,0 +1,67 @@ +from __future__ import absolute_import + + +class Node(object): + __slots__ = ('hash_value',) + + __front__ = () + __back__ = () + + def __getinitargs__(self, children): + front_args = [getattr(self, name) for name in self.__front__] + back_args = [getattr(self, name) for name in self.__back__] + + return tuple(front_args) + tuple(children) + tuple(back_args) + + def reconstruct(self, *args): + return type(self)(*self.__getinitargs__(args)) + + def __repr__(self): + init_args = self.__getinitargs__(self.children) + return "%s(%s)" % (type(self).__name__, ", ".join(map(repr, init_args))) + + def __eq__(self, other): + """Provides equality testing with quick positive and negative + paths based on :func:`id` and :meth:`__hash__`. + """ + if self is other: + return True + elif hash(self) != hash(other): + return False + else: + return self.is_equal(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + """Provides caching for hash values.""" + try: + return self.hash_value + except AttributeError: + self.hash_value = self.get_hash() + return self.hash_value + + def is_equal(self, other): + if type(self) != type(other): + return False + self_initargs = self.__getinitargs__(self.children) + other_initargs = other.__getinitargs__(other.children) + return self_initargs == other_initargs + + def get_hash(self): + return hash((type(self),) + self.__getinitargs__(self.children)) + + +def traversal(expression_dags): + """Pre-order traversal of the nodes of expression DAGs.""" + seen = set(expression_dags) + lifo = list(expression_dags) + + while lifo: + node = lifo.pop() + yield node + for child in node.children: + if child not in seen: + seen.add(child) + lifo.append(child) diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py new file mode 100644 index 0000000000..24713a7f8f --- /dev/null +++ b/tsfc/scheduling.py @@ -0,0 +1,174 @@ +from __future__ import absolute_import + +import collections + +from singledispatch import singledispatch + +from tsfc import einstein as ein, impero as imp +from tsfc.node import traversal + + +class OrderedDefaultDict(collections.OrderedDict): + """A dictionary that provides a default value and ordered iteration. + + :arg factory: The callable used to create the default value. + + See :class:`collections.OrderedDict` for description of the + remaining arguments. + """ + def __init__(self, factory, *args, **kwargs): + self.factory = factory + super(OrderedDefaultDict, self).__init__(*args, **kwargs) + + def __missing__(self, key): + val = self[key] = self.factory() + return val + + +class Queue(object): + def __init__(self, reference_count, get_indices): + self.waiting = reference_count.copy() + # Need to have deterministic iteration over the queue. + self.queue = OrderedDefaultDict(list) + self.get_indices = get_indices + + def reference(self, o): + if o not in self.waiting: + return + + assert 1 <= self.waiting[o] + + self.waiting[o] -= 1 + if self.waiting[o] == 0: + self.insert(o, self.get_indices(o)) + + def insert(self, o, indices): + self.queue[indices].append(o) + + def __iter__(self): + indices = () + while self.queue: + # Find innermost non-empty outer loop + while indices not in (i[:len(indices)] for i in self.queue.keys()): + indices = indices[:-1] + + # Pick a loop + for i in self.queue.keys(): + if i[:len(indices)] == indices: + indices = i + break + + while self.queue[indices]: + yield self.queue[indices].pop() + del self.queue[indices] + + +def count_references(expressions): + result = collections.Counter(expressions) + for node in traversal(expressions): + result.update(node.children) + return result + + +@singledispatch +def impero_indices(node, indices): + raise AssertionError("Cannot handle type: %s" % type(node)) + + +@impero_indices.register(imp.Return) # noqa: Not actually redefinition +def _(node, indices): + assert set(node.variable.free_indices) >= set(node.expression.free_indices) + return indices(node.variable) + + +@impero_indices.register(imp.Initialise) # noqa: Not actually redefinition +def _(node, indices): + return indices(node.indexsum) + + +@impero_indices.register(imp.Accumulate) # noqa: Not actually redefinition +def _(node, indices): + return indices(node.indexsum.children[0]) + + +@impero_indices.register(imp.Evaluate) # noqa: Not actually redefinition +def _(node, indices): + return indices(node.expression) + + +@singledispatch +def handle(node, enqueue, emit): + raise AssertionError("Cannot handle foreign type: %s" % type(node)) + + +@handle.register(ein.Node) # noqa: Not actually redefinition +def _(node, enqueue, emit): + emit(imp.Evaluate(node)) + for child in node.children: + enqueue(child) + + +@handle.register(ein.Variable) # noqa: Not actually redefinition +def _(node, enqueue, emit): + pass + + +@handle.register(ein.Literal) # noqa: Not actually redefinition +@handle.register(ein.Zero) +def _(node, enqueue, emit): + if node.shape: + emit(imp.Evaluate(node)) + + +@handle.register(ein.Indexed) # noqa: Not actually redefinition +def _(node, enqueue, emit): + enqueue(node.children[0]) + + +@handle.register(ein.IndexSum) # noqa: Not actually redefinition +def _(node, enqueue, emit): + enqueue(imp.Accumulate(node)) + + +@handle.register(imp.Initialise) # noqa: Not actually redefinition +def _(op, enqueue, emit): + emit(op) + + +@handle.register(imp.Accumulate) # noqa: Not actually redefinition +def _(op, enqueue, emit): + emit(op) + enqueue(imp.Initialise(op.indexsum)) + enqueue(op.indexsum.children[0]) + + +@handle.register(imp.Return) # noqa: Not actually redefinition +def _(op, enqueue, emit): + emit(op) + enqueue(op.expression) + + +def make_ordering(assignments, indices_map): + assignments = filter(lambda x: not isinstance(x[1], ein.Zero), assignments) + expressions = [expression for variable, expression in assignments] + queue = Queue(count_references(expressions), indices_map) + + for variable, expression in assignments: + queue.insert(imp.Return(variable, expression), indices_map(expression)) + + result = [] + + def emit(op): + return result.append((impero_indices(op, indices_map), op)) + + def enqueue(item): + if isinstance(item, ein.Node): + queue.reference(item) + elif isinstance(item, imp.Node): + queue.insert(item, impero_indices(item, indices_map)) + else: + raise AssertionError("should never happen") + + for o in queue: + handle(o, enqueue, emit) + return list(reversed(result)) From 363be1e9cb9e1fef0ff50b127c2c34c98b14ddb1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 1 Feb 2016 14:25:56 +0000 Subject: [PATCH 005/809] remove FFC dependecy --- tsfc/driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b04634a9b5..ce6c8078fa 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -5,8 +5,8 @@ import collections from ufl.algorithms import compute_form_data +from ufl.log import GREEN -from ffc.log import info_green from tsfc.fiatinterface import create_element from tsfc.mixedelement import MixedElement as ffc_MixedElement from tsfc.quadrature import create_quadrature, QuadratureRule @@ -186,7 +186,7 @@ def compile_integral(integral, idata, fd, prefix, parameters): pred=["static", "inline"]) kernel.ast = ast - info_green("TSFC finished in %g seconds." % (time.time() - cpu_time)) + print GREEN % ("TSFC finished in %g seconds." % (time.time() - cpu_time)) return kernel From 38f983dcfe4b175203313021b93c53068de0b694 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 1 Feb 2016 14:29:29 +0000 Subject: [PATCH 006/809] flake8 fixes --- tsfc/driver.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index ce6c8078fa..2b8040d65f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -181,8 +181,7 @@ def compile_integral(integral, idata, fd, prefix, parameters): body.open_scope = False funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) - ast = coffee.FunDecl("void", funname, arglist, coffee.Block(prepare + [body] - + finalise), + ast = coffee.FunDecl("void", funname, arglist, coffee.Block(prepare + [body] + finalise), pred=["static", "inline"]) kernel.ast = ast From cde0c77b982a3483384af5b31e0b1491e06c4bdb Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 1 Feb 2016 17:48:42 +0000 Subject: [PATCH 007/809] generate better code faster --- tsfc/driver.py | 3 ++- tsfc/einstein.py | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2b8040d65f..15cc70ab5d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -151,7 +151,8 @@ def compile_integral(integral, idata, fd, prefix, parameters): quadrature_index, nonfem, cell_orientations = \ fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, argument_indices, coefficient_map) nonfem = [ein.IndexSum(e, quadrature_index) for e in nonfem] - simplified = [ein.inline_indices(e) for e in nonfem] + inlining_cache = {} + simplified = [ein.inline_indices(e, inlining_cache) for e in nonfem] if cell_orientations: decl = coffee.Decl("const int *restrict *restrict", coffee.Symbol("cell_orientations")) diff --git a/tsfc/einstein.py b/tsfc/einstein.py index 93fcf2b1e3..2d8fe7f617 100644 --- a/tsfc/einstein.py +++ b/tsfc/einstein.py @@ -470,9 +470,7 @@ def index_sum(self, o, summand, indices): return IndexSum(summand, index) -def inline_indices(expression): - result_cache = {} - +def inline_indices(expression, result_cache): def cached_handle(node, subst): cache_key = (node, tuple(sorted(subst.items()))) try: From 68fb3472e8e5b85ffabf88ddf2bbedba5a4e3118 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 2 Feb 2016 10:48:52 +0000 Subject: [PATCH 008/809] Handle Variable and Label in FromUFLMixin --- tsfc/einstein.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tsfc/einstein.py b/tsfc/einstein.py index 2d8fe7f617..22f933a036 100644 --- a/tsfc/einstein.py +++ b/tsfc/einstein.py @@ -469,6 +469,14 @@ def index_sum(self, o, summand, indices): else: return IndexSum(summand, index) + def variable(self, o, expression, label): + """Only used by UFL AD, at this point, the bare expression is what we want.""" + return expression + + def label(self, o): + """Only used by UFL AD, don't need it at this point.""" + pass + def inline_indices(expression, result_cache): def cached_handle(node, subst): From 816035c51eb0b431900eb6d7f91f4e22da1557f9 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 2 Feb 2016 13:27:43 +0000 Subject: [PATCH 009/809] Fix order of parameter updates --- tsfc/driver.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 15cc70ab5d..8887277bbf 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -72,6 +72,12 @@ def __init__(self, ast=None, integral_type=None, oriented=False, def compile_integral(integral, idata, fd, prefix, parameters): cpu_time = time.time() + # Remove these here, they're handled below. + if parameters.get("quadrature_degree") == "auto": + del parameters["quadrature_degree"] + if parameters.get("quadrature_rule") == "auto": + del parameters["quadrature_rule"] + _ = {} # Record per-integral parameters _.update(integral.metadata()) @@ -79,10 +85,6 @@ def compile_integral(integral, idata, fd, prefix, parameters): _.update(parameters) parameters = _ - if parameters.get("quadrature_degree") == "auto": - del parameters["quadrature_degree"] - if parameters.get("quadrature_rule") == "auto": - del parameters["quadrature_rule"] # Check if the integral has a quad degree attached, otherwise use # the estimated polynomial degree attached by compute_form_data quadrature_degree = parameters.get("quadrature_degree", From 853e0698890902302f8364b81b63ebd04a04efe0 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 2 Feb 2016 17:10:30 +0000 Subject: [PATCH 010/809] Print timings for stages --- tsfc/driver.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 8887277bbf..84c1f1ce60 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -19,6 +19,8 @@ def compile_form(form, prefix="form", parameters=None): + cpu_time = time.time() + assert not isinstance(form, (list, tuple)) if parameters is None: @@ -34,15 +36,20 @@ def compile_form(form, prefix="form", parameters=None): do_apply_geometry_lowering=True, do_apply_restrictions=True, do_estimate_degrees=True) + print GREEN % ("compute_form_data finished in %g seconds." % (time.time() - cpu_time)) kernels = [] for idata in fd.integral_data: if len(idata.integrals) != 1: raise NotImplementedError("Don't support IntegralData with more than one integral") for integral in idata.integrals: + start = time.time() kernel = compile_integral(integral, idata, fd, prefix, parameters) if kernel is not None: + print GREEN % ("compile_integral finished in %g seconds." % (time.time() - start)) kernels.append(kernel) + + print GREEN % ("TSFC finished in %g seconds." % (time.time() - cpu_time)) return kernels @@ -70,8 +77,6 @@ def __init__(self, ast=None, integral_type=None, oriented=False, def compile_integral(integral, idata, fd, prefix, parameters): - cpu_time = time.time() - # Remove these here, they're handled below. if parameters.get("quadrature_degree") == "auto": del parameters["quadrature_degree"] @@ -188,7 +193,6 @@ def compile_integral(integral, idata, fd, prefix, parameters): pred=["static", "inline"]) kernel.ast = ast - print GREEN % ("TSFC finished in %g seconds." % (time.time() - cpu_time)) return kernel From f92ae0129a3e180d69fa9c7b420b6a0764a9bbaa Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 2 Feb 2016 15:02:40 +0000 Subject: [PATCH 011/809] CSE in equality comparison --- tsfc/einstein.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tsfc/einstein.py b/tsfc/einstein.py index 22f933a036..e7571a9892 100644 --- a/tsfc/einstein.py +++ b/tsfc/einstein.py @@ -7,7 +7,7 @@ import ufl -from tsfc.node import Node as node_Node, traversal +from tsfc.node import Node as NodeBase, traversal class NodeMeta(type): @@ -25,11 +25,17 @@ def __call__(self, *args, **kwargs): return obj -class Node(node_Node): +class Node(NodeBase): __metaclass__ = NodeMeta __slots__ = ('free_indices') + def is_equal(self, other): + result = NodeBase.is_equal(self, other) + if result: + self.children = other.children + return result + class Scalar(Node): __slots__ = () @@ -46,6 +52,9 @@ def __init__(self, shape=()): children = () + def is_equal(self, other): + return NodeBase.is_equal(self, other) + @property def value(self): assert not self.shape @@ -360,9 +369,10 @@ def __repr__(self): def is_equal(self, other): if type(self) != type(other): return False - if self.shape != other.shape: - return False - return self.children == other.children + result = (self.array == other.array).all() + if result: + self.array = other.array + return result def get_hash(self): return hash((type(self), self.shape, self.children)) From 34b294adba2bc41aa703cfb1d72be36679036633 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 2 Feb 2016 17:44:07 +0000 Subject: [PATCH 012/809] einstein: Add Terminal class --- tsfc/einstein.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tsfc/einstein.py b/tsfc/einstein.py index e7571a9892..3954149ee2 100644 --- a/tsfc/einstein.py +++ b/tsfc/einstein.py @@ -37,31 +37,34 @@ def is_equal(self, other): return result +class Terminal(Node): + __slots__ = () + + children = () + + is_equal = NodeBase.is_equal + + class Scalar(Node): __slots__ = () shape = () -class Zero(Node): +class Zero(Terminal): __slots__ = ('shape',) __front__ = ('shape',) def __init__(self, shape=()): self.shape = shape - children = () - - def is_equal(self, other): - return NodeBase.is_equal(self, other) - @property def value(self): assert not self.shape return 0.0 -class Literal(Node): +class Literal(Terminal): __slots__ = ('array',) __front__ = ('array',) @@ -75,8 +78,6 @@ def __new__(cls, array): def __init__(self, array): self.array = numpy.asarray(array, dtype=float) - children = () - def is_equal(self, other): if type(self) != type(other): return False @@ -96,7 +97,7 @@ def shape(self): return self.array.shape -class Variable(Node): +class Variable(Terminal): __slots__ = ('name', 'shape') __front__ = ('name', 'shape') @@ -104,8 +105,6 @@ def __init__(self, name, shape): self.name = name self.shape = shape - children = () - class Sum(Scalar): __slots__ = ('children',) From d72acdf9993b894c41f2322b1cbeac558599af65 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 3 Feb 2016 12:12:25 +0000 Subject: [PATCH 013/809] document node.py --- tsfc/node.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tsfc/node.py b/tsfc/node.py index 3e1f4c8675..853fda5e90 100644 --- a/tsfc/node.py +++ b/tsfc/node.py @@ -1,19 +1,51 @@ +"""Generic abstract node class and utility functions for creating +expression DAG languages.""" + from __future__ import absolute_import class Node(object): + """Abstract node class. + + Nodes are not meant to be modified. + + A node can reference other nodes; they are called children. A node + might contain data, or reference other objects which are not + themselves nodes; they are not called children. + + Both the children (if any) and non-child data (if any) are + required to create a node, or determine the equality of two + nodes. For reconstruction, however, only the new children are + necessary. + """ + __slots__ = ('hash_value',) + # Non-child data as the first arguments of the constructor. + # To be (potentially) overridden by derived node classes. __front__ = () + + # Non-child data as the last arguments of the constructor. + # To be (potentially) overridden by derived node classes. __back__ = () def __getinitargs__(self, children): + """Constructs an argument list for the constructor with + non-child data from 'self' and children from 'children'. + + Internally used utility function. + """ front_args = [getattr(self, name) for name in self.__front__] back_args = [getattr(self, name) for name in self.__back__] return tuple(front_args) + tuple(children) + tuple(back_args) def reconstruct(self, *args): + """Reconstructs the node with new children from + 'args'. Non-child data are copied from 'self'. + + Returns a new object. + """ return type(self)(*self.__getinitargs__(args)) def __repr__(self): @@ -43,6 +75,11 @@ def __hash__(self): return self.hash_value def is_equal(self, other): + """Equality predicate. + + This is the method to potentially override in derived classes, + not :meth:`__eq__` or :meth:`__ne__`. + """ if type(self) != type(other): return False self_initargs = self.__getinitargs__(self.children) @@ -50,6 +87,11 @@ def is_equal(self, other): return self_initargs == other_initargs def get_hash(self): + """Hash function. + + This is the method to potentially override in derived classes, + not :meth:`__hash__`. + """ return hash((type(self),) + self.__getinitargs__(self.children)) From b1335b1c650ce7aafe77a4115bd0dc288bd695d6 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 3 Feb 2016 12:19:52 +0000 Subject: [PATCH 014/809] rename: einstein.py -> gem.py --- tsfc/coffee.py | 2 +- tsfc/driver.py | 2 +- tsfc/fem.py | 4 ++-- tsfc/{einstein.py => gem.py} | 0 tsfc/scheduling.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename tsfc/{einstein.py => gem.py} (100%) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index e66c5ab474..fa5b1e5404 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -9,7 +9,7 @@ import coffee.base as coffee -from tsfc import einstein as ein, impero as imp +from tsfc import gem as ein, impero as imp from tsfc.constants import NUMPY_TYPE, SCALAR_TYPE, PRECISION diff --git a/tsfc/driver.py b/tsfc/driver.py index 84c1f1ce60..9614b2b87a 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -13,7 +13,7 @@ import coffee.base as coffee -from tsfc import fem, einstein as ein, impero as imp, scheduling as sch +from tsfc import fem, gem as ein, impero as imp, scheduling as sch from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee from tsfc.constants import default_parameters diff --git a/tsfc/fem.py b/tsfc/fem.py index b9e2699268..5192177343 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -20,8 +20,8 @@ from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal from tsfc.constants import NUMPY_TYPE, PRECISION -from tsfc import einstein as ein -from tsfc.einstein import FromUFLMixin +from tsfc import gem as ein +from tsfc.gem import FromUFLMixin # FFC uses one less digits for rounding than for printing diff --git a/tsfc/einstein.py b/tsfc/gem.py similarity index 100% rename from tsfc/einstein.py rename to tsfc/gem.py diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py index 24713a7f8f..ca1556f6c1 100644 --- a/tsfc/scheduling.py +++ b/tsfc/scheduling.py @@ -4,7 +4,7 @@ from singledispatch import singledispatch -from tsfc import einstein as ein, impero as imp +from tsfc import gem as ein, impero as imp from tsfc.node import traversal From 26cc3207ffd13b0ef126a9f10fb6030ff2cab6c9 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 3 Feb 2016 16:32:50 +0000 Subject: [PATCH 015/809] optimise generated code - Expand geometric loops - Inline unnecessary temporaries --- tsfc/driver.py | 21 +++++++++++++++++++++ tsfc/gem.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9614b2b87a..bd93156aba 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -16,6 +16,7 @@ from tsfc import fem, gem as ein, impero as imp, scheduling as sch from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee from tsfc.constants import default_parameters +from tsfc.node import traversal def compile_form(form, prefix="form", parameters=None): @@ -161,6 +162,22 @@ def compile_integral(integral, idata, fd, prefix, parameters): inlining_cache = {} simplified = [ein.inline_indices(e, inlining_cache) for e in nonfem] + simplified = ein.expand_indexsum(simplified, max_extent=3) + inlining_cache = {} + simplified = [ein.inline_indices(e, inlining_cache) for e in simplified] + + refcount = sch.count_references(simplified) + candidates = set() + for node in traversal(simplified): + if isinstance(node, ein.IndexSum): + if refcount[node.children[0]] == 1: + candidates.add(node.children[0]) + else: + for child in node.children: + if set(child.free_indices) == set(node.free_indices) and refcount[child] == 1: + if not (isinstance(child, ein.Literal) and child.shape): + candidates.add(child) + if cell_orientations: decl = coffee.Decl("const int *restrict *restrict", coffee.Symbol("cell_orientations")) arglist.insert(2, decl) @@ -178,6 +195,10 @@ def compile_integral(integral, idata, fd, prefix, parameters): ordered_shape_map = lambda expr: apply_ordering(shape_map(expr)) indexed_ops = sch.make_ordering(zip(expressions, simplified), ordered_shape_map) + indexed_ops = [(multiindex, op) + for multiindex, op in indexed_ops + if not (isinstance(op, imp.Evaluate) and op.expression in candidates)] + # Zero-simplification occurred if len(indexed_ops) == 0: return None diff --git a/tsfc/gem.py b/tsfc/gem.py index 3954149ee2..8b1f4093b0 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -323,10 +323,15 @@ class IndexSum(Scalar): __back__ = ('index',) def __new__(cls, summand, index): + # Sum zeros assert not summand.shape if isinstance(summand, Zero): return summand + # Sum a single expression + if index.extent == 1: + return Indexed(ComponentTensor(summand, (index,)), (0,)) + self = super(IndexSum, cls).__new__(cls) self.children = (summand,) self.index = index @@ -531,6 +536,44 @@ def _(node, subst): return cached_handle(expression, {}) +def expand_indexsum(expressions, max_extent): + result_cache = {} + + def cached_handle(node): + try: + return result_cache[node] + except KeyError: + result = handle(node) + result_cache[node] = result + return result + + @singledispatch + def handle(node): + raise AssertionError("Cannot handle foreign type: %s" % type(node)) + + @handle.register(Node) # noqa: Not actually redefinition + def _(node): + new_children = [cached_handle(child) for child in node.children] + if all(nc == c for nc, c in zip(new_children, node.children)): + return node + else: + return node.reconstruct(*new_children) + + @handle.register(IndexSum) # noqa: Not actually redefinition + def _(node): + if node.index.extent <= max_extent: + summand = cached_handle(node.children[0]) + ct = ComponentTensor(summand, (node.index,)) + result = Zero() + for i in xrange(node.index.extent): + result = Sum(result, Indexed(ct, (i,))) + return result + else: + return node.reconstruct(*[cached_handle(child) for child in node.children]) + + return [cached_handle(expression) for expression in expressions] + + def collect_index_extents(expression): result = collections.OrderedDict() From 6cd5fd5b5051d07f27961f535cc78d277738cb4b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 3 Feb 2016 17:01:06 +0000 Subject: [PATCH 016/809] Inline indexing tables with fixed indices --- tsfc/coffee.py | 3 +-- tsfc/gem.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index fa5b1e5404..381fe625b4 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -321,8 +321,7 @@ def _(expr, parameters): if isnan(expr.value): return coffee.Symbol("NAN") else: - # Formatting? Symbol? - return expr.value + return coffee.Symbol(("%%.%dg" % PRECISION) % expr.value) @handle.register(ein.Variable) # noqa: Not actually redefinition diff --git a/tsfc/gem.py b/tsfc/gem.py index 8b1f4093b0..8d1d102557 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -526,6 +526,8 @@ def _(node, subst): return cached_handle(child.children[0], filtered_subst) elif isinstance(child, ListTensor) and all(isinstance(i, int) for i in multiindex): return cached_handle(child.array[multiindex], subst) + elif isinstance(child, Literal) and all(isinstance(i, int) for i in multiindex): + return Literal(child.array[multiindex]) else: new_child = cached_handle(child, subst) if new_child == child and multiindex == node.multiindex: From 877fe315aa15b7c21a0a5c406b48b6a2dffe9327 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 3 Feb 2016 18:22:59 +0000 Subject: [PATCH 017/809] More error checking in quadrature creation --- tsfc/quadrature.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tsfc/quadrature.py b/tsfc/quadrature.py index a5bce62e3c..b0de25a1eb 100644 --- a/tsfc/quadrature.py +++ b/tsfc/quadrature.py @@ -69,7 +69,7 @@ def fiat_scheme(cell, degree): except TypeError: points = (degree + 2) // 2 - if numpy.prod(points) < 0: + if numpy.prod(points) <= 0: raise ValueError("Requested a quadrature rule with a negative number of points") if numpy.prod(points) > 500: raise RuntimeError("Requested a quadrature rule with more than 500") @@ -330,6 +330,8 @@ def create_quadrature_rule(cell, degree, scheme="default"): degree = (degree, degree) if cellname == "vertex": + if degree < 0: + raise ValueError("Need positive degree, not %d" % degree) return QuadratureRule(numpy.zeros((1, 0), dtype=numpy.float64), numpy.ones(1, dtype=numpy.float64)) cell = as_fiat_cell(cell) @@ -378,6 +380,9 @@ def select_degree(degree, cell, integral_type): if integral_type == "cell": return degree if integral_type in ("exterior_facet", "interior_facet"): + if cell.cellname() == "OuterProductCell": + raise ValueError("Integral type '%s' invalid for cell '%s'" % + (integral_type, cell.cellname())) if cell.cellname() == "quadrilateral": try: d1, d2 = degree @@ -389,6 +394,9 @@ def select_degree(degree, cell, integral_type): except TypeError: return degree return degree + if cell.cellname() != "OuterProductCell": + raise ValueError("Integral type '%s' invalid for cell '%s'" % + (integral_type, cell.cellname())) if integral_type in ("exterior_facet_top", "exterior_facet_bottom", "interior_facet_horiz"): return degree[0] @@ -396,6 +404,7 @@ def select_degree(degree, cell, integral_type): if cell.topological_dimension() == 2: return degree[1] return degree + raise ValueError("Invalid cell, integral_type combination") def create_quadrature(cell, integral_type, degree, scheme="default"): From 1b2db80ec72cae49fc3dbad1cdee4ba1cb89f2a8 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 3 Feb 2016 18:24:20 +0000 Subject: [PATCH 018/809] Add some tests of quadrature and element creation --- tests/test_create_element.py | 107 +++++++++++++++++++++++++ tests/test_create_quadrature.py | 137 ++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 tests/test_create_element.py create mode 100644 tests/test_create_quadrature.py diff --git a/tests/test_create_element.py b/tests/test_create_element.py new file mode 100644 index 0000000000..2fb923e2b0 --- /dev/null +++ b/tests/test_create_element.py @@ -0,0 +1,107 @@ +from tsfc import fiatinterface as f +import pytest +import ufl + + +@pytest.fixture(params=["BDM", + "BDFM", + "DRT", + "Lagrange", + "N1curl", + "N2curl", + "RT", + "Regge"]) +def triangle_names(request): + return request.param + + +@pytest.fixture +def ufl_element(triangle_names): + return ufl.FiniteElement(triangle_names, ufl.triangle, 2) + + +def test_triangle_basic(ufl_element): + element = f.create_element(ufl_element) + assert isinstance(element, f.supported_elements[ufl_element.family()]) + + +@pytest.fixture +def ufl_vector_element(triangle_names): + return ufl.VectorElement(triangle_names, ufl.triangle, 2) + + +@pytest.mark.parametrize("mixed", + [False, True]) +def test_triangle_vector(mixed, ufl_element, ufl_vector_element): + scalar = f.create_element(ufl_element) + vector = f.create_element(ufl_vector_element, vector_is_mixed=mixed) + + if not mixed: + assert isinstance(scalar, f.supported_elements[ufl_element.family()]) + assert isinstance(vector, f.supported_elements[ufl_element.family()]) + + else: + assert isinstance(vector, f.MixedElement) + assert isinstance(vector.elements()[0], f.supported_elements[ufl_element.family()]) + assert len(vector.elements()) == ufl_vector_element.num_sub_elements() + + +@pytest.fixture(params=["CG", "DG"]) +def tensor_name(request): + return request.param + + +@pytest.fixture(params=[ufl.interval, ufl.triangle, + ufl.quadrilateral], + ids=lambda x: x.cellname()) +def ufl_A(request, tensor_name): + return ufl.FiniteElement(tensor_name, request.param, 1) + + +@pytest.fixture +def ufl_B(tensor_name): + return ufl.FiniteElement(tensor_name, ufl.interval, 1) + + +def test_tensor_prod_simple(ufl_A, ufl_B): + tensor_ufl = ufl.OuterProductElement(ufl_A, ufl_B) + + tensor = f.create_element(tensor_ufl) + A = f.create_element(ufl_A) + B = f.create_element(ufl_B) + + assert isinstance(tensor, f.supported_elements[tensor_ufl.family()]) + + assert tensor.A is A + assert tensor.B is B + + +def test_cache_hit(ufl_element): + A = f.create_element(ufl_element) + B = f.create_element(ufl_element) + + assert A is B + + +def test_cache_hit_vector(ufl_vector_element): + A = f.create_element(ufl_vector_element) + B = f.create_element(ufl_vector_element) + + assert A is B + + assert all(a == A.elements()[0] for a in A.elements()) + + +def test_cache_miss_vector(ufl_vector_element): + A = f.create_element(ufl_vector_element) + B = f.create_element(ufl_vector_element, vector_is_mixed=False) + + assert A is not B + + assert A.elements()[0] is not B + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) diff --git a/tests/test_create_quadrature.py b/tests/test_create_quadrature.py new file mode 100644 index 0000000000..4102f7f4a1 --- /dev/null +++ b/tests/test_create_quadrature.py @@ -0,0 +1,137 @@ +from tsfc import quadrature as q +import ufl +import pytest + + +def test_invalid_quadrature_rule(): + with pytest.raises(ValueError): + q.QuadratureRule([[0.5, 0.5]], [0.5, 0.5, 0.5]) + + +@pytest.fixture(params=["interval", "triangle", + "tetrahedron", "quadrilateral"]) +def cell(request): + cells = {"interval": ufl.interval, + "triangle": ufl.triangle, + "tetrahedron": ufl.tetrahedron, + "quadrilateral": ufl.quadrilateral} + + return cells[request.param] + + +@pytest.fixture +def tensor_product_cell(cell): + if cell.cellname() == "tetrahedron": + pytest.skip("Tensor-producted tet not supported") + + return ufl.OuterProductCell(cell, ufl.interval) + + +@pytest.mark.parametrize("degree", + [1, 2]) +@pytest.mark.parametrize("itype", + ["cell", "interior_facet", "exterior_facet"]) +def test_select_degree(cell, degree, itype): + selected = q.select_degree(degree, cell, itype) + assert selected == degree + + +@pytest.mark.parametrize("degree", + [(1, 1), (2, 2)]) +@pytest.mark.parametrize("itype", + ["interior_facet", "exterior_facet"]) +def test_select_degree_facet_quad(degree, itype): + selected = q.select_degree(degree, ufl.quadrilateral, itype) + assert selected == degree[0] + + +@pytest.mark.parametrize("degree", + [(1, 2), (2,)]) +@pytest.mark.parametrize("itype", + ["interior_facet", "exterior_facet"]) +def test_select_invalid_degree_facet_quad(degree, itype): + with pytest.raises(ValueError): + q.select_degree(degree, ufl.quadrilateral, itype) + + +@pytest.mark.parametrize("degree", + [(1, 2), (2, 3)]) +@pytest.mark.parametrize("itype", + ["interior_facet_horiz", "exterior_facet_top", + "exterior_facet_bottom"]) +def test_select_degree_horiz_facet(tensor_product_cell, degree, itype): + selected = q.select_degree(degree, tensor_product_cell, itype) + assert selected == degree[0] + + +@pytest.mark.parametrize("degree", + [(1, 2), (2, 3)]) +@pytest.mark.parametrize("itype", + ["interior_facet_vert", "exterior_facet_vert"]) +def test_select_degree_vert_facet(tensor_product_cell, degree, itype): + selected = q.select_degree(degree, tensor_product_cell, itype) + if tensor_product_cell.topological_dimension() == 2: + assert selected == degree[1] + else: + assert selected == degree + + +@pytest.mark.parametrize("itype", + ["interior_facet_horiz", + "interior_facet_vert", + "exterior_facet_vert", + "exterior_facet_top", + "exterior_facet_bottom", + "nonsense"]) +def test_invalid_integral_type(cell, itype): + with pytest.raises(ValueError): + q.select_degree(1, cell, itype) + + +@pytest.mark.parametrize("itype", + ["interior_facet", + "exterior_facet", + "nonsense"]) +def test_invalid_integral_type_tensor_prod(tensor_product_cell, itype): + with pytest.raises(ValueError): + q.select_degree((1, 1), tensor_product_cell, itype) + + +@pytest.mark.parametrize("itype", + ["interior_facet", + "exterior_facet", + "cell"]) +@pytest.mark.parametrize("scheme", + ["default", + "canonical"]) +def test_invalid_quadrature_degree(cell, itype, scheme): + with pytest.raises(ValueError): + q.create_quadrature(cell, itype, -1, scheme) + + +@pytest.mark.parametrize("itype", + ["interior_facet_horiz", + "interior_facet_vert", + "exterior_facet_vert", + "exterior_facet_top", + "exterior_facet_bottom", + "cell"]) +def test_invalid_quadrature_degree_tensor_prod(tensor_product_cell, itype): + with pytest.raises(ValueError): + q.create_quadrature(tensor_product_cell, itype, (-1, -1)) + + +def test_high_degree_runtime_error(cell): + with pytest.raises(RuntimeError): + q.create_quadrature(cell, "cell", 2000) + + +def test_high_degree_runtime_error_tensor_prod(tensor_product_cell): + with pytest.raises(RuntimeError): + q.create_quadrature(tensor_product_cell, "cell", (20, 100)) + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 60ecb8daac5e10607e67e47a0b48c4fcae2ab10f Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 3 Feb 2016 18:31:19 +0000 Subject: [PATCH 019/809] some documentation for GEM and refactoring --- tsfc/driver.py | 10 +- tsfc/fem.py | 6 +- tsfc/gem.py | 315 ++++++++++++----------------------------------- tsfc/optimise.py | 109 ++++++++++++++++ tsfc/ufl2gem.py | 121 ++++++++++++++++++ 5 files changed, 320 insertions(+), 241 deletions(-) create mode 100644 tsfc/optimise.py create mode 100644 tsfc/ufl2gem.py diff --git a/tsfc/driver.py b/tsfc/driver.py index bd93156aba..7f8cd3ce09 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -13,7 +13,7 @@ import coffee.base as coffee -from tsfc import fem, gem as ein, impero as imp, scheduling as sch +from tsfc import fem, gem as ein, impero as imp, scheduling as sch, optimise as opt from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee from tsfc.constants import default_parameters from tsfc.node import traversal @@ -160,11 +160,11 @@ def compile_integral(integral, idata, fd, prefix, parameters): fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, argument_indices, coefficient_map) nonfem = [ein.IndexSum(e, quadrature_index) for e in nonfem] inlining_cache = {} - simplified = [ein.inline_indices(e, inlining_cache) for e in nonfem] + simplified = [opt.inline_indices(e, inlining_cache) for e in nonfem] - simplified = ein.expand_indexsum(simplified, max_extent=3) + simplified = opt.expand_indexsum(simplified, max_extent=3) inlining_cache = {} - simplified = [ein.inline_indices(e, inlining_cache) for e in simplified] + simplified = [opt.inline_indices(e, inlining_cache) for e in simplified] refcount = sch.count_references(simplified) candidates = set() @@ -186,7 +186,7 @@ def compile_integral(integral, idata, fd, prefix, parameters): # Need a deterministic ordering for these index_extents = collections.OrderedDict() for e in simplified: - index_extents.update(ein.collect_index_extents(e)) + index_extents.update(opt.collect_index_extents(e)) index_ordering = apply_prefix_ordering(index_extents.keys(), (quadrature_index,) + argument_indices) apply_ordering = make_index_orderer(index_ordering) diff --git a/tsfc/fem.py b/tsfc/fem.py index 5192177343..5788128d71 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -21,7 +21,7 @@ from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal from tsfc.constants import NUMPY_TYPE, PRECISION from tsfc import gem as ein -from tsfc.gem import FromUFLMixin +from tsfc import ufl2gem # FFC uses one less digits for rounding than for printing @@ -427,11 +427,11 @@ def get(self, key, restriction, cellwise_constant=False): indices) -class Translator(MultiFunction, ModifiedTerminalMixin, FromUFLMixin): +class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): def __init__(self, weights, quadrature_index, argument_indices, tabulation_manager, coefficient_map): MultiFunction.__init__(self) - FromUFLMixin.__init__(self) + ufl2gem.Mixin.__init__(self) self.weights = ein.Literal(weights) self.quadrature_index = quadrature_index self.argument_indices = argument_indices diff --git a/tsfc/gem.py b/tsfc/gem.py index 8d1d102557..b65c448e31 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -1,36 +1,60 @@ +"""GEM is the intermediate language of TSFC for describing +tensor-valued mathematical expressions and tensor operations. +It is similar to Einstein's notation. + +Its design was heavily inspired by UFL, with some major differences: + - GEM has got nothing FEM-specific. + - In UFL free indices are just unrolled shape, thus UFL is very + restrictive about operations on expressions with different sets of + free indices. GEM is much more relaxed about free indices. + +Similarly to UFL, all GEM nodes have 'shape' and 'free_indices' +attributes / properties. Unlike UFL, however, index extents live on +the Index objects in GEM, not on all the nodes that have those free +indices. +""" + from __future__ import absolute_import -import collections -import numpy +from itertools import chain +from numpy import asarray, unique -from singledispatch import singledispatch +from tsfc.node import Node as NodeBase -import ufl -from tsfc.node import Node as NodeBase, traversal +class NodeMeta(type): + """Metaclass of GEM nodes. + When a GEM node is constructed, this metaclass automatically + collects its free indices if 'free_indices' has not been set yet. + """ -class NodeMeta(type): def __call__(self, *args, **kwargs): # Create and initialise object obj = super(NodeMeta, self).__call__(*args, **kwargs) # Set free_indices if not set already if not hasattr(obj, 'free_indices'): - free_indices = set() - for child in obj.children: - free_indices |= set(child.free_indices) - obj.free_indices = tuple(free_indices) + cfi = list(chain(*[c.free_indices for c in obj.children])) + obj.free_indices = tuple(unique(cfi)) return obj class Node(NodeBase): + """Abstract GEM node class.""" + __metaclass__ = NodeMeta __slots__ = ('free_indices') def is_equal(self, other): + """Common subexpression eliminating equality predicate. + + When two (sub)expressions are equal, the children of one + object are reassigned to the children of the other, so some + duplicated subexpressions are eliminated. + """ result = NodeBase.is_equal(self, other) if result: self.children = other.children @@ -38,6 +62,8 @@ def is_equal(self, other): class Terminal(Node): + """Abstract class for terminal GEM nodes.""" + __slots__ = () children = () @@ -46,12 +72,16 @@ class Terminal(Node): class Scalar(Node): + """Abstract class for scalar-valued GEM nodes.""" + __slots__ = () shape = () class Zero(Terminal): + """Symbolic zero tensor""" + __slots__ = ('shape',) __front__ = ('shape',) @@ -65,18 +95,21 @@ def value(self): class Literal(Terminal): + """Tensor-valued constant""" + __slots__ = ('array',) __front__ = ('array',) def __new__(cls, array): - array = numpy.asarray(array) + array = asarray(array) if (array == 0).all(): + # All zeros, make symbolic zero return Zero(array.shape) else: return super(Literal, cls).__new__(cls) def __init__(self, array): - self.array = numpy.asarray(array, dtype=float) + self.array = asarray(array, dtype=float) def is_equal(self, other): if type(self) != type(other): @@ -98,6 +131,8 @@ def shape(self): class Variable(Terminal): + """Symbolic variable tensor""" + __slots__ = ('name', 'shape') __front__ = ('name', 'shape') @@ -113,6 +148,7 @@ def __new__(cls, a, b): assert not a.shape assert not b.shape + # Zero folding if isinstance(a, Zero): return b elif isinstance(b, Zero): @@ -130,6 +166,7 @@ def __new__(cls, a, b): assert not a.shape assert not b.shape + # Zero folding if isinstance(a, Zero) or isinstance(b, Zero): return Zero() @@ -145,6 +182,7 @@ def __new__(cls, a, b): assert not a.shape assert not b.shape + # Zero folding if isinstance(b, Zero): raise ValueError("division by zero") if isinstance(a, Zero): @@ -162,6 +200,7 @@ def __new__(cls, base, exponent): assert not base.shape assert not exponent.shape + # Zero folding if isinstance(base, Zero): if isinstance(exponent, Zero): raise ValueError("cannot solve 0^0") @@ -262,12 +301,16 @@ def __init__(self, condition, then, else_): class Index(object): + """Free index""" + __slots__ = ('extent') def __init__(self): + # Initialise with indefinite extent self.extent = None def set_extent(self, value): + # Set extent, check for consistency if self.extent is None: self.extent = value elif self.extent != value: @@ -284,11 +327,13 @@ class Indexed(Scalar): __back__ = ('multiindex',) def __new__(cls, aggregate, multiindex): + # Set index extents from shape assert len(aggregate.shape) == len(multiindex) for index, extent in zip(multiindex, aggregate.shape): if isinstance(index, Index): index.set_extent(extent) + # Zero folding if isinstance(aggregate, Zero): return Zero() else: @@ -298,24 +343,35 @@ def __init__(self, aggregate, multiindex): self.children = (aggregate,) self.multiindex = multiindex - new_indices = set(i for i in multiindex if isinstance(i, Index)) - self.free_indices = tuple(set(aggregate.free_indices) | new_indices) + new_indices = tuple(i for i in multiindex if isinstance(i, Index)) + self.free_indices = tuple(unique(aggregate.free_indices + new_indices)) class ComponentTensor(Node): __slots__ = ('children', 'multiindex', 'shape') __back__ = ('multiindex',) - def __init__(self, expression, multiindex): + def __new__(cls, expression, multiindex): assert not expression.shape - # assert set(multiindex) <= set(expression.free_indices) - assert all(index.extent for index in multiindex) + # Collect shape + shape = tuple(index.extent for index in multiindex) + assert all(shape) + + # Zero folding + if isinstance(expression, Zero): + return Zero(shape) + + self = super(ComponentTensor, cls).__new__(cls) self.children = (expression,) self.multiindex = multiindex + self.shape = shape + # Collect free indices + assert set(multiindex) <= set(expression.free_indices) self.free_indices = tuple(set(expression.free_indices) - set(multiindex)) - self.shape = tuple(index.extent for index in multiindex) + + return self class IndexSum(Scalar): @@ -336,6 +392,7 @@ def __new__(cls, summand, index): self.children = (summand,) self.index = index + # Collect shape and free indices assert index in summand.free_indices self.free_indices = tuple(set(summand.free_indices) - {index}) @@ -346,8 +403,9 @@ class ListTensor(Node): __slots__ = ('array',) def __new__(cls, array): - array = numpy.asarray(array) + array = asarray(array) + # Zero folding if all(isinstance(elem, Zero) for elem in array.flat): assert all(elem.shape == () for elem in array.flat) return Zero(array.shape) @@ -365,228 +423,19 @@ def shape(self): return self.array.shape def reconstruct(self, *args): - return ListTensor(numpy.asarray(args).reshape(self.array.shape)) + return ListTensor(asarray(args).reshape(self.array.shape)) def __repr__(self): return "ListTensor(%r)" % self.array.tolist() def is_equal(self, other): + """Common subexpression eliminating equality predicate.""" if type(self) != type(other): return False - result = (self.array == other.array).all() - if result: + if (self.array == other.array).all(): self.array = other.array - return result + return True + return False def get_hash(self): return hash((type(self), self.shape, self.children)) - - -class FromUFLMixin(object): - def __init__(self): - self.index_map = collections.defaultdict(Index) - - def scalar_value(self, o): - return Literal(o.value()) - - def identity(self, o): - return Literal(numpy.eye(*o.ufl_shape)) - - def zero(self, o): - return Zero(o.ufl_shape) - - def sum(self, o, *ops): - if o.ufl_shape: - indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(Sum(*[Indexed(op, indices) for op in ops]), indices) - else: - return Sum(*ops) - - def product(self, o, *ops): - assert o.ufl_shape == () - return Product(*ops) - - def division(self, o, numerator, denominator): - return Division(numerator, denominator) - - def abs(self, o, expr): - if o.ufl_shape: - indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(MathFunction('abs', Indexed(expr, indices)), indices) - else: - return MathFunction('abs', expr) - - def power(self, o, base, exponent): - return Power(base, exponent) - - def math_function(self, o, expr): - return MathFunction(o._name, expr) - - def min_value(self, o, *ops): - return MinValue(*ops) - - def max_value(self, o, *ops): - return MaxValue(*ops) - - def binary_condition(self, o, left, right): - return Comparison(o._name, left, right) - - def not_condition(self, o, expr): - return LogicalNot(expr) - - def and_condition(self, o, *ops): - return LogicalAnd(*ops) - - def or_condition(self, o, *ops): - return LogicalOr(*ops) - - def conditional(self, o, condition, then, else_): - assert o.ufl_shape == () # TODO - return Conditional(condition, then, else_) - - def multi_index(self, o): - indices = [] - for i in o: - if isinstance(i, ufl.classes.FixedIndex): - indices.append(int(i)) - elif isinstance(i, ufl.classes.Index): - indices.append(self.index_map[i.count()]) - return tuple(indices) - - def indexed(self, o, aggregate, index): - return Indexed(aggregate, index) - - def list_tensor(self, o, *ops): - nesting = [isinstance(op, ListTensor) for op in ops] - if all(nesting): - return ListTensor(numpy.array([op.array for op in ops])) - elif len(o.ufl_shape) > 1: - children = [] - for op in ops: - child = numpy.zeros(o.ufl_shape[1:], dtype=object) - for multiindex in numpy.ndindex(child.shape): - child[multiindex] = Indexed(op, multiindex) - children.append(child) - return ListTensor(numpy.array(children)) - else: - return ListTensor(numpy.array(ops)) - - def component_tensor(self, o, expression, index): - return ComponentTensor(expression, index) - - def index_sum(self, o, summand, indices): - index, = indices - - if o.ufl_shape: - indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(IndexSum(Indexed(summand, indices), index), indices) - else: - return IndexSum(summand, index) - - def variable(self, o, expression, label): - """Only used by UFL AD, at this point, the bare expression is what we want.""" - return expression - - def label(self, o): - """Only used by UFL AD, don't need it at this point.""" - pass - - -def inline_indices(expression, result_cache): - def cached_handle(node, subst): - cache_key = (node, tuple(sorted(subst.items()))) - try: - return result_cache[cache_key] - except KeyError: - result = handle(node, subst) - result_cache[cache_key] = result - return result - - @singledispatch - def handle(node, subst): - raise AssertionError("Cannot handle foreign type: %s" % type(node)) - - @handle.register(Node) # noqa: Not actually redefinition - def _(node, subst): - new_children = [cached_handle(child, subst) for child in node.children] - if all(nc == c for nc, c in zip(new_children, node.children)): - return node - else: - return node.reconstruct(*new_children) - - @handle.register(Indexed) # noqa: Not actually redefinition - def _(node, subst): - child, = node.children - multiindex = tuple(subst.get(i, i) for i in node.multiindex) - if isinstance(child, ComponentTensor): - new_subst = dict(zip(child.multiindex, multiindex)) - composed_subst = {k: new_subst.get(v, v) for k, v in subst.items()} - composed_subst.update(new_subst) - filtered_subst = {k: v for k, v in composed_subst.items() if k in child.children[0].free_indices} - return cached_handle(child.children[0], filtered_subst) - elif isinstance(child, ListTensor) and all(isinstance(i, int) for i in multiindex): - return cached_handle(child.array[multiindex], subst) - elif isinstance(child, Literal) and all(isinstance(i, int) for i in multiindex): - return Literal(child.array[multiindex]) - else: - new_child = cached_handle(child, subst) - if new_child == child and multiindex == node.multiindex: - return node - else: - return Indexed(new_child, multiindex) - - return cached_handle(expression, {}) - - -def expand_indexsum(expressions, max_extent): - result_cache = {} - - def cached_handle(node): - try: - return result_cache[node] - except KeyError: - result = handle(node) - result_cache[node] = result - return result - - @singledispatch - def handle(node): - raise AssertionError("Cannot handle foreign type: %s" % type(node)) - - @handle.register(Node) # noqa: Not actually redefinition - def _(node): - new_children = [cached_handle(child) for child in node.children] - if all(nc == c for nc, c in zip(new_children, node.children)): - return node - else: - return node.reconstruct(*new_children) - - @handle.register(IndexSum) # noqa: Not actually redefinition - def _(node): - if node.index.extent <= max_extent: - summand = cached_handle(node.children[0]) - ct = ComponentTensor(summand, (node.index,)) - result = Zero() - for i in xrange(node.index.extent): - result = Sum(result, Indexed(ct, (i,))) - return result - else: - return node.reconstruct(*[cached_handle(child) for child in node.children]) - - return [cached_handle(expression) for expression in expressions] - - -def collect_index_extents(expression): - result = collections.OrderedDict() - - for node in traversal([expression]): - if isinstance(node, Indexed): - assert len(node.multiindex) == len(node.children[0].shape) - for index, extent in zip(node.multiindex, node.children[0].shape): - if isinstance(index, Index): - if index not in result: - result[index] = extent - elif result[index] != extent: - raise AssertionError("Inconsistent index extents!") - - return result diff --git a/tsfc/optimise.py b/tsfc/optimise.py new file mode 100644 index 0000000000..4c5f027b95 --- /dev/null +++ b/tsfc/optimise.py @@ -0,0 +1,109 @@ +from __future__ import absolute_import + +import collections + +from singledispatch import singledispatch + +from tsfc.node import traversal +from tsfc.gem import (Node, Literal, Zero, Sum, Index, Indexed, + IndexSum, ComponentTensor, ListTensor) + + +def inline_indices(expression, result_cache): + def cached_handle(node, subst): + cache_key = (node, tuple(sorted(subst.items()))) + try: + return result_cache[cache_key] + except KeyError: + result = handle(node, subst) + result_cache[cache_key] = result + return result + + @singledispatch + def handle(node, subst): + raise AssertionError("Cannot handle foreign type: %s" % type(node)) + + @handle.register(Node) # noqa: Not actually redefinition + def _(node, subst): + new_children = [cached_handle(child, subst) for child in node.children] + if all(nc == c for nc, c in zip(new_children, node.children)): + return node + else: + return node.reconstruct(*new_children) + + @handle.register(Indexed) # noqa: Not actually redefinition + def _(node, subst): + child, = node.children + multiindex = tuple(subst.get(i, i) for i in node.multiindex) + if isinstance(child, ComponentTensor): + new_subst = dict(zip(child.multiindex, multiindex)) + composed_subst = {k: new_subst.get(v, v) for k, v in subst.items()} + composed_subst.update(new_subst) + filtered_subst = {k: v for k, v in composed_subst.items() if k in child.children[0].free_indices} + return cached_handle(child.children[0], filtered_subst) + elif isinstance(child, ListTensor) and all(isinstance(i, int) for i in multiindex): + return cached_handle(child.array[multiindex], subst) + elif isinstance(child, Literal) and all(isinstance(i, int) for i in multiindex): + return Literal(child.array[multiindex]) + else: + new_child = cached_handle(child, subst) + if new_child == child and multiindex == node.multiindex: + return node + else: + return Indexed(new_child, multiindex) + + return cached_handle(expression, {}) + + +def expand_indexsum(expressions, max_extent): + result_cache = {} + + def cached_handle(node): + try: + return result_cache[node] + except KeyError: + result = handle(node) + result_cache[node] = result + return result + + @singledispatch + def handle(node): + raise AssertionError("Cannot handle foreign type: %s" % type(node)) + + @handle.register(Node) # noqa: Not actually redefinition + def _(node): + new_children = [cached_handle(child) for child in node.children] + if all(nc == c for nc, c in zip(new_children, node.children)): + return node + else: + return node.reconstruct(*new_children) + + @handle.register(IndexSum) # noqa: Not actually redefinition + def _(node): + if node.index.extent <= max_extent: + summand = cached_handle(node.children[0]) + ct = ComponentTensor(summand, (node.index,)) + result = Zero() + for i in xrange(node.index.extent): + result = Sum(result, Indexed(ct, (i,))) + return result + else: + return node.reconstruct(*[cached_handle(child) for child in node.children]) + + return [cached_handle(expression) for expression in expressions] + + +def collect_index_extents(expression): + result = collections.OrderedDict() + + for node in traversal([expression]): + if isinstance(node, Indexed): + assert len(node.multiindex) == len(node.children[0].shape) + for index, extent in zip(node.multiindex, node.children[0].shape): + if isinstance(index, Index): + if index not in result: + result[index] = extent + elif result[index] != extent: + raise AssertionError("Inconsistent index extents!") + + return result diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py new file mode 100644 index 0000000000..d4f326b653 --- /dev/null +++ b/tsfc/ufl2gem.py @@ -0,0 +1,121 @@ +from __future__ import absolute_import + +import collections +import numpy +import ufl + +from tsfc.gem import (Literal, Zero, Sum, Product, Division, Power, + MathFunction, MinValue, MaxValue, Comparison, + LogicalNot, LogicalAnd, LogicalOr, Conditional, + Index, Indexed, ComponentTensor, IndexSum, + ListTensor) + + +class Mixin(object): + def __init__(self): + self.index_map = collections.defaultdict(Index) + + def scalar_value(self, o): + return Literal(o.value()) + + def identity(self, o): + return Literal(numpy.eye(*o.ufl_shape)) + + def zero(self, o): + return Zero(o.ufl_shape) + + def sum(self, o, *ops): + if o.ufl_shape: + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(Sum(*[Indexed(op, indices) for op in ops]), indices) + else: + return Sum(*ops) + + def product(self, o, *ops): + assert o.ufl_shape == () + return Product(*ops) + + def division(self, o, numerator, denominator): + return Division(numerator, denominator) + + def abs(self, o, expr): + if o.ufl_shape: + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(MathFunction('abs', Indexed(expr, indices)), indices) + else: + return MathFunction('abs', expr) + + def power(self, o, base, exponent): + return Power(base, exponent) + + def math_function(self, o, expr): + return MathFunction(o._name, expr) + + def min_value(self, o, *ops): + return MinValue(*ops) + + def max_value(self, o, *ops): + return MaxValue(*ops) + + def binary_condition(self, o, left, right): + return Comparison(o._name, left, right) + + def not_condition(self, o, expr): + return LogicalNot(expr) + + def and_condition(self, o, *ops): + return LogicalAnd(*ops) + + def or_condition(self, o, *ops): + return LogicalOr(*ops) + + def conditional(self, o, condition, then, else_): + assert o.ufl_shape == () # TODO + return Conditional(condition, then, else_) + + def multi_index(self, o): + indices = [] + for i in o: + if isinstance(i, ufl.classes.FixedIndex): + indices.append(int(i)) + elif isinstance(i, ufl.classes.Index): + indices.append(self.index_map[i.count()]) + return tuple(indices) + + def indexed(self, o, aggregate, index): + return Indexed(aggregate, index) + + def list_tensor(self, o, *ops): + nesting = [isinstance(op, ListTensor) for op in ops] + if all(nesting): + return ListTensor(numpy.array([op.array for op in ops])) + elif len(o.ufl_shape) > 1: + children = [] + for op in ops: + child = numpy.zeros(o.ufl_shape[1:], dtype=object) + for multiindex in numpy.ndindex(child.shape): + child[multiindex] = Indexed(op, multiindex) + children.append(child) + return ListTensor(numpy.array(children)) + else: + return ListTensor(numpy.array(ops)) + + def component_tensor(self, o, expression, index): + return ComponentTensor(expression, index) + + def index_sum(self, o, summand, indices): + index, = indices + + if o.ufl_shape: + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(IndexSum(Indexed(summand, indices), index), indices) + else: + return IndexSum(summand, index) + + def variable(self, o, expression, label): + """Only used by UFL AD, at this point, the bare expression is what we want.""" + return expression + + def label(self, o): + """Only used by UFL AD, don't need it at this point.""" + pass From 580a89fa8b2180144725cd3abc032a5e8eeb878f Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 4 Feb 2016 10:53:38 +0000 Subject: [PATCH 020/809] Always build coffee Symbols with string as name Concatenate rank if we got a symbol in the name slot. --- tsfc/coffee.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 381fe625b4..cccd001e01 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -142,19 +142,34 @@ def recurse(expr, loop_indices): return indices, declare +def _coffee_symbol(symbol, rank=()): + """Build a coffee Symbol, concatenating rank. + + :arg symbol: Either a symbol name, or else an existing coffee Symbol. + :arg rank: The ``rank`` argument to the coffee Symbol constructor. + + If symbol is a symbol, then the returned symbol has rank + ``symbol.rank + rank``.""" + if isinstance(symbol, coffee.Symbol): + rank = symbol.rank + rank + symbol = symbol.symbol + else: + assert isinstance(symbol, str) + return coffee.Symbol(symbol, rank=rank) + + def _decl_symbol(expr, parameters): multiindex = parameters.indices[expr] rank = tuple(parameters.index_extents[index] for index in multiindex) if hasattr(expr, 'shape'): rank += expr.shape - return coffee.Symbol(parameters.names[expr], rank=rank) + return _coffee_symbol(parameters.names[expr], rank=rank) def _ref_symbol(expr, parameters): multiindex = parameters.indices[expr] - # TODO: Shall I make a COFFEE Symbol here? rank = tuple(parameters.names[index] for index in multiindex) - return coffee.Symbol(parameters.names[expr], rank=tuple(rank)) + return _coffee_symbol(parameters.names[expr], rank=tuple(rank)) @singledispatch @@ -175,7 +190,7 @@ def _(tree, parameters): def _(tree, parameters): extent = tree.index.extent assert extent - i = coffee.Symbol(parameters.names[tree.index]) + i = _coffee_symbol(parameters.names[tree.index]) # TODO: symbolic constant for "int" return coffee.For(coffee.Decl("int", i, init=0), coffee.Less(i, extent), @@ -223,7 +238,7 @@ def _(leaf, parameters): else: ops = [] for multiindex, value in numpy.ndenumerate(expr.array): - coffee_sym = coffee.Symbol(_ref_symbol(expr, parameters), rank=multiindex) + coffee_sym = _coffee_symbol(_ref_symbol(expr, parameters), rank=multiindex) ops.append(coffee.Assign(coffee_sym, expression(value, parameters))) return coffee.Block(ops, open_scope=False) elif isinstance(expr, ein.Literal): @@ -326,7 +341,7 @@ def _(expr, parameters): @handle.register(ein.Variable) # noqa: Not actually redefinition def _(expr, parameters): - return coffee.Symbol(expr.name) + return _coffee_symbol(expr.name) @handle.register(ein.Indexed) # noqa: Not actually redefinition @@ -339,5 +354,5 @@ def _(expr, parameters): rank.append(index.name) else: rank.append(index) - return coffee.Symbol(expression(expr.children[0], parameters), - rank=tuple(rank)) + return _coffee_symbol(expression(expr.children[0], parameters), + rank=tuple(rank)) From ba505b758be00d82318054d96007cb3efa966c53 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 4 Feb 2016 10:54:30 +0000 Subject: [PATCH 021/809] Migrate const to Decl qualifiers list --- tsfc/driver.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7f8cd3ce09..310fbd185c 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -138,10 +138,12 @@ def compile_integral(integral, idata, fd, prefix, parameters): kernel.coefficient_numbers = tuple(coefficient_numbers) if integral_type in ["exterior_facet", "exterior_facet_vert"]: - decl = coffee.Decl("const unsigned int", coffee.Symbol("facet", rank=(1,))) + decl = coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(1,)), + qualifiers=["const"]) arglist.append(decl) elif integral_type in ["interior_facet", "interior_facet_vert"]: - decl = coffee.Decl("const unsigned int", coffee.Symbol("facet", rank=(2,))) + decl = coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(2,)), + qualifiers=["const"]) arglist.append(decl) cell = integrand.ufl_domain().ufl_cell() @@ -179,7 +181,8 @@ def compile_integral(integral, idata, fd, prefix, parameters): candidates.add(child) if cell_orientations: - decl = coffee.Decl("const int *restrict *restrict", coffee.Symbol("cell_orientations")) + decl = coffee.Decl("int *restrict *restrict", coffee.Symbol("cell_orientations"), + qualifiers=["const"]) arglist.insert(2, decl) kernel.oriented = True @@ -224,7 +227,8 @@ def prepare_coefficient(integral_type, coefficient, name): shape = coefficient.ufl_shape or (1,) - funarg = coffee.Decl("const %s" % SCALAR_TYPE, coffee.Symbol(name, rank=shape)) + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), + qualifiers=["const"]) expression = ein.Variable(name, shape) if coefficient.ufl_shape == (): expression = ein.Indexed(expression, (0,)) @@ -237,7 +241,8 @@ def prepare_coefficient(integral_type, coefficient, name): # Simple case shape = (fiat_element.space_dimension(),) - funarg = coffee.Decl("const %s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape)) + funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), + qualifiers=["const"]) i = ein.Index() expression = ein.ComponentTensor( @@ -252,7 +257,8 @@ def prepare_coefficient(integral_type, coefficient, name): shape = (2, fiat_element.space_dimension()) - funarg = coffee.Decl("const %s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape)) + funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), + qualifiers=["const"]) expression = ein.Variable(name, shape + (1,)) f, i = ein.Index(), ein.Index() @@ -267,7 +273,8 @@ def prepare_coefficient(integral_type, coefficient, name): name_ = name + "_" shape = (2, fiat_element.space_dimension()) - funarg = coffee.Decl("const %s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_)) + funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_), + qualifiers=["const"]) prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] expression = ein.Variable(name, shape) From a8e723c7ba2d97bbe57814bef529cf7dc9e3de0b Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 4 Feb 2016 12:03:31 +0000 Subject: [PATCH 022/809] Better checking for high quad degree --- tests/test_create_quadrature.py | 4 ++-- tsfc/quadrature.py | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_create_quadrature.py b/tests/test_create_quadrature.py index 4102f7f4a1..79b331777b 100644 --- a/tests/test_create_quadrature.py +++ b/tests/test_create_quadrature.py @@ -123,12 +123,12 @@ def test_invalid_quadrature_degree_tensor_prod(tensor_product_cell, itype): def test_high_degree_runtime_error(cell): with pytest.raises(RuntimeError): - q.create_quadrature(cell, "cell", 2000) + q.create_quadrature(cell, "cell", 60) def test_high_degree_runtime_error_tensor_prod(tensor_product_cell): with pytest.raises(RuntimeError): - q.create_quadrature(tensor_product_cell, "cell", (20, 100)) + q.create_quadrature(tensor_product_cell, "cell", (60, 60)) if __name__ == "__main__": diff --git a/tsfc/quadrature.py b/tsfc/quadrature.py index b0de25a1eb..e3b9312800 100644 --- a/tsfc/quadrature.py +++ b/tsfc/quadrature.py @@ -68,11 +68,14 @@ def fiat_scheme(cell, degree): points = tuple((d + 2) // 2 for d in degree) except TypeError: points = (degree + 2) // 2 - + tdim = cell.get_spatial_dimension() + if points > 30: + raise RuntimeError("Requested a quadrature rule with %d points per direction (%d points)" % + (points, points**tdim)) if numpy.prod(points) <= 0: raise ValueError("Requested a quadrature rule with a negative number of points") - if numpy.prod(points) > 500: - raise RuntimeError("Requested a quadrature rule with more than 500") + if numpy.prod(points) > 900: + raise RuntimeError("Requested a quadrature rule with %d points" % numpy.prod(points)) quad = FIAT.make_quadrature(cell, points) return QuadratureRule(quad.get_points(), quad.get_weights()) From 0e9e80536c2bc3e8d1fcf43ae49f02facf6dcbeb Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 4 Feb 2016 12:18:11 +0000 Subject: [PATCH 023/809] Handle conditionals with non-empty shape --- tsfc/ufl2gem.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index d4f326b653..d8addd0d6e 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -70,8 +70,14 @@ def or_condition(self, o, *ops): return LogicalOr(*ops) def conditional(self, o, condition, then, else_): - assert o.ufl_shape == () # TODO - return Conditional(condition, then, else_) + assert condition.shape == () + if o.ufl_shape: + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(Conditional(condition, Indexed(then, indices), + Indexed(else_, indices)), + indices) + else: + return Conditional(condition, then, else_) def multi_index(self, o): indices = [] From f36a11622143d6486e02b0366747ab5297dc97c0 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 4 Feb 2016 12:41:49 +0000 Subject: [PATCH 024/809] Better precision formatting for Literals --- tsfc/coffee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index cccd001e01..9bb6b7f103 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -336,7 +336,7 @@ def _(expr, parameters): if isnan(expr.value): return coffee.Symbol("NAN") else: - return coffee.Symbol(("%%.%dg" % PRECISION) % expr.value) + return coffee.Symbol(("%%.%dg" % (PRECISION - 1)) % expr.value) @handle.register(ein.Variable) # noqa: Not actually redefinition From 8ccaf319d93f5ff468900b16d108338d6f2b4738 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 4 Feb 2016 14:34:42 +0000 Subject: [PATCH 025/809] Temporary fix for multiple integrals per integral data Should figure out how to merge kernel bodies. --- tsfc/driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 310fbd185c..d377b95045 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -41,8 +41,8 @@ def compile_form(form, prefix="form", parameters=None): kernels = [] for idata in fd.integral_data: - if len(idata.integrals) != 1: - raise NotImplementedError("Don't support IntegralData with more than one integral") + # TODO: Merge kernel bodies for multiple integrals with same + # integral-data (same mesh iteration space). for integral in idata.integrals: start = time.time() kernel = compile_integral(integral, idata, fd, prefix, parameters) From 4d5522b225a0d402ae4e8e0ce31226981bf91258 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 4 Feb 2016 15:07:16 +0000 Subject: [PATCH 026/809] clean up indices a bit --- tsfc/coffee.py | 20 +++++--------------- tsfc/driver.py | 21 ++++++++++----------- tsfc/fem.py | 2 +- tsfc/gem.py | 12 ++++++++++-- tsfc/optimise.py | 21 +-------------------- 5 files changed, 27 insertions(+), 49 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 9bb6b7f103..a846de22f9 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -22,7 +22,7 @@ class OrderedCounter(collections.Counter, collections.OrderedDict): pass -def generate(indexed_ops, temporaries, shape_map, apply_ordering, index_extents, index_names): +def generate(indexed_ops, temporaries, shape_map, apply_ordering): temporaries_set = set(temporaries) ops = [op for indices, op in indexed_ops] @@ -36,7 +36,6 @@ def generate(indexed_ops, temporaries, shape_map, apply_ordering, index_extents, indices, declare = place_declarations(code, reference_count, shape_map, apply_ordering, ops) parameters = Bunch() - parameters.index_extents = index_extents parameters.declare = declare parameters.indices = indices parameters.names = {} @@ -44,15 +43,6 @@ def generate(indexed_ops, temporaries, shape_map, apply_ordering, index_extents, for i, temp in enumerate(temporaries): parameters.names[temp] = "t%d" % i - for index, name in index_names: - parameters.names[index] = name - - index_counter = 0 - for index in index_extents: - if index not in parameters.names: - index_counter += 1 - parameters.names[index] = "i_%d" % index_counter - return arabica(code, parameters) @@ -160,7 +150,7 @@ def _coffee_symbol(symbol, rank=()): def _decl_symbol(expr, parameters): multiindex = parameters.indices[expr] - rank = tuple(parameters.index_extents[index] for index in multiindex) + rank = tuple(index.extent for index in multiindex) if hasattr(expr, 'shape'): rank += expr.shape return _coffee_symbol(parameters.names[expr], rank=rank) @@ -168,7 +158,7 @@ def _decl_symbol(expr, parameters): def _ref_symbol(expr, parameters): multiindex = parameters.indices[expr] - rank = tuple(parameters.names[index] for index in multiindex) + rank = tuple(index.name for index in multiindex) return _coffee_symbol(parameters.names[expr], rank=tuple(rank)) @@ -190,7 +180,7 @@ def _(tree, parameters): def _(tree, parameters): extent = tree.index.extent assert extent - i = _coffee_symbol(parameters.names[tree.index]) + i = _coffee_symbol(tree.index.name) # TODO: symbolic constant for "int" return coffee.For(coffee.Decl("int", i, init=0), coffee.Less(i, extent), @@ -349,7 +339,7 @@ def _(expr, parameters): rank = [] for index in expr.multiindex: if isinstance(index, ein.Index): - rank.append(parameters.names[index]) + rank.append(index.name) elif isinstance(index, ein.VariableIndex): rank.append(index.name) else: diff --git a/tsfc/driver.py b/tsfc/driver.py index d377b95045..9f5334285a 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -2,7 +2,6 @@ import numpy import time -import collections from ufl.algorithms import compute_form_data from ufl.log import GREEN @@ -104,6 +103,7 @@ def compile_integral(integral, idata, fd, prefix, parameters): coefficient_map = {} funarg, prepare_, expressions, finalise = prepare_arguments(integral_type, fd.preprocessed_form.arguments()) + argument_indices = sorted(expressions[0].free_indices, key=lambda index: index.name) arglist.append(funarg) prepare += prepare_ @@ -187,11 +187,12 @@ def compile_integral(integral, idata, fd, prefix, parameters): kernel.oriented = True # Need a deterministic ordering for these - index_extents = collections.OrderedDict() - for e in simplified: - index_extents.update(opt.collect_index_extents(e)) - index_ordering = apply_prefix_ordering(index_extents.keys(), - (quadrature_index,) + argument_indices) + indices = set() + for node in traversal(simplified): + indices.update(node.free_indices) + indices = sorted(indices) + + index_ordering = apply_prefix_ordering(indices, (quadrature_index,) + argument_indices) apply_ordering = make_index_orderer(index_ordering) shape_map = lambda expr: expr.free_indices @@ -205,11 +206,9 @@ def compile_integral(integral, idata, fd, prefix, parameters): # Zero-simplification occurred if len(indexed_ops) == 0: return None - temporaries = make_temporaries(op for indices, op in indexed_ops) + temporaries = make_temporaries(op for loop_indices, op in indexed_ops) - index_names = zip((quadrature_index,) + argument_indices, ['ip', 'j', 'k']) - body = generate_coffee(indexed_ops, temporaries, shape_map, - apply_ordering, index_extents, index_names) + body = generate_coffee(indexed_ops, temporaries, shape_map, apply_ordering) body.open_scope = False funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) @@ -307,7 +306,7 @@ def prepare_arguments(integral_type, arguments): return funarg, [], [expression], [] elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - indices = tuple(ein.Index() for i in xrange(len(arguments))) + indices = tuple(ein.Index(name=name) for i, name in zip(range(len(arguments)), ['j', 'k'])) if not integral_type.startswith("interior_facet"): # Not an interior facet integral diff --git a/tsfc/fem.py b/tsfc/fem.py index 5788128d71..d64fb70f9f 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -664,7 +664,7 @@ def process(integral_type, integrand, tabulation_manager, quadrature_weights, ar # Translate UFL to Einstein's notation, # lowering finite element specific nodes - quadrature_index = ein.Index() + quadrature_index = ein.Index(name='ip') translator = Translator(quadrature_weights, quadrature_index, argument_indices, tabulation_manager, diff --git a/tsfc/gem.py b/tsfc/gem.py index b65c448e31..84c2770b85 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -303,9 +303,17 @@ def __init__(self, condition, then, else_): class Index(object): """Free index""" - __slots__ = ('extent') + # Not true object count, just for naming purposes + count = 0 + + __slots__ = ('name', 'extent') + + def __init__(self, name=None): + if name is None: + Index.count += 1 + name = "i_%d" % Index.count + self.name = name - def __init__(self): # Initialise with indefinite extent self.extent = None diff --git a/tsfc/optimise.py b/tsfc/optimise.py index 4c5f027b95..1fa48813ef 100644 --- a/tsfc/optimise.py +++ b/tsfc/optimise.py @@ -1,11 +1,8 @@ from __future__ import absolute_import -import collections - from singledispatch import singledispatch -from tsfc.node import traversal -from tsfc.gem import (Node, Literal, Zero, Sum, Index, Indexed, +from tsfc.gem import (Node, Literal, Zero, Sum, Indexed, IndexSum, ComponentTensor, ListTensor) @@ -91,19 +88,3 @@ def _(node): return node.reconstruct(*[cached_handle(child) for child in node.children]) return [cached_handle(expression) for expression in expressions] - - -def collect_index_extents(expression): - result = collections.OrderedDict() - - for node in traversal([expression]): - if isinstance(node, Indexed): - assert len(node.multiindex) == len(node.children[0].shape) - for index, extent in zip(node.multiindex, node.children[0].shape): - if isinstance(index, Index): - if index not in result: - result[index] = extent - elif result[index] != extent: - raise AssertionError("Inconsistent index extents!") - - return result From 720d8060ebdaf49a07fb19aef12425e8ffa1b971 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 4 Feb 2016 15:37:04 +0000 Subject: [PATCH 027/809] write docstrings for impero.py --- tsfc/impero.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tsfc/impero.py b/tsfc/impero.py index 210afc7522..9c56c19bde 100644 --- a/tsfc/impero.py +++ b/tsfc/impero.py @@ -1,19 +1,37 @@ +"""Impero is a helper AST for generating C code (or equivalent, +e.g. COFFEE) from GEM. An Impero expression is a proper tree, not +directed acyclic graph (DAG). Impero is a helper AST, not a +standalone language; it is incomplete without GEM as its terminals +refer to nodes from GEM expressions. + +Trivia: + - Impero helps translating GEM into an imperative language. + - Byzantine units in Age of Empires II sometimes say 'Impero?' + (Command?) after clicking on them. +""" + from __future__ import absolute_import -from tsfc.node import Node as node_Node +from tsfc.node import Node as NodeBase + +class Node(NodeBase): + """Base class of all Impero nodes""" -class Node(node_Node): __slots__ = () class Terminal(Node): + """Abstract class for terminal Impero nodes""" + __slots__ = () children = () class Evaluate(Terminal): + """Assign the value of a GEM expression to a temporary.""" + __slots__ = ('expression',) __front__ = ('expression',) @@ -22,6 +40,8 @@ def __init__(self, expression): class Initialise(Terminal): + """Initialise an :class:`gem.IndexSum`.""" + __slots__ = ('indexsum',) __front__ = ('indexsum',) @@ -30,6 +50,8 @@ def __init__(self, indexsum): class Accumulate(Terminal): + """Accumulate terms into an :class:`gem.IndexSum`.""" + __slots__ = ('indexsum',) __front__ = ('indexsum',) @@ -38,6 +60,9 @@ def __init__(self, indexsum): class Return(Terminal): + """Save value of GEM expression into an lvalue. Used to "return" + values from a kernel.""" + __slots__ = ('variable', 'expression') __front__ = ('variable', 'expression') @@ -47,6 +72,9 @@ def __init__(self, variable, expression): class Block(Node): + """An ordered set of Impero expressions. Corresponds to a curly + braces block in C.""" + __slots__ = ('children',) def __init__(self, statements): @@ -54,6 +82,9 @@ def __init__(self, statements): class For(Node): + """For loop with an index which stores its extent, and a loop body + expression which is usually a :class:`Block`.""" + __slots__ = ('index', 'children') __front__ = ('index',) From 7f90d56455eed1fd4e5ad6258e113ea8b520f166 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 5 Feb 2016 12:04:19 +0000 Subject: [PATCH 028/809] pull COFFEE-independent code out of coffee.py --- tsfc/coffee.py | 107 +------------------------------------- tsfc/driver.py | 8 ++- tsfc/impero_utils.py | 121 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 108 deletions(-) create mode 100644 tsfc/impero_utils.py diff --git a/tsfc/coffee.py b/tsfc/coffee.py index a846de22f9..a659529c9a 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -1,7 +1,5 @@ from __future__ import absolute_import -import collections -import itertools from math import isnan import numpy @@ -17,24 +15,7 @@ class Bunch(object): pass -class OrderedCounter(collections.Counter, collections.OrderedDict): - """A Counter object that has deterministic iteration order.""" - pass - - -def generate(indexed_ops, temporaries, shape_map, apply_ordering): - temporaries_set = set(temporaries) - ops = [op for indices, op in indexed_ops] - - code = make_loop_tree(indexed_ops) - - reference_count = collections.Counter() - for op in ops: - reference_count.update(count_references(temporaries_set, op)) - assert temporaries_set == set(reference_count) - - indices, declare = place_declarations(code, reference_count, shape_map, apply_ordering, ops) - +def generate(temporaries, code, indices, declare): parameters = Bunch() parameters.declare = declare parameters.indices = indices @@ -46,92 +27,6 @@ def generate(indexed_ops, temporaries, shape_map, apply_ordering): return arabica(code, parameters) -def make_loop_tree(indexed_ops, level=0): - keyfunc = lambda (indices, op): indices[level:level+1] - statements = [] - for first_index, op_group in itertools.groupby(indexed_ops, keyfunc): - if first_index: - inner_block = make_loop_tree(op_group, level+1) - statements.append(imp.For(first_index[0], inner_block)) - else: - statements.extend(op for indices, op in op_group) - return imp.Block(statements) - - -def count_references(temporaries, op): - counter = collections.Counter() - - def recurse(o, top=False): - if o in temporaries: - counter[o] += 1 - - if top or o not in temporaries: - if isinstance(o, (ein.Literal, ein.Variable)): - pass - elif isinstance(o, ein.Indexed): - recurse(o.children[0]) - else: - for c in o.children: - recurse(c) - - if isinstance(op, imp.Evaluate): - recurse(op.expression, top=True) - elif isinstance(op, imp.Initialise): - counter[op.indexsum] += 1 - elif isinstance(op, imp.Return): - recurse(op.expression) - elif isinstance(op, imp.Accumulate): - counter[op.indexsum] += 1 - recurse(op.indexsum.children[0]) - else: - raise AssertionError("unhandled operation: %s" % type(op)) - - return counter - - -def place_declarations(tree, reference_count, shape_map, apply_ordering, operations): - temporaries = set(reference_count) - indices = {} - # We later iterate over declare keys, so need this to be ordered - declare = collections.OrderedDict() - - def recurse(expr, loop_indices): - if isinstance(expr, imp.Block): - declare[expr] = [] - # Need to iterate over counter in given order - counter = OrderedCounter() - for statement in expr.children: - counter.update(recurse(statement, loop_indices)) - for e, count in counter.items(): - if count == reference_count[e]: - indices[e] = apply_ordering(set(shape_map(e)) - loop_indices) - if indices[e]: - declare[expr].append(e) - del counter[e] - return counter - elif isinstance(expr, imp.For): - return recurse(expr.children[0], loop_indices | {expr.index}) - else: - return count_references(temporaries, expr) - - remainder = recurse(tree, set()) - assert not remainder - - for op in operations: - declare[op] = False - if isinstance(op, imp.Evaluate): - e = op.expression - elif isinstance(op, imp.Initialise): - e = op.indexsum - else: - continue - - if len(indices[e]) == 0: - declare[op] = True - - return indices, declare - - def _coffee_symbol(symbol, rank=()): """Build a coffee Symbol, concatenating rank. diff --git a/tsfc/driver.py b/tsfc/driver.py index 9f5334285a..a49c0382a2 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -12,7 +12,7 @@ import coffee.base as coffee -from tsfc import fem, gem as ein, impero as imp, scheduling as sch, optimise as opt +from tsfc import fem, gem as ein, impero as imp, scheduling as sch, optimise as opt, impero_utils from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee from tsfc.constants import default_parameters from tsfc.node import traversal @@ -208,7 +208,11 @@ def compile_integral(integral, idata, fd, prefix, parameters): return None temporaries = make_temporaries(op for loop_indices, op in indexed_ops) - body = generate_coffee(indexed_ops, temporaries, shape_map, apply_ordering) + tree, indices, declare = impero_utils.process(indexed_ops, + temporaries, + shape_map, + apply_ordering) + body = generate_coffee(temporaries, tree, indices, declare) body.open_scope = False funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) diff --git a/tsfc/impero_utils.py b/tsfc/impero_utils.py new file mode 100644 index 0000000000..f17529f5eb --- /dev/null +++ b/tsfc/impero_utils.py @@ -0,0 +1,121 @@ +from __future__ import absolute_import + +import collections +import itertools + +from tsfc import gem, impero as imp + + +class OrderedCounter(collections.Counter, collections.OrderedDict): + """A Counter object that has deterministic iteration order.""" + pass + + +def process(indexed_ops, temporaries, shape_map, apply_ordering): + temporaries_set = set(temporaries) + ops = [op for indices, op in indexed_ops] + + code = make_loop_tree(indexed_ops) + + reference_count = collections.Counter() + for op in ops: + reference_count.update(count_references(temporaries_set, op)) + assert temporaries_set == set(reference_count) + + indices, declare = place_declarations(code, reference_count, shape_map, apply_ordering, ops) + + return code, indices, declare + + +def make_loop_tree(indexed_ops, level=0): + """Creates an Impero AST with loops from a list of operations and + their respective free indices. + + :arg indexed_ops: A list of (free indices, operation) pairs, each + operation must be an Impero terminal node. + :arg level: depth of loop nesting + :returns: Impero AST with loops, without declarations + """ + keyfunc = lambda (indices, op): indices[level:level+1] + statements = [] + for first_index, op_group in itertools.groupby(indexed_ops, keyfunc): + if first_index: + inner_block = make_loop_tree(op_group, level+1) + statements.append(imp.For(first_index[0], inner_block)) + else: + statements.extend(op for indices, op in op_group) + return imp.Block(statements) + + +def count_references(temporaries, op): + counter = collections.Counter() + + def recurse(o, top=False): + if o in temporaries: + counter[o] += 1 + + if top or o not in temporaries: + if isinstance(o, (gem.Literal, gem.Variable)): + pass + elif isinstance(o, gem.Indexed): + recurse(o.children[0]) + else: + for c in o.children: + recurse(c) + + if isinstance(op, imp.Evaluate): + recurse(op.expression, top=True) + elif isinstance(op, imp.Initialise): + counter[op.indexsum] += 1 + elif isinstance(op, imp.Return): + recurse(op.expression) + elif isinstance(op, imp.Accumulate): + counter[op.indexsum] += 1 + recurse(op.indexsum.children[0]) + else: + raise AssertionError("unhandled operation: %s" % type(op)) + + return counter + + +def place_declarations(tree, reference_count, shape_map, apply_ordering, operations): + temporaries = set(reference_count) + indices = {} + # We later iterate over declare keys, so need this to be ordered + declare = collections.OrderedDict() + + def recurse(expr, loop_indices): + if isinstance(expr, imp.Block): + declare[expr] = [] + # Need to iterate over counter in given order + counter = OrderedCounter() + for statement in expr.children: + counter.update(recurse(statement, loop_indices)) + for e, count in counter.items(): + if count == reference_count[e]: + indices[e] = apply_ordering(set(shape_map(e)) - loop_indices) + if indices[e]: + declare[expr].append(e) + del counter[e] + return counter + elif isinstance(expr, imp.For): + return recurse(expr.children[0], loop_indices | {expr.index}) + else: + return count_references(temporaries, expr) + + remainder = recurse(tree, set()) + assert not remainder + + for op in operations: + declare[op] = False + if isinstance(op, imp.Evaluate): + e = op.expression + elif isinstance(op, imp.Initialise): + e = op.indexsum + else: + continue + + if len(indices[e]) == 0: + declare[op] = True + + return indices, declare From 4cd99bb29a810f326143f435f5c943de4c76a0d4 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 5 Feb 2016 13:42:37 +0000 Subject: [PATCH 029/809] nicer printing for gem.Index --- tsfc/gem.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tsfc/gem.py b/tsfc/gem.py index 84c2770b85..27b9907d7e 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -324,6 +324,12 @@ def set_extent(self, value): elif self.extent != value: raise ValueError("Inconsistent index extents!") + def __str__(self): + return self.name + + def __repr__(self): + return "Index(%r)" % self.name + class VariableIndex(object): def __init__(self, name): From cf2a5a02f2e5dbcdab8fc333fabd81f3f2a299e3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 5 Feb 2016 16:18:09 +0000 Subject: [PATCH 030/809] figuring out how to do dag visiting --- tsfc/driver.py | 8 +-- tsfc/optimise.py | 163 +++++++++++++++++++++++++++-------------------- 2 files changed, 98 insertions(+), 73 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index a49c0382a2..f35d8c5415 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -161,12 +161,10 @@ def compile_integral(integral, idata, fd, prefix, parameters): quadrature_index, nonfem, cell_orientations = \ fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, argument_indices, coefficient_map) nonfem = [ein.IndexSum(e, quadrature_index) for e in nonfem] - inlining_cache = {} - simplified = [opt.inline_indices(e, inlining_cache) for e in nonfem] + simplified = opt.remove_componenttensors(nonfem) - simplified = opt.expand_indexsum(simplified, max_extent=3) - inlining_cache = {} - simplified = [opt.inline_indices(e, inlining_cache) for e in simplified] + simplified = opt.unroll_indexsum(simplified, max_extent=3) + simplified = opt.remove_componenttensors(simplified) refcount = sch.count_references(simplified) candidates = set() diff --git a/tsfc/optimise.py b/tsfc/optimise.py index 1fa48813ef..4d22fbe207 100644 --- a/tsfc/optimise.py +++ b/tsfc/optimise.py @@ -6,85 +6,112 @@ IndexSum, ComponentTensor, ListTensor) -def inline_indices(expression, result_cache): - def cached_handle(node, subst): - cache_key = (node, tuple(sorted(subst.items()))) +class Memoizer(object): + def __init__(self, function): + self.cache = {} + self.function = function + + def __call__(self, node): try: - return result_cache[cache_key] + return self.cache[node] except KeyError: - result = handle(node, subst) - result_cache[cache_key] = result + result = self.function(node, self) + self.cache[node] = result return result - @singledispatch - def handle(node, subst): - raise AssertionError("Cannot handle foreign type: %s" % type(node)) - - @handle.register(Node) # noqa: Not actually redefinition - def _(node, subst): - new_children = [cached_handle(child, subst) for child in node.children] - if all(nc == c for nc, c in zip(new_children, node.children)): - return node - else: - return node.reconstruct(*new_children) - - @handle.register(Indexed) # noqa: Not actually redefinition - def _(node, subst): - child, = node.children - multiindex = tuple(subst.get(i, i) for i in node.multiindex) - if isinstance(child, ComponentTensor): - new_subst = dict(zip(child.multiindex, multiindex)) - composed_subst = {k: new_subst.get(v, v) for k, v in subst.items()} - composed_subst.update(new_subst) - filtered_subst = {k: v for k, v in composed_subst.items() if k in child.children[0].free_indices} - return cached_handle(child.children[0], filtered_subst) - elif isinstance(child, ListTensor) and all(isinstance(i, int) for i in multiindex): - return cached_handle(child.array[multiindex], subst) - elif isinstance(child, Literal) and all(isinstance(i, int) for i in multiindex): - return Literal(child.array[multiindex]) - else: - new_child = cached_handle(child, subst) - if new_child == child and multiindex == node.multiindex: - return node - else: - return Indexed(new_child, multiindex) - - return cached_handle(expression, {}) - -def expand_indexsum(expressions, max_extent): - result_cache = {} +class MemoizerWithArgs(object): + def __init__(self, function, argskeyfunc): + self.cache = {} + self.function = function + self.argskeyfunc = argskeyfunc - def cached_handle(node): + def __call__(self, node, *args, **kwargs): + cache_key = (node, self.argskeyfunc(*args, **kwargs)) try: - return result_cache[node] + return self.cache[cache_key] except KeyError: - result = handle(node) - result_cache[node] = result + result = self.function(node, self, *args, **kwargs) + self.cache[cache_key] = result return result - @singledispatch - def handle(node): - raise AssertionError("Cannot handle foreign type: %s" % type(node)) - @handle.register(Node) # noqa: Not actually redefinition - def _(node): - new_children = [cached_handle(child) for child in node.children] - if all(nc == c for nc, c in zip(new_children, node.children)): +def reuse_if_untouched(node, self): + new_children = map(self, node.children) + if all(nc == c for nc, c in zip(new_children, node.children)): + return node + else: + return node.reconstruct(*new_children) + + +def reuse_if_untouched_with_args(node, self, *args, **kwargs): + new_children = [self(child, *args, **kwargs) for child in node.children] + if all(nc == c for nc, c in zip(new_children, node.children)): + return node + else: + return node.reconstruct(*new_children) + + +@singledispatch +def replace_indices(node, self, subst): + raise AssertionError("cannot handle type %s" % type(node)) + + +replace_indices.register(Node)(reuse_if_untouched_with_args) + + +@replace_indices.register(Indexed) # noqa +def _(node, self, subst): + child, = node.children + multiindex = tuple(subst.get(i, i) for i in node.multiindex) + if isinstance(child, ComponentTensor): + new_subst = dict(zip(child.multiindex, multiindex)) + composed_subst = {k: new_subst.get(v, v) for k, v in subst.items()} + composed_subst.update(new_subst) + filtered_subst = {k: v for k, v in composed_subst.items() if k in child.children[0].free_indices} + return self(child.children[0], filtered_subst) + elif isinstance(child, ListTensor) and all(isinstance(i, int) for i in multiindex): + return self(child.array[multiindex], subst) + elif isinstance(child, Literal) and all(isinstance(i, int) for i in multiindex): + return Literal(child.array[multiindex]) + else: + new_child = self(child, subst) + if new_child == child and multiindex == node.multiindex: return node else: - return node.reconstruct(*new_children) - - @handle.register(IndexSum) # noqa: Not actually redefinition - def _(node): - if node.index.extent <= max_extent: - summand = cached_handle(node.children[0]) - ct = ComponentTensor(summand, (node.index,)) - result = Zero() - for i in xrange(node.index.extent): - result = Sum(result, Indexed(ct, (i,))) - return result - else: - return node.reconstruct(*[cached_handle(child) for child in node.children]) + return Indexed(new_child, multiindex) + + +def remove_componenttensors(expressions): + def argskeyfunc(subst): + return tuple(sorted(subst.items())) + + mapper = MemoizerWithArgs(replace_indices, argskeyfunc) + return [mapper(expression, {}) for expression in expressions] + + +@singledispatch +def _unroll_indexsum(node, self): + raise AssertionError("cannot handle type %s" % type(node)) + + +_unroll_indexsum.register(Node)(reuse_if_untouched) + + +@_unroll_indexsum.register(IndexSum) # noqa +def _(node, self): + if node.index.extent <= self.max_extent: + summand = self(node.children[0]) + ct = ComponentTensor(summand, (node.index,)) + return reduce(Sum, + (Indexed(ct, (i,)) + for i in range(node.index.extent)), + Zero()) + else: + return reuse_if_untouched(node, self) + - return [cached_handle(expression) for expression in expressions] +def unroll_indexsum(expressions, max_extent): + mapper = Memoizer(_unroll_indexsum) + mapper.max_extent = max_extent + return map(mapper, expressions) From 8eee8e1ebe47ce2cb0044b4640adbc9d5668e0be Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 5 Feb 2016 17:57:11 +0000 Subject: [PATCH 031/809] improve remove_componenttensors performance --- tsfc/optimise.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tsfc/optimise.py b/tsfc/optimise.py index 4d22fbe207..ae32ff4e76 100644 --- a/tsfc/optimise.py +++ b/tsfc/optimise.py @@ -68,8 +68,7 @@ def _(node, self, subst): new_subst = dict(zip(child.multiindex, multiindex)) composed_subst = {k: new_subst.get(v, v) for k, v in subst.items()} composed_subst.update(new_subst) - filtered_subst = {k: v for k, v in composed_subst.items() if k in child.children[0].free_indices} - return self(child.children[0], filtered_subst) + return self(child.children[0], composed_subst) elif isinstance(child, ListTensor) and all(isinstance(i, int) for i in multiindex): return self(child.array[multiindex], subst) elif isinstance(child, Literal) and all(isinstance(i, int) for i in multiindex): @@ -86,7 +85,11 @@ def remove_componenttensors(expressions): def argskeyfunc(subst): return tuple(sorted(subst.items())) - mapper = MemoizerWithArgs(replace_indices, argskeyfunc) + def filtered(node, self, subst): + filtered_subst = {k: v for k, v in subst.items() if k in node.free_indices} + return replace_indices(node, self, filtered_subst) + + mapper = MemoizerWithArgs(filtered, argskeyfunc) return [mapper(expression, {}) for expression in expressions] From 29f1b2f86e89e96d36ca0d5444232f0c4e008063 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 5 Feb 2016 18:13:50 +0000 Subject: [PATCH 032/809] optimise expansion of IndexSums --- tsfc/driver.py | 2 -- tsfc/optimise.py | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index f35d8c5415..16308b9ffe 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -162,9 +162,7 @@ def compile_integral(integral, idata, fd, prefix, parameters): fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, argument_indices, coefficient_map) nonfem = [ein.IndexSum(e, quadrature_index) for e in nonfem] simplified = opt.remove_componenttensors(nonfem) - simplified = opt.unroll_indexsum(simplified, max_extent=3) - simplified = opt.remove_componenttensors(simplified) refcount = sch.count_references(simplified) candidates = set() diff --git a/tsfc/optimise.py b/tsfc/optimise.py index ae32ff4e76..f44467d879 100644 --- a/tsfc/optimise.py +++ b/tsfc/optimise.py @@ -81,10 +81,11 @@ def _(node, self, subst): return Indexed(new_child, multiindex) -def remove_componenttensors(expressions): - def argskeyfunc(subst): - return tuple(sorted(subst.items())) +def argskeyfunc(subst): + return tuple(sorted(subst.items())) + +def remove_componenttensors(expressions): def filtered(node, self, subst): filtered_subst = {k: v for k, v in subst.items() if k in node.free_indices} return replace_indices(node, self, filtered_subst) @@ -105,16 +106,23 @@ def _unroll_indexsum(node, self): def _(node, self): if node.index.extent <= self.max_extent: summand = self(node.children[0]) - ct = ComponentTensor(summand, (node.index,)) return reduce(Sum, - (Indexed(ct, (i,)) + (self.replace(summand, {node.index: i}) for i in range(node.index.extent)), Zero()) else: return reuse_if_untouched(node, self) +def replace_indices_top(node, self, subst): + if subst: + return replace_indices(node, self, subst) + else: + return node + + def unroll_indexsum(expressions, max_extent): mapper = Memoizer(_unroll_indexsum) mapper.max_extent = max_extent + mapper.replace = MemoizerWithArgs(replace_indices_top, argskeyfunc) return map(mapper, expressions) From 2d965e602649d012f88b5756c8d030cc7e76460e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 5 Feb 2016 18:42:33 +0000 Subject: [PATCH 033/809] cleanup + little bit faster --- tsfc/gem.py | 13 ++++++++++--- tsfc/optimise.py | 25 ++++++++++--------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tsfc/gem.py b/tsfc/gem.py index 27b9907d7e..627fc17d0c 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -350,16 +350,23 @@ def __new__(cls, aggregate, multiindex): # Zero folding if isinstance(aggregate, Zero): return Zero() - else: - return super(Indexed, cls).__new__(cls) - def __init__(self, aggregate, multiindex): + # All indices fixed + if all(isinstance(i, int) for i in multiindex): + if isinstance(aggregate, Literal): + return Literal(aggregate.array[multiindex]) + elif isinstance(aggregate, ListTensor): + return aggregate.array[multiindex] + + self = super(Indexed, cls).__new__(cls) self.children = (aggregate,) self.multiindex = multiindex new_indices = tuple(i for i in multiindex if isinstance(i, Index)) self.free_indices = tuple(unique(aggregate.free_indices + new_indices)) + return self + class ComponentTensor(Node): __slots__ = ('children', 'multiindex', 'shape') diff --git a/tsfc/optimise.py b/tsfc/optimise.py index f44467d879..0745a25fef 100644 --- a/tsfc/optimise.py +++ b/tsfc/optimise.py @@ -2,8 +2,8 @@ from singledispatch import singledispatch -from tsfc.gem import (Node, Literal, Zero, Sum, Indexed, - IndexSum, ComponentTensor, ListTensor) +from tsfc.gem import (Node, Zero, Sum, Indexed, IndexSum, + ComponentTensor) class Memoizer(object): @@ -63,16 +63,11 @@ def replace_indices(node, self, subst): @replace_indices.register(Indexed) # noqa def _(node, self, subst): child, = node.children - multiindex = tuple(subst.get(i, i) for i in node.multiindex) + substitute = dict(subst) + multiindex = tuple(substitute.get(i, i) for i in node.multiindex) if isinstance(child, ComponentTensor): - new_subst = dict(zip(child.multiindex, multiindex)) - composed_subst = {k: new_subst.get(v, v) for k, v in subst.items()} - composed_subst.update(new_subst) - return self(child.children[0], composed_subst) - elif isinstance(child, ListTensor) and all(isinstance(i, int) for i in multiindex): - return self(child.array[multiindex], subst) - elif isinstance(child, Literal) and all(isinstance(i, int) for i in multiindex): - return Literal(child.array[multiindex]) + substitute.update(zip(child.multiindex, multiindex)) + return self(child.children[0], tuple(sorted(substitute.items()))) else: new_child = self(child, subst) if new_child == child and multiindex == node.multiindex: @@ -82,16 +77,16 @@ def _(node, self, subst): def argskeyfunc(subst): - return tuple(sorted(subst.items())) + return subst def remove_componenttensors(expressions): def filtered(node, self, subst): - filtered_subst = {k: v for k, v in subst.items() if k in node.free_indices} + filtered_subst = tuple((k, v) for k, v in subst if k in node.free_indices) return replace_indices(node, self, filtered_subst) mapper = MemoizerWithArgs(filtered, argskeyfunc) - return [mapper(expression, {}) for expression in expressions] + return [mapper(expression, ()) for expression in expressions] @singledispatch @@ -107,7 +102,7 @@ def _(node, self): if node.index.extent <= self.max_extent: summand = self(node.children[0]) return reduce(Sum, - (self.replace(summand, {node.index: i}) + (self.replace(summand, ((node.index, i),)) for i in range(node.index.extent)), Zero()) else: From 0bd52f14f9e7e619d3cf43b142c1b40a2e98a843 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 5 Feb 2016 18:51:35 +0000 Subject: [PATCH 034/809] slightly more optimised --- tsfc/optimise.py | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/tsfc/optimise.py b/tsfc/optimise.py index 0745a25fef..f81d0a5232 100644 --- a/tsfc/optimise.py +++ b/tsfc/optimise.py @@ -2,8 +2,7 @@ from singledispatch import singledispatch -from tsfc.gem import (Node, Zero, Sum, Indexed, IndexSum, - ComponentTensor) +from tsfc.gem import Node, Zero, Sum, Indexed, IndexSum, ComponentTensor class Memoizer(object): @@ -20,18 +19,17 @@ def __call__(self, node): return result -class MemoizerWithArgs(object): - def __init__(self, function, argskeyfunc): +class MemoizerWithArg(object): + def __init__(self, function): self.cache = {} self.function = function - self.argskeyfunc = argskeyfunc - def __call__(self, node, *args, **kwargs): - cache_key = (node, self.argskeyfunc(*args, **kwargs)) + def __call__(self, node, arg): + cache_key = (node, arg) try: return self.cache[cache_key] except KeyError: - result = self.function(node, self, *args, **kwargs) + result = self.function(node, self, arg) self.cache[cache_key] = result return result @@ -44,8 +42,8 @@ def reuse_if_untouched(node, self): return node.reconstruct(*new_children) -def reuse_if_untouched_with_args(node, self, *args, **kwargs): - new_children = [self(child, *args, **kwargs) for child in node.children] +def reuse_if_untouched_with_arg(node, self, arg): + new_children = [self(child, arg) for child in node.children] if all(nc == c for nc, c in zip(new_children, node.children)): return node else: @@ -57,7 +55,7 @@ def replace_indices(node, self, subst): raise AssertionError("cannot handle type %s" % type(node)) -replace_indices.register(Node)(reuse_if_untouched_with_args) +replace_indices.register(Node)(reuse_if_untouched_with_arg) @replace_indices.register(Indexed) # noqa @@ -76,16 +74,20 @@ def _(node, self, subst): return Indexed(new_child, multiindex) -def argskeyfunc(subst): - return subst +def filtered_replace_indices(node, self, subst): + filtered_subst = tuple((k, v) for k, v in subst if k in node.free_indices) + return replace_indices(node, self, filtered_subst) -def remove_componenttensors(expressions): - def filtered(node, self, subst): - filtered_subst = tuple((k, v) for k, v in subst if k in node.free_indices) - return replace_indices(node, self, filtered_subst) +def replace_indices_top(node, self, subst): + if subst: + return filtered_replace_indices(node, self, subst) + else: + return node + - mapper = MemoizerWithArgs(filtered, argskeyfunc) +def remove_componenttensors(expressions): + mapper = MemoizerWithArg(filtered_replace_indices) return [mapper(expression, ()) for expression in expressions] @@ -109,15 +111,8 @@ def _(node, self): return reuse_if_untouched(node, self) -def replace_indices_top(node, self, subst): - if subst: - return replace_indices(node, self, subst) - else: - return node - - def unroll_indexsum(expressions, max_extent): mapper = Memoizer(_unroll_indexsum) mapper.max_extent = max_extent - mapper.replace = MemoizerWithArgs(replace_indices_top, argskeyfunc) + mapper.replace = MemoizerWithArg(replace_indices_top) return map(mapper, expressions) From ee499fdc74c5bd2c04e2401d65c5c593cd967255 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 8 Feb 2016 11:01:42 +0000 Subject: [PATCH 035/809] move general functions to node.py + add docstrings --- tsfc/node.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ tsfc/optimise.py | 88 ++++++++++++++++++++-------------------------- 2 files changed, 128 insertions(+), 51 deletions(-) diff --git a/tsfc/node.py b/tsfc/node.py index 853fda5e90..6fb66e9145 100644 --- a/tsfc/node.py +++ b/tsfc/node.py @@ -107,3 +107,94 @@ def traversal(expression_dags): if child not in seen: seen.add(child) lifo.append(child) + + +def noop_recursive(function): + """No-op wrapper for functions with overridable recursive calls. + + :arg function: a function with parameters (value, rec), where + ``rec`` is expected to be a function used for + recursive calls. + :returns: a function with working recursion and nothing fancy + """ + def recursive(node): + return function(node, recursive) + return recursive + + +def noop_recursive_arg(function): + """No-op wrapper for functions with overridable recursive calls + and an argument. + + :arg function: a function with parameters (value, rec, arg), where + ``rec`` is expected to be a function used for + recursive calls. + :returns: a function with working recursion and nothing fancy + """ + def recursive(node, arg): + return function(node, recursive, arg) + return recursive + + +class Memoizer(object): + """Caching wrapper for functions with overridable recursive calls. + The lifetime of the cache is the lifetime of the object instance. + + :arg function: a function with parameters (value, rec), where + ``rec`` is expected to be a function used for + recursive calls. + :returns: a function with working recursion and caching + """ + def __init__(self, function): + self.cache = {} + self.function = function + + def __call__(self, node): + try: + return self.cache[node] + except KeyError: + result = self.function(node, self) + self.cache[node] = result + return result + + +class MemoizerArg(object): + """Caching wrapper for functions with overridable recursive calls + and an argument. The lifetime of the cache is the lifetime of the + object instance. + + :arg function: a function with parameters (value, rec, arg), where + ``rec`` is expected to be a function used for + recursive calls. + :returns: a function with working recursion and caching + """ + def __init__(self, function): + self.cache = {} + self.function = function + + def __call__(self, node, arg): + cache_key = (node, arg) + try: + return self.cache[cache_key] + except KeyError: + result = self.function(node, self, arg) + self.cache[cache_key] = result + return result + + +def reuse_if_untouched(node, self): + """Reuse if untouched recipe""" + new_children = map(self, node.children) + if all(nc == c for nc, c in zip(new_children, node.children)): + return node + else: + return node.reconstruct(*new_children) + + +def reuse_if_untouched_arg(node, self, arg): + """Reuse if touched recipe propagating an extra argument""" + new_children = [self(child, arg) for child in node.children] + if all(nc == c for nc, c in zip(new_children, node.children)): + return node + else: + return node.reconstruct(*new_children) diff --git a/tsfc/optimise.py b/tsfc/optimise.py index f81d0a5232..d2460e5f3d 100644 --- a/tsfc/optimise.py +++ b/tsfc/optimise.py @@ -2,60 +2,24 @@ from singledispatch import singledispatch +from tsfc.node import Memoizer, MemoizerArg, reuse_if_untouched, reuse_if_untouched_arg from tsfc.gem import Node, Zero, Sum, Indexed, IndexSum, ComponentTensor -class Memoizer(object): - def __init__(self, function): - self.cache = {} - self.function = function - - def __call__(self, node): - try: - return self.cache[node] - except KeyError: - result = self.function(node, self) - self.cache[node] = result - return result - - -class MemoizerWithArg(object): - def __init__(self, function): - self.cache = {} - self.function = function - - def __call__(self, node, arg): - cache_key = (node, arg) - try: - return self.cache[cache_key] - except KeyError: - result = self.function(node, self, arg) - self.cache[cache_key] = result - return result - - -def reuse_if_untouched(node, self): - new_children = map(self, node.children) - if all(nc == c for nc, c in zip(new_children, node.children)): - return node - else: - return node.reconstruct(*new_children) - - -def reuse_if_untouched_with_arg(node, self, arg): - new_children = [self(child, arg) for child in node.children] - if all(nc == c for nc, c in zip(new_children, node.children)): - return node - else: - return node.reconstruct(*new_children) - - @singledispatch def replace_indices(node, self, subst): + """Replace free indices in a GEM expression. + + :arg node: root of the expression + :arg self: function for recursive calls + :arg subst: tuple of pairs; each pair is a substitution + rule with a free index to replace and an index to + replace with. + """ raise AssertionError("cannot handle type %s" % type(node)) -replace_indices.register(Node)(reuse_if_untouched_with_arg) +replace_indices.register(Node)(reuse_if_untouched_arg) @replace_indices.register(Indexed) # noqa @@ -64,9 +28,12 @@ def _(node, self, subst): substitute = dict(subst) multiindex = tuple(substitute.get(i, i) for i in node.multiindex) if isinstance(child, ComponentTensor): + # Indexing into ComponentTensor + # Inline ComponentTensor and augment the substitution rules substitute.update(zip(child.multiindex, multiindex)) return self(child.children[0], tuple(sorted(substitute.items()))) else: + # Replace indices new_child = self(child, subst) if new_child == child and multiindex == node.multiindex: return node @@ -75,24 +42,36 @@ def _(node, self, subst): def filtered_replace_indices(node, self, subst): + """Wrapper for :func:`replace_indices`. At each call removes + substitution rules that do not apply.""" filtered_subst = tuple((k, v) for k, v in subst if k in node.free_indices) return replace_indices(node, self, filtered_subst) -def replace_indices_top(node, self, subst): - if subst: - return filtered_replace_indices(node, self, subst) +def filtered_replace_indices_top(node, self, subst): + """Wrapper for :func:`replace_indices`. At each call removes + substitution rules that do not apply. Stops recursion when there + is nothing to substitute.""" + filtered_subst = tuple((k, v) for k, v in subst if k in node.free_indices) + if filtered_subst: + return replace_indices(node, self, filtered_subst) else: return node def remove_componenttensors(expressions): - mapper = MemoizerWithArg(filtered_replace_indices) + """Removes all ComponentTensors from a list of expression DAGs.""" + mapper = MemoizerArg(filtered_replace_indices) return [mapper(expression, ()) for expression in expressions] @singledispatch def _unroll_indexsum(node, self): + """Unrolls IndexSums below a certain extent. + + :arg node: root of the expression + :arg self: function for recursive calls + """ raise AssertionError("cannot handle type %s" % type(node)) @@ -102,6 +81,7 @@ def _unroll_indexsum(node, self): @_unroll_indexsum.register(IndexSum) # noqa def _(node, self): if node.index.extent <= self.max_extent: + # Unrolling summand = self(node.children[0]) return reduce(Sum, (self.replace(summand, ((node.index, i),)) @@ -112,7 +92,13 @@ def _(node, self): def unroll_indexsum(expressions, max_extent): + """Unrolls IndexSums below a specified extent. + + :arg expressions: list of expression DAGs + :arg max_extent: maximum extent for which IndexSums are unrolled + :returns: list of expression DAGs with some unrolled IndexSums + """ mapper = Memoizer(_unroll_indexsum) mapper.max_extent = max_extent - mapper.replace = MemoizerWithArg(replace_indices_top) + mapper.replace = MemoizerArg(filtered_replace_indices_top) return map(mapper, expressions) From 8df02cd70c5e9bdba7e643e5fd390f4db7e108be Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 8 Feb 2016 10:20:15 +0000 Subject: [PATCH 036/809] Merge kernel bodies for integrals with different metadata Rather than spewing a new kernel for each integral in an integral data, merge the kernel bodies, fixes #2. This currently produces slightly sub-optimal code in that on affine cells, the Jacobian need only be computed once, but we compute one per integral. --- tsfc/driver.py | 75 ++++++++++++++++++++++++++++---------------------- tsfc/fem.py | 6 ++-- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9f5334285a..7186f3fecc 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -42,12 +42,11 @@ def compile_form(form, prefix="form", parameters=None): for idata in fd.integral_data: # TODO: Merge kernel bodies for multiple integrals with same # integral-data (same mesh iteration space). - for integral in idata.integrals: - start = time.time() - kernel = compile_integral(integral, idata, fd, prefix, parameters) - if kernel is not None: - print GREEN % ("compile_integral finished in %g seconds." % (time.time() - start)) - kernels.append(kernel) + start = time.time() + kernel = compile_integral(idata, fd, prefix, parameters) + if kernel is not None: + print GREEN % ("compile_integral finished in %g seconds." % (time.time() - start)) + kernels.append(kernel) print GREEN % ("TSFC finished in %g seconds." % (time.time() - cpu_time)) return kernels @@ -76,27 +75,15 @@ def __init__(self, ast=None, integral_type=None, oriented=False, super(Kernel, self).__init__() -def compile_integral(integral, idata, fd, prefix, parameters): +def compile_integral(idata, fd, prefix, parameters): # Remove these here, they're handled below. if parameters.get("quadrature_degree") == "auto": del parameters["quadrature_degree"] if parameters.get("quadrature_rule") == "auto": del parameters["quadrature_rule"] - _ = {} - # Record per-integral parameters - _.update(integral.metadata()) - # parameters override per-integral metadata - _.update(parameters) - parameters = _ - - # Check if the integral has a quad degree attached, otherwise use - # the estimated polynomial degree attached by compute_form_data - quadrature_degree = parameters.get("quadrature_degree", - parameters["estimated_polynomial_degree"]) - integral_type = integral.integral_type() - integrand = integral.integrand() - kernel = Kernel(integral_type=integral_type, subdomain_id=integral.subdomain_id()) + integral_type = idata.integral_type + kernel = Kernel(integral_type=integral_type, subdomain_id=idata.subdomain_id) arglist = [] prepare = [] @@ -146,21 +133,43 @@ def compile_integral(integral, idata, fd, prefix, parameters): qualifiers=["const"]) arglist.append(decl) - cell = integrand.ufl_domain().ufl_cell() - - quad_rule = parameters.get("quadrature_rule", + nonfem_ = [] + quadrature_indices = [] + cell = idata.domain.ufl_cell() + for i, integral in enumerate(idata.integrals): + params = {} + # Record per-integral parameters + params.update(integral.metadata()) + # parameters override per-integral metadata + params.update(parameters) + + # Check if the integral has a quad degree attached, otherwise use + # the estimated polynomial degree attached by compute_form_data + quadrature_degree = params.get("quadrature_degree", + params["estimated_polynomial_degree"]) + quad_rule = params.get("quadrature_rule", create_quadrature(cell, integral_type, quadrature_degree)) - if not isinstance(quad_rule, QuadratureRule): - raise ValueError("Expected to find a QuadratureRule object, not a %s" % - type(quad_rule)) + if not isinstance(quad_rule, QuadratureRule): + raise ValueError("Expected to find a QuadratureRule object, not a %s" % + type(quad_rule)) + + tabulation_manager = fem.TabulationManager(integral_type, cell, + quad_rule.points) + + integrand = fem.replace_coordinates(integral.integrand(), coordinates) + quadrature_index = ein.Index(name="ip%d" % i) + quadrature_indices.append(quadrature_index) + nonfem, cell_orientations = \ + fem.process(integral_type, integrand, tabulation_manager, + quad_rule.weights, quadrature_index, + argument_indices, coefficient_map) + nonfem_.append([ein.IndexSum(e, quadrature_index) for e in nonfem]) + + # Sum the expressions that are part of the same restriction + nonfem = list(reduce(ein.Sum, e, ein.Zero()) for e in zip(*nonfem_)) - tabulation_manager = fem.TabulationManager(integral_type, cell, quad_rule.points) - integrand = fem.replace_coordinates(integrand, coordinates) - quadrature_index, nonfem, cell_orientations = \ - fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, argument_indices, coefficient_map) - nonfem = [ein.IndexSum(e, quadrature_index) for e in nonfem] inlining_cache = {} simplified = [opt.inline_indices(e, inlining_cache) for e in nonfem] @@ -192,7 +201,7 @@ def compile_integral(integral, idata, fd, prefix, parameters): indices.update(node.free_indices) indices = sorted(indices) - index_ordering = apply_prefix_ordering(indices, (quadrature_index,) + argument_indices) + index_ordering = apply_prefix_ordering(indices, tuple(quadrature_indices) + argument_indices) apply_ordering = make_index_orderer(index_ordering) shape_map = lambda expr: expr.free_indices diff --git a/tsfc/fem.py b/tsfc/fem.py index d64fb70f9f..aff71738e9 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -634,7 +634,7 @@ def replace_coordinates(integrand, coordinate_coefficient): return map_expr_dag(ReplaceSpatialCoordinates(coordinate_coefficient), integrand) -def process(integral_type, integrand, tabulation_manager, quadrature_weights, argument_indices, coefficient_map): +def process(integral_type, integrand, tabulation_manager, quadrature_weights, quadrature_index, argument_indices, coefficient_map): # Abs-simplification integrand = map_expr_dag(SimplifyExpr(), integrand) @@ -664,12 +664,10 @@ def process(integral_type, integrand, tabulation_manager, quadrature_weights, ar # Translate UFL to Einstein's notation, # lowering finite element specific nodes - quadrature_index = ein.Index(name='ip') - translator = Translator(quadrature_weights, quadrature_index, argument_indices, tabulation_manager, coefficient_map) - return quadrature_index, map_expr_dags(translator, expressions), translator.cell_orientations + return map_expr_dags(translator, expressions), translator.cell_orientations def make_cell_facet_jacobian(terminal): From efbfb358d19a47b36e4a9d47674f287ebbbcdb6a Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 8 Feb 2016 11:43:23 +0000 Subject: [PATCH 037/809] gem: Add equality and hash to VariableIndex --- tsfc/gem.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tsfc/gem.py b/tsfc/gem.py index 84c2770b85..5ccb73818f 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -329,6 +329,18 @@ class VariableIndex(object): def __init__(self, name): self.name = name + def __eq__(self, other): + """VariableIndices are equal if their names are equal.""" + if self is other: + return True + if type(self) is not type(other): + return False + return self.name == other.name + + def __hash__(self): + """VariableIndices hash to the hash of their name.""" + return hash(self.name) + class Indexed(Scalar): __slots__ = ('children', 'multiindex') From d486544b75a800eac4369c11fa5ad7b09c2bf1f4 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 8 Feb 2016 11:43:46 +0000 Subject: [PATCH 038/809] ufl2gem: Reuse free indices where appropriate Allows for loop merging and CSE when we have multiple integrals in the kernel body. --- tsfc/driver.py | 8 +++++++- tsfc/fem.py | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7186f3fecc..91fa11538d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -136,6 +136,12 @@ def compile_integral(idata, fd, prefix, parameters): nonfem_ = [] quadrature_indices = [] cell = idata.domain.ufl_cell() + # Map from UFL FiniteElement objects to Index instances. This is + # so we reuse Index instances when evaluating the same coefficient + # multiple times with the same table. Occurs, for example, if we + # have multiple integrals here (and the affine coordinate + # evaluation can be hoisted). + index_cache = {} for i, integral in enumerate(idata.integrals): params = {} # Record per-integral parameters @@ -164,7 +170,7 @@ def compile_integral(idata, fd, prefix, parameters): nonfem, cell_orientations = \ fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, quadrature_index, - argument_indices, coefficient_map) + argument_indices, coefficient_map, index_cache) nonfem_.append([ein.IndexSum(e, quadrature_index) for e in nonfem]) # Sum the expressions that are part of the same restriction diff --git a/tsfc/fem.py b/tsfc/fem.py index aff71738e9..e37636b965 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -429,7 +429,8 @@ def get(self, key, restriction, cellwise_constant=False): class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): - def __init__(self, weights, quadrature_index, argument_indices, tabulation_manager, coefficient_map): + def __init__(self, weights, quadrature_index, argument_indices, tabulation_manager, + coefficient_map, index_cache): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) self.weights = ein.Literal(weights) @@ -439,6 +440,7 @@ def __init__(self, weights, quadrature_index, argument_indices, tabulation_manag self.coefficient_map = coefficient_map self.cell_orientations = False self.facet = tabulation_manager.facet + self.index_cache = index_cache def modified_terminal(self, o): mt = analyse_modified_terminal(o) @@ -509,12 +511,16 @@ def _(terminal, e, mt, params): degree = map_expr_dag(FindPolynomialDegree(), e) cellwise_constant = not (degree is None or degree > 0) - def evaluate_at(params, key): + def evaluate_at(params, key, index_key): table = params.tabulation_manager.get(key, mt.restriction, cellwise_constant) kernel_argument = params.coefficient_map[terminal] q = ein.Index() - r = ein.Index() + try: + r = params.index_cache[index_key] + except KeyError: + r = ein.Index() + params.index_cache[index_key] = r if mt.restriction is None: kar = ein.Indexed(kernel_argument, (r,)) @@ -545,7 +551,7 @@ def evaluate_at(params, key): for multiindex, key in zip(numpy.ndindex(e.ufl_shape), table_keys(terminal.ufl_element(), mt.local_derivatives)): - result[multiindex] = evaluate_at(params, key) + result[multiindex] = evaluate_at(params, key, terminal.ufl_element()) if result.shape: return ein.ListTensor(result) @@ -634,7 +640,8 @@ def replace_coordinates(integrand, coordinate_coefficient): return map_expr_dag(ReplaceSpatialCoordinates(coordinate_coefficient), integrand) -def process(integral_type, integrand, tabulation_manager, quadrature_weights, quadrature_index, argument_indices, coefficient_map): +def process(integral_type, integrand, tabulation_manager, quadrature_weights, quadrature_index, + argument_indices, coefficient_map, index_cache): # Abs-simplification integrand = map_expr_dag(SimplifyExpr(), integrand) @@ -666,7 +673,7 @@ def process(integral_type, integrand, tabulation_manager, quadrature_weights, qu # lowering finite element specific nodes translator = Translator(quadrature_weights, quadrature_index, argument_indices, tabulation_manager, - coefficient_map) + coefficient_map, index_cache) return map_expr_dags(translator, expressions), translator.cell_orientations From 4101d13bd2c2b3bd727da7403828d5fb62a1a839 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 8 Feb 2016 16:06:57 +0000 Subject: [PATCH 039/809] rename: OuterProduct* -> TensorProduct* --- tests/test_create_element.py | 2 +- tests/test_create_quadrature.py | 2 +- tsfc/fem.py | 20 ++++++++++---------- tsfc/fiatinterface.py | 12 ++++++------ tsfc/quadrature.py | 8 ++++---- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/test_create_element.py b/tests/test_create_element.py index 2fb923e2b0..b3b3449994 100644 --- a/tests/test_create_element.py +++ b/tests/test_create_element.py @@ -64,7 +64,7 @@ def ufl_B(tensor_name): def test_tensor_prod_simple(ufl_A, ufl_B): - tensor_ufl = ufl.OuterProductElement(ufl_A, ufl_B) + tensor_ufl = ufl.TensorProductElement(ufl_A, ufl_B) tensor = f.create_element(tensor_ufl) A = f.create_element(ufl_A) diff --git a/tests/test_create_quadrature.py b/tests/test_create_quadrature.py index 79b331777b..66df464282 100644 --- a/tests/test_create_quadrature.py +++ b/tests/test_create_quadrature.py @@ -24,7 +24,7 @@ def tensor_product_cell(cell): if cell.cellname() == "tetrahedron": pytest.skip("Tensor-producted tet not supported") - return ufl.OuterProductCell(cell, ufl.interval) + return ufl.TensorProductCell(cell, ufl.interval) @pytest.mark.parametrize("degree", diff --git a/tsfc/fem.py b/tsfc/fem.py index e37636b965..edc1b83d36 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -157,7 +157,7 @@ def _spanning_degree(self, element): elif cell.cellname() == "quadrilateral": # TODO: Tensor-product space assumed return 2*element.degree() - elif cell.cellname() == "OuterProductCell": + elif cell.cellname() == "TensorProductCell": try: return sum(element.degree()) except TypeError: @@ -620,9 +620,9 @@ def _(terminal, e, mt, params): ufl.Cell("triangle"): 1.0/2.0, ufl.Cell("quadrilateral"): 1.0, ufl.Cell("tetrahedron"): 1.0/6.0, - ufl.OuterProductCell(ufl.Cell("interval"), ufl.Cell("interval")): 1.0, - ufl.OuterProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): 1.0/2.0, - ufl.OuterProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): 1.0} + ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")): 1.0, + ufl.TensorProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): 1.0/2.0, + ufl.TensorProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): 1.0} return ein.Literal(volume[cell]) @@ -726,9 +726,9 @@ def make_cell_facet_jacobian(terminal): ufl.Cell("triangle"): triangle, ufl.Cell("quadrilateral"): quadrilateral, ufl.Cell("tetrahedron"): tetrahedron, - ufl.OuterProductCell(ufl.Cell("interval"), ufl.Cell("interval")): interval_x_interval, - ufl.OuterProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): triangle_x_interval, - ufl.OuterProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): quadrilateral_x_interval} + ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")): interval_x_interval, + ufl.TensorProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): triangle_x_interval, + ufl.TensorProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): quadrilateral_x_interval} table = cell_to_table[cell] @@ -779,9 +779,9 @@ def make_reference_normal(terminal): ufl.Cell("triangle"): triangle, ufl.Cell("quadrilateral"): quadrilateral, ufl.Cell("tetrahedron"): tetrahedron, - ufl.OuterProductCell(ufl.Cell("interval"), ufl.Cell("interval")): interval_x_interval, - ufl.OuterProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): triangle_x_interval, - ufl.OuterProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): quadrilateral_x_interval} + ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")): interval_x_interval, + ufl.TensorProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): triangle_x_interval, + ufl.TensorProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): quadrilateral_x_interval} table = cell_to_table[cell] diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 8569169797..73bbbc6087 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -56,7 +56,7 @@ "Lagrange": FIAT.Lagrange, "Nedelec 1st kind H(curl)": FIAT.Nedelec, "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, - "OuterProductElement": FIAT.TensorFiniteElement, + "TensorProductElement": FIAT.TensorFiniteElement, "Raviart-Thomas": FIAT.RaviartThomas, "TraceElement": FIAT.HDivTrace, "Regge": FIAT.Regge, @@ -168,10 +168,10 @@ def _(element, vector_is_mixed): # Now for the OPE-specific stuff -@convert.register(ufl.OuterProductElement) # noqa +@convert.register(ufl.TensorProductElement) # noqa def _(element, vector_is_mixed): cell = element.cell() - if type(cell) is not ufl.OuterProductCell: + if type(cell) is not ufl.TensorProductCell: raise ValueError("OPE not on OPC?") A = element._A B = element._B @@ -196,8 +196,8 @@ def _(element, vector_is_mixed): if not vector_is_mixed: assert isinstance(element, (ufl.VectorElement, ufl.TensorElement, - ufl.OuterProductVectorElement, - ufl.OuterProductTensorElement)) + ufl.TensorProductVectorElement, + ufl.TensorProductTensorElement)) return create_element(element.sub_elements()[0], vector_is_mixed) elements = [] @@ -215,7 +215,7 @@ def rec(eles): return MixedElement(fiat_elements) -quad_opc = ufl.OuterProductCell(ufl.Cell("interval"), ufl.Cell("interval")) +quad_opc = ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")) _cache = weakref.WeakKeyDictionary() diff --git a/tsfc/quadrature.py b/tsfc/quadrature.py index e3b9312800..5a935da015 100644 --- a/tsfc/quadrature.py +++ b/tsfc/quadrature.py @@ -324,10 +324,10 @@ def create_quadrature_rule(cell, degree, scheme="default"): try: degree = tuple(degree) - if cellname != "OuterProductCell": + if cellname != "TensorProductCell": raise ValueError("Not expecting tuple of degrees") except TypeError: - if cellname == "OuterProductCell": + if cellname == "TensorProductCell": # We got a single degree, assume we meant that degree in # each direction. degree = (degree, degree) @@ -383,7 +383,7 @@ def select_degree(degree, cell, integral_type): if integral_type == "cell": return degree if integral_type in ("exterior_facet", "interior_facet"): - if cell.cellname() == "OuterProductCell": + if cell.cellname() == "TensorProductCell": raise ValueError("Integral type '%s' invalid for cell '%s'" % (integral_type, cell.cellname())) if cell.cellname() == "quadrilateral": @@ -397,7 +397,7 @@ def select_degree(degree, cell, integral_type): except TypeError: return degree return degree - if cell.cellname() != "OuterProductCell": + if cell.cellname() != "TensorProductCell": raise ValueError("Integral type '%s' invalid for cell '%s'" % (integral_type, cell.cellname())) if integral_type in ("exterior_facet_top", "exterior_facet_bottom", From d4554b588734b77ebb6d7967792f1a2abced0981 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 8 Feb 2016 16:47:42 +0000 Subject: [PATCH 040/809] further fixes --- tsfc/fem.py | 10 +++++----- tsfc/fiatinterface.py | 6 +++--- tsfc/quadrature.py | 26 +++++++++++++++----------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index edc1b83d36..185d55f44d 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -157,7 +157,7 @@ def _spanning_degree(self, element): elif cell.cellname() == "quadrilateral": # TODO: Tensor-product space assumed return 2*element.degree() - elif cell.cellname() == "TensorProductCell": + elif isinstance(cell, ufl.TensorProductCell): try: return sum(element.degree()) except TypeError: @@ -374,7 +374,7 @@ def __init__(self, integral_type, cell, points): self.tabulators.append(NumericTabulator(numpy.asarray(map(t, points)))) elif integral_type in ['exterior_facet_vert', 'interior_facet_vert']: - for entity in range(cell._A.num_facets()): # "base cell" facets + for entity in range(cell.sub_cells()[0].num_facets()): # "base cell" facets t = as_fiat_cell(cell).get_vert_facet_transform(entity) self.tabulators.append(NumericTabulator(numpy.asarray(map(t, points)))) @@ -790,11 +790,11 @@ def make_reference_normal(terminal): def make_cell_edge_vectors(terminal): - from FIAT.reference_element import two_product_cell + from FIAT.reference_element import TensorProductCell shape = terminal.ufl_shape cell = as_fiat_cell(terminal.ufl_domain().ufl_cell()) - if isinstance(cell, two_product_cell): - raise NotImplementedError("CEV not implemented on OPEs yet") + if isinstance(cell, TensorProductCell): + raise NotImplementedError("CEV not implemented on TPEs yet") nedge = len(cell.get_topology()[1]) vecs = numpy.vstack(tuple(cell.compute_edge_tangent(i) for i in range(nedge))).astype(NUMPY_TYPE) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 73bbbc6087..fd78b03bfd 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -56,7 +56,7 @@ "Lagrange": FIAT.Lagrange, "Nedelec 1st kind H(curl)": FIAT.Nedelec, "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, - "TensorProductElement": FIAT.TensorFiniteElement, + "TensorProductElement": FIAT.TensorProductElement, "Raviart-Thomas": FIAT.RaviartThomas, "TraceElement": FIAT.HDivTrace, "Regge": FIAT.Regge, @@ -175,8 +175,8 @@ def _(element, vector_is_mixed): raise ValueError("OPE not on OPC?") A = element._A B = element._B - return FIAT.TensorFiniteElement(create_element(A, vector_is_mixed), - create_element(B, vector_is_mixed)) + return FIAT.TensorProductElement(create_element(A, vector_is_mixed), + create_element(B, vector_is_mixed)) @convert.register(ufl.HDivElement) # noqa diff --git a/tsfc/quadrature.py b/tsfc/quadrature.py index 5a935da015..5e74304841 100644 --- a/tsfc/quadrature.py +++ b/tsfc/quadrature.py @@ -29,7 +29,7 @@ import FIAT from FIAT.reference_element import UFCInterval, UFCTriangle, UFCTetrahedron, \ - FiredrakeQuadrilateral, two_product_cell + FiredrakeQuadrilateral, TensorProductCell import ufl from tsfc.fiatinterface import as_fiat_cell @@ -94,7 +94,7 @@ def default_scheme(cell, degree): raise ValueError("No scheme handler defined for %s" % cell) -@default_scheme.register(two_product_cell) # noqa +@default_scheme.register(TensorProductCell) # noqa @default_scheme.register(FiredrakeQuadrilateral) @default_scheme.register(UFCInterval) def _(cell, degree): @@ -320,19 +320,17 @@ def create_quadrature_rule(cell, degree, scheme="default"): if scheme not in ("default", "canonical"): raise ValueError("Unknown quadrature scheme '%s'" % scheme) - cellname = cell.cellname() - try: degree = tuple(degree) - if cellname != "TensorProductCell": + if not isinstance(cell, ufl.TensorProductCell): raise ValueError("Not expecting tuple of degrees") except TypeError: - if cellname == "TensorProductCell": + if isinstance(cell, ufl.TensorProductCell): # We got a single degree, assume we meant that degree in # each direction. degree = (degree, degree) - if cellname == "vertex": + if cell.cellname() == "vertex": if degree < 0: raise ValueError("Need positive degree, not %d" % degree) return QuadratureRule(numpy.zeros((1, 0), dtype=numpy.float64), @@ -360,11 +358,17 @@ def integration_cell(cell, integral_type): "quadrilateral": ufl.interval, "tetrahedron": ufl.triangle, "hexahedron": ufl.quadrilateral}[cell.cellname()] + # Extruded cases + base_cell, interval = cell.sub_cells() + assert interval.cellname() == "interval" if integral_type in ("exterior_facet_top", "exterior_facet_bottom", "interior_facet_horiz"): - return cell.facet_horiz + return base_cell if integral_type in ("exterior_facet_vert", "interior_facet_vert"): - return cell.facet_vert + if base_cell.topological_dimension() == 2: + return ufl.TensorProductCell(ufl.interval, ufl.interval) + elif base_cell.topological_dimension() == 1: + return ufl.interval raise ValueError("Don't know how to find an integration cell") @@ -383,7 +387,7 @@ def select_degree(degree, cell, integral_type): if integral_type == "cell": return degree if integral_type in ("exterior_facet", "interior_facet"): - if cell.cellname() == "TensorProductCell": + if isinstance(cell, ufl.TensorProductCell): raise ValueError("Integral type '%s' invalid for cell '%s'" % (integral_type, cell.cellname())) if cell.cellname() == "quadrilateral": @@ -397,7 +401,7 @@ def select_degree(degree, cell, integral_type): except TypeError: return degree return degree - if cell.cellname() != "TensorProductCell": + if not isinstance(cell, ufl.TensorProductCell): raise ValueError("Integral type '%s' invalid for cell '%s'" % (integral_type, cell.cellname())) if integral_type in ("exterior_facet_top", "exterior_facet_bottom", From 1b87072af247fcd7d525f896296cf01c3a2121e2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 8 Feb 2016 12:00:29 +0000 Subject: [PATCH 041/809] move reference geometry handling into a new file --- tsfc/fem.py | 234 +++++----------------------------------------- tsfc/gem.py | 23 +++++ tsfc/geometric.py | 182 ++++++++++++++++++++++++++++++++++++ 3 files changed, 227 insertions(+), 212 deletions(-) create mode 100644 tsfc/geometric.py diff --git a/tsfc/fem.py b/tsfc/fem.py index 185d55f44d..aa197f4a81 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -9,19 +9,18 @@ import ufl from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, CellEdgeVectors, CellFacetJacobian, - CellOrientation, Coefficient, FormArgument, - QuadratureWeight, ReferenceCellVolume, - ReferenceNormal, ReferenceValue, ScalarValue, - Zero) +from ufl.classes import (Argument, Coefficient, FormArgument, + GeometricQuantity, QuadratureWeight, + ReferenceValue, Zero) from ufl.domain import find_geometric_dimension from tsfc.fiatinterface import create_element, as_fiat_cell from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal -from tsfc.constants import NUMPY_TYPE, PRECISION +from tsfc.constants import PRECISION from tsfc import gem as ein from tsfc import ufl2gem +from tsfc import geometric # FFC uses one less digits for rounding than for printing @@ -437,11 +436,24 @@ def __init__(self, weights, quadrature_index, argument_indices, tabulation_manag self.quadrature_index = quadrature_index self.argument_indices = argument_indices self.tabulation_manager = tabulation_manager + self.integral_type = tabulation_manager.integral_type self.coefficient_map = coefficient_map self.cell_orientations = False self.facet = tabulation_manager.facet self.index_cache = index_cache + def get_cell_orientations(self): + try: + return self._cell_orientations + except AttributeError: + if self.integral_type.startswith("interior_facet"): + result = ein.Variable("cell_orientations", (2, 1)) + else: + result = ein.Variable("cell_orientations", (1, 1)) + self.cell_orientations = True + self._cell_orientations = result + return result + def modified_terminal(self, o): mt = analyse_modified_terminal(o) return translate(mt.terminal, o, mt, self) @@ -474,19 +486,14 @@ def translate(terminal, e, mt, params): raise AssertionError("Cannot handle terminal type: %s" % type(terminal)) -@translate.register(Zero) # noqa: Not actually redefinition -def _(terminal, e, mt, params): - assert False - - -@translate.register(ScalarValue) # noqa: Not actually redefinition +@translate.register(QuadratureWeight) # noqa: Not actually redefinition def _(terminal, e, mt, params): - assert False + return ein.Indexed(params.weights, (params.quadrature_index,)) -@translate.register(QuadratureWeight) # noqa: Not actually redefinition +@translate.register(GeometricQuantity) # noqa: Not actually redefinition def _(terminal, e, mt, params): - return ein.Indexed(params.weights, (params.quadrature_index,)) + return geometric.translate(terminal, mt, params) @translate.register(Argument) # noqa: Not actually redefinition @@ -559,78 +566,6 @@ def evaluate_at(params, key, index_key): return result[()] -@translate.register(CellFacetJacobian) # noqa: Not actually redefinition -def _(terminal, e, mt, params): - i = ein.Index() - j = ein.Index() - f = params.facet[mt.restriction] - table = make_cell_facet_jacobian(terminal) - if params.tabulation_manager.integral_type in ["exterior_facet_bottom", - "exterior_facet_top", - "interior_facet_horiz"]: - table = table[:2] - elif params.tabulation_manager.integral_type in ["exterior_facet_vert", "interior_facet_vert"]: - table = table[2:] - return ein.ComponentTensor( - ein.Indexed( - ein.Literal(table), - (f, i, j)), - (i, j)) - - -@translate.register(ReferenceNormal) # noqa: Not actually redefinition -def _(terminal, e, mt, params): - i = ein.Index() - f = params.facet[mt.restriction] - table = make_reference_normal(terminal) - if params.tabulation_manager.integral_type in ["exterior_facet_bottom", - "exterior_facet_top", - "interior_facet_horiz"]: - table = table[:2] - elif params.tabulation_manager.integral_type in ["exterior_facet_vert", "interior_facet_vert"]: - table = table[2:] - return ein.ComponentTensor( - ein.Indexed( - ein.Literal(table), - (f, i,)), - (i,)) - - -@translate.register(CellOrientation) # noqa: Not actually redefinition -def _(terminal, e, mt, params): - if mt.restriction == '+' or mt.restriction is None: - f = 0 - elif mt.restriction == '-': - f = 1 - else: - assert False - params.cell_orientations = True - raw = ein.Indexed(ein.Variable("cell_orientations", (2, 1)), (f, 0)) # TODO: (2, 1) and (1, 1) - return ein.Conditional(ein.Comparison("==", raw, ein.Literal(1)), - ein.Literal(-1), - ein.Conditional(ein.Comparison("==", raw, ein.Zero()), - ein.Literal(1), - ein.Literal(numpy.nan))) - - -@translate.register(ReferenceCellVolume) # noqa: Not actually redefinition -def _(terminal, e, mt, params): - cell = terminal.ufl_domain().ufl_cell() - volume = {ufl.Cell("interval"): 1.0, - ufl.Cell("triangle"): 1.0/2.0, - ufl.Cell("quadrilateral"): 1.0, - ufl.Cell("tetrahedron"): 1.0/6.0, - ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")): 1.0, - ufl.TensorProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): 1.0/2.0, - ufl.TensorProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): 1.0} - return ein.Literal(volume[cell]) - - -@translate.register(CellEdgeVectors) # noqa: Not actually redefinition -def _(terminal, e, mt, params): - return ein.Literal(make_cell_edge_vectors(terminal)) - - def coordinate_coefficient(domain): return ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) @@ -675,128 +610,3 @@ def process(integral_type, integrand, tabulation_manager, quadrature_weights, qu argument_indices, tabulation_manager, coefficient_map, index_cache) return map_expr_dags(translator, expressions), translator.cell_orientations - - -def make_cell_facet_jacobian(terminal): - - interval = numpy.array([[1.0], - [1.0]], dtype=NUMPY_TYPE) - - triangle = numpy.array([[-1.0, 1.0], - [0.0, 1.0], - [1.0, 0.0]], dtype=NUMPY_TYPE) - - tetrahedron = numpy.array([[-1.0, -1.0, 1.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]], dtype=NUMPY_TYPE) - - quadrilateral = numpy.array([[0.0, 1.0], - [0.0, 1.0], - [1.0, 0.0], - [1.0, 0.0]], dtype=NUMPY_TYPE) - - # Outer product cells - # Convention is: - # Bottom facet, top facet, then the extruded facets in the order - # of the base cell - interval_x_interval = numpy.array([[1.0, 0.0], - [1.0, 0.0], - [0.0, 1.0], - [0.0, 1.0]], dtype=NUMPY_TYPE) - - triangle_x_interval = numpy.array([[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [-1.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0]], dtype=NUMPY_TYPE) - - quadrilateral_x_interval = numpy.array([[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0]], - dtype=NUMPY_TYPE) - - cell = terminal.ufl_domain().ufl_cell() - cell = cell.reconstruct(geometric_dimension=cell.topological_dimension()) - - cell_to_table = {ufl.Cell("interval"): interval, - ufl.Cell("triangle"): triangle, - ufl.Cell("quadrilateral"): quadrilateral, - ufl.Cell("tetrahedron"): tetrahedron, - ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")): interval_x_interval, - ufl.TensorProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): triangle_x_interval, - ufl.TensorProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): quadrilateral_x_interval} - - table = cell_to_table[cell] - - shape = table.shape[:1] + terminal.ufl_shape - return table.reshape(shape) - - -def make_reference_normal(terminal): - interval = numpy.array([[-1.0], - [1.0]], dtype=NUMPY_TYPE) - - triangle = numpy.array([[1.0, 1.0], - [-1.0, 0.0], - [0.0, -1.]], dtype=NUMPY_TYPE) - - tetrahedron = numpy.array([[1.0, 1.0, 1.0], - [-1.0, 0.0, 0.0], - [0.0, -1.0, 0.0], - [0.0, 0.0, -1.0]], dtype=NUMPY_TYPE) - - quadrilateral = numpy.array([[-1.0, 0.0], - [1.0, 0.0], - [0.0, -1.0], - [0.0, 1.0]], dtype=NUMPY_TYPE) - - interval_x_interval = numpy.array([[0.0, -1.0], - [0.0, 1.0], - [-1.0, 0.0], - [1.0, 0.0]], dtype=NUMPY_TYPE) - - triangle_x_interval = numpy.array([[0.0, 0.0, -1.0], - [0.0, 0.0, 1.0], - [1.0, 1.0, 0.0], - [-1.0, 0.0, 0.0], - [0.0, -1.0, 0.0]], dtype=NUMPY_TYPE) - - quadrilateral_x_interval = numpy.array([[0.0, 0.0, -1.0], - [0.0, 0.0, 1.0], - [-1.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.0, -1.0, 0.0], - [0.0, 1.0, 0.0]], dtype=NUMPY_TYPE) - - cell = terminal.ufl_domain().ufl_cell() - cell = cell.reconstruct(geometric_dimension=cell.topological_dimension()) - - cell_to_table = {ufl.Cell("interval"): interval, - ufl.Cell("triangle"): triangle, - ufl.Cell("quadrilateral"): quadrilateral, - ufl.Cell("tetrahedron"): tetrahedron, - ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")): interval_x_interval, - ufl.TensorProductCell(ufl.Cell("triangle"), ufl.Cell("interval")): triangle_x_interval, - ufl.TensorProductCell(ufl.Cell("quadrilateral"), ufl.Cell("interval")): quadrilateral_x_interval} - - table = cell_to_table[cell] - - shape = table.shape[:1] + terminal.ufl_shape - return table.reshape(shape) - - -def make_cell_edge_vectors(terminal): - from FIAT.reference_element import TensorProductCell - shape = terminal.ufl_shape - cell = as_fiat_cell(terminal.ufl_domain().ufl_cell()) - if isinstance(cell, TensorProductCell): - raise NotImplementedError("CEV not implemented on TPEs yet") - nedge = len(cell.get_topology()[1]) - vecs = numpy.vstack(tuple(cell.compute_edge_tangent(i) for i in range(nedge))).astype(NUMPY_TYPE) - - assert vecs.shape == shape - return vecs diff --git a/tsfc/gem.py b/tsfc/gem.py index 4eca5fbfac..81b890dc11 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -472,3 +472,26 @@ def is_equal(self, other): def get_hash(self): return hash((type(self), self.shape, self.children)) + + +def partial_indexed(tensor, indices): + """Generalised indexing into a tensor. The number of indices may + be less than or equal to the rank of the tensor, so the result may + have a non-empty shape. + + :arg tensor: tensor-valued GEM expression + :arg indices: indices, at most as many as the rank of the tensor + :returns: a potentially tensor-valued expression + """ + if len(indices) == 0: + return tensor + elif len(indices) < len(tensor.shape): + rank = len(tensor.shape) - len(indices) + shape_indices = tuple(Index() for i in range(rank)) + return ComponentTensor( + Indexed(tensor, indices + shape_indices), + shape_indices) + elif len(indices) == len(tensor.shape): + return Indexed(tensor, indices) + else: + raise ValueError("More indices than rank!") diff --git a/tsfc/geometric.py b/tsfc/geometric.py new file mode 100644 index 0000000000..0155bce69f --- /dev/null +++ b/tsfc/geometric.py @@ -0,0 +1,182 @@ +"""Functions to translate UFL reference geometric quantities into GEM +expressions.""" + +from __future__ import absolute_import + +from numpy import array, nan, vstack +from singledispatch import singledispatch + +from ufl import interval, triangle, quadrilateral, tetrahedron +from ufl import TensorProductCell +from ufl.classes import (CellEdgeVectors, CellFacetJacobian, + CellOrientation, ReferenceCellVolume, + ReferenceNormal) + +from tsfc.constants import NUMPY_TYPE +from tsfc.fiatinterface import as_fiat_cell +from tsfc import gem + + +interval_x_interval = TensorProductCell(interval, interval) +triangle_x_interval = TensorProductCell(triangle, interval) +quadrilateral_x_interval = TensorProductCell(quadrilateral, interval) + + +# Volume of the reference cells +reference_cell_volume = { + interval: 1.0, + triangle: 1.0/2.0, + quadrilateral: 1.0, + tetrahedron: 1.0/6.0, + interval_x_interval: 1.0, + triangle_x_interval: 1.0/2.0, + quadrilateral_x_interval: 1.0, +} + + +# Jacobian of the mapping from a facet to the cell on the reference cell +cell_facet_jacobian = { + interval: array([[1.0], + [1.0]], dtype=NUMPY_TYPE), + triangle: array([[-1.0, 1.0], + [0.0, 1.0], + [1.0, 0.0]], dtype=NUMPY_TYPE), + quadrilateral: array([[0.0, 1.0], + [0.0, 1.0], + [1.0, 0.0], + [1.0, 0.0]], dtype=NUMPY_TYPE), + tetrahedron: array([[-1.0, -1.0, 1.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]], dtype=NUMPY_TYPE), + # Product cells. Convention: + # Bottom, top, then vertical facets in the order of the base cell + interval_x_interval: array([[1.0, 0.0], + [1.0, 0.0], + [0.0, 1.0], + [0.0, 1.0]], dtype=NUMPY_TYPE), + triangle_x_interval: array([[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [-1.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0]], + dtype=NUMPY_TYPE), + quadrilateral_x_interval: array([[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0]], + dtype=NUMPY_TYPE), +} + + +# Facet normals of the reference cells +reference_normal = { + interval: array([[-1.0], + [1.0]], dtype=NUMPY_TYPE), + triangle: array([[1.0, 1.0], + [-1.0, 0.0], + [0.0, -1.0]], dtype=NUMPY_TYPE), + quadrilateral: array([[-1.0, 0.0], + [1.0, 0.0], + [0.0, -1.0], + [0.0, 1.0]], dtype=NUMPY_TYPE), + tetrahedron: array([[1.0, 1.0, 1.0], + [-1.0, 0.0, 0.0], + [0.0, -1.0, 0.0], + [0.0, 0.0, -1.0]], dtype=NUMPY_TYPE), + # Product cells. Convention: + # Bottom, top, then vertical facets in the order of the base cell + interval_x_interval: array([[0.0, -1.0], + [0.0, 1.0], + [-1.0, 0.0], + [1.0, 0.0]], dtype=NUMPY_TYPE), + triangle_x_interval: array([[0.0, 0.0, -1.0], + [0.0, 0.0, 1.0], + [1.0, 1.0, 0.0], + [-1.0, 0.0, 0.0], + [0.0, -1.0, 0.0]], dtype=NUMPY_TYPE), + quadrilateral_x_interval: array([[0.0, 0.0, -1.0], + [0.0, 0.0, 1.0], + [-1.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, -1.0, 0.0], + [0.0, 1.0, 0.0]], dtype=NUMPY_TYPE), +} + + +def reference_cell(terminal): + """Reference cell of terminal""" + cell = terminal.ufl_domain().ufl_cell() + return cell.reconstruct(geometric_dimension=cell.topological_dimension()) + + +def strip_table(table, integral_type): + """Select horizontal or vertical parts for facet integrals on + extruded cells. No-op in all other cases.""" + if integral_type in ["exterior_facet_bottom", "exterior_facet_top", "interior_facet_horiz"]: + # Bottom and top + return table[:2] + elif integral_type in ["exterior_facet_vert", "interior_facet_vert"]: + # Vertical facets in the order of the base cell + return table[2:] + else: + return table + + +@singledispatch +def translate(terminal, mt, params): + """Translate geometric UFL quantity into GEM expression. + + :arg terminal: UFL geometric quantity terminal + :arg mt: modified terminal data (e.g. for restriction) + :arg params: miscellaneous + """ + raise AssertionError("Cannot handle geometric quantity type: %s" % type(terminal)) + + +@translate.register(CellOrientation) # noqa +def _(terminal, mt, params): + cell_orientations = params.get_cell_orientations() + f = {None: 0, '+': 0, '-': 1}[mt.restriction] + co_int = gem.Indexed(cell_orientations, (f, 0)) + return gem.Conditional(gem.Comparison("==", co_int, gem.Literal(1)), + gem.Literal(-1), + gem.Conditional(gem.Comparison("==", co_int, gem.Zero()), + gem.Literal(1), + gem.Literal(nan))) + + +@translate.register(ReferenceCellVolume) # noqa +def _(terminal, mt, params): + return gem.Literal(reference_cell_volume[reference_cell(terminal)]) + + +@translate.register(CellFacetJacobian) # noqa +def _(terminal, mt, params): + table = cell_facet_jacobian[reference_cell(terminal)] + table = strip_table(table, params.integral_type) + table = table.reshape(table.shape[:1] + terminal.ufl_shape) + return gem.partial_indexed(gem.Literal(table), (params.facet[mt.restriction],)) + + +@translate.register(ReferenceNormal) # noqa +def _(terminal, mt, params): + table = reference_normal[reference_cell(terminal)] + table = strip_table(table, params.integral_type) + table = table.reshape(table.shape[:1] + terminal.ufl_shape) + return gem.partial_indexed(gem.Literal(table), (params.facet[mt.restriction],)) + + +@translate.register(CellEdgeVectors) # noqa +def _(terminal, mt, params): + from FIAT.reference_element import TensorProductCell as fiat_TensorProductCell + fiat_cell = as_fiat_cell(terminal.ufl_domain().ufl_cell()) + if isinstance(fiat_cell, fiat_TensorProductCell): + raise NotImplementedError("CellEdgeVectors not implemented on TensorProductElements yet") + + nedges = len(fiat_cell.get_topology()[1]) + vecs = vstack(map(fiat_cell.compute_edge_tangent, range(nedges))).astype(NUMPY_TYPE) + assert vecs.shape == terminal.ufl_shape + return gem.Literal(vecs) From 9d5ac46264adc53fdbb0ce664b113c0940d4f776 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 10 Feb 2016 15:21:42 +0000 Subject: [PATCH 042/809] Add an interpreter for GEM Takes a GEM tree with some optional data bindings for Variable and VariableIndex nodes and evaluates it. --- tsfc/interpreter.py | 306 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 tsfc/interpreter.py diff --git a/tsfc/interpreter.py b/tsfc/interpreter.py new file mode 100644 index 0000000000..1a556cfea2 --- /dev/null +++ b/tsfc/interpreter.py @@ -0,0 +1,306 @@ +""" +An interpreter for GEM trees. +""" +from __future__ import absolute_import + +import numpy +import operator +import math +from singledispatch import singledispatch +import itertools + +from tsfc import gem, node + +__all__ = ("evaluate", ) + + +class Result(object): + """An array object that tracks which axes of the array correspond to + gem free indices (and what those free indices are). + + :arg arr: The array. + :arg fids: The free indices. + + The first ``len(fids)`` axes of the provided array correspond to + the free indices, the remaining axes are the shape of each entry. + """ + def __init__(self, arr, fids=None): + self.arr = arr + self.fids = fids if fids is not None else () + + def filter(self, idx, fids): + """Given an index tuple and some free indices, return a + "filtered" index tuple which removes entries that correspond + to indices in fids that are not in ``self.fids``. + + :arg idx: The index tuple to filter. + :arg fids: The free indices for the index tuple. + """ + return tuple(idx[fids.index(i)] for i in self.fids) + idx[len(fids):] + + def __getitem__(self, idx): + return self.arr[idx] + + def __setitem__(self, idx, val): + self.arr[idx] = val + + @property + def tshape(self): + """The total shape of the result array.""" + return self.arr.shape + + @property + def fshape(self): + """The shape of the free index part of the result array.""" + return self.tshape[:len(self.fids)] + + @property + def shape(self): + """The shape of the shape part of the result array.""" + return self.tshape[len(self.fids):] + + def __repr__(self): + return "Result(%r, %r)" % (self.arr, self.fids) + + def __str__(self): + return repr(self) + + @classmethod + def empty(cls, *children, **kwargs): + """Build an empty Result object. + + :arg children: The children used to determine the shape and + free indices. + :kwarg dtype: The data type of the result array. + """ + dtype = kwargs.get("dtype", float) + assert all(children[0].shape == c.shape for c in children) + fids = [] + for f in itertools.chain(*(c.fids for c in children)): + if f not in fids: + fids.append(f) + shape = tuple(i.extent for i in fids) + children[0].shape + return cls(numpy.empty(shape, dtype=dtype), tuple(fids)) + + +@singledispatch +def _evaluate(expression, self): + """Evaluate an expression using a provided callback handler. + + :arg expression: The expression to evaluation. + :arg self: The callback handler (should provide bindings). + """ + raise ValueError("Unhandled node type %s" % type(expression)) + + +@_evaluate.register(gem.Zero) # noqa: not actually redefinition +def _(e, self): + """Zeros produce an array of zeros.""" + return Result(numpy.zeros(e.shape, dtype=float)) + + +@_evaluate.register(gem.Literal) # noqa: not actually redefinition +def _(e, self): + """Literals return their array.""" + return Result(e.array) + + +@_evaluate.register(gem.Variable) # noqa: not actually redefinition +def _(e, self): + """Look up variables in the provided bindings.""" + try: + val = self.bindings[e] + except KeyError: + raise ValueError("Binding for %s not found" % e) + if val.shape != e.shape: + raise ValueError("Binding for %s has wrong shape. %s, not %s." % + (e, val.shape, e.shape)) + return Result(val) + + +@_evaluate.register(gem.Power) # noqa: not actually redefinition +@_evaluate.register(gem.Division) +@_evaluate.register(gem.Product) +@_evaluate.register(gem.Sum) +def _(e, self): + op = {gem.Product: operator.mul, + gem.Division: operator.div, + gem.Sum: operator.add, + gem.Power: operator.pow}[type(e)] + + a, b = [self(o) for o in e.children] + result = Result.empty(a, b) + fids = result.fids + for idx in numpy.ndindex(result.tshape): + result[idx] = op(a[a.filter(idx, fids)], b[b.filter(idx, fids)]) + return result + + +@_evaluate.register(gem.MathFunction) # noqa: not actually redefinition +def _(e, self): + ops = [self(o) for o in e.children] + result = Result.empty(*ops) + names = {"abs": abs, + "log": math.log} + op = names[e.name] + for idx in numpy.ndindex(result.tshape): + result[idx] = op(*(o[o.filter(idx, result.fids)] for o in ops)) + return result + + +@_evaluate.register(gem.MaxValue) # noqa: not actually redefinition +@_evaluate.register(gem.MinValue) +def _(e, self): + ops = [self(o) for o in e.children] + result = Result.empty(*ops) + op = {gem.MinValue: min, + gem.MaxValue: max}[type(e)] + for idx in numpy.ndindex(result.tshape): + result[idx] = op(*(o[o.filter(idx, result.fids)] for o in ops)) + return result + + +@_evaluate.register(gem.Comparison) # noqa: not actually redefinition +def _(e, self): + ops = [self(o) for o in e.children] + op = {">": operator.gt, + ">=": operator.ge, + "==": operator.eq, + "!=": operator.ne, + "<": operator.lt, + "<=": operator.le}[e.operator] + result = Result.empty(*ops, dtype=bool) + for idx in numpy.ndindex(result.tshape): + result[idx] = op(*(o[o.filter(idx, result.fids)] for o in ops)) + return result + + +@_evaluate.register(gem.LogicalNot) # noqa: not actually redefinition +def _(e, self): + val = self(e.children[0]) + assert val.arr.dtype == numpy.dtype("bool") + result = Result.empty(val, bool) + for idx in numpy.ndindex(result.tshape): + result[idx] = not val[val.filter(idx, result.fids)] + return result + + +@_evaluate.register(gem.LogicalAnd) # noqa: not actually redefinition +def _(e, self): + a, b = [self(o) for o in e.children] + assert a.arr.dtype == numpy.dtype("bool") + assert b.arr.dtype == numpy.dtype("bool") + result = Result.empty(a, b, bool) + for idx in numpy.ndindex(result.tshape): + result[idx] = a[a.filter(idx, result.fids)] and \ + b[b.filter(idx, result.fids)] + return result + + +@_evaluate.register(gem.LogicalOr) # noqa: not actually redefinition +def _(e, self): + a, b = [self(o) for o in e.children] + assert a.arr.dtype == numpy.dtype("bool") + assert b.arr.dtype == numpy.dtype("bool") + result = Result.empty(a, b, dtype=bool) + for idx in numpy.ndindex(result.tshape): + result[idx] = a[a.filter(idx, result.fids)] or \ + b[b.filter(idx, result.fids)] + return result + + +@_evaluate.register(gem.Conditional) # noqa: not actually redefinition +def _(e, self): + cond, then, else_ = [self(o) for o in e.children] + assert cond.arr.dtype == numpy.dtype("bool") + result = Result.empty(cond, then, else_) + for idx in numpy.ndindex(result.tshape): + if cond[cond.filter(idx, result.fids)]: + result[idx] = then[then.filter(idx, result.fids)] + else: + result[idx] = else_[else_.filter(idx, result.fids)] + return result + + +@_evaluate.register(gem.Indexed) # noqa: not actually redefinition +def _(e, self): + """Indexing maps shape to free indices""" + val = self(e.children[0]) + fids = tuple(i for i in e.multiindex if isinstance(i, gem.Index)) + + idx = [] + # First pick up all the existing free indices + for _ in val.fids: + idx.append(Ellipsis) + # Now grab the shape axes + for i in e.multiindex: + if isinstance(i, gem.Index): + # Free index, want entire extent + idx.append(Ellipsis) + elif isinstance(i, gem.VariableIndex): + try: + # Variable indices must be provided in bindings + idx.append(self.bindings[i]) + except KeyError: + raise ValueError("Binding for %s not found" % i) + else: + # Fixed index, just pick that value + idx.append(i) + assert len(idx) == len(val.tshape) + return Result(val[idx], val.fids + fids) + + +@_evaluate.register(gem.ComponentTensor) # noqa: not actually redefinition +def _(e, self): + """Component tensors map free indices to shape.""" + val = self(e.children[0]) + axes = [] + fids = [] + # First grab the free indices that aren't bound + for a, f in enumerate(val.fids): + if f not in e.multiindex: + axes.append(a) + fids.append(f) + # Now the bound free indices + for i in e.multiindex: + axes.append(val.fids.index(i)) + # Now the existing shape + axes.extend(range(len(val.fshape), len(val.tshape))) + return Result(numpy.transpose(val.arr, axes=axes), + tuple(fids)) + + +@_evaluate.register(gem.IndexSum) # noqa: not actually redefinition +def _(e, self): + """Index sums reduce over the given axis.""" + val = self(e.children[0]) + idx = val.fids.index(e.index) + return Result(val.arr.sum(axis=idx), + val.fids[:idx] + val.fids[idx+1:]) + + +@_evaluate.register(gem.ListTensor) # noqa: not actually redefinition +def _(e, self): + """List tensors just turn into arrays.""" + ops = [self(o) for o in e.children] + assert all(ops[0].fids == o.fids for o in ops) + return Result(numpy.asarray([o.arr for o in ops]).reshape(e.shape), + ops[0].fids) + + +def evaluate(expressions, bindings=None): + """Evaluate some GEM expressions given variable bindings. + + :arg expressions: A single GEM expression, or iterable of + expressions to evaluate. + :kwarg bindings: An optional dict mapping GEM :class:`gem.Variable` and + :class:`gem.VariableIndex` nodes to data. + :returns: a list of the evaluated expressions. + """ + try: + exprs = tuple(expressions) + except TypeError: + exprs = (expressions, ) + mapper = node.Memoizer(_evaluate) + mapper.bindings = bindings if bindings is not None else {} + return map(mapper, exprs) From 432358a2be43b8a77116606ea8fc4e8af2bb426b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 10 Feb 2016 16:43:04 +0000 Subject: [PATCH 043/809] fix degree estimation bug on hexahedra Fixes #9. --- tsfc/fem.py | 66 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index aa197f4a81..709061871c 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -124,6 +124,39 @@ def modified_terminal(self, o): return o +def _spanning_degree(cell, degree): + if cell is None: + assert degree == 0 + return degree + elif cell.cellname() in ["interval", "triangle", "tetrahedron"]: + return degree + elif cell.cellname() == "quadrilateral": + # TODO: Tensor-product space assumed + return 2 * degree + elif isinstance(cell, ufl.TensorProductCell): + try: + return sum(_spanning_degree(sub_cell, d) + for sub_cell, d in zip(cell.sub_cells(), degree)) + except TypeError: + assert degree == 0 + return 0 + else: + raise ValueError("Unknown cell %s" % cell.cellname()) + + +def spanning_degree(element): + """Determine the degree of the polynomial space spanning an element. + + :arg element: The element to determine the degree of. + + .. warning:: + + For non-simplex elements, this assumes a tensor-product + space. + """ + return _spanning_degree(element.cell(), element.degree()) + + class FindPolynomialDegree(MultiFunction): """Simple-minded degree estimator. @@ -138,33 +171,6 @@ class FindPolynomialDegree(MultiFunction): zero but d^2/dxdy is not. """ - def _spanning_degree(self, element): - """Determine the degree of the polynomial space spanning an element. - - :arg element: The element to determine the degree of. - - .. warning:: - - For non-simplex elements, this assumes a tensor-product - space. - """ - cell = element.cell() - if cell is None: - return element.degree() - if cell.cellname() in ("interval", "triangle", "tetrahedron"): - return element.degree() - elif cell.cellname() == "quadrilateral": - # TODO: Tensor-product space assumed - return 2*element.degree() - elif isinstance(cell, ufl.TensorProductCell): - try: - return sum(element.degree()) - except TypeError: - assert element.degree() == 0 - return 0 - else: - raise ValueError("Unknown cell %s" % cell.cellname()) - def quadrature_weight(self, o): return 0 @@ -177,10 +183,10 @@ def expr(self, o): # Coefficient-like things, compute degree of spanning polynomial space def spatial_coordinate(self, o): - return self._spanning_degree(o.ufl_domain().ufl_coordinate_element()) + return spanning_degree(o.ufl_domain().ufl_coordinate_element()) def form_argument(self, o): - return self._spanning_degree(o.ufl_element()) + return spanning_degree(o.ufl_element()) # Index-like operations, return degree of operand def component_tensor(self, o, op, idx): @@ -404,6 +410,8 @@ def get(self, key, restriction, cellwise_constant=False): except KeyError: tables = [tabulator[key] for tabulator in self.tabulators] if cellwise_constant: + for table in tables: + assert numpy.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) tables = [table[0] for table in tables] if self.integral_type == 'cell': From 2d32b65487719c6a6e2c5ec0c55a9ef847256d7c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 9 Feb 2016 12:13:16 +0000 Subject: [PATCH 044/809] little refactoring in fem.py --- tsfc/fem.py | 99 ++++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 51 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 709061871c..48a3cf0c36 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -18,7 +18,7 @@ from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal from tsfc.constants import PRECISION -from tsfc import gem as ein +from tsfc import gem from tsfc import ufl2gem from tsfc import geometric @@ -357,7 +357,6 @@ class TabulationManager(object): def __init__(self, integral_type, cell, points): self.integral_type = integral_type - self.cell = cell self.points = points self.tabulators = [] @@ -386,27 +385,13 @@ def __init__(self, integral_type, cell, points): else: raise NotImplementedError("integral type %s not supported" % integral_type) - if integral_type in ['exterior_facet', 'exterior_facet_vert']: - self.facet = {None: ein.VariableIndex('facet[0]')} - elif integral_type in ['interior_facet', 'interior_facet_vert']: - self.facet = {'+': ein.VariableIndex('facet[0]'), - '-': ein.VariableIndex('facet[1]')} - elif integral_type == 'exterior_facet_bottom': - self.facet = {None: 0} - elif integral_type == 'exterior_facet_top': - self.facet = {None: 1} - elif integral_type == 'interior_facet_horiz': - self.facet = {'+': 1, '-': 0} - else: - self.facet = None - def tabulate(self, ufl_element, max_deriv): for tabulator in self.tabulators: tabulator.tabulate(ufl_element, max_deriv) - def get(self, key, restriction, cellwise_constant=False): + def get(self, key, cellwise_constant=False): try: - table = self.tables[(key, cellwise_constant)] + return self.tables[(key, cellwise_constant)] except KeyError: tables = [tabulator[key] for tabulator in self.tabulators] if cellwise_constant: @@ -420,18 +405,7 @@ def get(self, key, restriction, cellwise_constant=False): table = numpy.array(tables) self.tables[(key, cellwise_constant)] = table - - if self.integral_type == 'cell': - return ein.Literal(table) - else: - f = self.facet[restriction] - - indices = tuple(ein.Index() for i in range(len(table.shape)-1)) - return ein.ComponentTensor( - ein.Indexed( - ein.Literal(table), - (f,) + indices), - indices) + return table class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): @@ -440,28 +414,49 @@ def __init__(self, weights, quadrature_index, argument_indices, tabulation_manag coefficient_map, index_cache): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) - self.weights = ein.Literal(weights) + integral_type = tabulation_manager.integral_type + self.weights = gem.Literal(weights) self.quadrature_index = quadrature_index self.argument_indices = argument_indices self.tabulation_manager = tabulation_manager - self.integral_type = tabulation_manager.integral_type + self.integral_type = integral_type self.coefficient_map = coefficient_map self.cell_orientations = False - self.facet = tabulation_manager.facet self.index_cache = index_cache + if integral_type in ['exterior_facet', 'exterior_facet_vert']: + self.facet = {None: gem.VariableIndex('facet[0]')} + elif integral_type in ['interior_facet', 'interior_facet_vert']: + self.facet = {'+': gem.VariableIndex('facet[0]'), + '-': gem.VariableIndex('facet[1]')} + elif integral_type == 'exterior_facet_bottom': + self.facet = {None: 0} + elif integral_type == 'exterior_facet_top': + self.facet = {None: 1} + elif integral_type == 'interior_facet_horiz': + self.facet = {'+': 1, '-': 0} + else: + self.facet = None + def get_cell_orientations(self): try: return self._cell_orientations except AttributeError: if self.integral_type.startswith("interior_facet"): - result = ein.Variable("cell_orientations", (2, 1)) + result = gem.Variable("cell_orientations", (2, 1)) else: - result = ein.Variable("cell_orientations", (1, 1)) + result = gem.Variable("cell_orientations", (1, 1)) self.cell_orientations = True self._cell_orientations = result return result + def select_facet(self, tensor, restriction): + if self.integral_type == 'cell': + return tensor + else: + f = self.facet[restriction] + return gem.partial_indexed(tensor, (f,)) + def modified_terminal(self, o): mt = analyse_modified_terminal(o) return translate(mt.terminal, o, mt, self) @@ -496,7 +491,7 @@ def translate(terminal, e, mt, params): @translate.register(QuadratureWeight) # noqa: Not actually redefinition def _(terminal, e, mt, params): - return ein.Indexed(params.weights, (params.quadrature_index,)) + return gem.Indexed(params.weights, (params.quadrature_index,)) @translate.register(GeometricQuantity) # noqa: Not actually redefinition @@ -512,11 +507,12 @@ def _(terminal, e, mt, params): for multiindex, key in zip(numpy.ndindex(e.ufl_shape), table_keys(terminal.ufl_element(), mt.local_derivatives)): - table = params.tabulation_manager.get(key, mt.restriction) - result[multiindex] = ein.Indexed(table, (params.quadrature_index, argument_index)) + table = params.tabulation_manager.get(key) + table = params.select_facet(gem.Literal(table), mt.restriction) + result[multiindex] = gem.Indexed(table, (params.quadrature_index, argument_index)) if result.shape: - return ein.ListTensor(result) + return gem.ListTensor(result) else: return result[()] @@ -527,32 +523,33 @@ def _(terminal, e, mt, params): cellwise_constant = not (degree is None or degree > 0) def evaluate_at(params, key, index_key): - table = params.tabulation_manager.get(key, mt.restriction, cellwise_constant) + table = params.tabulation_manager.get(key, cellwise_constant) + table = params.select_facet(gem.Literal(table), mt.restriction) kernel_argument = params.coefficient_map[terminal] - q = ein.Index() + q = gem.Index() try: r = params.index_cache[index_key] except KeyError: - r = ein.Index() + r = gem.Index() params.index_cache[index_key] = r if mt.restriction is None: - kar = ein.Indexed(kernel_argument, (r,)) + kar = gem.Indexed(kernel_argument, (r,)) elif mt.restriction is '+': - kar = ein.Indexed(kernel_argument, (0, r)) + kar = gem.Indexed(kernel_argument, (0, r)) elif mt.restriction is '-': - kar = ein.Indexed(kernel_argument, (1, r)) + kar = gem.Indexed(kernel_argument, (1, r)) else: assert False if cellwise_constant: - return ein.IndexSum(ein.Product(ein.Indexed(table, (r,)), kar), r) + return gem.IndexSum(gem.Product(gem.Indexed(table, (r,)), kar), r) else: - return ein.Indexed( - ein.ComponentTensor( - ein.IndexSum( - ein.Product(ein.Indexed(table, (q, r)), + return gem.Indexed( + gem.ComponentTensor( + gem.IndexSum( + gem.Product(gem.Indexed(table, (q, r)), kar), r), (q,)), @@ -569,7 +566,7 @@ def evaluate_at(params, key, index_key): result[multiindex] = evaluate_at(params, key, terminal.ufl_element()) if result.shape: - return ein.ListTensor(result) + return gem.ListTensor(result) else: return result[()] From 6382a8773baf5a0ec308f8921f809cb3a6716e60 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 10 Feb 2016 15:39:00 +0000 Subject: [PATCH 045/809] cellwise constant Argument + refactorings --- tsfc/driver.py | 9 ++-- tsfc/fem.py | 131 ++++++++++++++++++++++++------------------------- 2 files changed, 69 insertions(+), 71 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b67435206c..d84cec07b7 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,8 +1,10 @@ from __future__ import absolute_import -import numpy +import collections import time +import numpy + from ufl.algorithms import compute_form_data from ufl.log import GREEN @@ -141,7 +143,7 @@ def compile_integral(idata, fd, prefix, parameters): # multiple times with the same table. Occurs, for example, if we # have multiple integrals here (and the affine coordinate # evaluation can be hoisted). - index_cache = {} + index_cache = collections.defaultdict(ein.Index) for i, integral in enumerate(idata.integrals): params = {} # Record per-integral parameters @@ -171,7 +173,8 @@ def compile_integral(idata, fd, prefix, parameters): fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, quadrature_index, argument_indices, coefficient_map, index_cache) - nonfem_.append([ein.IndexSum(e, quadrature_index) for e in nonfem]) + nonfem_.append([(ein.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) + for e in nonfem]) # Sum the expressions that are part of the same restriction nonfem = list(reduce(ein.Sum, e, ein.Zero()) for e in zip(*nonfem_)) diff --git a/tsfc/fem.py b/tsfc/fem.py index 48a3cf0c36..60dc1f9934 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -14,10 +14,9 @@ ReferenceValue, Zero) from ufl.domain import find_geometric_dimension +from tsfc.constants import PRECISION from tsfc.fiatinterface import create_element, as_fiat_cell - from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal -from tsfc.constants import PRECISION from tsfc import gem from tsfc import ufl2gem from tsfc import geometric @@ -327,30 +326,35 @@ def abs(self, o, op): return self.expr(o, op) -class NumericTabulator(object): +def _tabulate(ufl_element, order, points): + element = create_element(ufl_element) + phi = element.space_dimension() + C = ufl_element.reference_value_size() - len(ufl_element.symmetry()) + q = len(points) + for D, fiat_table in element.tabulate(order, points).iteritems(): + reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) + for c, table in enumerate(reordered_table): + yield (ufl_element, c, D), table - def __init__(self, points): - self.points = points - self.tables = {} - def tabulate(self, ufl_element, max_deriv): - element = create_element(ufl_element) - phi = element.space_dimension() - C = ufl_element.reference_value_size() - len(ufl_element.symmetry()) - q = len(self.points) - for D, fiat_table in element.tabulate(max_deriv, self.points).iteritems(): - reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) - for c, table in enumerate(reordered_table): - # Copied from FFC (ffc/quadrature/quadratureutils.py) - table[abs(table) < epsilon] = 0 - table[abs(table - 1.0) < epsilon] = 1.0 - table[abs(table + 1.0) < epsilon] = -1.0 - table[abs(table - 0.5) < epsilon] = 0.5 - table[abs(table + 0.5) < epsilon] = -0.5 - self.tables[(ufl_element, c, D)] = table +def tabulate(ufl_element, order, points): + for key, table in _tabulate(ufl_element, order, points): + # Copied from FFC (ffc/quadrature/quadratureutils.py) + table[abs(table) < epsilon] = 0 + table[abs(table - 1.0) < epsilon] = 1.0 + table[abs(table + 1.0) < epsilon] = -1.0 + table[abs(table - 0.5) < epsilon] = 0.5 + table[abs(table + 0.5) < epsilon] = -0.5 - def __getitem__(self, key): - return self.tables[key] + if FindPolynomialDegree()._spanning_degree(ufl_element) <= sum(key[2]): + assert numpy.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) + table = table[0] + + yield key, table + + +def make_tabulator(points): + return lambda elem, order: tabulate(elem, order, points) class TabulationManager(object): @@ -363,49 +367,41 @@ def __init__(self, integral_type, cell, points): self.tables = {} if integral_type == 'cell': - self.tabulators.append(NumericTabulator(points)) + self.tabulators.append(make_tabulator(points)) elif integral_type in ['exterior_facet', 'interior_facet']: - # TODO: handle and test integration on facets of intervals - for entity in range(cell.num_facets()): t = as_fiat_cell(cell).get_facet_transform(entity) - self.tabulators.append(NumericTabulator(numpy.asarray(map(t, points)))) + self.tabulators.append(make_tabulator(numpy.asarray(map(t, points)))) elif integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: for entity in range(2): # top and bottom t = as_fiat_cell(cell).get_horiz_facet_transform(entity) - self.tabulators.append(NumericTabulator(numpy.asarray(map(t, points)))) + self.tabulators.append(make_tabulator(numpy.asarray(map(t, points)))) elif integral_type in ['exterior_facet_vert', 'interior_facet_vert']: for entity in range(cell.sub_cells()[0].num_facets()): # "base cell" facets t = as_fiat_cell(cell).get_vert_facet_transform(entity) - self.tabulators.append(NumericTabulator(numpy.asarray(map(t, points)))) + self.tabulators.append(make_tabulator(numpy.asarray(map(t, points)))) else: raise NotImplementedError("integral type %s not supported" % integral_type) def tabulate(self, ufl_element, max_deriv): + store = collections.defaultdict(list) for tabulator in self.tabulators: - tabulator.tabulate(ufl_element, max_deriv) + for key, table in tabulator(ufl_element, max_deriv): + store[key].append(table) - def get(self, key, cellwise_constant=False): - try: - return self.tables[(key, cellwise_constant)] - except KeyError: - tables = [tabulator[key] for tabulator in self.tabulators] - if cellwise_constant: - for table in tables: - assert numpy.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) - tables = [table[0] for table in tables] - - if self.integral_type == 'cell': - table, = tables - else: - table = numpy.array(tables) + if self.integral_type == 'cell': + for key, (table,) in store.iteritems(): + self.tables[key] = table + else: + for key, tables in store.iteritems(): + self.tables[key] = numpy.array(tables) - self.tables[(key, cellwise_constant)] = table - return table + def __getitem__(self, key): + return self.tables[key] class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): @@ -507,9 +503,17 @@ def _(terminal, e, mt, params): for multiindex, key in zip(numpy.ndindex(e.ufl_shape), table_keys(terminal.ufl_element(), mt.local_derivatives)): - table = params.tabulation_manager.get(key) + table = params.tabulation_manager[key] table = params.select_facet(gem.Literal(table), mt.restriction) - result[multiindex] = gem.Indexed(table, (params.quadrature_index, argument_index)) + if len(table.shape) == 1: + # Cellwise constant + row = table + elif len(table.shape) == 2: + # Varying on cell + row = gem.partial_indexed(table, (params.quadrature_index,)) + else: + assert False + result[multiindex] = gem.Indexed(row, (argument_index,)) if result.shape: return gem.ListTensor(result) @@ -519,20 +523,12 @@ def _(terminal, e, mt, params): @translate.register(Coefficient) # noqa: Not actually redefinition def _(terminal, e, mt, params): - degree = map_expr_dag(FindPolynomialDegree(), e) - cellwise_constant = not (degree is None or degree > 0) - def evaluate_at(params, key, index_key): - table = params.tabulation_manager.get(key, cellwise_constant) + table = params.tabulation_manager[key] table = params.select_facet(gem.Literal(table), mt.restriction) kernel_argument = params.coefficient_map[terminal] - q = gem.Index() - try: - r = params.index_cache[index_key] - except KeyError: - r = gem.Index() - params.index_cache[index_key] = r + r = params.index_cache[index_key] if mt.restriction is None: kar = gem.Indexed(kernel_argument, (r,)) @@ -543,17 +539,16 @@ def evaluate_at(params, key, index_key): else: assert False - if cellwise_constant: - return gem.IndexSum(gem.Product(gem.Indexed(table, (r,)), kar), r) + if len(table.shape) == 1: + # Cellwise constant + row = table + elif len(table.shape) == 2: + # Varying on cell + row = gem.partial_indexed(table, (params.quadrature_index,)) else: - return gem.Indexed( - gem.ComponentTensor( - gem.IndexSum( - gem.Product(gem.Indexed(table, (q, r)), - kar), - r), - (q,)), - (params.quadrature_index,)) + assert False + + return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), kar), r) if terminal.ufl_element().family() == 'Real': assert mt.local_derivatives == 0 From 4a943ec2a0eebe84dceb71e0efa254704340027c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 10 Feb 2016 16:43:04 +0000 Subject: [PATCH 046/809] remove zero-simplification code Already handled in GEM. Effect on compilation time not tested. --- tsfc/fem.py | 142 +--------------------------------------------------- 1 file changed, 1 insertion(+), 141 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 60dc1f9934..2bc568f45d 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -12,7 +12,6 @@ from ufl.classes import (Argument, Coefficient, FormArgument, GeometricQuantity, QuadratureWeight, ReferenceValue, Zero) -from ufl.domain import find_geometric_dimension from tsfc.constants import PRECISION from tsfc.fiatinterface import create_element, as_fiat_cell @@ -156,150 +155,11 @@ def spanning_degree(element): return _spanning_degree(element.cell(), element.degree()) -class FindPolynomialDegree(MultiFunction): - - """Simple-minded degree estimator. - - Attempt to estimate the polynomial degree of an expression. Used - to determine whether something we're taking a gradient of is - cellwise constant. Returns either the degree of the expression, - or else ``None`` if the degree could not be determined. - - To do this properly, we'd need to carry around a tensor-valued - degree object such that we can determine when (say) d^2/dx^2 is - zero but d^2/dxdy is not. - - """ - def quadrature_weight(self, o): - return 0 - - def multi_index(self, o): - return 0 - - # Default handler, no estimation. - def expr(self, o): - return None - - # Coefficient-like things, compute degree of spanning polynomial space - def spatial_coordinate(self, o): - return spanning_degree(o.ufl_domain().ufl_coordinate_element()) - - def form_argument(self, o): - return spanning_degree(o.ufl_element()) - - # Index-like operations, return degree of operand - def component_tensor(self, o, op, idx): - return op - - def indexed(self, o, op, idx): - return op - - def index_sum(self, o, op, idx): - return op - - def list_tensor(self, o, *ops): - if any(ops is None for op in ops): - return None - return max(*ops) - - # No change - def reference_value(self, o, op): - return op - - def restricted(self, o, op): - return op - - # Constants are constant - def constant_value(self, o): - return 0 - - # Multiplication adds degrees - def product(self, o, a, b): - if a is None or b is None: - return None - return a + b - - # If the degree of the exponent is zero, use degree of operand, - # otherwise don't guess. - def power(self, o, a, b): - if b == 0: - return a - return None - - # Pick maximal degree - def conditional(self, o, test, a, b): - if a is None or b is None: - return None - return max(a, b) - - def min_value(self, o, a, b): - if a is None or b is None: - return None - return max(a, b) - - def max_value(self, o, a, b): - if a is None or b is None: - return None - return max(a, b) - - def sum(self, o, a, b): - if a is None or b is None: - return None - return max(a, b) - - # If denominator is constant, use degree of numerator, otherwise - # don't guess - def division(self, o, a, b): - if b == 0: - return a - return None - - def abs(self, o, a): - if a == 0: - return a - return None - - # If operand is constant, return 0, otherwise don't guess. - def math_function(self, o, op): - if op == 0: - return 0 - return None - - # Reduce degrees! - def reference_grad(self, o, degree): - if degree is None: - return None - return max(degree - 1, 0) - - class SimplifyExpr(MultiFunction): """Apply some simplification passes to an expression.""" - def __init__(self): - MultiFunction.__init__(self) - self.mapper = FindPolynomialDegree() - expr = MultiFunction.reuse_if_untouched - def reference_grad(self, o): - """Try and zero-simplify ``RGrad(expr)`` where the degree of - ``expr`` can be determined. - - Uses :class:`FindPolynomialDegree` to determine the degree of - ``expr``.""" - # Find degree of operand - degree = map_expr_dag(self.mapper, o.ufl_operands[0]) - # Either we have non-constant, or we didn't know, in which - # case return ourselves. - if degree is None or degree > 0: - return o - # We are RGrad(constant-function), return Zero of appropriate shape - op = o.ufl_operands[0] - gdim = find_geometric_dimension(op) - return ufl.classes.Zero(op.ufl_shape + (gdim, ), - op.ufl_free_indices, - op.ufl_index_dimensions) - def abs(self, o, op): """Convert Abs(CellOrientation * ...) -> Abs(...)""" if isinstance(op, ufl.classes.CellOrientation): @@ -346,7 +206,7 @@ def tabulate(ufl_element, order, points): table[abs(table - 0.5) < epsilon] = 0.5 table[abs(table + 0.5) < epsilon] = -0.5 - if FindPolynomialDegree()._spanning_degree(ufl_element) <= sum(key[2]): + if spanning_degree(ufl_element) <= sum(key[2]): assert numpy.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) table = table[0] From abd4b934e95b1bc31a5e4d77ed4c604342c049d7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 10 Feb 2016 17:59:55 +0000 Subject: [PATCH 047/809] rewrite: SimplifyExpr -> simplify_abs Cell orientations used 2 more times. Maybe because of delaying zero simplification? --- tsfc/fem.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 2bc568f45d..c4f9fc6349 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -12,10 +12,13 @@ from ufl.classes import (Argument, Coefficient, FormArgument, GeometricQuantity, QuadratureWeight, ReferenceValue, Zero) +from ufl.classes import (Abs, CellOrientation, Expr, FloatValue, + Division, Product, ScalarValue) from tsfc.constants import PRECISION from tsfc.fiatinterface import create_element, as_fiat_cell from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal +from tsfc.node import MemoizerArg from tsfc import gem from tsfc import ufl2gem from tsfc import geometric @@ -155,6 +158,75 @@ def spanning_degree(element): return _spanning_degree(element.cell(), element.degree()) +def ufl_reuse_if_untouched(o, *ops): + """Reuse object if operands are the same objects.""" + if all(a is b for a, b in zip(o.ufl_operands, ops)): + return o + else: + return o._ufl_expr_reconstruct_(*ops) + + +@singledispatch +def _simplify_abs(o, self, in_abs): + raise AssertionError("UFL node expected, not %s" % type(o)) + + +@_simplify_abs.register(Expr) # noqa +def _(o, self, in_abs): + operands = [self(op, False) for op in o.ufl_operands] + result = ufl_reuse_if_untouched(o, *operands) + if in_abs: + result = Abs(result) + return result + + +@_simplify_abs.register(ScalarValue) # noqa +def _(o, self, in_abs): + if not in_abs: + return o + # Inline abs(constant) + return ufl.as_ufl(abs(o._value)) + + +@_simplify_abs.register(CellOrientation) # noqa +def _(o, self, in_abs): + if not in_abs: + return o + # Cell orientation is +-1 + return FloatValue(1) + + +@_simplify_abs.register(Division) # noqa +@_simplify_abs.register(Product) +def _(o, self, in_abs): + if not in_abs: + ops = [self(op, False) for op in o.ufl_operands] + return ufl_reuse_if_untouched(o, *ops) + + # Visit children, distributing Abs + ops = [self(op, True) for op in o.ufl_operands] + + # Strip Abs off again (we will put it outside now) + strip_ops = [] + for op in ops: + if isinstance(op, Abs): + strip_ops.append(op.ufl_operands[0]) + else: + strip_ops.append(op) + + # Rebuild + return Abs(ufl_reuse_if_untouched(o, *strip_ops)) + + +@_simplify_abs.register(Abs) # noqa +def _(o, self, in_abs): + return self(o.ufl_operands[0], True) + + +def simplify_abs(expression): + return MemoizerArg(_simplify_abs)(expression, False) + + class SimplifyExpr(MultiFunction): """Apply some simplification passes to an expression.""" @@ -438,7 +510,7 @@ def replace_coordinates(integrand, coordinate_coefficient): def process(integral_type, integrand, tabulation_manager, quadrature_weights, quadrature_index, argument_indices, coefficient_map, index_cache): # Abs-simplification - integrand = map_expr_dag(SimplifyExpr(), integrand) + integrand = simplify_abs(integrand) # Collect modified terminals modified_terminals = [] From 3c1666b26b0d45e0ff8a2100bceb17e1fcf1e144 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 10 Feb 2016 22:03:13 +0000 Subject: [PATCH 048/809] no more fabs(sqrt(...)) --- tsfc/fem.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index c4f9fc6349..42a89051b1 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -13,7 +13,7 @@ GeometricQuantity, QuadratureWeight, ReferenceValue, Zero) from ufl.classes import (Abs, CellOrientation, Expr, FloatValue, - Division, Product, ScalarValue) + Division, Product, ScalarValue, Sqrt) from tsfc.constants import PRECISION from tsfc.fiatinterface import create_element, as_fiat_cell @@ -180,6 +180,11 @@ def _(o, self, in_abs): return result +@_simplify_abs.register(Sqrt) # noqa +def _(o, self, in_abs): + return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) + + @_simplify_abs.register(ScalarValue) # noqa def _(o, self, in_abs): if not in_abs: @@ -207,15 +212,20 @@ def _(o, self, in_abs): ops = [self(op, True) for op in o.ufl_operands] # Strip Abs off again (we will put it outside now) + stripped = False strip_ops = [] for op in ops: if isinstance(op, Abs): + stripped = True strip_ops.append(op.ufl_operands[0]) else: strip_ops.append(op) # Rebuild - return Abs(ufl_reuse_if_untouched(o, *strip_ops)) + result = ufl_reuse_if_untouched(o, *strip_ops) + if stripped: + result = Abs(result) + return result @_simplify_abs.register(Abs) # noqa From 4b1593632df246593c2d5d98830e385bf9756c88 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 11 Feb 2016 10:41:29 +0000 Subject: [PATCH 049/809] look for cell_orientations later Now we have 2 less kernels with cell orientations instead of 2 more. --- tsfc/driver.py | 12 ++++++++---- tsfc/fem.py | 49 +++++------------------------------------------ tsfc/geometric.py | 2 +- 3 files changed, 14 insertions(+), 49 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index d84cec07b7..7208d01666 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -169,10 +169,10 @@ def compile_integral(idata, fd, prefix, parameters): integrand = fem.replace_coordinates(integral.integrand(), coordinates) quadrature_index = ein.Index(name="ip%d" % i) quadrature_indices.append(quadrature_index) - nonfem, cell_orientations = \ - fem.process(integral_type, integrand, tabulation_manager, - quad_rule.weights, quadrature_index, - argument_indices, coefficient_map, index_cache) + nonfem = fem.process(integral_type, integrand, + tabulation_manager, quad_rule.weights, + quadrature_index, argument_indices, + coefficient_map, index_cache) nonfem_.append([(ein.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) for e in nonfem]) @@ -182,6 +182,7 @@ def compile_integral(idata, fd, prefix, parameters): simplified = opt.remove_componenttensors(nonfem) simplified = opt.unroll_indexsum(simplified, max_extent=3) + cell_orientations = False refcount = sch.count_references(simplified) candidates = set() for node in traversal(simplified): @@ -194,6 +195,9 @@ def compile_integral(idata, fd, prefix, parameters): if not (isinstance(child, ein.Literal) and child.shape): candidates.add(child) + if isinstance(node, ein.Variable) and node.name == "cell_orientations": + cell_orientations = True + if cell_orientations: decl = coffee.Decl("int *restrict *restrict", coffee.Symbol("cell_orientations"), qualifiers=["const"]) diff --git a/tsfc/fem.py b/tsfc/fem.py index 42a89051b1..9212893c84 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -237,37 +237,6 @@ def simplify_abs(expression): return MemoizerArg(_simplify_abs)(expression, False) -class SimplifyExpr(MultiFunction): - """Apply some simplification passes to an expression.""" - - expr = MultiFunction.reuse_if_untouched - - def abs(self, o, op): - """Convert Abs(CellOrientation * ...) -> Abs(...)""" - if isinstance(op, ufl.classes.CellOrientation): - # Cell orientation is +-1 - return ufl.classes.FloatValue(1) - if isinstance(op, ufl.classes.ScalarValue): - # Inline abs(constant) - return self.expr(op, abs(op._value)) - if isinstance(op, (ufl.classes.Division, ufl.classes.Product)): - # Visit children, distributing Abs - ops = tuple(map_expr_dag(self, ufl.classes.Abs(_)) - for _ in op.ufl_operands) - new_ops = [] - # Strip Abs off again (we'll put it outside the product now) - for _ in ops: - if isinstance(_, ufl.classes.Abs): - new_ops.append(_.ufl_operands[0]) - else: - new_ops.append(_) - # Rebuild product - new_prod = self.expr(op, *new_ops) - # Rebuild Abs - return self.expr(o, new_prod) - return self.expr(o, op) - - def _tabulate(ufl_element, order, points): element = create_element(ufl_element) phi = element.space_dimension() @@ -359,7 +328,6 @@ def __init__(self, weights, quadrature_index, argument_indices, tabulation_manag self.tabulation_manager = tabulation_manager self.integral_type = integral_type self.coefficient_map = coefficient_map - self.cell_orientations = False self.index_cache = index_cache if integral_type in ['exterior_facet', 'exterior_facet_vert']: @@ -376,17 +344,10 @@ def __init__(self, weights, quadrature_index, argument_indices, tabulation_manag else: self.facet = None - def get_cell_orientations(self): - try: - return self._cell_orientations - except AttributeError: - if self.integral_type.startswith("interior_facet"): - result = gem.Variable("cell_orientations", (2, 1)) - else: - result = gem.Variable("cell_orientations", (1, 1)) - self.cell_orientations = True - self._cell_orientations = result - return result + if self.integral_type.startswith("interior_facet"): + self.cell_orientations = gem.Variable("cell_orientations", (2, 1)) + else: + self.cell_orientations = gem.Variable("cell_orientations", (1, 1)) def select_facet(self, tensor, restriction): if self.integral_type == 'cell': @@ -551,4 +512,4 @@ def process(integral_type, integrand, tabulation_manager, quadrature_weights, qu translator = Translator(quadrature_weights, quadrature_index, argument_indices, tabulation_manager, coefficient_map, index_cache) - return map_expr_dags(translator, expressions), translator.cell_orientations + return map_expr_dags(translator, expressions) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 0155bce69f..689ead5e4d 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -138,7 +138,7 @@ def translate(terminal, mt, params): @translate.register(CellOrientation) # noqa def _(terminal, mt, params): - cell_orientations = params.get_cell_orientations() + cell_orientations = params.cell_orientations f = {None: 0, '+': 0, '-': 1}[mt.restriction] co_int = gem.Indexed(cell_orientations, (f, 0)) return gem.Conditional(gem.Comparison("==", co_int, gem.Literal(1)), From 4b44bbe1a1909012165c087eea5e92234cc4ccd9 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 11 Feb 2016 12:15:27 +0000 Subject: [PATCH 050/809] cellwise constant is the same on all facets Fix bug if some roots are the same when node.traversal. Wise assertion in the scheduler. --- tsfc/fem.py | 37 ++++++++++++++++++++++--------------- tsfc/node.py | 11 +++++++++-- tsfc/scheduling.py | 1 + 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 9212893c84..5fe93da793 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -309,7 +309,12 @@ def tabulate(self, ufl_element, max_deriv): self.tables[key] = table else: for key, tables in store.iteritems(): - self.tables[key] = numpy.array(tables) + table = numpy.array(tables) + if len(table.shape) == 2: + # Cellwise constant; must not depend on the facet + assert numpy.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) + table = table[0] + self.tables[key] = table def __getitem__(self, key): return self.tables[key] @@ -407,12 +412,14 @@ def _(terminal, e, mt, params): table_keys(terminal.ufl_element(), mt.local_derivatives)): table = params.tabulation_manager[key] - table = params.select_facet(gem.Literal(table), mt.restriction) if len(table.shape) == 1: # Cellwise constant + table = gem.Literal(table) row = table - elif len(table.shape) == 2: + elif len(table.shape) in [2, 3]: # Varying on cell + table = params.select_facet(gem.Literal(table), mt.restriction) + assert len(table.shape) == 2 row = gem.partial_indexed(table, (params.quadrature_index,)) else: assert False @@ -428,11 +435,20 @@ def _(terminal, e, mt, params): def _(terminal, e, mt, params): def evaluate_at(params, key, index_key): table = params.tabulation_manager[key] - table = params.select_facet(gem.Literal(table), mt.restriction) - kernel_argument = params.coefficient_map[terminal] + if len(table.shape) == 1: + # Cellwise constant + table = gem.Literal(table) + row = table + elif len(table.shape) in [2, 3]: + # Varying on cell + table = params.select_facet(gem.Literal(table), mt.restriction) + assert len(table.shape) == 2 + row = gem.partial_indexed(table, (params.quadrature_index,)) + else: + assert False + kernel_argument = params.coefficient_map[terminal] r = params.index_cache[index_key] - if mt.restriction is None: kar = gem.Indexed(kernel_argument, (r,)) elif mt.restriction is '+': @@ -442,15 +458,6 @@ def evaluate_at(params, key, index_key): else: assert False - if len(table.shape) == 1: - # Cellwise constant - row = table - elif len(table.shape) == 2: - # Varying on cell - row = gem.partial_indexed(table, (params.quadrature_index,)) - else: - assert False - return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), kar), r) if terminal.ufl_element().family() == 'Real': diff --git a/tsfc/node.py b/tsfc/node.py index 6fb66e9145..a985a48d4a 100644 --- a/tsfc/node.py +++ b/tsfc/node.py @@ -97,8 +97,15 @@ def get_hash(self): def traversal(expression_dags): """Pre-order traversal of the nodes of expression DAGs.""" - seen = set(expression_dags) - lifo = list(expression_dags) + seen = set() + lifo = [] + # Some roots might be same, but they must be visited only once. + # Keep the original ordering of roots, for deterministic code + # generation. + for root in expression_dags: + if root not in seen: + seen.add(root) + lifo.append(root) while lifo: node = lifo.pop() diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py index ca1556f6c1..790e17d996 100644 --- a/tsfc/scheduling.py +++ b/tsfc/scheduling.py @@ -171,4 +171,5 @@ def enqueue(item): for o in queue: handle(o, enqueue, emit) + assert not any(queue.waiting.values()) return list(reversed(result)) From b05bb7dddf9b70e38ec6dc4a20fb0aee8e3f7052 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 11 Feb 2016 14:24:02 +0000 Subject: [PATCH 051/809] expand some cellwise constant evaluations Especially for Jacobians on affine cells. --- tsfc/fem.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 5fe93da793..62685b0613 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -434,11 +434,29 @@ def _(terminal, e, mt, params): @translate.register(Coefficient) # noqa: Not actually redefinition def _(terminal, e, mt, params): def evaluate_at(params, key, index_key): + kernel_argument = params.coefficient_map[terminal] + if mt.restriction is None: + ka = kernel_argument + elif mt.restriction is '+': + ka = gem.partial_indexed(kernel_argument, (0,)) + elif mt.restriction is '-': + ka = gem.partial_indexed(kernel_argument, (1,)) + else: + assert False + table = params.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant - table = gem.Literal(table) - row = table + if numpy.count_nonzero(table) <= 2: + assert table.shape == ka.shape + size, = table.shape # asserts rank 1 + return reduce(gem.Sum, + [gem.Product(gem.Literal(t), kai) + for t, kai in zip(table, [gem.Indexed(ka, (i,)) + for i in range(size)])], + gem.Zero()) + else: + row = gem.Literal(table) elif len(table.shape) in [2, 3]: # Varying on cell table = params.select_facet(gem.Literal(table), mt.restriction) @@ -447,18 +465,8 @@ def evaluate_at(params, key, index_key): else: assert False - kernel_argument = params.coefficient_map[terminal] r = params.index_cache[index_key] - if mt.restriction is None: - kar = gem.Indexed(kernel_argument, (r,)) - elif mt.restriction is '+': - kar = gem.Indexed(kernel_argument, (0, r)) - elif mt.restriction is '-': - kar = gem.Indexed(kernel_argument, (1, r)) - else: - assert False - - return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), kar), r) + return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), gem.Indexed(ka, (r,))), r) if terminal.ufl_element().family() == 'Real': assert mt.local_derivatives == 0 From 3b6057994cfcaf64938d99285800819c847df725 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 11 Feb 2016 16:48:39 +0000 Subject: [PATCH 052/809] refactor Coefficient and Argument translation --- tsfc/fem.py | 118 ++++++++++++++++++++-------------------------------- 1 file changed, 44 insertions(+), 74 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 62685b0613..118ebc4f3b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -363,125 +363,95 @@ def select_facet(self, tensor, restriction): def modified_terminal(self, o): mt = analyse_modified_terminal(o) - return translate(mt.terminal, o, mt, self) + return translate(mt.terminal, mt, self) -def table_keys(ufl_element, local_derivatives): - # TODO: - # Consider potential duplicate calculation due to second - # derivatives and symmetries. - - size = ufl_element.reference_value_size() +def iterate_shape(mt, callback): + ufl_element = mt.terminal.ufl_element() dim = ufl_element.cell().topological_dimension() def flat_index(ordered_deriv): - result = [0] * dim - for i in ordered_deriv: - result[i] += 1 - return tuple(result) + return tuple((numpy.asarray(ordered_deriv) == d).sum() for d in range(dim)) - ordered_derivs = itertools.product(range(dim), repeat=local_derivatives) + ordered_derivs = itertools.product(range(dim), repeat=mt.local_derivatives) flat_derivs = map(flat_index, ordered_derivs) - return [(ufl_element, c, flat_deriv) - for c in xrange(size) - for flat_deriv in flat_derivs] + result = [] + for c in range(ufl_element.reference_value_size()): + for flat_deriv in flat_derivs: + result.append(callback((ufl_element, c, flat_deriv))) + + shape = mt.expr.ufl_shape + assert len(result) == numpy.prod(shape) + + if shape: + return gem.ListTensor(numpy.asarray(result).reshape(shape)) + else: + return result[0] @singledispatch -def translate(terminal, e, mt, params): +def translate(terminal, mt, params): raise AssertionError("Cannot handle terminal type: %s" % type(terminal)) @translate.register(QuadratureWeight) # noqa: Not actually redefinition -def _(terminal, e, mt, params): +def _(terminal, mt, params): return gem.Indexed(params.weights, (params.quadrature_index,)) @translate.register(GeometricQuantity) # noqa: Not actually redefinition -def _(terminal, e, mt, params): +def _(terminal, mt, params): return geometric.translate(terminal, mt, params) @translate.register(Argument) # noqa: Not actually redefinition -def _(terminal, e, mt, params): +def _(terminal, mt, params): argument_index = params.argument_indices[terminal.number()] - result = numpy.zeros(e.ufl_shape, dtype=object) - for multiindex, key in zip(numpy.ndindex(e.ufl_shape), - table_keys(terminal.ufl_element(), - mt.local_derivatives)): + def callback(key): table = params.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant - table = gem.Literal(table) - row = table - elif len(table.shape) in [2, 3]: - # Varying on cell + row = gem.Literal(table) + else: table = params.select_facet(gem.Literal(table), mt.restriction) - assert len(table.shape) == 2 row = gem.partial_indexed(table, (params.quadrature_index,)) - else: - assert False - result[multiindex] = gem.Indexed(row, (argument_index,)) + return gem.Indexed(row, (argument_index,)) - if result.shape: - return gem.ListTensor(result) - else: - return result[()] + return iterate_shape(mt, callback) @translate.register(Coefficient) # noqa: Not actually redefinition -def _(terminal, e, mt, params): - def evaluate_at(params, key, index_key): - kernel_argument = params.coefficient_map[terminal] - if mt.restriction is None: - ka = kernel_argument - elif mt.restriction is '+': - ka = gem.partial_indexed(kernel_argument, (0,)) - elif mt.restriction is '-': - ka = gem.partial_indexed(kernel_argument, (1,)) - else: - assert False +def _(terminal, mt, params): + kernel_arg = params.coefficient_map[terminal] + if terminal.ufl_element().family() == 'Real': + assert mt.local_derivatives == 0 + return kernel_arg + + ka = gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[mt.restriction]) + + def callback(key): table = params.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant + row = gem.Literal(table) if numpy.count_nonzero(table) <= 2: - assert table.shape == ka.shape - size, = table.shape # asserts rank 1 + assert row.shape == ka.shape return reduce(gem.Sum, - [gem.Product(gem.Literal(t), kai) - for t, kai in zip(table, [gem.Indexed(ka, (i,)) - for i in range(size)])], + [gem.Product(gem.Indexed(row, (i,)), gem.Indexed(ka, (i,))) + for i in range(row.shape[0])], gem.Zero()) - else: - row = gem.Literal(table) - elif len(table.shape) in [2, 3]: - # Varying on cell + else: table = params.select_facet(gem.Literal(table), mt.restriction) - assert len(table.shape) == 2 row = gem.partial_indexed(table, (params.quadrature_index,)) - else: - assert False - r = params.index_cache[index_key] - return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), gem.Indexed(ka, (r,))), r) + r = params.index_cache[terminal.ufl_element()] + return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), + gem.Indexed(ka, (r,))), r) - if terminal.ufl_element().family() == 'Real': - assert mt.local_derivatives == 0 - return params.coefficient_map[terminal] - - result = numpy.zeros(e.ufl_shape, dtype=object) - for multiindex, key in zip(numpy.ndindex(e.ufl_shape), - table_keys(terminal.ufl_element(), - mt.local_derivatives)): - result[multiindex] = evaluate_at(params, key, terminal.ufl_element()) - - if result.shape: - return gem.ListTensor(result) - else: - return result[()] + return iterate_shape(mt, callback) def coordinate_coefficient(domain): From 928aef198967601ad1b0d4a331210a5ce844a981 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 11 Feb 2016 18:34:09 +0000 Subject: [PATCH 053/809] add some documentation --- tsfc/fem.py | 79 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 118ebc4f3b..e07a7227bf 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -29,7 +29,12 @@ class ReplaceSpatialCoordinates(MultiFunction): + """Replace SpatialCoordinate nodes with the ReferenceValue of a + Coefficient. Assumes that the coordinate element only needs + affine mapping. + :arg coordinates: the coefficient to replace spatial coordinates with + """ def __init__(self, coordinates): self.coordinates = coordinates MultiFunction.__init__(self) @@ -44,6 +49,8 @@ def spatial_coordinate(self, o): class ModifiedTerminalMixin(object): + """Mixin to use with MultiFunctions that operate on modified + terminals.""" def unexpected(self, o): assert False, "Not expected %r at this stage." % o @@ -77,7 +84,10 @@ def _modified_terminal(self, o): class CollectModifiedTerminals(MultiFunction, ModifiedTerminalMixin): + """Collect the modified terminals in a UFL expression. + :arg return_list: modified terminals will be appended to this list + """ def __init__(self, return_list): MultiFunction.__init__(self) self.return_list = return_list @@ -136,6 +146,7 @@ def _spanning_degree(cell, degree): return 2 * degree elif isinstance(cell, ufl.TensorProductCell): try: + # A component cell might be a quadrilateral, so recurse. return sum(_spanning_degree(sub_cell, d) for sub_cell, d in zip(cell.sub_cells(), degree)) except TypeError: @@ -168,11 +179,21 @@ def ufl_reuse_if_untouched(o, *ops): @singledispatch def _simplify_abs(o, self, in_abs): + """Single-dispatch function to simplify absolute values. + + :arg o: UFL node + :arg self: Callback handler for recursion + :arg in_abs: Is ``o`` inside an absolute value? + + When ``in_abs`` we must return a non-negative value, potentially + by wrapping the returned node with ``Abs``. + """ raise AssertionError("UFL node expected, not %s" % type(o)) @_simplify_abs.register(Expr) # noqa def _(o, self, in_abs): + # General case, only wrap the outer expression (if necessary) operands = [self(op, False) for op in o.ufl_operands] result = ufl_reuse_if_untouched(o, *operands) if in_abs: @@ -182,6 +203,7 @@ def _(o, self, in_abs): @_simplify_abs.register(Sqrt) # noqa def _(o, self, in_abs): + # Square root is always non-negative return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) @@ -205,6 +227,7 @@ def _(o, self, in_abs): @_simplify_abs.register(Product) def _(o, self, in_abs): if not in_abs: + # Just reconstruct ops = [self(op, False) for op in o.ufl_operands] return ufl_reuse_if_untouched(o, *ops) @@ -221,7 +244,7 @@ def _(o, self, in_abs): else: strip_ops.append(op) - # Rebuild + # Rebuild, and wrap with Abs if necessary result = ufl_reuse_if_untouched(o, *strip_ops) if stripped: result = Abs(result) @@ -234,10 +257,26 @@ def _(o, self, in_abs): def simplify_abs(expression): + """Simplify absolute values in a UFL expression. Its primary + purpose is to "neutralise" CellOrientation nodes that are + surrounded by absolute values and thus not at all necessary.""" return MemoizerArg(_simplify_abs)(expression, False) def _tabulate(ufl_element, order, points): + """Ask FIAT to tabulate ``points`` up to order ``order``, then + rearranges the result into a series of ``(c, D, table)`` tuples, + where: + + c: component index (for vector-valued and tensor-valued elements) + D: derivative tuple (e.g. (1, 2) means d/dx d^2/dy^2) + table: tabulation matrix for the given component and derivative. + shape: len(points) x space_dimension + + :arg ufl_element: element to tabulate + :arg order: FIAT gives all derivatives up to this order + :arg points: points to tabulate the element on + """ element = create_element(ufl_element) phi = element.space_dimension() C = ufl_element.reference_value_size() - len(ufl_element.symmetry()) @@ -245,11 +284,14 @@ def _tabulate(ufl_element, order, points): for D, fiat_table in element.tabulate(order, points).iteritems(): reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) for c, table in enumerate(reordered_table): - yield (ufl_element, c, D), table + yield c, D, table def tabulate(ufl_element, order, points): - for key, table in _tabulate(ufl_element, order, points): + """Same as the above, but also applies FFC rounding and recognises + cellwise constantness. Cellwise constantness is determined + symbolically, but we also check the numerics to be safe.""" + for c, D, table in _tabulate(ufl_element, order, points): # Copied from FFC (ffc/quadrature/quadratureutils.py) table[abs(table) < epsilon] = 0 table[abs(table - 1.0) < epsilon] = 1.0 @@ -257,18 +299,21 @@ def tabulate(ufl_element, order, points): table[abs(table - 0.5) < epsilon] = 0.5 table[abs(table + 0.5) < epsilon] = -0.5 - if spanning_degree(ufl_element) <= sum(key[2]): + if spanning_degree(ufl_element) <= sum(D): assert numpy.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) table = table[0] - yield key, table + yield c, D, table def make_tabulator(points): + """Creates a tabulator for an array of points.""" return lambda elem, order: tabulate(elem, order, points) class TabulationManager(object): + """Manages the generation of tabulation matrices for the different + integral types.""" def __init__(self, integral_type, cell, points): self.integral_type = integral_type @@ -301,8 +346,8 @@ def __init__(self, integral_type, cell, points): def tabulate(self, ufl_element, max_deriv): store = collections.defaultdict(list) for tabulator in self.tabulators: - for key, table in tabulator(ufl_element, max_deriv): - store[key].append(table) + for c, D, table in tabulator(ufl_element, max_deriv): + store[(ufl_element, c, D)].append(table) if self.integral_type == 'cell': for key, (table,) in store.iteritems(): @@ -321,6 +366,7 @@ def __getitem__(self, key): class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): + """Contains all the context necessary to translate UFL into GEM.""" def __init__(self, weights, quadrature_index, argument_indices, tabulation_manager, coefficient_map, index_cache): @@ -367,6 +413,18 @@ def modified_terminal(self, o): def iterate_shape(mt, callback): + """Iterates through the components of a modified terminal, and + calls ``callback`` with ``(ufl_element, c, D)`` keys which are + used to look up tabulation matrix for that component. Then + assembles the result into a GEM tensor (if tensor-valued) + corresponding to the modified terminal. + + :arg mt: analysed modified terminal + :arg callback: callback to get the GEM translation of a component + :returns: GEM translation of the modified terminal + + This is a helper for translating Arguments and Coefficients. + """ ufl_element = mt.terminal.ufl_element() dim = ufl_element.cell().topological_dimension() @@ -392,6 +450,13 @@ def flat_index(ordered_deriv): @singledispatch def translate(terminal, mt, params): + """Translates modified terminals into GEM. + + :arg terminal: terminal, for dispatching + :arg mt: analysed modified terminal + :arg params: translator context + :returns: GEM translation of the modified terminal + """ raise AssertionError("Cannot handle terminal type: %s" % type(terminal)) From c301063a8f21abad7dceac85f888b6b76dae541a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 12 Feb 2016 11:54:21 +0000 Subject: [PATCH 054/809] assert coordinate element mapping --- tsfc/fem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/fem.py b/tsfc/fem.py index e07a7227bf..7f770d3901 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -45,6 +45,7 @@ def terminal(self, t): return t def spatial_coordinate(self, o): + assert o.ufl_domain().ufl_coordinate_element().mapping() == "identity" return ReferenceValue(self.coordinates) From 529e512df30b28e6d1551f80f96a33ee09ad4cfd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 12 Feb 2016 13:48:05 +0000 Subject: [PATCH 055/809] add impero.ReturnAccumulate Merges impero.Return and impero.Accumulate, so a gem.IndexSum is directly accumulated into output tensor. --- tsfc/coffee.py | 6 ++++++ tsfc/driver.py | 2 +- tsfc/impero.py | 12 ++++++++++++ tsfc/impero_utils.py | 2 ++ tsfc/scheduling.py | 29 ++++++++++++++++++++++++++--- 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index a659529c9a..0c88d0b987 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -103,6 +103,12 @@ def _(leaf, parameters): expression(leaf.expression, parameters)) +@arabica.register(imp.ReturnAccumulate) # noqa: Not actually redefinition +def _(leaf, parameters): + return coffee.Incr(expression(leaf.variable, parameters), + expression(leaf.indexsum.children[0], parameters)) + + @arabica.register(imp.Evaluate) # noqa: Not actually redefinition def _(leaf, parameters): expr = leaf.expression diff --git a/tsfc/driver.py b/tsfc/driver.py index 7208d01666..71c4c1e4d6 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -436,7 +436,7 @@ def make_temporary(o): list.append(o) for op in operations: - if isinstance(op, (imp.Initialise, imp.Return)): + if isinstance(op, (imp.Initialise, imp.Return, imp.ReturnAccumulate)): pass elif isinstance(op, imp.Accumulate): make_temporary(op.indexsum) diff --git a/tsfc/impero.py b/tsfc/impero.py index 9c56c19bde..6c10979b35 100644 --- a/tsfc/impero.py +++ b/tsfc/impero.py @@ -71,6 +71,18 @@ def __init__(self, variable, expression): self.expression = expression +class ReturnAccumulate(Terminal): + """Accumulate an :class:`gem.IndexSum` directly into a return + variable.""" + + __slots__ = ('variable', 'indexsum') + __front__ = ('variable', 'indexsum') + + def __init__(self, variable, indexsum): + self.variable = variable + self.indexsum = indexsum + + class Block(Node): """An ordered set of Impero expressions. Corresponds to a curly braces block in C.""" diff --git a/tsfc/impero_utils.py b/tsfc/impero_utils.py index f17529f5eb..f940b542db 100644 --- a/tsfc/impero_utils.py +++ b/tsfc/impero_utils.py @@ -72,6 +72,8 @@ def recurse(o, top=False): elif isinstance(op, imp.Accumulate): counter[op.indexsum] += 1 recurse(op.indexsum.children[0]) + elif isinstance(op, imp.ReturnAccumulate): + recurse(op.indexsum.children[0]) else: raise AssertionError("unhandled operation: %s" % type(op)) diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py index 790e17d996..b0ba8bb160 100644 --- a/tsfc/scheduling.py +++ b/tsfc/scheduling.py @@ -77,7 +77,7 @@ def impero_indices(node, indices): @impero_indices.register(imp.Return) # noqa: Not actually redefinition def _(node, indices): - assert set(node.variable.free_indices) >= set(node.expression.free_indices) + assert set(node.variable.free_indices) == set(node.expression.free_indices) return indices(node.variable) @@ -96,6 +96,12 @@ def _(node, indices): return indices(node.expression) +@impero_indices.register(imp.ReturnAccumulate) # noqa: Not actually redefinition +def _(node, indices): + assert set(node.variable.free_indices) == set(node.indexsum.free_indices) + return indices(node.indexsum.children[0]) + + @singledispatch def handle(node, enqueue, emit): raise AssertionError("Cannot handle foreign type: %s" % type(node)) @@ -148,13 +154,30 @@ def _(op, enqueue, emit): enqueue(op.expression) +@handle.register(imp.ReturnAccumulate) # noqa: Not actually redefinition +def _(op, enqueue, emit): + emit(op) + enqueue(op.indexsum.children[0]) + + def make_ordering(assignments, indices_map): assignments = filter(lambda x: not isinstance(x[1], ein.Zero), assignments) expressions = [expression for variable, expression in assignments] - queue = Queue(count_references(expressions), indices_map) + refcount = count_references(expressions) + staging = [] for variable, expression in assignments: - queue.insert(imp.Return(variable, expression), indices_map(expression)) + if isinstance(expression, ein.IndexSum) and refcount[expression] == 1: + staging.append((imp.ReturnAccumulate(variable, expression), + indices_map(expression.children[0]))) + refcount[expression] -= 1 + else: + staging.append((imp.Return(variable, expression), + indices_map(expression))) + + queue = Queue(refcount, indices_map) + for op, indices in staging: + queue.insert(op, indices) result = [] From 6d9db36b9dd0840744e7a9a7839c4ee73612c9fd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 12 Feb 2016 14:47:48 +0000 Subject: [PATCH 056/809] preserve quadrature loops (except for single-point ones) --- tsfc/driver.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 71c4c1e4d6..805a035237 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -173,14 +173,13 @@ def compile_integral(idata, fd, prefix, parameters): tabulation_manager, quad_rule.weights, quadrature_index, argument_indices, coefficient_map, index_cache) + nonfem = opt.unroll_indexsum(nonfem, max_extent=3) nonfem_.append([(ein.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) for e in nonfem]) # Sum the expressions that are part of the same restriction nonfem = list(reduce(ein.Sum, e, ein.Zero()) for e in zip(*nonfem_)) - simplified = opt.remove_componenttensors(nonfem) - simplified = opt.unroll_indexsum(simplified, max_extent=3) cell_orientations = False refcount = sch.count_references(simplified) From 1295e7a0ead6564a5956d74420c6fb0593bae210 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 12 Feb 2016 15:18:27 +0000 Subject: [PATCH 057/809] faster Jacobian in interior facet integrals on affine cells Switchable, new code generation for evaluating mixed element coefficients in interior facet integrals. This composes better with the optimised evaluation of cellwise constant functions. --- tsfc/driver.py | 71 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 805a035237..0b02ff8f27 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -100,7 +100,10 @@ def compile_integral(idata, fd, prefix, parameters): mesh = idata.domain coordinates = fem.coordinate_coefficient(mesh) - funarg, prepare_, expression = prepare_coefficient(integral_type, coordinates, "coords") + if mesh.ufl_cell().cellname() in ["interval", "triangle", "tetrahedron"]: + funarg, prepare_, expression = prepare_coefficient(integral_type, coordinates, "coords", mode='list_tensor') + else: + funarg, prepare_, expression = prepare_coefficient(integral_type, coordinates, "coords") arglist.append(funarg) prepare += prepare_ @@ -240,7 +243,11 @@ def compile_integral(idata, fd, prefix, parameters): return kernel -def prepare_coefficient(integral_type, coefficient, name): +def prepare_coefficient(integral_type, coefficient, name, mode=None): + if mode is None: + mode = 'manual_loop' + + assert mode in ['manual_loop', 'list_tensor'] if coefficient.ufl_element().family() == 'Real': # Constant @@ -290,30 +297,54 @@ def prepare_coefficient(integral_type, coefficient, name): return funarg, [], expression # Interior facet integral + mixed / vector element - name_ = name + "_" - shape = (2, fiat_element.space_dimension()) + if mode == 'manual_loop': + name_ = name + "_" + shape = (2, fiat_element.space_dimension()) - funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_), - qualifiers=["const"]) - prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] - expression = ein.Variable(name, shape) + funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_), + qualifiers=["const"]) + prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] + expression = ein.Variable(name, shape) + + offset = 0 + i = coffee.Symbol("i") + for element in fiat_element.elements(): + space_dim = element.space_dimension() - offset = 0 - i = coffee.Symbol("i") - for element in fiat_element.elements(): - space_dim = element.space_dimension() + loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, coffee.Sum(offset, i))), + coffee.Symbol(name_, rank=(coffee.Sum(2 * offset, i), 0))) + prepare.append(coffee_for(i, space_dim, loop_body)) - loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, coffee.Sum(offset, i))), - coffee.Symbol(name_, rank=(coffee.Sum(2 * offset, i), 0))) - prepare.append(coffee_for(i, space_dim, loop_body)) + loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, coffee.Sum(offset, i))), + coffee.Symbol(name_, rank=(coffee.Sum(2 * offset + space_dim, i), 0))) + prepare.append(coffee_for(i, space_dim, loop_body)) - loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, coffee.Sum(offset, i))), - coffee.Symbol(name_, rank=(coffee.Sum(2 * offset + space_dim, i), 0))) - prepare.append(coffee_for(i, space_dim, loop_body)) + offset += space_dim - offset += space_dim + return funarg, prepare, expression - return funarg, prepare, expression + elif mode == 'list_tensor': + funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name), + qualifiers=["const"]) + + variable = ein.Variable(name, (2 * fiat_element.space_dimension(), 1)) + + facet_0 = [] + facet_1 = [] + offset = 0 + for element in fiat_element.elements(): + space_dim = element.space_dimension() + + for i in range(offset, offset + space_dim): + facet_0.append(ein.Indexed(variable, (i, 0))) + offset += space_dim + + for i in range(offset, offset + space_dim): + facet_1.append(ein.Indexed(variable, (i, 0))) + offset += space_dim + + expression = ein.ListTensor(numpy.array([facet_0, facet_1])) + return funarg, [], expression def prepare_arguments(integral_type, arguments): From 573cc61fd3cd0e7f95eb7377356aac2480b9b366 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 12 Feb 2016 16:50:36 +0000 Subject: [PATCH 058/809] Add tests that compile_form is idempotent Calling compile_form multiple times on the same form should produce the same code. --- tests/test_idempotency.py | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/test_idempotency.py diff --git a/tests/test_idempotency.py b/tests/test_idempotency.py new file mode 100644 index 0000000000..c780ea2604 --- /dev/null +++ b/tests/test_idempotency.py @@ -0,0 +1,54 @@ +import ufl +from tsfc import compile_form +import pytest + + +@pytest.fixture(params=[ufl.interval, + pytest.mark.xfail(reason="indices")(ufl.triangle), + pytest.mark.xfail(reason="indices")(ufl.quadrilateral), + pytest.mark.xfail(reason="indices")(ufl.tetrahedron)], + ids=lambda x: x.cellname()) +def cell(request): + return request.param + + +@pytest.fixture +def mesh(cell): + c = ufl.VectorElement("CG", cell, 2) + return ufl.Mesh(c) + + +@pytest.fixture(params=[ufl.FiniteElement, + ufl.VectorElement, + ufl.TensorElement], + ids=["FE", "VE", "TE"]) +def V(request, mesh): + return ufl.FunctionSpace(mesh, request.param("CG", mesh.ufl_cell(), 2)) + + +@pytest.fixture(params=["functional", "1-form", "2-form"]) +def form(V, request): + if request.param == "functional": + u = ufl.Coefficient(V) + v = ufl.Coefficient(V) + elif request.param == "1-form": + u = ufl.Coefficient(V) + v = ufl.TestFunction(V) + elif request.param == "2-form": + u = ufl.TrialFunction(V) + v = ufl.TestFunction(V) + + return ufl.inner(u, v)*ufl.dx + + +def test_idempotency(form): + k1 = compile_form(form)[0] + k2 = compile_form(form)[0] + + assert k1.ast.gencode() == k2.ast.gencode() + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From a5a4426892eb6504e0af2bec1f2369fad6e89cf0 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 12 Feb 2016 16:52:10 +0000 Subject: [PATCH 059/809] Make kernel generation idempotent Use a name map for indices that are not given an explicit name, ensuring that compile_form produces the same results when called multiple times on the same form. Fixes #12. --- tests/test_idempotency.py | 6 +++--- tsfc/coffee.py | 16 +++++++++++++--- tsfc/gem.py | 14 ++++++++------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/tests/test_idempotency.py b/tests/test_idempotency.py index c780ea2604..5a4eed78cd 100644 --- a/tests/test_idempotency.py +++ b/tests/test_idempotency.py @@ -4,9 +4,9 @@ @pytest.fixture(params=[ufl.interval, - pytest.mark.xfail(reason="indices")(ufl.triangle), - pytest.mark.xfail(reason="indices")(ufl.quadrilateral), - pytest.mark.xfail(reason="indices")(ufl.tetrahedron)], + ufl.triangle, + ufl.quadrilateral, + ufl.tetrahedron], ids=lambda x: x.cellname()) def cell(request): return request.param diff --git a/tsfc/coffee.py b/tsfc/coffee.py index a659529c9a..f12b77f5a6 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -4,6 +4,8 @@ import numpy from singledispatch import singledispatch +from collections import defaultdict +import itertools import coffee.base as coffee @@ -20,6 +22,8 @@ def generate(temporaries, code, indices, declare): parameters.declare = declare parameters.indices = indices parameters.names = {} + counter = itertools.count() + parameters.index_names = defaultdict(lambda: "i_%d" % next(counter)) for i, temp in enumerate(temporaries): parameters.names[temp] = "t%d" % i @@ -51,9 +55,15 @@ def _decl_symbol(expr, parameters): return _coffee_symbol(parameters.names[expr], rank=rank) +def _index_name(index, parameters): + if index.name is None: + return parameters.index_names[index] + return index.name + + def _ref_symbol(expr, parameters): multiindex = parameters.indices[expr] - rank = tuple(index.name for index in multiindex) + rank = tuple(_index_name(index, parameters) for index in multiindex) return _coffee_symbol(parameters.names[expr], rank=tuple(rank)) @@ -75,7 +85,7 @@ def _(tree, parameters): def _(tree, parameters): extent = tree.index.extent assert extent - i = _coffee_symbol(tree.index.name) + i = _coffee_symbol(_index_name(tree.index, parameters)) # TODO: symbolic constant for "int" return coffee.For(coffee.Decl("int", i, init=0), coffee.Less(i, extent), @@ -234,7 +244,7 @@ def _(expr, parameters): rank = [] for index in expr.multiindex: if isinstance(index, ein.Index): - rank.append(index.name) + rank.append(_index_name(index, parameters)) elif isinstance(index, ein.VariableIndex): rank.append(index.name) else: diff --git a/tsfc/gem.py b/tsfc/gem.py index 81b890dc11..b6c33de18c 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -304,16 +304,14 @@ class Index(object): """Free index""" # Not true object count, just for naming purposes - count = 0 + _count = 0 - __slots__ = ('name', 'extent') + __slots__ = ('name', 'extent', 'count') def __init__(self, name=None): - if name is None: - Index.count += 1 - name = "i_%d" % Index.count self.name = name - + Index._count += 1 + self.count = Index._count # Initialise with indefinite extent self.extent = None @@ -325,9 +323,13 @@ def set_extent(self, value): raise ValueError("Inconsistent index extents!") def __str__(self): + if self.name is None: + return "i_%d" % self.count return self.name def __repr__(self): + if self.name is None: + return "Index(%r)" % self.count return "Index(%r)" % self.name From 40641568207344ff58f60a692b2355127341b804 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 12 Feb 2016 18:21:46 +0000 Subject: [PATCH 060/809] reduce the harmful effect of 6d9db36 on compilation time Please do not squash this commit. --- tsfc/optimise.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tsfc/optimise.py b/tsfc/optimise.py index d2460e5f3d..7610af21e4 100644 --- a/tsfc/optimise.py +++ b/tsfc/optimise.py @@ -48,17 +48,6 @@ def filtered_replace_indices(node, self, subst): return replace_indices(node, self, filtered_subst) -def filtered_replace_indices_top(node, self, subst): - """Wrapper for :func:`replace_indices`. At each call removes - substitution rules that do not apply. Stops recursion when there - is nothing to substitute.""" - filtered_subst = tuple((k, v) for k, v in subst if k in node.free_indices) - if filtered_subst: - return replace_indices(node, self, filtered_subst) - else: - return node - - def remove_componenttensors(expressions): """Removes all ComponentTensors from a list of expression DAGs.""" mapper = MemoizerArg(filtered_replace_indices) @@ -84,7 +73,7 @@ def _(node, self): # Unrolling summand = self(node.children[0]) return reduce(Sum, - (self.replace(summand, ((node.index, i),)) + (Indexed(ComponentTensor(summand, (node.index,)), (i,)) for i in range(node.index.extent)), Zero()) else: @@ -100,5 +89,4 @@ def unroll_indexsum(expressions, max_extent): """ mapper = Memoizer(_unroll_indexsum) mapper.max_extent = max_extent - mapper.replace = MemoizerArg(filtered_replace_indices_top) return map(mapper, expressions) From 3995a20add8b41a59a1f80c880c97a91754abd97 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 15 Feb 2016 15:22:26 +0000 Subject: [PATCH 061/809] add clarifications --- tsfc/driver.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 0b02ff8f27..beacc73f71 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -100,9 +100,12 @@ def compile_integral(idata, fd, prefix, parameters): mesh = idata.domain coordinates = fem.coordinate_coefficient(mesh) - if mesh.ufl_cell().cellname() in ["interval", "triangle", "tetrahedron"]: + if is_mesh_affine(mesh): + # For affine mesh geometries we prefer code generation that + # composes well with optimisations. funarg, prepare_, expression = prepare_coefficient(integral_type, coordinates, "coords", mode='list_tensor') else: + # Otherwise we use the approach that might be faster (?) funarg, prepare_, expression = prepare_coefficient(integral_type, coordinates, "coords") arglist.append(funarg) @@ -243,6 +246,13 @@ def compile_integral(idata, fd, prefix, parameters): return kernel +def is_mesh_affine(mesh): + """Tells if a mesh geometry is affine.""" + affine_cells = ["interval", "triangle", "tetrahedron"] + degree = mesh.ufl_coordinate_element().degree() + return mesh.ufl_cell().cellname() in affine_cells and degree == 1 + + def prepare_coefficient(integral_type, coefficient, name, mode=None): if mode is None: mode = 'manual_loop' @@ -297,7 +307,23 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): return funarg, [], expression # Interior facet integral + mixed / vector element + + # Here we need to reorder the coefficient values. + # + # Incoming ordering: E1+ E1- E2+ E2- E3+ E3- + # Required ordering: E1+ E2+ E3+ E1- E2- E3- + # + # Each of E[n]{+,-} is a vector of basis function coefficients for + # subelement E[n]. + # + # There are two code generation method to reorder the values. + # We have not done extensive research yet as to which way yield + # faster code. + if mode == 'manual_loop': + # In this case we generate loops outside the GEM abstraction + # to reorder the values. A whole E[n]{+,-} block is copied by + # a single loop. name_ = name + "_" shape = (2, fiat_element.space_dimension()) @@ -324,6 +350,9 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): return funarg, prepare, expression elif mode == 'list_tensor': + # In this case we generate a gem.ListTensor to do the + # reordering. Every single element in a E[n]{+,-} block is + # referenced separately. funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name), qualifiers=["const"]) From 097b10bf036d57255ac569ac0707f192a6ada963 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 15 Feb 2016 14:56:22 +0000 Subject: [PATCH 062/809] clean up scheduling.py --- tsfc/driver.py | 3 +- tsfc/impero.py | 33 ++++++ tsfc/scheduling.py | 270 +++++++++++++++++++++++---------------------- 3 files changed, 174 insertions(+), 132 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index beacc73f71..7c8e1fb8a0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -221,7 +221,8 @@ def compile_integral(idata, fd, prefix, parameters): shape_map = lambda expr: expr.free_indices ordered_shape_map = lambda expr: apply_ordering(shape_map(expr)) - indexed_ops = sch.make_ordering(zip(expressions, simplified), ordered_shape_map) + ops = sch.emit_operations(zip(expressions, simplified), ordered_shape_map) + indexed_ops = [(op.loop_shape(ordered_shape_map), op) for op in ops] indexed_ops = [(multiindex, op) for multiindex, op in indexed_ops if not (isinstance(op, imp.Evaluate) and op.expression in candidates)] diff --git a/tsfc/impero.py b/tsfc/impero.py index 6c10979b35..ea01e50bf3 100644 --- a/tsfc/impero.py +++ b/tsfc/impero.py @@ -12,6 +12,8 @@ from __future__ import absolute_import +from abc import ABCMeta, abstractmethod + from tsfc.node import Node as NodeBase @@ -24,10 +26,22 @@ class Node(NodeBase): class Terminal(Node): """Abstract class for terminal Impero nodes""" + __metaclass__ = ABCMeta + __slots__ = () children = () + @abstractmethod + def loop_shape(self, free_indices): + """Gives the loop shape, an ordering of indices for an Impero + terminal. + + :arg free_indices: a mapping of GEM expressions to ordered + free indices. + """ + pass + class Evaluate(Terminal): """Assign the value of a GEM expression to a temporary.""" @@ -38,6 +52,9 @@ class Evaluate(Terminal): def __init__(self, expression): self.expression = expression + def loop_shape(self, free_indices): + return free_indices(self.expression) + class Initialise(Terminal): """Initialise an :class:`gem.IndexSum`.""" @@ -48,6 +65,9 @@ class Initialise(Terminal): def __init__(self, indexsum): self.indexsum = indexsum + def loop_shape(self, free_indices): + return free_indices(self.indexsum) + class Accumulate(Terminal): """Accumulate terms into an :class:`gem.IndexSum`.""" @@ -58,6 +78,9 @@ class Accumulate(Terminal): def __init__(self, indexsum): self.indexsum = indexsum + def loop_shape(self, free_indices): + return free_indices(self.indexsum.children[0]) + class Return(Terminal): """Save value of GEM expression into an lvalue. Used to "return" @@ -67,9 +90,14 @@ class Return(Terminal): __front__ = ('variable', 'expression') def __init__(self, variable, expression): + assert set(variable.free_indices) == set(expression.free_indices) + self.variable = variable self.expression = expression + def loop_shape(self, free_indices): + return free_indices(self.expression) + class ReturnAccumulate(Terminal): """Accumulate an :class:`gem.IndexSum` directly into a return @@ -79,9 +107,14 @@ class ReturnAccumulate(Terminal): __front__ = ('variable', 'indexsum') def __init__(self, variable, indexsum): + assert set(variable.free_indices) == set(indexsum.free_indices) + self.variable = variable self.indexsum = indexsum + def loop_shape(self, free_indices): + return free_indices(self.indexsum.children[0]) + class Block(Node): """An ordered set of Impero expressions. Corresponds to a curly diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py index b0ba8bb160..2c6b1e7514 100644 --- a/tsfc/scheduling.py +++ b/tsfc/scheduling.py @@ -1,10 +1,12 @@ +"""Schedules operations to evaluate a multi-root expression DAG, +forming an ordered list of Impero terminals.""" + from __future__ import absolute_import import collections +import functools -from singledispatch import singledispatch - -from tsfc import gem as ein, impero as imp +from tsfc import gem, impero from tsfc.node import traversal @@ -25,27 +27,68 @@ def __missing__(self, key): return val -class Queue(object): - def __init__(self, reference_count, get_indices): +class ReferenceStager(object): + """Provides staging for nodes in reference counted expression + DAGs. A callback function is called once the reference count is + exhausted.""" + + def __init__(self, reference_count, callback): + """Initialises a ReferenceStager. + + :arg reference_count: initial reference counts for all + expected nodes + :arg callback: function to call on each node when + reference count is exhausted + """ self.waiting = reference_count.copy() - # Need to have deterministic iteration over the queue. - self.queue = OrderedDefaultDict(list) - self.get_indices = get_indices + self.callback = callback def reference(self, o): - if o not in self.waiting: - return - + """References a node, decreasing its reference count, and + possibly triggering a callback (when the reference count + becomes zero).""" assert 1 <= self.waiting[o] self.waiting[o] -= 1 if self.waiting[o] == 0: - self.insert(o, self.get_indices(o)) + self.callback(o) + + def empty(self): + """All reference counts exhausted?""" + return not any(self.waiting.values()) + + +class Queue(object): + """Special queue for operation scheduling. GEM / Impero nodes are + inserted when they are ready to be scheduled, i.e. any operation + which depends on the operation to be inserted must have been + scheduled already. This class implements a heuristic for ordering + operations within the constraints in a way which aims to achieve + maximum loop fusion to minimise the size of temporaries which need + to be introduced. + """ + def __init__(self, callback): + """Initialises a Queue. + + :arg callback: function called on each element "popped" from the queue + """ + # Must have deterministic iteration over the queue + self.queue = OrderedDefaultDict(list) + self.callback = callback + + def insert(self, indices, elem): + """Insert element into queue. - def insert(self, o, indices): - self.queue[indices].append(o) + :arg indices: loop indices used by the scheduling heuristic + :arg elem: element to be scheduled + """ + self.queue[indices].append(elem) - def __iter__(self): + def process(self): + """Pops elements from the queue and calls the callback + function on them until the queue is empty. The callback + function can insert further elements into the queue. + """ indices = () while self.queue: # Find innermost non-empty outer loop @@ -59,140 +102,105 @@ def __iter__(self): break while self.queue[indices]: - yield self.queue[indices].pop() + self.callback(self.queue[indices].pop()) del self.queue[indices] def count_references(expressions): + """Collects reference counts for a multi-root expression DAG.""" result = collections.Counter(expressions) for node in traversal(expressions): result.update(node.children) return result -@singledispatch -def impero_indices(node, indices): - raise AssertionError("Cannot handle type: %s" % type(node)) - - -@impero_indices.register(imp.Return) # noqa: Not actually redefinition -def _(node, indices): - assert set(node.variable.free_indices) == set(node.expression.free_indices) - return indices(node.variable) - - -@impero_indices.register(imp.Initialise) # noqa: Not actually redefinition -def _(node, indices): - return indices(node.indexsum) - - -@impero_indices.register(imp.Accumulate) # noqa: Not actually redefinition -def _(node, indices): - return indices(node.indexsum.children[0]) - - -@impero_indices.register(imp.Evaluate) # noqa: Not actually redefinition -def _(node, indices): - return indices(node.expression) - - -@impero_indices.register(imp.ReturnAccumulate) # noqa: Not actually redefinition -def _(node, indices): - assert set(node.variable.free_indices) == set(node.indexsum.free_indices) - return indices(node.indexsum.children[0]) - - -@singledispatch -def handle(node, enqueue, emit): - raise AssertionError("Cannot handle foreign type: %s" % type(node)) - - -@handle.register(ein.Node) # noqa: Not actually redefinition -def _(node, enqueue, emit): - emit(imp.Evaluate(node)) - for child in node.children: - enqueue(child) - - -@handle.register(ein.Variable) # noqa: Not actually redefinition -def _(node, enqueue, emit): - pass - - -@handle.register(ein.Literal) # noqa: Not actually redefinition -@handle.register(ein.Zero) -def _(node, enqueue, emit): - if node.shape: - emit(imp.Evaluate(node)) - - -@handle.register(ein.Indexed) # noqa: Not actually redefinition -def _(node, enqueue, emit): - enqueue(node.children[0]) - - -@handle.register(ein.IndexSum) # noqa: Not actually redefinition -def _(node, enqueue, emit): - enqueue(imp.Accumulate(node)) - - -@handle.register(imp.Initialise) # noqa: Not actually redefinition -def _(op, enqueue, emit): - emit(op) - - -@handle.register(imp.Accumulate) # noqa: Not actually redefinition -def _(op, enqueue, emit): - emit(op) - enqueue(imp.Initialise(op.indexsum)) - enqueue(op.indexsum.children[0]) - - -@handle.register(imp.Return) # noqa: Not actually redefinition -def _(op, enqueue, emit): - emit(op) - enqueue(op.expression) - - -@handle.register(imp.ReturnAccumulate) # noqa: Not actually redefinition -def _(op, enqueue, emit): - emit(op) - enqueue(op.indexsum.children[0]) - +def handle(ops, push, ref, node): + """Helper function for scheduling""" + if isinstance(node, gem.Variable): + # Declared in the kernel header + pass + elif isinstance(node, gem.Literal): + # Constant literals inlined, unless tensor-valued + if node.shape: + ops.append(impero.Evaluate(node)) + elif isinstance(node, gem.Zero): # should rarely happen + assert not node.shape + elif isinstance(node, gem.Indexed): + # Indexing always inlined + ref(node.children[0]) + elif isinstance(node, gem.IndexSum): + push(impero.Accumulate(node)) + elif isinstance(node, gem.Node): + ops.append(impero.Evaluate(node)) + for child in node.children: + ref(child) + elif isinstance(node, impero.Initialise): + ops.append(node) + elif isinstance(node, impero.Accumulate): + ops.append(node) + push(impero.Initialise(node.indexsum)) + ref(node.indexsum.children[0]) + elif isinstance(node, impero.Return): + ops.append(node) + ref(node.expression) + elif isinstance(node, impero.ReturnAccumulate): + ops.append(node) + ref(node.indexsum.children[0]) + else: + raise AssertionError("no handler for node type %s" % type(node)) + + +def emit_operations(assignments, index_ordering): + """Makes an ordering of operations to evaluate a multi-root + expression DAG. + + :arg assignments: Iterable of (variable, expression) pairs. + The value of expression is written into variable + upon execution. + :arg index_ordering: mapping from GEM nodes to an ordering of free + indices + :returns: list of Impero terminals correctly ordered to evaluate + the assignments + """ + # Filter out zeros + assignments = [(variable, expression) + for variable, expression in assignments + if not isinstance(expression, gem.Zero)] -def make_ordering(assignments, indices_map): - assignments = filter(lambda x: not isinstance(x[1], ein.Zero), assignments) - expressions = [expression for variable, expression in assignments] - refcount = count_references(expressions) + # Prepare reference counts + refcount = count_references([e for v, e in assignments]) + # Stage return operations staging = [] for variable, expression in assignments: - if isinstance(expression, ein.IndexSum) and refcount[expression] == 1: - staging.append((imp.ReturnAccumulate(variable, expression), - indices_map(expression.children[0]))) + if isinstance(expression, gem.IndexSum) and refcount[expression] == 1: + staging.append(impero.ReturnAccumulate(variable, expression)) refcount[expression] -= 1 else: - staging.append((imp.Return(variable, expression), - indices_map(expression))) + staging.append(impero.Return(variable, expression)) - queue = Queue(refcount, indices_map) - for op, indices in staging: - queue.insert(op, indices) + # Prepare data structures + def push_node(node): + queue.insert(index_ordering(node), node) - result = [] + def push_op(op): + queue.insert(op.loop_shape(index_ordering), op) - def emit(op): - return result.append((impero_indices(op, indices_map), op)) + ops = [] - def enqueue(item): - if isinstance(item, ein.Node): - queue.reference(item) - elif isinstance(item, imp.Node): - queue.insert(item, impero_indices(item, indices_map)) - else: - raise AssertionError("should never happen") + stager = ReferenceStager(refcount, push_node) + queue = Queue(functools.partial(handle, ops, push_op, stager.reference)) + + # Enqueue return operations + for op in staging: + push_op(op) + + # Schedule operations + queue.process() + + # Assert that nothing left unprocessed + assert stager.empty() - for o in queue: - handle(o, enqueue, emit) - assert not any(queue.waiting.values()) - return list(reversed(result)) + # Return + ops.reverse() + return ops From 0eccac6c9d4f75386e4118c7f9ea9d37f98d4a7c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 15 Feb 2016 18:59:45 +0000 Subject: [PATCH 063/809] clean up impero_utils.py --- tsfc/coffee.py | 10 +- tsfc/driver.py | 143 ++++++++-------------- tsfc/impero_utils.py | 282 +++++++++++++++++++++++++++++++------------ tsfc/node.py | 10 ++ tsfc/scheduling.py | 22 ++-- 5 files changed, 282 insertions(+), 185 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index d11b2ca8d8..2e596489b3 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -17,18 +17,18 @@ class Bunch(object): pass -def generate(temporaries, code, indices, declare): +def generate(impero_c): parameters = Bunch() - parameters.declare = declare - parameters.indices = indices + parameters.declare = impero_c.declare + parameters.indices = impero_c.indices parameters.names = {} counter = itertools.count() parameters.index_names = defaultdict(lambda: "i_%d" % next(counter)) - for i, temp in enumerate(temporaries): + for i, temp in enumerate(impero_c.temporaries): parameters.names[temp] = "t%d" % i - return arabica(code, parameters) + return arabica(impero_c.tree, parameters) def _coffee_symbol(symbol, rank=()): diff --git a/tsfc/driver.py b/tsfc/driver.py index 7c8e1fb8a0..acd55009fb 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -14,7 +14,7 @@ import coffee.base as coffee -from tsfc import fem, gem as ein, impero as imp, scheduling as sch, optimise as opt, impero_utils +from tsfc import fem, gem, scheduling as sch, optimise as opt, impero_utils from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee from tsfc.constants import default_parameters from tsfc.node import traversal @@ -96,7 +96,7 @@ def compile_integral(idata, fd, prefix, parameters): arglist.append(funarg) prepare += prepare_ - argument_indices = tuple(index for index in expressions[0].multiindex if isinstance(index, ein.Index)) + argument_indices = tuple(index for index in expressions[0].multiindex if isinstance(index, gem.Index)) mesh = idata.domain coordinates = fem.coordinate_coefficient(mesh) @@ -149,7 +149,7 @@ def compile_integral(idata, fd, prefix, parameters): # multiple times with the same table. Occurs, for example, if we # have multiple integrals here (and the affine coordinate # evaluation can be hoisted). - index_cache = collections.defaultdict(ein.Index) + index_cache = collections.defaultdict(gem.Index) for i, integral in enumerate(idata.integrals): params = {} # Record per-integral parameters @@ -173,34 +173,23 @@ def compile_integral(idata, fd, prefix, parameters): quad_rule.points) integrand = fem.replace_coordinates(integral.integrand(), coordinates) - quadrature_index = ein.Index(name="ip%d" % i) + quadrature_index = gem.Index(name="ip%d" % i) quadrature_indices.append(quadrature_index) nonfem = fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, quadrature_index, argument_indices, coefficient_map, index_cache) nonfem = opt.unroll_indexsum(nonfem, max_extent=3) - nonfem_.append([(ein.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) + nonfem_.append([(gem.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) for e in nonfem]) # Sum the expressions that are part of the same restriction - nonfem = list(reduce(ein.Sum, e, ein.Zero()) for e in zip(*nonfem_)) + nonfem = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*nonfem_)) simplified = opt.remove_componenttensors(nonfem) cell_orientations = False - refcount = sch.count_references(simplified) - candidates = set() for node in traversal(simplified): - if isinstance(node, ein.IndexSum): - if refcount[node.children[0]] == 1: - candidates.add(node.children[0]) - else: - for child in node.children: - if set(child.free_indices) == set(node.free_indices) and refcount[child] == 1: - if not (isinstance(child, ein.Literal) and child.shape): - candidates.add(child) - - if isinstance(node, ein.Variable) and node.name == "cell_orientations": + if isinstance(node, gem.Variable) and node.name == "cell_orientations": cell_orientations = True if cell_orientations: @@ -209,34 +198,34 @@ def compile_integral(idata, fd, prefix, parameters): arglist.insert(2, decl) kernel.oriented = True - # Need a deterministic ordering for these - indices = set() + # Collect indices in a deterministic order + indices = [] for node in traversal(simplified): - indices.update(node.free_indices) - indices = sorted(indices) + if isinstance(node, gem.Indexed): + indices.extend(node.multiindex) + _, unique_indices = numpy.unique(indices, return_index=True) + indices = numpy.asarray(indices)[sorted(unique_indices)] - index_ordering = apply_prefix_ordering(indices, tuple(quadrature_indices) + argument_indices) + # Build ordered index map + index_ordering = make_prefix_ordering(indices, tuple(quadrature_indices) + argument_indices) apply_ordering = make_index_orderer(index_ordering) - shape_map = lambda expr: expr.free_indices - ordered_shape_map = lambda expr: apply_ordering(shape_map(expr)) + get_indices = lambda expr: apply_ordering(expr.free_indices) - ops = sch.emit_operations(zip(expressions, simplified), ordered_shape_map) - indexed_ops = [(op.loop_shape(ordered_shape_map), op) for op in ops] - indexed_ops = [(multiindex, op) - for multiindex, op in indexed_ops - if not (isinstance(op, imp.Evaluate) and op.expression in candidates)] + # Build operation ordering + ops = sch.emit_operations(zip(expressions, simplified), get_indices) # Zero-simplification occurred - if len(indexed_ops) == 0: + if len(ops) == 0: return None - temporaries = make_temporaries(op for loop_indices, op in indexed_ops) - tree, indices, declare = impero_utils.process(indexed_ops, - temporaries, - shape_map, - apply_ordering) - body = generate_coffee(temporaries, tree, indices, declare) + # Drop unnecessary temporaries + ops = impero_utils.inline_temporaries(simplified, ops) + + # Prepare ImperoC (Impero AST + other data for code generation) + impero_c = impero_utils.process(ops, get_indices) + + body = generate_coffee(impero_c) body.open_scope = False funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) @@ -267,9 +256,9 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), qualifiers=["const"]) - expression = ein.Variable(name, shape) + expression = gem.Variable(name, shape) if coefficient.ufl_shape == (): - expression = ein.Indexed(expression, (0,)) + expression = gem.Indexed(expression, (0,)) return funarg, [], expression @@ -282,9 +271,9 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), qualifiers=["const"]) - i = ein.Index() - expression = ein.ComponentTensor( - ein.Indexed(ein.Variable(name, shape + (1,)), + i = gem.Index() + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, shape + (1,)), (i, 0)), (i,)) @@ -297,11 +286,11 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), qualifiers=["const"]) - expression = ein.Variable(name, shape + (1,)) + expression = gem.Variable(name, shape + (1,)) - f, i = ein.Index(), ein.Index() - expression = ein.ComponentTensor( - ein.Indexed(ein.Variable(name, shape + (1,)), + f, i = gem.Index(), gem.Index() + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, shape + (1,)), (f, i, 0)), (f, i,)) @@ -331,7 +320,7 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_), qualifiers=["const"]) prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] - expression = ein.Variable(name, shape) + expression = gem.Variable(name, shape) offset = 0 i = coffee.Symbol("i") @@ -357,7 +346,7 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name), qualifiers=["const"]) - variable = ein.Variable(name, (2 * fiat_element.space_dimension(), 1)) + variable = gem.Variable(name, (2 * fiat_element.space_dimension(), 1)) facet_0 = [] facet_1 = [] @@ -366,14 +355,14 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): space_dim = element.space_dimension() for i in range(offset, offset + space_dim): - facet_0.append(ein.Indexed(variable, (i, 0))) + facet_0.append(gem.Indexed(variable, (i, 0))) offset += space_dim for i in range(offset, offset + space_dim): - facet_1.append(ein.Indexed(variable, (i, 0))) + facet_1.append(gem.Indexed(variable, (i, 0))) offset += space_dim - expression = ein.ListTensor(numpy.array([facet_0, facet_1])) + expression = gem.ListTensor(numpy.array([facet_0, facet_1])) return funarg, [], expression @@ -383,19 +372,19 @@ def prepare_arguments(integral_type, arguments): if len(arguments) == 0: # No arguments funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=(1,))) - expression = ein.Indexed(ein.Variable("A", (1,)), (0,)) + expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) return funarg, [], [expression], [] elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - indices = tuple(ein.Index(name=name) for i, name in zip(range(len(arguments)), ['j', 'k'])) + indices = tuple(gem.Index(name=name) for i, name in zip(range(len(arguments)), ['j', 'k'])) if not integral_type.startswith("interior_facet"): # Not an interior facet integral shape = tuple(element.space_dimension() for element in elements) funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) - expression = ein.Indexed(ein.Variable("A", shape), indices) + expression = gem.Indexed(gem.Variable("A", shape), indices) return funarg, [], [expression], [] @@ -407,12 +396,12 @@ def prepare_arguments(integral_type, arguments): shape = tuple(shape) funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) - varexp = ein.Variable("A", shape) + varexp = gem.Variable("A", shape) expressions = [] for restrictions in product((0, 1), repeat=len(arguments)): is_ = tuple(chain(*zip(restrictions, indices))) - expressions.append(ein.Indexed(varexp, is_)) + expressions.append(gem.Indexed(varexp, is_)) return funarg, [], expressions, [] @@ -431,7 +420,7 @@ def prepare_arguments(integral_type, arguments): prepare.append(coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), init=coffee.ArrayInit(numpy.zeros(1)))) - expressions.append(ein.Indexed(ein.Variable(name, shape), indices)) + expressions.append(gem.Indexed(gem.Variable(name, shape), indices)) for multiindex in numpy.ndindex(shape): references.append(coffee.Symbol(name, multiindex)) @@ -469,40 +458,14 @@ def coffee_for(index, extent, body): body) -def make_index_orderer(index_ordering): - idx2pos = {idx: pos for pos, idx in enumerate(index_ordering)} - - def apply_ordering(shape): - return tuple(sorted(shape, key=lambda i: idx2pos[i])) - return apply_ordering - - -def apply_prefix_ordering(indices, prefix_ordering): - rest = set(indices) - set(prefix_ordering) +def make_prefix_ordering(indices, prefix_ordering): # Need to return deterministically ordered indices - return tuple(prefix_ordering) + tuple(k for k in indices if k in rest) - - -def make_temporaries(operations): - # For fast look up - set_ = set() - - # For ordering - list = [] + return tuple(prefix_ordering) + tuple(k for k in indices if k not in prefix_ordering) - def make_temporary(o): - if o not in set_: - set_.add(o) - list.append(o) - for op in operations: - if isinstance(op, (imp.Initialise, imp.Return, imp.ReturnAccumulate)): - pass - elif isinstance(op, imp.Accumulate): - make_temporary(op.indexsum) - elif isinstance(op, imp.Evaluate): - make_temporary(op.expression) - else: - raise AssertionError("unhandled operation: %s" % type(op)) +def make_index_orderer(index_ordering): + idx2pos = {idx: pos for pos, idx in enumerate(index_ordering)} - return list + def apply_ordering(indices): + return tuple(sorted(indices, key=lambda i: idx2pos[i])) + return apply_ordering diff --git a/tsfc/impero_utils.py b/tsfc/impero_utils.py index f940b542db..e406d29523 100644 --- a/tsfc/impero_utils.py +++ b/tsfc/impero_utils.py @@ -1,123 +1,255 @@ +"""Utilities for building an Impero AST from an ordered list of +terminal Impero operations, and for building any additional data +required for straightforward C code generation. + +What this module does is independent of whether we eventually generate +C code or a COFFEE AST. +""" + from __future__ import absolute_import import collections import itertools -from tsfc import gem, impero as imp +from singledispatch import singledispatch +from tsfc.node import traversal, collect_refcount +from tsfc import impero as imp -class OrderedCounter(collections.Counter, collections.OrderedDict): - """A Counter object that has deterministic iteration order.""" - pass +# ImperoC is named tuple for C code generation. +# +# Attributes: +# tree - Impero AST describing the loop structure and operations +# temporaries - List of GEM expressions which have assigned temporaries +# declare - Where to declare temporaries to get correct C code +# indices - Indices for declarations and referencing values +ImperoC = collections.namedtuple('ImperoC', ['tree', 'temporaries', 'declare', 'indices']) -def process(indexed_ops, temporaries, shape_map, apply_ordering): - temporaries_set = set(temporaries) - ops = [op for indices, op in indexed_ops] - code = make_loop_tree(indexed_ops) +def inline_temporaries(expressions, ops, coffee_licm=False): + """Inline temporaries which could be inlined without blowing up + the code. + + :arg expressions: a multi-root GEM expression DAG, used for + reference counting + :arg ops: ordered list of Impero terminals + :arg coffee_licm: Trust COFFEE to do LICM. If enabled, inlining + can move calculations inside inner loops. + :returns: a filtered ``ops``, without the unnecessary + :class:`impero.Evaluate`s + """ + refcount = collect_refcount(expressions) - reference_count = collections.Counter() + candidates = set() # candidates for inlining for op in ops: - reference_count.update(count_references(temporaries_set, op)) - assert temporaries_set == set(reference_count) + if isinstance(op, imp.Evaluate): + expr = op.expression + if expr.shape == () and refcount[expr] == 1: + candidates.add(expr) + + if not coffee_licm: + # Prevent inlining that pulls expressions into inner loops + for node in traversal(expressions): + for child in node.children: + if child in candidates and set(child.free_indices) != set(node.free_indices): + candidates.remove(child) + + # Filter out candidates + return [op for op in ops if not (isinstance(op, imp.Evaluate) and op.expression in candidates)] - indices, declare = place_declarations(code, reference_count, shape_map, apply_ordering, ops) - return code, indices, declare +def process(ops, get_indices): + # Build Impero AST + tree = make_loop_tree(ops, get_indices) + # Collect temporaries + temporaries = collect_temporaries(ops) -def make_loop_tree(indexed_ops, level=0): + # Determine declarations (incomplete) + declare, indices = place_declarations(ops, tree, temporaries, get_indices) + + return ImperoC(tree, temporaries, declare, indices) + + +def collect_temporaries(ops): + """Collects GEM expressions to assign to temporaries from a list + of Impero terminals.""" + result = [] + for op in ops: + # IndexSum temporaries should be added either at Initialise or + # at Accumulate. The difference is only in ordering + # (numbering). We chose Accumulate here. + if isinstance(op, (imp.Initialise, imp.Return, imp.ReturnAccumulate)): + pass + elif isinstance(op, imp.Accumulate): + result.append(op.indexsum) + elif isinstance(op, imp.Evaluate): + result.append(op.expression) + else: + raise AssertionError("unhandled operation: %s" % type(op)) + return result + + +def make_loop_tree(ops, get_indices, level=0): """Creates an Impero AST with loops from a list of operations and their respective free indices. - :arg indexed_ops: A list of (free indices, operation) pairs, each - operation must be an Impero terminal node. + :arg ops: a list of Impero terminal nodes + :arg get_indices: mapping from GEM nodes to an ordering of free + indices :arg level: depth of loop nesting :returns: Impero AST with loops, without declarations """ - keyfunc = lambda (indices, op): indices[level:level+1] + keyfunc = lambda op: op.loop_shape(get_indices)[level:level+1] statements = [] - for first_index, op_group in itertools.groupby(indexed_ops, keyfunc): + for first_index, op_group in itertools.groupby(ops, keyfunc): if first_index: - inner_block = make_loop_tree(op_group, level+1) + inner_block = make_loop_tree(op_group, get_indices, level+1) statements.append(imp.For(first_index[0], inner_block)) else: - statements.extend(op for indices, op in op_group) + statements.extend(op_group) return imp.Block(statements) -def count_references(temporaries, op): - counter = collections.Counter() +def place_declarations(ops, tree, temporaries, get_indices): + """Determines where and how to declare temporaries for an Impero AST. - def recurse(o, top=False): - if o in temporaries: - counter[o] += 1 - - if top or o not in temporaries: - if isinstance(o, (gem.Literal, gem.Variable)): - pass - elif isinstance(o, gem.Indexed): - recurse(o.children[0]) - else: - for c in o.children: - recurse(c) - - if isinstance(op, imp.Evaluate): - recurse(op.expression, top=True) - elif isinstance(op, imp.Initialise): - counter[op.indexsum] += 1 - elif isinstance(op, imp.Return): - recurse(op.expression) - elif isinstance(op, imp.Accumulate): - counter[op.indexsum] += 1 - recurse(op.indexsum.children[0]) - elif isinstance(op, imp.ReturnAccumulate): - recurse(op.indexsum.children[0]) - else: - raise AssertionError("unhandled operation: %s" % type(op)) + :arg ops: terminals of ``tree`` + :arg tree: Impero AST to determine the declarations for + :arg temporaries: list of GEM expressions which are assigned to + temporaries + :arg get_indices: mapping from GEM nodes to an ordering of free + indices + """ - return counter + temporaries_set = set(temporaries) + assert len(temporaries_set) == len(temporaries) + # Collect the total number of temporary references + total_refcount = collections.Counter() + for op in ops: + total_refcount.update(temp_refcount(temporaries_set, op)) + assert temporaries_set == set(total_refcount) -def place_declarations(tree, reference_count, shape_map, apply_ordering, operations): - temporaries = set(reference_count) + # Result + declare = {} indices = {} - # We later iterate over declare keys, so need this to be ordered - declare = collections.OrderedDict() + @singledispatch def recurse(expr, loop_indices): - if isinstance(expr, imp.Block): - declare[expr] = [] - # Need to iterate over counter in given order - counter = OrderedCounter() - for statement in expr.children: - counter.update(recurse(statement, loop_indices)) - for e, count in counter.items(): - if count == reference_count[e]: - indices[e] = apply_ordering(set(shape_map(e)) - loop_indices) - if indices[e]: - declare[expr].append(e) - del counter[e] - return counter - elif isinstance(expr, imp.For): - return recurse(expr.children[0], loop_indices | {expr.index}) - else: - return count_references(temporaries, expr) - - remainder = recurse(tree, set()) + """Visit an Impero AST to collect declarations. + + :arg expr: Impero tree node + :arg loop_indices: loop indices (in order) from the outer + loops surrounding ``expr`` + :returns: :class:`collections.Counter` with the reference + counts for each temporary in the subtree whose root + is ``expr`` + """ + return AssertionError("unsupported expression type %s" % type(expr)) + + @recurse.register(imp.Terminal) + def recurse_terminal(expr, loop_indices): + return temp_refcount(temporaries_set, expr) + + @recurse.register(imp.For) + def recurse_for(expr, loop_indices): + return recurse(expr.children[0], loop_indices + (expr.index,)) + + @recurse.register(imp.Block) + def recurse_block(expr, loop_indices): + # Temporaries declared at the beginning of the block are + # collected here + declare[expr] = [] + + # Collect reference counts for the block + refcount = collections.Counter() + for statement in expr.children: + refcount.update(recurse(statement, loop_indices)) + + # Visit :class:`collections.Counter` in deterministic order + for e in sorted(refcount.keys(), key=temporaries.index): + if refcount[e] == total_refcount[e]: + # If all references are within this block, then this + # block is the right place to declare the temporary. + assert loop_indices == get_indices(e)[:len(loop_indices)] + indices[e] = get_indices(e)[len(loop_indices):] + if indices[e]: + # Scalar-valued temporaries are not declared until + # their value is assigned. This does not really + # matter, but produces a more compact and nicer to + # read C code. + declare[expr].append(e) + # Remove expression from the ``refcount`` so it will + # not be declared again. + del refcount[e] + return refcount + + # Populate result + remainder = recurse(tree, ()) assert not remainder - for op in operations: + # Set in ``declare`` for Impero terminals whether they should + # declare the temporary that they are writing to. + for op in ops: declare[op] = False if isinstance(op, imp.Evaluate): e = op.expression elif isinstance(op, imp.Initialise): e = op.indexsum - else: + elif isinstance(op, (imp.Accumulate, imp.Return, imp.ReturnAccumulate)): continue + else: + raise AssertionError("unhandled operation type %s" % type(op)) if len(indices[e]) == 0: declare[op] = True - return indices, declare + return declare, indices + + +def temp_refcount(temporaries, op): + """Collects the number of times temporaries are referenced when + generating code for an Impero terminal. + + :arg temporaries: set of temporaries + :arg op: Impero terminal + :returns: :class:`collections.Counter` object mapping some of + elements from ``temporaries`` to the number of times + they will referenced from ``op`` + """ + counter = collections.Counter() + + def recurse(o): + """Traverses expression until reaching temporaries, counting + temporary references.""" + if o in temporaries: + counter[o] += 1 + else: + for c in o.children: + recurse(c) + + def recurse_top(o): + """Traverses expression until reaching temporaries, counting + temporary references. Always descends into children at least + once, even when the root is a temporary.""" + if o in temporaries: + counter[o] += 1 + for c in o.children: + recurse(c) + + if isinstance(op, imp.Initialise): + counter[op.indexsum] += 1 + elif isinstance(op, imp.Accumulate): + recurse_top(op.indexsum) + elif isinstance(op, imp.Evaluate): + recurse_top(op.expression) + elif isinstance(op, imp.Return): + recurse(op.expression) + elif isinstance(op, imp.ReturnAccumulate): + recurse(op.indexsum.children[0]) + else: + raise AssertionError("unhandled operation: %s" % type(op)) + + return counter diff --git a/tsfc/node.py b/tsfc/node.py index a985a48d4a..d1c72cc71c 100644 --- a/tsfc/node.py +++ b/tsfc/node.py @@ -3,6 +3,8 @@ from __future__ import absolute_import +import collections + class Node(object): """Abstract node class. @@ -116,6 +118,14 @@ def traversal(expression_dags): lifo.append(child) +def collect_refcount(expression_dags): + """Collects reference counts for a multi-root expression DAG.""" + result = collections.Counter(expression_dags) + for node in traversal(expression_dags): + result.update(node.children) + return result + + def noop_recursive(function): """No-op wrapper for functions with overridable recursive calls. diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py index 2c6b1e7514..7e07d4d982 100644 --- a/tsfc/scheduling.py +++ b/tsfc/scheduling.py @@ -7,7 +7,7 @@ import functools from tsfc import gem, impero -from tsfc.node import traversal +from tsfc.node import collect_refcount class OrderedDefaultDict(collections.OrderedDict): @@ -106,14 +106,6 @@ def process(self): del self.queue[indices] -def count_references(expressions): - """Collects reference counts for a multi-root expression DAG.""" - result = collections.Counter(expressions) - for node in traversal(expressions): - result.update(node.children) - return result - - def handle(ops, push, ref, node): """Helper function for scheduling""" if isinstance(node, gem.Variable): @@ -150,15 +142,15 @@ def handle(ops, push, ref, node): raise AssertionError("no handler for node type %s" % type(node)) -def emit_operations(assignments, index_ordering): +def emit_operations(assignments, get_indices): """Makes an ordering of operations to evaluate a multi-root expression DAG. :arg assignments: Iterable of (variable, expression) pairs. The value of expression is written into variable upon execution. - :arg index_ordering: mapping from GEM nodes to an ordering of free - indices + :arg get_indices: mapping from GEM nodes to an ordering of free + indices :returns: list of Impero terminals correctly ordered to evaluate the assignments """ @@ -168,7 +160,7 @@ def emit_operations(assignments, index_ordering): if not isinstance(expression, gem.Zero)] # Prepare reference counts - refcount = count_references([e for v, e in assignments]) + refcount = collect_refcount([e for v, e in assignments]) # Stage return operations staging = [] @@ -181,10 +173,10 @@ def emit_operations(assignments, index_ordering): # Prepare data structures def push_node(node): - queue.insert(index_ordering(node), node) + queue.insert(get_indices(node), node) def push_op(op): - queue.insert(op.loop_shape(index_ordering), op) + queue.insert(op.loop_shape(get_indices), op) ops = [] From 57af164a2e26cb5e771bf771127866f9e58002f1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 16 Feb 2016 16:36:49 +0000 Subject: [PATCH 064/809] a little clean up of driver.py --- tsfc/driver.py | 88 +++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index acd55009fb..2c3d34215e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -5,11 +5,12 @@ import numpy +from ufl.classes import Form from ufl.algorithms import compute_form_data from ufl.log import GREEN from tsfc.fiatinterface import create_element -from tsfc.mixedelement import MixedElement as ffc_MixedElement +from tsfc.mixedelement import MixedElement from tsfc.quadrature import create_quadrature, QuadratureRule import coffee.base as coffee @@ -20,10 +21,33 @@ from tsfc.node import traversal +class Kernel(object): + __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + "coefficient_numbers", "__weakref__") + """A compiled Kernel object. + + :kwarg ast: The COFFEE ast for the kernel. + :kwarg integral_type: The type of integral. + :kwarg oriented: Does the kernel require cell_orientations. + :kwarg subdomain_id: What is the subdomain id for this kernel. + :kwarg coefficient_numbers: A list of which coefficients from the + form the kernel needs. + """ + def __init__(self, ast=None, integral_type=None, oriented=False, + subdomain_id=None, coefficient_numbers=()): + # Defaults + self.ast = ast + self.integral_type = integral_type + self.oriented = oriented + self.subdomain_id = subdomain_id + self.coefficient_numbers = coefficient_numbers + super(Kernel, self).__init__() + + def compile_form(form, prefix="form", parameters=None): cpu_time = time.time() - assert not isinstance(form, (list, tuple)) + assert isinstance(form, Form) if parameters is None: parameters = default_parameters() @@ -41,42 +65,17 @@ def compile_form(form, prefix="form", parameters=None): print GREEN % ("compute_form_data finished in %g seconds." % (time.time() - cpu_time)) kernels = [] - for idata in fd.integral_data: - # TODO: Merge kernel bodies for multiple integrals with same - # integral-data (same mesh iteration space). + for integral_data in fd.integral_data: start = time.time() - kernel = compile_integral(idata, fd, prefix, parameters) + kernel = compile_integral(integral_data, fd, prefix, parameters) if kernel is not None: - print GREEN % ("compile_integral finished in %g seconds." % (time.time() - start)) kernels.append(kernel) + print GREEN % ("compile_integral finished in %g seconds." % (time.time() - start)) print GREEN % ("TSFC finished in %g seconds." % (time.time() - cpu_time)) return kernels -class Kernel(object): - __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", - "coefficient_numbers", "__weakref__") - """A compiled Kernel object. - - :kwarg ast: The COFFEE ast for the kernel. - :kwarg integral_type: The type of integral. - :kwarg oriented: Does the kernel require cell_orientations. - :kwarg subdomain_id: What is the subdomain id for this kernel. - :kwarg coefficient_numbers: A list of which coefficients from the - form the kernel needs. - """ - def __init__(self, ast=None, integral_type=None, oriented=False, - subdomain_id=None, coefficient_numbers=()): - # Defaults - self.ast = ast - self.integral_type = integral_type - self.oriented = oriented - self.subdomain_id = subdomain_id - self.coefficient_numbers = coefficient_numbers - super(Kernel, self).__init__() - - def compile_integral(idata, fd, prefix, parameters): # Remove these here, they're handled below. if parameters.get("quadrature_degree") == "auto": @@ -187,16 +186,16 @@ def compile_integral(idata, fd, prefix, parameters): nonfem = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*nonfem_)) simplified = opt.remove_componenttensors(nonfem) - cell_orientations = False + # Look for cell orientations in the simplified GEM + kernel.oriented = False for node in traversal(simplified): if isinstance(node, gem.Variable) and node.name == "cell_orientations": - cell_orientations = True - - if cell_orientations: - decl = coffee.Decl("int *restrict *restrict", coffee.Symbol("cell_orientations"), - qualifiers=["const"]) - arglist.insert(2, decl) - kernel.oriented = True + kernel.oriented = True + decl = coffee.Decl("int *restrict *restrict", + coffee.Symbol("cell_orientations"), + qualifiers=["const"]) + arglist.insert(2, decl) + break # Collect indices in a deterministic order indices = [] @@ -225,13 +224,14 @@ def compile_integral(idata, fd, prefix, parameters): # Prepare ImperoC (Impero AST + other data for code generation) impero_c = impero_utils.process(ops, get_indices) + # Generate COFFEE body = generate_coffee(impero_c) body.open_scope = False funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) - ast = coffee.FunDecl("void", funname, arglist, coffee.Block(prepare + [body] + finalise), - pred=["static", "inline"]) - kernel.ast = ast + kernel.ast = coffee.FunDecl("void", funname, arglist, + coffee.Block(prepare + [body] + finalise), + pred=["static", "inline"]) return kernel @@ -279,7 +279,7 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): return funarg, [], expression - if not isinstance(fiat_element, ffc_MixedElement): + if not isinstance(fiat_element, MixedElement): # Interior facet integral shape = (2, fiat_element.space_dimension()) @@ -388,7 +388,7 @@ def prepare_arguments(integral_type, arguments): return funarg, [], [expression], [] - if not any(isinstance(element, ffc_MixedElement) for element in elements): + if not any(isinstance(element, MixedElement) for element in elements): # Interior facet integral, but no vector (mixed) arguments shape = [] for element in elements: @@ -427,7 +427,7 @@ def prepare_arguments(integral_type, arguments): restriction_shape = [] for e in elements: - if isinstance(e, ffc_MixedElement): + if isinstance(e, MixedElement): restriction_shape += [len(e.elements()), e.elements()[0].space_dimension()] else: From 6e07694d9c1089d6ed5bf80165f807895baa60e5 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 16 Feb 2016 16:49:22 +0000 Subject: [PATCH 065/809] renames in coffee.py --- tsfc/coffee.py | 94 +++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 2e596489b3..3970d962fa 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -28,7 +28,7 @@ def generate(impero_c): for i, temp in enumerate(impero_c.temporaries): parameters.names[temp] = "t%d" % i - return arabica(impero_c.tree, parameters) + return statement(impero_c.tree, parameters) def _coffee_symbol(symbol, rank=()): @@ -68,21 +68,21 @@ def _ref_symbol(expr, parameters): @singledispatch -def arabica(tree, parameters): +def statement(tree, parameters): raise AssertionError("cannot generate COFFEE from %s" % type(tree)) -@arabica.register(imp.Block) # noqa: Not actually redefinition -def _(tree, parameters): - statements = [arabica(child, parameters) for child in tree.children] +@statement.register(imp.Block) +def statement_block(tree, parameters): + statements = [statement(child, parameters) for child in tree.children] declares = [] for expr in parameters.declare[tree]: declares.append(coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters))) return coffee.Block(declares + statements, open_scope=True) -@arabica.register(imp.For) # noqa: Not actually redefinition -def _(tree, parameters): +@statement.register(imp.For) +def statement_for(tree, parameters): extent = tree.index.extent assert extent i = _coffee_symbol(_index_name(tree.index, parameters)) @@ -90,37 +90,37 @@ def _(tree, parameters): return coffee.For(coffee.Decl("int", i, init=0), coffee.Less(i, extent), coffee.Incr(i, 1), - arabica(tree.children[0], parameters)) + statement(tree.children[0], parameters)) -@arabica.register(imp.Initialise) # noqa: Not actually redefinition -def _(leaf, parameters): +@statement.register(imp.Initialise) +def statement_initialise(leaf, parameters): if parameters.declare[leaf]: return coffee.Decl(SCALAR_TYPE, _decl_symbol(leaf.indexsum, parameters), 0.0) else: return coffee.Assign(_ref_symbol(leaf.indexsum, parameters), 0.0) -@arabica.register(imp.Accumulate) # noqa: Not actually redefinition -def _(leaf, parameters): +@statement.register(imp.Accumulate) +def statement_accumulate(leaf, parameters): return coffee.Incr(_ref_symbol(leaf.indexsum, parameters), expression(leaf.indexsum.children[0], parameters)) -@arabica.register(imp.Return) # noqa: Not actually redefinition -def _(leaf, parameters): +@statement.register(imp.Return) +def statement_return(leaf, parameters): return coffee.Incr(expression(leaf.variable, parameters), expression(leaf.expression, parameters)) -@arabica.register(imp.ReturnAccumulate) # noqa: Not actually redefinition -def _(leaf, parameters): +@statement.register(imp.ReturnAccumulate) +def statement_returnaccumulate(leaf, parameters): return coffee.Incr(expression(leaf.variable, parameters), expression(leaf.indexsum.children[0], parameters)) -@arabica.register(imp.Evaluate) # noqa: Not actually redefinition -def _(leaf, parameters): +@statement.register(imp.Evaluate) +def statement_evaluate(leaf, parameters): expr = leaf.expression if isinstance(expr, ein.ListTensor): # TODO: remove constant float branch. @@ -160,47 +160,47 @@ def expression(expr, parameters, top=False): if not top and expr in parameters.names: return _ref_symbol(expr, parameters) else: - return handle(expr, parameters) + return _expression(expr, parameters) @singledispatch -def handle(expr, parameters): +def _expression(expr, parameters): raise AssertionError("cannot generate COFFEE from %s" % type(expr)) -@handle.register(ein.Product) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.Product) +def _expression_product(expr, parameters): return coffee.Prod(*[expression(c, parameters) for c in expr.children]) -@handle.register(ein.Sum) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.Sum) +def _expression_sum(expr, parameters): return coffee.Sum(*[expression(c, parameters) for c in expr.children]) -@handle.register(ein.Division) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.Division) +def _expression_division(expr, parameters): return coffee.Div(*[expression(c, parameters) for c in expr.children]) -@handle.register(ein.Power) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.Power) +def _expression_power(expr, parameters): base, exponent = expr.children return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) -@handle.register(ein.MathFunction) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.MathFunction) +def _expression_mathfunction(expr, parameters): name_map = {'abs': 'fabs', 'ln': 'log'} name = name_map.get(expr.name, expr.name) return coffee.FunCall(name, expression(expr.children[0], parameters)) -@handle.register(ein.Comparison) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.Comparison) +def _expression_comparison(expr, parameters): type_map = {">": coffee.Greater, ">=": coffee.GreaterEq, "==": coffee.Eq, @@ -210,29 +210,29 @@ def _(expr, parameters): return type_map[expr.operator](*[expression(c, parameters) for c in expr.children]) -@handle.register(ein.LogicalNot) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.LogicalNot) +def _expression_logicalnot(expr, parameters): return coffee.Not(*[expression(c, parameters) for c in expr.children]) -@handle.register(ein.LogicalAnd) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.LogicalAnd) +def _expression_logicaland(expr, parameters): return coffee.And(*[expression(c, parameters) for c in expr.children]) -@handle.register(ein.LogicalOr) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.LogicalOr) +def _expression_logicalor(expr, parameters): return coffee.Or(*[expression(c, parameters) for c in expr.children]) -@handle.register(ein.Conditional) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.Conditional) +def _expression_conditional(expr, parameters): return coffee.Ternary(*[expression(c, parameters) for c in expr.children]) -@handle.register(ein.Literal) # noqa: Not actually redefinition -@handle.register(ein.Zero) -def _(expr, parameters): +@_expression.register(ein.Literal) +@_expression.register(ein.Zero) +def _expression_scalar(expr, parameters): assert not expr.shape if isnan(expr.value): return coffee.Symbol("NAN") @@ -240,13 +240,13 @@ def _(expr, parameters): return coffee.Symbol(("%%.%dg" % (PRECISION - 1)) % expr.value) -@handle.register(ein.Variable) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.Variable) +def _expression_variable(expr, parameters): return _coffee_symbol(expr.name) -@handle.register(ein.Indexed) # noqa: Not actually redefinition -def _(expr, parameters): +@_expression.register(ein.Indexed) +def _expression_indexed(expr, parameters): rank = [] for index in expr.multiindex: if isinstance(index, ein.Index): From d22d3f22435de2ca63d4ae273977ee853c4381aa Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 16 Feb 2016 17:35:02 +0000 Subject: [PATCH 066/809] a little clean up in coffee.py --- tsfc/coffee.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 3970d962fa..163da78b3a 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -10,7 +10,7 @@ import coffee.base as coffee from tsfc import gem as ein, impero as imp -from tsfc.constants import NUMPY_TYPE, SCALAR_TYPE, PRECISION +from tsfc.constants import SCALAR_TYPE, PRECISION class Bunch(object): @@ -49,9 +49,7 @@ def _coffee_symbol(symbol, rank=()): def _decl_symbol(expr, parameters): multiindex = parameters.indices[expr] - rank = tuple(index.extent for index in multiindex) - if hasattr(expr, 'shape'): - rank += expr.shape + rank = tuple(index.extent for index in multiindex) + expr.shape return _coffee_symbol(parameters.names[expr], rank=rank) @@ -123,19 +121,12 @@ def statement_returnaccumulate(leaf, parameters): def statement_evaluate(leaf, parameters): expr = leaf.expression if isinstance(expr, ein.ListTensor): - # TODO: remove constant float branch. if parameters.declare[leaf]: - values = numpy.array([expression(v, parameters) for v in expr.array.flat], dtype=object) - if all(isinstance(value, float) for value in values): - qualifiers = ["static", "const"] - values = numpy.array(values, dtype=NUMPY_TYPE) - else: - qualifiers = [] - values = values.reshape(expr.shape) + array_expression = numpy.vectorize(lambda v: expression(v, parameters)) return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), - coffee.ArrayInit(values, precision=PRECISION), - qualifiers=qualifiers) + coffee.ArrayInit(array_expression(expr.array), + precision=PRECISION)) else: ops = [] for multiindex, value in numpy.ndenumerate(expr.array): From dc9bf6a8e6d4039d76403055a84867e6762a0871 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 16 Feb 2016 17:28:16 +0000 Subject: [PATCH 067/809] Backwards compat for numpy versions < 1.10 equal_nan keyword argument to allclose only appeared in 1.10. --- tsfc/compat.py | 25 +++++++++++++++++++++++++ tsfc/fem.py | 5 +++-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tsfc/compat.py diff --git a/tsfc/compat.py b/tsfc/compat.py new file mode 100644 index 0000000000..b41be73e52 --- /dev/null +++ b/tsfc/compat.py @@ -0,0 +1,25 @@ +""" +Backwards compatibility for some functionality. +""" +import numpy +from distutils.version import StrictVersion + + +if StrictVersion(numpy.__version__) < StrictVersion("1.10"): + def allclose(a, b, rtol=1e-5, atol=1e-8, equal_nan=False): + """Wrapper around ``numpy.allclose``, which see. + + If ``equal_nan`` is ``True``, consider nan values to be equal + in comparisons. + """ + if not equal_nan: + return numpy.allclose(a, b, rtol=rtol, atol=atol) + nana = numpy.isnan(a) + if not nana.any(): + return numpy.allclose(a, b, rtol=rtol, atol=atol) + nanb = numpy.isnan(b) + equal_nan = numpy.allclose(nana, nanb) + return equal_nan and numpy.allclose(a[~nana], b[~nanb], + rtol=rtol, atol=atol) +else: + allclose = numpy.allclose diff --git a/tsfc/fem.py b/tsfc/fem.py index 7f770d3901..410efc8d62 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -19,6 +19,7 @@ from tsfc.fiatinterface import create_element, as_fiat_cell from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal from tsfc.node import MemoizerArg +from tsfc import compat from tsfc import gem from tsfc import ufl2gem from tsfc import geometric @@ -301,7 +302,7 @@ def tabulate(ufl_element, order, points): table[abs(table + 0.5) < epsilon] = -0.5 if spanning_degree(ufl_element) <= sum(D): - assert numpy.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) + assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) table = table[0] yield c, D, table @@ -358,7 +359,7 @@ def tabulate(self, ufl_element, max_deriv): table = numpy.array(tables) if len(table.shape) == 2: # Cellwise constant; must not depend on the facet - assert numpy.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) + assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) table = table[0] self.tables[key] = table From f4165b08473f218b4f8614566f65721e6a28ef7e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 16 Feb 2016 18:44:45 +0000 Subject: [PATCH 068/809] restore old index naming in code generation --- tsfc/coffee.py | 26 +++++++++++--------------- tsfc/driver.py | 9 ++++++++- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 163da78b3a..427c517b56 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -1,11 +1,11 @@ from __future__ import absolute_import +from collections import defaultdict from math import isnan +import itertools import numpy from singledispatch import singledispatch -from collections import defaultdict -import itertools import coffee.base as coffee @@ -17,17 +17,19 @@ class Bunch(object): pass -def generate(impero_c): +def generate(impero_c, index_names): parameters = Bunch() parameters.declare = impero_c.declare parameters.indices = impero_c.indices - parameters.names = {} - counter = itertools.count() - parameters.index_names = defaultdict(lambda: "i_%d" % next(counter)) + parameters.names = {} for i, temp in enumerate(impero_c.temporaries): parameters.names[temp] = "t%d" % i + counter = itertools.count() + parameters.index_names = defaultdict(lambda: "i_%d" % next(counter)) + parameters.index_names.update(index_names) + return statement(impero_c.tree, parameters) @@ -53,15 +55,9 @@ def _decl_symbol(expr, parameters): return _coffee_symbol(parameters.names[expr], rank=rank) -def _index_name(index, parameters): - if index.name is None: - return parameters.index_names[index] - return index.name - - def _ref_symbol(expr, parameters): multiindex = parameters.indices[expr] - rank = tuple(_index_name(index, parameters) for index in multiindex) + rank = tuple(parameters.index_names[index] for index in multiindex) return _coffee_symbol(parameters.names[expr], rank=tuple(rank)) @@ -83,7 +79,7 @@ def statement_block(tree, parameters): def statement_for(tree, parameters): extent = tree.index.extent assert extent - i = _coffee_symbol(_index_name(tree.index, parameters)) + i = _coffee_symbol(parameters.index_names[tree.index]) # TODO: symbolic constant for "int" return coffee.For(coffee.Decl("int", i, init=0), coffee.Less(i, extent), @@ -241,7 +237,7 @@ def _expression_indexed(expr, parameters): rank = [] for index in expr.multiindex: if isinstance(index, ein.Index): - rank.append(_index_name(index, parameters)) + rank.append(parameters.index_names[index]) elif isinstance(index, ein.VariableIndex): rank.append(index.name) else: diff --git a/tsfc/driver.py b/tsfc/driver.py index 2c3d34215e..7ebf1fc837 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -225,7 +225,14 @@ def compile_integral(idata, fd, prefix, parameters): impero_c = impero_utils.process(ops, get_indices) # Generate COFFEE - body = generate_coffee(impero_c) + index_names = zip(argument_indices, ['j', 'k']) + if len(quadrature_indices) == 1: + index_names.append((quadrature_indices[0], 'ip')) + else: + for i, quadrature_index in enumerate(quadrature_indices): + index_names.append((quadrature_index, 'ip_%d' % i)) + + body = generate_coffee(impero_c, index_names) body.open_scope = False funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) From c475710ef390a6e0bf228ab36a6c3e64744c9fc2 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 16 Feb 2016 18:46:21 +0000 Subject: [PATCH 069/809] Update idempotency tests to hit equal_nan allclose call --- tests/test_idempotency.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/test_idempotency.py b/tests/test_idempotency.py index 5a4eed78cd..7656592e2b 100644 --- a/tests/test_idempotency.py +++ b/tests/test_idempotency.py @@ -12,9 +12,15 @@ def cell(request): return request.param +@pytest.fixture(params=[1, 2], + ids=lambda x: "P%d-coords" % x) +def coord_degree(request): + return request.param + + @pytest.fixture -def mesh(cell): - c = ufl.VectorElement("CG", cell, 2) +def mesh(cell, coord_degree): + c = ufl.VectorElement("CG", cell, coord_degree) return ufl.Mesh(c) @@ -26,8 +32,13 @@ def V(request, mesh): return ufl.FunctionSpace(mesh, request.param("CG", mesh.ufl_cell(), 2)) +@pytest.fixture(params=["cell", "ext_facet", "int_facet"]) +def itype(request): + return request.param + + @pytest.fixture(params=["functional", "1-form", "2-form"]) -def form(V, request): +def form(V, itype, request): if request.param == "functional": u = ufl.Coefficient(V) v = ufl.Coefficient(V) @@ -38,7 +49,12 @@ def form(V, request): u = ufl.TrialFunction(V) v = ufl.TestFunction(V) - return ufl.inner(u, v)*ufl.dx + if itype == "cell": + return ufl.inner(u, v)*ufl.dx + elif itype == "ext_facet": + return ufl.inner(u, v)*ufl.ds + elif itype == "int_facet": + return ufl.inner(u('+'), v('-'))*ufl.dS def test_idempotency(form): From 5330f9d3204cfd9cd5050334c5ea37bc1a5ebf37 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 16 Feb 2016 18:47:58 +0000 Subject: [PATCH 070/809] add docstrings in coffee.py --- tsfc/coffee.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 427c517b56..e13504f0ec 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -1,3 +1,7 @@ +"""Generate COFFEE AST from ImperoC tuple data. + +This is the final stage of code generation in TSFC.""" + from __future__ import absolute_import from collections import defaultdict @@ -18,6 +22,12 @@ class Bunch(object): def generate(impero_c, index_names): + """Generates COFFEE code. + + :arg impero_c: ImperoC tuple with Impero AST and other data + :arg index_names: pre-assigned index names + :returns: COFFEE function body + """ parameters = Bunch() parameters.declare = impero_c.declare parameters.indices = impero_c.indices @@ -50,12 +60,14 @@ def _coffee_symbol(symbol, rank=()): def _decl_symbol(expr, parameters): + """Build a COFFEE Symbol for declaration.""" multiindex = parameters.indices[expr] rank = tuple(index.extent for index in multiindex) + expr.shape return _coffee_symbol(parameters.names[expr], rank=rank) def _ref_symbol(expr, parameters): + """Build a COFFEE Symbol for referencing a value.""" multiindex = parameters.indices[expr] rank = tuple(parameters.index_names[index] for index in multiindex) return _coffee_symbol(parameters.names[expr], rank=tuple(rank)) @@ -63,6 +75,13 @@ def _ref_symbol(expr, parameters): @singledispatch def statement(tree, parameters): + """Translates an Impero (sub)tree into a COFFEE AST corresponding + to a C statement. + + :arg tree: Impero (sub)tree + :arg parameters: miscellaneous code generation data + :returns: COFFEE AST + """ raise AssertionError("cannot generate COFFEE from %s" % type(tree)) @@ -144,6 +163,14 @@ def statement_evaluate(leaf, parameters): def expression(expr, parameters, top=False): + """Translates GEM expression into a COFFEE snippet, stopping at + temporaries. + + :arg expr: GEM expression + :arg parameters: miscellaneous code generation data + :arg top: do not generate temporary reference for the root node + :returns: COFFEE expression + """ if not top and expr in parameters.names: return _ref_symbol(expr, parameters) else: From 1c12ba66238faea7ee4553e8488d89a07ade0e12 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 17 Feb 2016 13:16:53 +0000 Subject: [PATCH 071/809] fixes for PR comments --- tsfc/driver.py | 5 ++++- tsfc/impero.py | 4 ++-- tsfc/impero_utils.py | 18 +++++++++++++----- tsfc/scheduling.py | 22 +++++++++++----------- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7ebf1fc837..58b564e850 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -202,8 +202,11 @@ def compile_integral(idata, fd, prefix, parameters): for node in traversal(simplified): if isinstance(node, gem.Indexed): indices.extend(node.multiindex) + # The next two lines remove duplicate elements from the list, but + # preserve the ordering, i.e. all elements will appear only once, + # in the order of their first occurance in the original list. _, unique_indices = numpy.unique(indices, return_index=True) - indices = numpy.asarray(indices)[sorted(unique_indices)] + indices = numpy.asarray(indices)[numpy.sort(unique_indices)] # Build ordered index map index_ordering = make_prefix_ordering(indices, tuple(quadrature_indices) + argument_indices) diff --git a/tsfc/impero.py b/tsfc/impero.py index ea01e50bf3..f954b80b0c 100644 --- a/tsfc/impero.py +++ b/tsfc/impero.py @@ -37,8 +37,8 @@ def loop_shape(self, free_indices): """Gives the loop shape, an ordering of indices for an Impero terminal. - :arg free_indices: a mapping of GEM expressions to ordered - free indices. + :arg free_indices: a callable mapping of GEM expressions to + ordered free indices. """ pass diff --git a/tsfc/impero_utils.py b/tsfc/impero_utils.py index e406d29523..e93a1bbf46 100644 --- a/tsfc/impero_utils.py +++ b/tsfc/impero_utils.py @@ -60,13 +60,21 @@ def inline_temporaries(expressions, ops, coffee_licm=False): def process(ops, get_indices): + """Process the scheduling of operations, builds an Impero AST + + other data which are directly used for C / COFFEE code generation. + + :arg ops: a list of Impero terminal nodes + :arg get_indices: callable mapping from GEM nodes to an ordering + of free indices + :returns: ImperoC named tuple + """ # Build Impero AST tree = make_loop_tree(ops, get_indices) # Collect temporaries temporaries = collect_temporaries(ops) - # Determine declarations (incomplete) + # Determine declarations declare, indices = place_declarations(ops, tree, temporaries, get_indices) return ImperoC(tree, temporaries, declare, indices) @@ -96,8 +104,8 @@ def make_loop_tree(ops, get_indices, level=0): their respective free indices. :arg ops: a list of Impero terminal nodes - :arg get_indices: mapping from GEM nodes to an ordering of free - indices + :arg get_indices: callable mapping from GEM nodes to an ordering + of free indices :arg level: depth of loop nesting :returns: Impero AST with loops, without declarations """ @@ -119,8 +127,8 @@ def place_declarations(ops, tree, temporaries, get_indices): :arg tree: Impero AST to determine the declarations for :arg temporaries: list of GEM expressions which are assigned to temporaries - :arg get_indices: mapping from GEM nodes to an ordering of free - indices + :arg get_indices: callable mapping from GEM nodes to an ordering + of free indices """ temporaries_set = set(temporaries) diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py index 7e07d4d982..28e13804e1 100644 --- a/tsfc/scheduling.py +++ b/tsfc/scheduling.py @@ -43,10 +43,10 @@ def __init__(self, reference_count, callback): self.waiting = reference_count.copy() self.callback = callback - def reference(self, o): - """References a node, decreasing its reference count, and - possibly triggering a callback (when the reference count - becomes zero).""" + def decref(self, o): + """Decreases the reference count of a node, and possibly + triggering a callback (when the reference count drops to + zero).""" assert 1 <= self.waiting[o] self.waiting[o] -= 1 @@ -106,7 +106,7 @@ def process(self): del self.queue[indices] -def handle(ops, push, ref, node): +def handle(ops, push, decref, node): """Helper function for scheduling""" if isinstance(node, gem.Variable): # Declared in the kernel header @@ -119,25 +119,25 @@ def handle(ops, push, ref, node): assert not node.shape elif isinstance(node, gem.Indexed): # Indexing always inlined - ref(node.children[0]) + decref(node.children[0]) elif isinstance(node, gem.IndexSum): push(impero.Accumulate(node)) elif isinstance(node, gem.Node): ops.append(impero.Evaluate(node)) for child in node.children: - ref(child) + decref(child) elif isinstance(node, impero.Initialise): ops.append(node) elif isinstance(node, impero.Accumulate): ops.append(node) push(impero.Initialise(node.indexsum)) - ref(node.indexsum.children[0]) + decref(node.indexsum.children[0]) elif isinstance(node, impero.Return): ops.append(node) - ref(node.expression) + decref(node.expression) elif isinstance(node, impero.ReturnAccumulate): ops.append(node) - ref(node.indexsum.children[0]) + decref(node.indexsum.children[0]) else: raise AssertionError("no handler for node type %s" % type(node)) @@ -181,7 +181,7 @@ def push_op(op): ops = [] stager = ReferenceStager(refcount, push_node) - queue = Queue(functools.partial(handle, ops, push_op, stager.reference)) + queue = Queue(functools.partial(handle, ops, push_op, stager.decref)) # Enqueue return operations for op in staging: From 3c5772197045690651f141d1b63a3bbd683175bb Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 17 Feb 2016 15:29:13 +0000 Subject: [PATCH 072/809] rename: ein. -> gem. --- tsfc/coffee.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index e13504f0ec..1f11057e68 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -13,7 +13,7 @@ import coffee.base as coffee -from tsfc import gem as ein, impero as imp +from tsfc import gem, impero as imp from tsfc.constants import SCALAR_TYPE, PRECISION @@ -135,7 +135,7 @@ def statement_returnaccumulate(leaf, parameters): @statement.register(imp.Evaluate) def statement_evaluate(leaf, parameters): expr = leaf.expression - if isinstance(expr, ein.ListTensor): + if isinstance(expr, gem.ListTensor): if parameters.declare[leaf]: array_expression = numpy.vectorize(lambda v: expression(v, parameters)) return coffee.Decl(SCALAR_TYPE, @@ -148,7 +148,7 @@ def statement_evaluate(leaf, parameters): coffee_sym = _coffee_symbol(_ref_symbol(expr, parameters), rank=multiindex) ops.append(coffee.Assign(coffee_sym, expression(value, parameters))) return coffee.Block(ops, open_scope=False) - elif isinstance(expr, ein.Literal): + elif isinstance(expr, gem.Literal): assert parameters.declare[leaf] return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), @@ -182,38 +182,38 @@ def _expression(expr, parameters): raise AssertionError("cannot generate COFFEE from %s" % type(expr)) -@_expression.register(ein.Product) +@_expression.register(gem.Product) def _expression_product(expr, parameters): return coffee.Prod(*[expression(c, parameters) for c in expr.children]) -@_expression.register(ein.Sum) +@_expression.register(gem.Sum) def _expression_sum(expr, parameters): return coffee.Sum(*[expression(c, parameters) for c in expr.children]) -@_expression.register(ein.Division) +@_expression.register(gem.Division) def _expression_division(expr, parameters): return coffee.Div(*[expression(c, parameters) for c in expr.children]) -@_expression.register(ein.Power) +@_expression.register(gem.Power) def _expression_power(expr, parameters): base, exponent = expr.children return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) -@_expression.register(ein.MathFunction) +@_expression.register(gem.MathFunction) def _expression_mathfunction(expr, parameters): name_map = {'abs': 'fabs', 'ln': 'log'} name = name_map.get(expr.name, expr.name) return coffee.FunCall(name, expression(expr.children[0], parameters)) -@_expression.register(ein.Comparison) +@_expression.register(gem.Comparison) def _expression_comparison(expr, parameters): type_map = {">": coffee.Greater, ">=": coffee.GreaterEq, @@ -224,28 +224,28 @@ def _expression_comparison(expr, parameters): return type_map[expr.operator](*[expression(c, parameters) for c in expr.children]) -@_expression.register(ein.LogicalNot) +@_expression.register(gem.LogicalNot) def _expression_logicalnot(expr, parameters): return coffee.Not(*[expression(c, parameters) for c in expr.children]) -@_expression.register(ein.LogicalAnd) +@_expression.register(gem.LogicalAnd) def _expression_logicaland(expr, parameters): return coffee.And(*[expression(c, parameters) for c in expr.children]) -@_expression.register(ein.LogicalOr) +@_expression.register(gem.LogicalOr) def _expression_logicalor(expr, parameters): return coffee.Or(*[expression(c, parameters) for c in expr.children]) -@_expression.register(ein.Conditional) +@_expression.register(gem.Conditional) def _expression_conditional(expr, parameters): return coffee.Ternary(*[expression(c, parameters) for c in expr.children]) -@_expression.register(ein.Literal) -@_expression.register(ein.Zero) +@_expression.register(gem.Literal) +@_expression.register(gem.Zero) def _expression_scalar(expr, parameters): assert not expr.shape if isnan(expr.value): @@ -254,18 +254,18 @@ def _expression_scalar(expr, parameters): return coffee.Symbol(("%%.%dg" % (PRECISION - 1)) % expr.value) -@_expression.register(ein.Variable) +@_expression.register(gem.Variable) def _expression_variable(expr, parameters): return _coffee_symbol(expr.name) -@_expression.register(ein.Indexed) +@_expression.register(gem.Indexed) def _expression_indexed(expr, parameters): rank = [] for index in expr.multiindex: - if isinstance(index, ein.Index): + if isinstance(index, gem.Index): rank.append(parameters.index_names[index]) - elif isinstance(index, ein.VariableIndex): + elif isinstance(index, gem.VariableIndex): rank.append(index.name) else: rank.append(index) From 41efa10e58adb3528b15245ba0dfa5a784e90de8 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 17 Feb 2016 15:35:42 +0000 Subject: [PATCH 073/809] no need for RESTRICTION_MAP --- tsfc/constants.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tsfc/constants.py b/tsfc/constants.py index e86915cb7d..c3216f12e2 100644 --- a/tsfc/constants.py +++ b/tsfc/constants.py @@ -11,8 +11,6 @@ numpy.dtype("float32"): "float"}[NUMPY_TYPE] -RESTRICTION_MAP = {"+": 0, "-": 1} - PARAMETERS = { "quadrature_rule": "auto", "quadrature_degree": "auto" From 32774dd55bfca63d6b2396d8de3886cdefacc24c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 17 Feb 2016 17:47:52 +0000 Subject: [PATCH 074/809] fix inlining for IndexSums --- tsfc/impero_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/impero_utils.py b/tsfc/impero_utils.py index e93a1bbf46..d36884acf7 100644 --- a/tsfc/impero_utils.py +++ b/tsfc/impero_utils.py @@ -52,7 +52,7 @@ def inline_temporaries(expressions, ops, coffee_licm=False): # Prevent inlining that pulls expressions into inner loops for node in traversal(expressions): for child in node.children: - if child in candidates and set(child.free_indices) != set(node.free_indices): + if child in candidates and set(child.free_indices) < set(node.free_indices): candidates.remove(child) # Filter out candidates From 509f854712a2a7056e876c4bf82df6f7a7d2e1fd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 18 Feb 2016 11:29:51 +0000 Subject: [PATCH 075/809] module docstring for optimise.py --- tsfc/optimise.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsfc/optimise.py b/tsfc/optimise.py index 7610af21e4..6a8b449d4f 100644 --- a/tsfc/optimise.py +++ b/tsfc/optimise.py @@ -1,3 +1,6 @@ +"""A set of routines implementing various transformations on GEM +expressions.""" + from __future__ import absolute_import from singledispatch import singledispatch From 11e775bccc25e408845728b05ce15c3f10e7921b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 18 Feb 2016 12:07:58 +0000 Subject: [PATCH 076/809] make gem.VariableIndex wrap a GEM expression --- tsfc/coffee.py | 2 +- tsfc/fem.py | 6 +++--- tsfc/gem.py | 19 +++++++++++++------ tsfc/interpreter.py | 13 ++++++------- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 1f11057e68..29d2ada56c 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -266,7 +266,7 @@ def _expression_indexed(expr, parameters): if isinstance(index, gem.Index): rank.append(parameters.index_names[index]) elif isinstance(index, gem.VariableIndex): - rank.append(index.name) + rank.append(expression(index.expression, parameters).gencode()) else: rank.append(index) return _coffee_symbol(expression(expr.children[0], parameters), diff --git a/tsfc/fem.py b/tsfc/fem.py index 410efc8d62..bdde002ca5 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -384,10 +384,10 @@ def __init__(self, weights, quadrature_index, argument_indices, tabulation_manag self.index_cache = index_cache if integral_type in ['exterior_facet', 'exterior_facet_vert']: - self.facet = {None: gem.VariableIndex('facet[0]')} + self.facet = {None: gem.VariableIndex(gem.Indexed(gem.Variable('facet', (1,)), (0,)))} elif integral_type in ['interior_facet', 'interior_facet_vert']: - self.facet = {'+': gem.VariableIndex('facet[0]'), - '-': gem.VariableIndex('facet[1]')} + self.facet = {'+': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (0,))), + '-': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (1,)))} elif integral_type == 'exterior_facet_bottom': self.facet = {None: 0} elif integral_type == 'exterior_facet_top': diff --git a/tsfc/gem.py b/tsfc/gem.py index b6c33de18c..38798a1a7a 100644 --- a/tsfc/gem.py +++ b/tsfc/gem.py @@ -334,20 +334,27 @@ def __repr__(self): class VariableIndex(object): - def __init__(self, name): - self.name = name + """An index that is constant during a single execution of the + kernel, but whose value is not known at compile time.""" + + def __init__(self, expression): + assert isinstance(expression, Node) + assert not expression.free_indices + assert not expression.shape + self.expression = expression def __eq__(self, other): - """VariableIndices are equal if their names are equal.""" if self is other: return True if type(self) is not type(other): return False - return self.name == other.name + return self.expression == other.expression + + def __ne__(self, other): + return not self.__eq__(other) def __hash__(self): - """VariableIndices hash to the hash of their name.""" - return hash(self.name) + return hash((VariableIndex, self.expression)) class Indexed(Scalar): diff --git a/tsfc/interpreter.py b/tsfc/interpreter.py index 1a556cfea2..44bc3a3ec0 100644 --- a/tsfc/interpreter.py +++ b/tsfc/interpreter.py @@ -238,11 +238,10 @@ def _(e, self): # Free index, want entire extent idx.append(Ellipsis) elif isinstance(i, gem.VariableIndex): - try: - # Variable indices must be provided in bindings - idx.append(self.bindings[i]) - except KeyError: - raise ValueError("Binding for %s not found" % i) + # Variable index, evaluate inner expression + result, = evaluate(i.expression, self.bindings) + assert not result.tshape + idx.append(result[()]) else: # Fixed index, just pick that value idx.append(i) @@ -293,8 +292,8 @@ def evaluate(expressions, bindings=None): :arg expressions: A single GEM expression, or iterable of expressions to evaluate. - :kwarg bindings: An optional dict mapping GEM :class:`gem.Variable` and - :class:`gem.VariableIndex` nodes to data. + :kwarg bindings: An optional dict mapping GEM :class:`gem.Variable` + nodes to data. :returns: a list of the evaluated expressions. """ try: From d98cf570299da2f117715729728ce497658b6d4e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 18 Feb 2016 13:30:45 +0000 Subject: [PATCH 077/809] comment on ufl2gem.py --- tsfc/ufl2gem.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index d8addd0d6e..352ab6e9d4 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -1,3 +1,5 @@ +"""Translation of UFL tensor-algebra into GEM tensor-algebra.""" + from __future__ import absolute_import import collections @@ -12,8 +14,15 @@ class Mixin(object): + """A mixin to be used with a UFL MultiFunction to translate UFL + algebra into GEM tensor-algebra. This node types translate pretty + straightforwardly to GEM. Other node types are not handled in + this mixin.""" + def __init__(self): self.index_map = collections.defaultdict(Index) + """A map for translating UFL free indices into GEM free + indices.""" def scalar_value(self, o): return Literal(o.value()) @@ -92,10 +101,27 @@ def indexed(self, o, aggregate, index): return Indexed(aggregate, index) def list_tensor(self, o, *ops): + # UFL and GEM have a few semantical differences when it comes + # to ListTensor. In UFL, a ListTensor increases the rank by + # one with respect to its constituents. So to build a matrix + # from scalars, one must build a ListTensor of ListTensors in + # UFL, while a GEM ListTensor can directly construct rank two + # tensor from scalars because it has an explicitly specified + # shape. nesting = [isinstance(op, ListTensor) for op in ops] if all(nesting): + # ListTensor of ListTensors in UFL, build a ListTensor of + # higher rank in GEM. return ListTensor(numpy.array([op.array for op in ops])) elif len(o.ufl_shape) > 1: + # On the other hand, TSFC only builds ListTensors from + # scalars, while the constituents of a UFL ListTensor can + # be non-scalar despite not being ListTensors themselves + # (e.g. ComponentTensor, Zero). + # + # In this case we currently break up the tensor-valued + # constituents into scalars by generating fixed indices to + # access to every single element. children = [] for op in ops: child = numpy.zeros(o.ufl_shape[1:], dtype=object) @@ -110,6 +136,8 @@ def component_tensor(self, o, expression, index): return ComponentTensor(expression, index) def index_sum(self, o, summand, indices): + # ufl.IndexSum technically has a MultiIndex, but it must have + # exactly one index in it. index, = indices if o.ufl_shape: @@ -119,9 +147,10 @@ def index_sum(self, o, summand, indices): return IndexSum(summand, index) def variable(self, o, expression, label): - """Only used by UFL AD, at this point, the bare expression is what we want.""" + # Only used by UFL AD, at this point, the bare expression is + # what we want. return expression def label(self, o): - """Only used by UFL AD, don't need it at this point.""" + # Only used by UFL AD, don't need it at this point. pass From 8163dc951bfddb36d302b77e522f018011758710 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 18 Feb 2016 14:23:40 +0000 Subject: [PATCH 078/809] add docstrings in driver.py and fem.py --- tsfc/driver.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ tsfc/fem.py | 24 +++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 58b564e850..c80040f39e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -45,6 +45,13 @@ def __init__(self, ast=None, integral_type=None, oriented=False, def compile_form(form, prefix="form", parameters=None): + """Compiles a UFL form into a set of assembly kernels. + + :arg form: UFL form + :arg prefix: kernel name will start with this string + :arg parameters: parameters object + :returns: list of kernels + """ cpu_time = time.time() assert isinstance(form, Form) @@ -77,6 +84,14 @@ def compile_form(form, prefix="form", parameters=None): def compile_integral(idata, fd, prefix, parameters): + """Compiles a UFL integral into an assembly kernel. + + :arg idata: UFL integral data + :arg fd: UFL form data + :arg prefix: kernel name will start with this string + :arg parameters: parameters object + :returns: a kernel, or None if the integral simplifies to zero + """ # Remove these here, they're handled below. if parameters.get("quadrature_degree") == "auto": del parameters["quadrature_degree"] @@ -254,6 +269,22 @@ def is_mesh_affine(mesh): def prepare_coefficient(integral_type, coefficient, name, mode=None): + """Bridges the kernel interface and the GEM abstraction for + Coefficients. Mixed element Coefficients are rearranged here for + interior facet integrals. + + :arg integral_type: integral type + :arg coefficient: UFL Coefficient + :arg name: unique name to refer to the Coefficient in the kernel + :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with + interior facet integrals on mixed elements + :returns: (funarg, prepare, expression) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expression - GEM expression referring to the Coefficient + values + """ if mode is None: mode = 'manual_loop' @@ -377,6 +408,20 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): def prepare_arguments(integral_type, arguments): + """Bridges the kernel interface and the GEM abstraction for + Arguments. Vector Arguments are rearranged here for interior + facet integrals. + + :arg integral_type: integral type + :arg arguments: UFL Arguments + :returns: (funarg, prepare, expression, finalise) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expression - GEM expression referring to the argument tensor + finalise - list of COFFEE nodes to be appended to the + kernel body + """ from itertools import chain, product if len(arguments) == 0: @@ -462,6 +507,13 @@ def prepare_arguments(integral_type, arguments): def coffee_for(index, extent, body): + """Helper function to make a COFFEE loop. + + :arg index: :class:`coffee.Symbol` loop index + :arg extent: loop extent (integer) + :arg body: loop body (COFFEE node) + :returns: COFFEE loop + """ return coffee.For(coffee.Decl("int", index, init=0), coffee.Less(index, extent), coffee.Incr(index, 1), @@ -469,11 +521,15 @@ def coffee_for(index, extent, body): def make_prefix_ordering(indices, prefix_ordering): + """Creates an ordering of ``indices`` which starts with those + indices in ``prefix_ordering``.""" # Need to return deterministically ordered indices return tuple(prefix_ordering) + tuple(k for k in indices if k not in prefix_ordering) def make_index_orderer(index_ordering): + """Returns a function which given a set of indices returns those + indices in the order as they appear in ``index_ordering``.""" idx2pos = {idx: pos for pos, idx in enumerate(index_ordering)} def apply_ordering(indices): diff --git a/tsfc/fem.py b/tsfc/fem.py index bdde002ca5..98a4fcf041 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -318,6 +318,13 @@ class TabulationManager(object): integral types.""" def __init__(self, integral_type, cell, points): + """Constructs a TabulationManager. + + :arg integral_type: integral type + :arg cell: UFL cell + :arg points: points on the integration entity (e.g. points on + an interval for facet integrals on a triangle) + """ self.integral_type = integral_type self.points = points @@ -346,6 +353,12 @@ def __init__(self, integral_type, cell, points): raise NotImplementedError("integral type %s not supported" % integral_type) def tabulate(self, ufl_element, max_deriv): + """Prepare the tabulations of a finite element up to a given + derivative order. + + :arg ufl_element: UFL element to tabulate + :arg max_deriv: tabulate derivatives up this order + """ store = collections.defaultdict(list) for tabulator in self.tabulators: for c, D, table in tabulator(ufl_element, max_deriv): @@ -403,6 +416,12 @@ def __init__(self, weights, quadrature_index, argument_indices, tabulation_manag self.cell_orientations = gem.Variable("cell_orientations", (1, 1)) def select_facet(self, tensor, restriction): + """Applies facet selection on a GEM tensor if necessary. + + :arg tensor: GEM tensor + :arg restriction: restriction on the modified terminal + :returns: another GEM tensor + """ if self.integral_type == 'cell': return tensor else: @@ -410,6 +429,8 @@ def select_facet(self, tensor, restriction): return gem.partial_indexed(tensor, (f,)) def modified_terminal(self, o): + """Overrides the modified terminal handler from + :class:`ModifiedTerminalMixin`.""" mt = analyse_modified_terminal(o) return translate(mt.terminal, mt, self) @@ -522,11 +543,12 @@ def callback(key): def coordinate_coefficient(domain): + """Create a fake coordinate coefficient for a domain.""" return ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) def replace_coordinates(integrand, coordinate_coefficient): - # Replace SpatialCoordinate nodes with Coefficients + """Replace SpatialCoordinate nodes with Coefficients.""" return map_expr_dag(ReplaceSpatialCoordinates(coordinate_coefficient), integrand) From d03e16391d894c17306dbdc27deb813c3fb94b98 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 18 Feb 2016 15:17:32 +0000 Subject: [PATCH 079/809] make optimisations configurable Options: - coffee_licm: Trust COFFEE to do LICM (default: False) - unroll_indexsum: Maximum extent to unroll index sums Use None, False or 0 to disable. Default: 3 --- tsfc/constants.py | 4 +++- tsfc/driver.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tsfc/constants.py b/tsfc/constants.py index c3216f12e2..07311c114b 100644 --- a/tsfc/constants.py +++ b/tsfc/constants.py @@ -13,7 +13,9 @@ PARAMETERS = { "quadrature_rule": "auto", - "quadrature_degree": "auto" + "quadrature_degree": "auto", + "coffee_licm": False, + "unroll_indexsum": 3, } diff --git a/tsfc/driver.py b/tsfc/driver.py index c80040f39e..3a62f5cac5 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -193,7 +193,8 @@ def compile_integral(idata, fd, prefix, parameters): tabulation_manager, quad_rule.weights, quadrature_index, argument_indices, coefficient_map, index_cache) - nonfem = opt.unroll_indexsum(nonfem, max_extent=3) + if parameters["unroll_indexsum"]: + nonfem = opt.unroll_indexsum(nonfem, max_extent=parameters["unroll_indexsum"]) nonfem_.append([(gem.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) for e in nonfem]) @@ -237,7 +238,7 @@ def compile_integral(idata, fd, prefix, parameters): return None # Drop unnecessary temporaries - ops = impero_utils.inline_temporaries(simplified, ops) + ops = impero_utils.inline_temporaries(simplified, ops, coffee_licm=parameters["coffee_licm"]) # Prepare ImperoC (Impero AST + other data for code generation) impero_c = impero_utils.process(ops, get_indices) From f3852dc36b4613d9151346e55b62ca6ebd011999 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 19 Feb 2016 12:12:28 +0000 Subject: [PATCH 080/809] fixes on pull request comments --- tsfc/constants.py | 10 ++++++++++ tsfc/interpreter.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tsfc/constants.py b/tsfc/constants.py index 07311c114b..56501564fb 100644 --- a/tsfc/constants.py +++ b/tsfc/constants.py @@ -14,7 +14,17 @@ PARAMETERS = { "quadrature_rule": "auto", "quadrature_degree": "auto", + + # Trust COFFEE to do loop-invariant code motion. Disabled by + # default as COFFEE does not work on TSFC-generated code yet. + # When enabled, it allows the inlining of expressions even if that + # pulls calculations into inner loops. "coffee_licm": False, + + # Maximum extent to unroll index sums. Default is 3, so that loops + # over geometric dimensions are unrolled; this improves assembly + # performance. Can be disabled by setting it to None, False or 0; + # that makes compilation time much shorter. "unroll_indexsum": 3, } diff --git a/tsfc/interpreter.py b/tsfc/interpreter.py index 44bc3a3ec0..fd4598621e 100644 --- a/tsfc/interpreter.py +++ b/tsfc/interpreter.py @@ -239,7 +239,7 @@ def _(e, self): idx.append(Ellipsis) elif isinstance(i, gem.VariableIndex): # Variable index, evaluate inner expression - result, = evaluate(i.expression, self.bindings) + result, = self(i.expression) assert not result.tshape idx.append(result[()]) else: From 9d2cd999391071e55e2f5dedd7d94d365b5c5591 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 10 Feb 2016 17:02:56 +0000 Subject: [PATCH 081/809] Purge references to TPVE and TPTE --- tsfc/fiatinterface.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index fd78b03bfd..d2b009ee7c 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -195,9 +195,7 @@ def _(element, vector_is_mixed): # If we're just trying to get the scalar part of a vector element? if not vector_is_mixed: assert isinstance(element, (ufl.VectorElement, - ufl.TensorElement, - ufl.TensorProductVectorElement, - ufl.TensorProductTensorElement)) + ufl.TensorElement)) return create_element(element.sub_elements()[0], vector_is_mixed) elements = [] From 2c1996ebcf0b4fcf959313762bf28e84b52df6cc Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 22 Feb 2016 17:55:02 +0000 Subject: [PATCH 082/809] loosen free indices requirement on impero.Return --- tsfc/impero.py | 4 ++-- tsfc/scheduling.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tsfc/impero.py b/tsfc/impero.py index f954b80b0c..8478ce7716 100644 --- a/tsfc/impero.py +++ b/tsfc/impero.py @@ -90,13 +90,13 @@ class Return(Terminal): __front__ = ('variable', 'expression') def __init__(self, variable, expression): - assert set(variable.free_indices) == set(expression.free_indices) + assert set(variable.free_indices) >= set(expression.free_indices) self.variable = variable self.expression = expression def loop_shape(self, free_indices): - return free_indices(self.expression) + return free_indices(self.variable) class ReturnAccumulate(Terminal): diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py index 28e13804e1..1387a15327 100644 --- a/tsfc/scheduling.py +++ b/tsfc/scheduling.py @@ -165,7 +165,8 @@ def emit_operations(assignments, get_indices): # Stage return operations staging = [] for variable, expression in assignments: - if isinstance(expression, gem.IndexSum) and refcount[expression] == 1: + if refcount[expression] == 1 and isinstance(expression, gem.IndexSum) \ + and set(variable.free_indices) == set(expression.free_indices): staging.append(impero.ReturnAccumulate(variable, expression)) refcount[expression] -= 1 else: From 32cf76971789abad6e2979acddde985a77b7138b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 22 Feb 2016 17:56:34 +0000 Subject: [PATCH 083/809] refactor code generation in the driver To share more code with Function.interpolate from Firedrake. --- tsfc/driver.py | 70 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 3a62f5cac5..81ac7d89b0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -110,7 +110,7 @@ def compile_integral(idata, fd, prefix, parameters): arglist.append(funarg) prepare += prepare_ - argument_indices = tuple(index for index in expressions[0].multiindex if isinstance(index, gem.Index)) + argument_indices = [index for index in expressions[0].multiindex if isinstance(index, gem.Index)] mesh = idata.domain coordinates = fem.coordinate_coefficient(mesh) @@ -200,22 +200,47 @@ def compile_integral(idata, fd, prefix, parameters): # Sum the expressions that are part of the same restriction nonfem = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*nonfem_)) - simplified = opt.remove_componenttensors(nonfem) + + index_names = zip(argument_indices, ['j', 'k']) + if len(quadrature_indices) == 1: + index_names.append((quadrature_indices[0], 'ip')) + else: + for i, quadrature_index in enumerate(quadrature_indices): + index_names.append((quadrature_index, 'ip_%d' % i)) + + body, kernel.oriented = build_kernel_body(expressions, nonfem, + quadrature_indices + argument_indices, + coffee_licm=parameters["coffee_licm"], + index_names=index_names) + if body is None: + return None + if kernel.oriented: + decl = coffee.Decl("int *restrict *restrict", + coffee.Symbol("cell_orientations"), + qualifiers=["const"]) + arglist.insert(2, decl) + + funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) + kernel.ast = coffee.FunDecl("void", funname, arglist, + coffee.Block(prepare + [body] + finalise), + pred=["static", "inline"]) + + return kernel + + +def build_kernel_body(return_variables, ir, prefix_ordering, coffee_licm=False, index_names=None): + ir = opt.remove_componenttensors(ir) # Look for cell orientations in the simplified GEM - kernel.oriented = False - for node in traversal(simplified): + oriented = False + for node in traversal(ir): if isinstance(node, gem.Variable) and node.name == "cell_orientations": - kernel.oriented = True - decl = coffee.Decl("int *restrict *restrict", - coffee.Symbol("cell_orientations"), - qualifiers=["const"]) - arglist.insert(2, decl) + oriented = True break # Collect indices in a deterministic order indices = [] - for node in traversal(simplified): + for node in traversal(ir): if isinstance(node, gem.Indexed): indices.extend(node.multiindex) # The next two lines remove duplicate elements from the list, but @@ -225,41 +250,30 @@ def compile_integral(idata, fd, prefix, parameters): indices = numpy.asarray(indices)[numpy.sort(unique_indices)] # Build ordered index map - index_ordering = make_prefix_ordering(indices, tuple(quadrature_indices) + argument_indices) + index_ordering = make_prefix_ordering(indices, prefix_ordering) apply_ordering = make_index_orderer(index_ordering) get_indices = lambda expr: apply_ordering(expr.free_indices) # Build operation ordering - ops = sch.emit_operations(zip(expressions, simplified), get_indices) + ops = sch.emit_operations(zip(return_variables, ir), get_indices) # Zero-simplification occurred if len(ops) == 0: - return None + return None, False # Drop unnecessary temporaries - ops = impero_utils.inline_temporaries(simplified, ops, coffee_licm=parameters["coffee_licm"]) + ops = impero_utils.inline_temporaries(ir, ops, coffee_licm=coffee_licm) # Prepare ImperoC (Impero AST + other data for code generation) impero_c = impero_utils.process(ops, get_indices) # Generate COFFEE - index_names = zip(argument_indices, ['j', 'k']) - if len(quadrature_indices) == 1: - index_names.append((quadrature_indices[0], 'ip')) - else: - for i, quadrature_index in enumerate(quadrature_indices): - index_names.append((quadrature_index, 'ip_%d' % i)) - + if index_names is None: + index_names = {} body = generate_coffee(impero_c, index_names) body.open_scope = False - - funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) - kernel.ast = coffee.FunDecl("void", funname, arglist, - coffee.Block(prepare + [body] + finalise), - pred=["static", "inline"]) - - return kernel + return body, oriented def is_mesh_affine(mesh): From 535c034532c2e432d02e46a3c2fbc64a7897b410 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 23 Feb 2016 12:35:37 +0000 Subject: [PATCH 084/809] add COFFEE codegen for MinValue and MaxValue --- tsfc/coffee.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 29d2ada56c..0b495cd6ec 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -213,6 +213,16 @@ def _expression_mathfunction(expr, parameters): return coffee.FunCall(name, expression(expr.children[0], parameters)) +@_expression.register(gem.MinValue) +def _expression_minvalue(expr, parameters): + return coffee.FunCall('fmin', *[expression(c, parameters) for c in expr.children]) + + +@_expression.register(gem.MaxValue) +def _expression_maxvalue(expr, parameters): + return coffee.FunCall('fmax', *[expression(c, parameters) for c in expr.children]) + + @_expression.register(gem.Comparison) def _expression_comparison(expr, parameters): type_map = {">": coffee.Greater, From 3ed194044893b19dec4481063a1821e78ab1e2b1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 15 Mar 2016 10:22:03 +0000 Subject: [PATCH 085/809] test case for bug #25 --- tests/test_codegen.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/test_codegen.py diff --git a/tests/test_codegen.py b/tests/test_codegen.py new file mode 100644 index 0000000000..f3967f91ae --- /dev/null +++ b/tests/test_codegen.py @@ -0,0 +1,26 @@ +from tsfc import driver, impero_utils, scheduling +from tsfc.gem import Index, Indexed, IndexSum, Product, Variable + + +def test_loop_fusion(): + i = Index() + j = Index() + Ri = Indexed(Variable('R', (6,)), (i,)) + + def make_expression(i, j): + A = Variable('A', (6,)) + s = IndexSum(Indexed(A, (j,)), j) + return Product(Indexed(A, (i,)), s) + + e1 = make_expression(i, j) + e2 = make_expression(i, i) + + apply_ordering = driver.make_index_orderer((i, j)) + get_indices = lambda expr: apply_ordering(expr.free_indices) + + def gencode(expr): + ops = scheduling.emit_operations([(Ri, expr)], get_indices) + impero_c = impero_utils.process(ops, get_indices) + return impero_c.tree + + assert len(gencode(e1).children) == len(gencode(e2).children) From 1ac71dba17d9a0905fe19d00297667fe8af8e5ba Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 15 Mar 2016 10:46:10 +0000 Subject: [PATCH 086/809] fix #25 --- tsfc/impero.py | 14 ++++++++++++++ tsfc/impero_utils.py | 15 ++++++--------- tsfc/scheduling.py | 1 + 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/tsfc/impero.py b/tsfc/impero.py index 8478ce7716..0cac80cc82 100644 --- a/tsfc/impero.py +++ b/tsfc/impero.py @@ -82,6 +82,20 @@ def loop_shape(self, free_indices): return free_indices(self.indexsum.children[0]) +class Noop(Terminal): + """No-op terminal. Does not generate code, but wraps a GEM + expression to have a loop shape, thus affects loop fusion.""" + + __slots__ = ('expression',) + __front__ = ('expression',) + + def __init__(self, expression): + self.expression = expression + + def loop_shape(self, free_indices): + return free_indices(self.expression) + + class Return(Terminal): """Save value of GEM expression into an lvalue. Used to "return" values from a kernel.""" diff --git a/tsfc/impero_utils.py b/tsfc/impero_utils.py index d36884acf7..8aa5a92241 100644 --- a/tsfc/impero_utils.py +++ b/tsfc/impero_utils.py @@ -88,14 +88,10 @@ def collect_temporaries(ops): # IndexSum temporaries should be added either at Initialise or # at Accumulate. The difference is only in ordering # (numbering). We chose Accumulate here. - if isinstance(op, (imp.Initialise, imp.Return, imp.ReturnAccumulate)): - pass - elif isinstance(op, imp.Accumulate): + if isinstance(op, imp.Accumulate): result.append(op.indexsum) elif isinstance(op, imp.Evaluate): result.append(op.expression) - else: - raise AssertionError("unhandled operation: %s" % type(op)) return result @@ -117,6 +113,8 @@ def make_loop_tree(ops, get_indices, level=0): statements.append(imp.For(first_index[0], inner_block)) else: statements.extend(op_group) + # Remove no-op terminals from the tree + statements = filter(lambda s: not isinstance(s, imp.Noop), statements) return imp.Block(statements) @@ -130,7 +128,6 @@ def place_declarations(ops, tree, temporaries, get_indices): :arg get_indices: callable mapping from GEM nodes to an ordering of free indices """ - temporaries_set = set(temporaries) assert len(temporaries_set) == len(temporaries) @@ -206,10 +203,8 @@ def recurse_block(expr, loop_indices): e = op.expression elif isinstance(op, imp.Initialise): e = op.indexsum - elif isinstance(op, (imp.Accumulate, imp.Return, imp.ReturnAccumulate)): - continue else: - raise AssertionError("unhandled operation type %s" % type(op)) + continue if len(indices[e]) == 0: declare[op] = True @@ -257,6 +252,8 @@ def recurse_top(o): recurse(op.expression) elif isinstance(op, imp.ReturnAccumulate): recurse(op.indexsum.children[0]) + elif isinstance(op, imp.Noop): + pass else: raise AssertionError("unhandled operation: %s" % type(op)) diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py index 1387a15327..2466e33eeb 100644 --- a/tsfc/scheduling.py +++ b/tsfc/scheduling.py @@ -121,6 +121,7 @@ def handle(ops, push, decref, node): # Indexing always inlined decref(node.children[0]) elif isinstance(node, gem.IndexSum): + ops.append(impero.Noop(node)) push(impero.Accumulate(node)) elif isinstance(node, gem.Node): ops.append(impero.Evaluate(node)) From 75e903a2c1fef554b0b1a85ec827c1d3b3ea4357 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 16 Mar 2016 12:13:17 +0000 Subject: [PATCH 087/809] create new file kernel_interface.py --- tsfc/driver.py | 280 +------------------------------------- tsfc/kernel_interface.py | 285 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+), 278 deletions(-) create mode 100644 tsfc/kernel_interface.py diff --git a/tsfc/driver.py b/tsfc/driver.py index 81ac7d89b0..23cf1a1d58 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -9,39 +9,15 @@ from ufl.algorithms import compute_form_data from ufl.log import GREEN -from tsfc.fiatinterface import create_element -from tsfc.mixedelement import MixedElement from tsfc.quadrature import create_quadrature, QuadratureRule import coffee.base as coffee from tsfc import fem, gem, scheduling as sch, optimise as opt, impero_utils -from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee +from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.node import traversal - - -class Kernel(object): - __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", - "coefficient_numbers", "__weakref__") - """A compiled Kernel object. - - :kwarg ast: The COFFEE ast for the kernel. - :kwarg integral_type: The type of integral. - :kwarg oriented: Does the kernel require cell_orientations. - :kwarg subdomain_id: What is the subdomain id for this kernel. - :kwarg coefficient_numbers: A list of which coefficients from the - form the kernel needs. - """ - def __init__(self, ast=None, integral_type=None, oriented=False, - subdomain_id=None, coefficient_numbers=()): - # Defaults - self.ast = ast - self.integral_type = integral_type - self.oriented = oriented - self.subdomain_id = subdomain_id - self.coefficient_numbers = coefficient_numbers - super(Kernel, self).__init__() +from tsfc.kernel_interface import Kernel, prepare_arguments, prepare_coefficient def compile_form(form, prefix="form", parameters=None): @@ -283,258 +259,6 @@ def is_mesh_affine(mesh): return mesh.ufl_cell().cellname() in affine_cells and degree == 1 -def prepare_coefficient(integral_type, coefficient, name, mode=None): - """Bridges the kernel interface and the GEM abstraction for - Coefficients. Mixed element Coefficients are rearranged here for - interior facet integrals. - - :arg integral_type: integral type - :arg coefficient: UFL Coefficient - :arg name: unique name to refer to the Coefficient in the kernel - :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with - interior facet integrals on mixed elements - :returns: (funarg, prepare, expression) - funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expression - GEM expression referring to the Coefficient - values - """ - if mode is None: - mode = 'manual_loop' - - assert mode in ['manual_loop', 'list_tensor'] - - if coefficient.ufl_element().family() == 'Real': - # Constant - - shape = coefficient.ufl_shape or (1,) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), - qualifiers=["const"]) - expression = gem.Variable(name, shape) - if coefficient.ufl_shape == (): - expression = gem.Indexed(expression, (0,)) - - return funarg, [], expression - - fiat_element = create_element(coefficient.ufl_element()) - - if not integral_type.startswith("interior_facet"): - # Simple case - - shape = (fiat_element.space_dimension(),) - funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), - qualifiers=["const"]) - - i = gem.Index() - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, shape + (1,)), - (i, 0)), - (i,)) - - return funarg, [], expression - - if not isinstance(fiat_element, MixedElement): - # Interior facet integral - - shape = (2, fiat_element.space_dimension()) - - funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), - qualifiers=["const"]) - expression = gem.Variable(name, shape + (1,)) - - f, i = gem.Index(), gem.Index() - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, shape + (1,)), - (f, i, 0)), - (f, i,)) - - return funarg, [], expression - - # Interior facet integral + mixed / vector element - - # Here we need to reorder the coefficient values. - # - # Incoming ordering: E1+ E1- E2+ E2- E3+ E3- - # Required ordering: E1+ E2+ E3+ E1- E2- E3- - # - # Each of E[n]{+,-} is a vector of basis function coefficients for - # subelement E[n]. - # - # There are two code generation method to reorder the values. - # We have not done extensive research yet as to which way yield - # faster code. - - if mode == 'manual_loop': - # In this case we generate loops outside the GEM abstraction - # to reorder the values. A whole E[n]{+,-} block is copied by - # a single loop. - name_ = name + "_" - shape = (2, fiat_element.space_dimension()) - - funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_), - qualifiers=["const"]) - prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] - expression = gem.Variable(name, shape) - - offset = 0 - i = coffee.Symbol("i") - for element in fiat_element.elements(): - space_dim = element.space_dimension() - - loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, coffee.Sum(offset, i))), - coffee.Symbol(name_, rank=(coffee.Sum(2 * offset, i), 0))) - prepare.append(coffee_for(i, space_dim, loop_body)) - - loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, coffee.Sum(offset, i))), - coffee.Symbol(name_, rank=(coffee.Sum(2 * offset + space_dim, i), 0))) - prepare.append(coffee_for(i, space_dim, loop_body)) - - offset += space_dim - - return funarg, prepare, expression - - elif mode == 'list_tensor': - # In this case we generate a gem.ListTensor to do the - # reordering. Every single element in a E[n]{+,-} block is - # referenced separately. - funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name), - qualifiers=["const"]) - - variable = gem.Variable(name, (2 * fiat_element.space_dimension(), 1)) - - facet_0 = [] - facet_1 = [] - offset = 0 - for element in fiat_element.elements(): - space_dim = element.space_dimension() - - for i in range(offset, offset + space_dim): - facet_0.append(gem.Indexed(variable, (i, 0))) - offset += space_dim - - for i in range(offset, offset + space_dim): - facet_1.append(gem.Indexed(variable, (i, 0))) - offset += space_dim - - expression = gem.ListTensor(numpy.array([facet_0, facet_1])) - return funarg, [], expression - - -def prepare_arguments(integral_type, arguments): - """Bridges the kernel interface and the GEM abstraction for - Arguments. Vector Arguments are rearranged here for interior - facet integrals. - - :arg integral_type: integral type - :arg arguments: UFL Arguments - :returns: (funarg, prepare, expression, finalise) - funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expression - GEM expression referring to the argument tensor - finalise - list of COFFEE nodes to be appended to the - kernel body - """ - from itertools import chain, product - - if len(arguments) == 0: - # No arguments - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=(1,))) - expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) - - return funarg, [], [expression], [] - - elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - indices = tuple(gem.Index(name=name) for i, name in zip(range(len(arguments)), ['j', 'k'])) - - if not integral_type.startswith("interior_facet"): - # Not an interior facet integral - shape = tuple(element.space_dimension() for element in elements) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) - expression = gem.Indexed(gem.Variable("A", shape), indices) - - return funarg, [], [expression], [] - - if not any(isinstance(element, MixedElement) for element in elements): - # Interior facet integral, but no vector (mixed) arguments - shape = [] - for element in elements: - shape += [2, element.space_dimension()] - shape = tuple(shape) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) - varexp = gem.Variable("A", shape) - - expressions = [] - for restrictions in product((0, 1), repeat=len(arguments)): - is_ = tuple(chain(*zip(restrictions, indices))) - expressions.append(gem.Indexed(varexp, is_)) - - return funarg, [], expressions, [] - - # Interior facet integral + vector (mixed) argument(s) - shape = tuple(element.space_dimension() for element in elements) - funarg_shape = tuple(s * 2 for s in shape) - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=funarg_shape)) - - prepare = [] - expressions = [] - - references = [] - for restrictions in product((0, 1), repeat=len(arguments)): - name = "A" + "".join(map(str, restrictions)) - - prepare.append(coffee.Decl(SCALAR_TYPE, - coffee.Symbol(name, rank=shape), - init=coffee.ArrayInit(numpy.zeros(1)))) - expressions.append(gem.Indexed(gem.Variable(name, shape), indices)) - - for multiindex in numpy.ndindex(shape): - references.append(coffee.Symbol(name, multiindex)) - - restriction_shape = [] - for e in elements: - if isinstance(e, MixedElement): - restriction_shape += [len(e.elements()), - e.elements()[0].space_dimension()] - else: - restriction_shape += [1, e.space_dimension()] - restriction_shape = tuple(restriction_shape) - - references = numpy.array(references) - if len(arguments) == 1: - references = references.reshape((2,) + restriction_shape) - references = references.transpose(1, 0, 2) - elif len(arguments) == 2: - references = references.reshape((2, 2) + restriction_shape) - references = references.transpose(2, 0, 3, 4, 1, 5) - references = references.reshape(funarg_shape) - - finalise = [] - for multiindex in numpy.ndindex(funarg_shape): - finalise.append(coffee.Assign(coffee.Symbol("A", rank=multiindex), - references[multiindex])) - - return funarg, prepare, expressions, finalise - - -def coffee_for(index, extent, body): - """Helper function to make a COFFEE loop. - - :arg index: :class:`coffee.Symbol` loop index - :arg extent: loop extent (integer) - :arg body: loop body (COFFEE node) - :returns: COFFEE loop - """ - return coffee.For(coffee.Decl("int", index, init=0), - coffee.Less(index, extent), - coffee.Incr(index, 1), - body) - - def make_prefix_ordering(indices, prefix_ordering): """Creates an ordering of ``indices`` which starts with those indices in ``prefix_ordering``.""" diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py new file mode 100644 index 0000000000..225067a832 --- /dev/null +++ b/tsfc/kernel_interface.py @@ -0,0 +1,285 @@ +from __future__ import absolute_import + +import numpy + +import coffee.base as coffee + +from tsfc import gem +from tsfc.fiatinterface import create_element +from tsfc.mixedelement import MixedElement +from tsfc.coffee import SCALAR_TYPE + + +class Kernel(object): + __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + "coefficient_numbers", "__weakref__") + """A compiled Kernel object. + + :kwarg ast: The COFFEE ast for the kernel. + :kwarg integral_type: The type of integral. + :kwarg oriented: Does the kernel require cell_orientations. + :kwarg subdomain_id: What is the subdomain id for this kernel. + :kwarg coefficient_numbers: A list of which coefficients from the + form the kernel needs. + """ + def __init__(self, ast=None, integral_type=None, oriented=False, + subdomain_id=None, coefficient_numbers=()): + # Defaults + self.ast = ast + self.integral_type = integral_type + self.oriented = oriented + self.subdomain_id = subdomain_id + self.coefficient_numbers = coefficient_numbers + super(Kernel, self).__init__() + + +def prepare_coefficient(integral_type, coefficient, name, mode=None): + """Bridges the kernel interface and the GEM abstraction for + Coefficients. Mixed element Coefficients are rearranged here for + interior facet integrals. + + :arg integral_type: integral type + :arg coefficient: UFL Coefficient + :arg name: unique name to refer to the Coefficient in the kernel + :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with + interior facet integrals on mixed elements + :returns: (funarg, prepare, expression) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expression - GEM expression referring to the Coefficient + values + """ + if mode is None: + mode = 'manual_loop' + + assert mode in ['manual_loop', 'list_tensor'] + + if coefficient.ufl_element().family() == 'Real': + # Constant + + shape = coefficient.ufl_shape or (1,) + + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), + qualifiers=["const"]) + expression = gem.Variable(name, shape) + if coefficient.ufl_shape == (): + expression = gem.Indexed(expression, (0,)) + + return funarg, [], expression + + fiat_element = create_element(coefficient.ufl_element()) + + if not integral_type.startswith("interior_facet"): + # Simple case + + shape = (fiat_element.space_dimension(),) + funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), + qualifiers=["const"]) + + i = gem.Index() + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, shape + (1,)), + (i, 0)), + (i,)) + + return funarg, [], expression + + if not isinstance(fiat_element, MixedElement): + # Interior facet integral + + shape = (2, fiat_element.space_dimension()) + + funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), + qualifiers=["const"]) + expression = gem.Variable(name, shape + (1,)) + + f, i = gem.Index(), gem.Index() + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, shape + (1,)), + (f, i, 0)), + (f, i,)) + + return funarg, [], expression + + # Interior facet integral + mixed / vector element + + # Here we need to reorder the coefficient values. + # + # Incoming ordering: E1+ E1- E2+ E2- E3+ E3- + # Required ordering: E1+ E2+ E3+ E1- E2- E3- + # + # Each of E[n]{+,-} is a vector of basis function coefficients for + # subelement E[n]. + # + # There are two code generation method to reorder the values. + # We have not done extensive research yet as to which way yield + # faster code. + + if mode == 'manual_loop': + # In this case we generate loops outside the GEM abstraction + # to reorder the values. A whole E[n]{+,-} block is copied by + # a single loop. + name_ = name + "_" + shape = (2, fiat_element.space_dimension()) + + funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_), + qualifiers=["const"]) + prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] + expression = gem.Variable(name, shape) + + offset = 0 + i = coffee.Symbol("i") + for element in fiat_element.elements(): + space_dim = element.space_dimension() + + loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, coffee.Sum(offset, i))), + coffee.Symbol(name_, rank=(coffee.Sum(2 * offset, i), 0))) + prepare.append(coffee_for(i, space_dim, loop_body)) + + loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, coffee.Sum(offset, i))), + coffee.Symbol(name_, rank=(coffee.Sum(2 * offset + space_dim, i), 0))) + prepare.append(coffee_for(i, space_dim, loop_body)) + + offset += space_dim + + return funarg, prepare, expression + + elif mode == 'list_tensor': + # In this case we generate a gem.ListTensor to do the + # reordering. Every single element in a E[n]{+,-} block is + # referenced separately. + funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name), + qualifiers=["const"]) + + variable = gem.Variable(name, (2 * fiat_element.space_dimension(), 1)) + + facet_0 = [] + facet_1 = [] + offset = 0 + for element in fiat_element.elements(): + space_dim = element.space_dimension() + + for i in range(offset, offset + space_dim): + facet_0.append(gem.Indexed(variable, (i, 0))) + offset += space_dim + + for i in range(offset, offset + space_dim): + facet_1.append(gem.Indexed(variable, (i, 0))) + offset += space_dim + + expression = gem.ListTensor(numpy.array([facet_0, facet_1])) + return funarg, [], expression + + +def prepare_arguments(integral_type, arguments): + """Bridges the kernel interface and the GEM abstraction for + Arguments. Vector Arguments are rearranged here for interior + facet integrals. + + :arg integral_type: integral type + :arg arguments: UFL Arguments + :returns: (funarg, prepare, expression, finalise) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expression - GEM expression referring to the argument tensor + finalise - list of COFFEE nodes to be appended to the + kernel body + """ + from itertools import chain, product + + if len(arguments) == 0: + # No arguments + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=(1,))) + expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) + + return funarg, [], [expression], [] + + elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + indices = tuple(gem.Index(name=name) for i, name in zip(range(len(arguments)), ['j', 'k'])) + + if not integral_type.startswith("interior_facet"): + # Not an interior facet integral + shape = tuple(element.space_dimension() for element in elements) + + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) + expression = gem.Indexed(gem.Variable("A", shape), indices) + + return funarg, [], [expression], [] + + if not any(isinstance(element, MixedElement) for element in elements): + # Interior facet integral, but no vector (mixed) arguments + shape = [] + for element in elements: + shape += [2, element.space_dimension()] + shape = tuple(shape) + + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) + varexp = gem.Variable("A", shape) + + expressions = [] + for restrictions in product((0, 1), repeat=len(arguments)): + is_ = tuple(chain(*zip(restrictions, indices))) + expressions.append(gem.Indexed(varexp, is_)) + + return funarg, [], expressions, [] + + # Interior facet integral + vector (mixed) argument(s) + shape = tuple(element.space_dimension() for element in elements) + funarg_shape = tuple(s * 2 for s in shape) + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=funarg_shape)) + + prepare = [] + expressions = [] + + references = [] + for restrictions in product((0, 1), repeat=len(arguments)): + name = "A" + "".join(map(str, restrictions)) + + prepare.append(coffee.Decl(SCALAR_TYPE, + coffee.Symbol(name, rank=shape), + init=coffee.ArrayInit(numpy.zeros(1)))) + expressions.append(gem.Indexed(gem.Variable(name, shape), indices)) + + for multiindex in numpy.ndindex(shape): + references.append(coffee.Symbol(name, multiindex)) + + restriction_shape = [] + for e in elements: + if isinstance(e, MixedElement): + restriction_shape += [len(e.elements()), + e.elements()[0].space_dimension()] + else: + restriction_shape += [1, e.space_dimension()] + restriction_shape = tuple(restriction_shape) + + references = numpy.array(references) + if len(arguments) == 1: + references = references.reshape((2,) + restriction_shape) + references = references.transpose(1, 0, 2) + elif len(arguments) == 2: + references = references.reshape((2, 2) + restriction_shape) + references = references.transpose(2, 0, 3, 4, 1, 5) + references = references.reshape(funarg_shape) + + finalise = [] + for multiindex in numpy.ndindex(funarg_shape): + finalise.append(coffee.Assign(coffee.Symbol("A", rank=multiindex), + references[multiindex])) + + return funarg, prepare, expressions, finalise + + +def coffee_for(index, extent, body): + """Helper function to make a COFFEE loop. + + :arg index: :class:`coffee.Symbol` loop index + :arg extent: loop extent (integer) + :arg body: loop body (COFFEE node) + :returns: COFFEE loop + """ + return coffee.For(coffee.Decl("int", index, init=0), + coffee.Less(index, extent), + coffee.Incr(index, 1), + body) From 8558fb6f511036943b983884b7e31b1084281802 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 16 Mar 2016 17:00:16 +0000 Subject: [PATCH 088/809] move all COFFEE from the driver --- tsfc/driver.py | 81 +++++++++++----------------------------- tsfc/kernel_interface.py | 73 +++++++++++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 65 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 23cf1a1d58..b04646dc93 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -11,13 +11,11 @@ from tsfc.quadrature import create_quadrature, QuadratureRule -import coffee.base as coffee - from tsfc import fem, gem, scheduling as sch, optimise as opt, impero_utils from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.node import traversal -from tsfc.kernel_interface import Kernel, prepare_arguments, prepare_coefficient +from tsfc.kernel_interface import Kernel, Interface def compile_form(form, prefix="form", parameters=None): @@ -59,11 +57,11 @@ def compile_form(form, prefix="form", parameters=None): return kernels -def compile_integral(idata, fd, prefix, parameters): +def compile_integral(integral_data, form_data, prefix, parameters): """Compiles a UFL integral into an assembly kernel. - :arg idata: UFL integral data - :arg fd: UFL form data + :arg integral_data: UFL integral data + :arg form_data: UFL form data :arg prefix: kernel name will start with this string :arg parameters: parameters object :returns: a kernel, or None if the integral simplifies to zero @@ -74,73 +72,46 @@ def compile_integral(idata, fd, prefix, parameters): if parameters.get("quadrature_rule") == "auto": del parameters["quadrature_rule"] - integral_type = idata.integral_type - kernel = Kernel(integral_type=integral_type, subdomain_id=idata.subdomain_id) - - arglist = [] - prepare = [] - coefficient_map = {} - - funarg, prepare_, expressions, finalise = prepare_arguments(integral_type, fd.preprocessed_form.arguments()) - argument_indices = sorted(expressions[0].free_indices, key=lambda index: index.name) - - arglist.append(funarg) - prepare += prepare_ - argument_indices = [index for index in expressions[0].multiindex if isinstance(index, gem.Index)] + integral_type = integral_data.integral_type + kernel = Kernel(integral_type=integral_type, subdomain_id=integral_data.subdomain_id) + interface = Interface(integral_type, form_data.preprocessed_form.arguments()) - mesh = idata.domain + mesh = integral_data.domain coordinates = fem.coordinate_coefficient(mesh) if is_mesh_affine(mesh): # For affine mesh geometries we prefer code generation that # composes well with optimisations. - funarg, prepare_, expression = prepare_coefficient(integral_type, coordinates, "coords", mode='list_tensor') + interface.preload(coordinates, "coords", mode='list_tensor') else: # Otherwise we use the approach that might be faster (?) - funarg, prepare_, expression = prepare_coefficient(integral_type, coordinates, "coords") - - arglist.append(funarg) - prepare += prepare_ - coefficient_map[coordinates] = expression + interface.preload(coordinates, "coords") coefficient_numbers = [] # enabled_coefficients is a boolean array that indicates which of # reduced_coefficients the integral requires. - for i, on in enumerate(idata.enabled_coefficients): + for i, on in enumerate(integral_data.enabled_coefficients): if not on: continue - coefficient = fd.reduced_coefficients[i] + coefficient = form_data.reduced_coefficients[i] # This is which coefficient in the original form the current # coefficient is. # Consider f*v*dx + g*v*ds, the full form contains two # coefficients, but each integral only requires one. - coefficient_numbers.append(fd.original_coefficient_positions[i]) - funarg, prepare_, expression = prepare_coefficient(integral_type, coefficient, "w_%d" % i) - - arglist.append(funarg) - prepare += prepare_ - coefficient_map[coefficient] = expression + coefficient_numbers.append(form_data.original_coefficient_positions[i]) + interface.preload(coefficient, "w_%d" % i) # TODO: Remove! kernel.coefficient_numbers = tuple(coefficient_numbers) - if integral_type in ["exterior_facet", "exterior_facet_vert"]: - decl = coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(1,)), - qualifiers=["const"]) - arglist.append(decl) - elif integral_type in ["interior_facet", "interior_facet_vert"]: - decl = coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(2,)), - qualifiers=["const"]) - arglist.append(decl) - nonfem_ = [] quadrature_indices = [] - cell = idata.domain.ufl_cell() + cell = integral_data.domain.ufl_cell() # Map from UFL FiniteElement objects to Index instances. This is # so we reuse Index instances when evaluating the same coefficient # multiple times with the same table. Occurs, for example, if we # have multiple integrals here (and the affine coordinate # evaluation can be hoisted). index_cache = collections.defaultdict(gem.Index) - for i, integral in enumerate(idata.integrals): + for i, integral in enumerate(integral_data.integrals): params = {} # Record per-integral parameters params.update(integral.metadata()) @@ -167,8 +138,8 @@ def compile_integral(idata, fd, prefix, parameters): quadrature_indices.append(quadrature_index) nonfem = fem.process(integral_type, integrand, tabulation_manager, quad_rule.weights, - quadrature_index, argument_indices, - coefficient_map, index_cache) + quadrature_index, interface.argument_indices(), # TODO + interface.coefficients, index_cache) # TODO if parameters["unroll_indexsum"]: nonfem = opt.unroll_indexsum(nonfem, max_extent=parameters["unroll_indexsum"]) nonfem_.append([(gem.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) @@ -177,30 +148,22 @@ def compile_integral(idata, fd, prefix, parameters): # Sum the expressions that are part of the same restriction nonfem = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*nonfem_)) - index_names = zip(argument_indices, ['j', 'k']) + index_names = zip(interface.argument_indices(), ['j', 'k']) # TODO if len(quadrature_indices) == 1: index_names.append((quadrature_indices[0], 'ip')) else: for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body, kernel.oriented = build_kernel_body(expressions, nonfem, - quadrature_indices + argument_indices, + body, kernel.oriented = build_kernel_body(interface.return_variables, nonfem, # TODO + quadrature_indices + list(interface.argument_indices()), # TODO coffee_licm=parameters["coffee_licm"], index_names=index_names) if body is None: return None - if kernel.oriented: - decl = coffee.Decl("int *restrict *restrict", - coffee.Symbol("cell_orientations"), - qualifiers=["const"]) - arglist.insert(2, decl) funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) - kernel.ast = coffee.FunDecl("void", funname, arglist, - coffee.Block(prepare + [body] + finalise), - pred=["static", "inline"]) - + kernel.ast = interface.construct_kernel_function(funname, body, kernel.oriented) return kernel diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 225067a832..10f8938bf1 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import itertools + import numpy import coffee.base as coffee @@ -33,12 +35,71 @@ def __init__(self, ast=None, integral_type=None, oriented=False, super(Kernel, self).__init__() -def prepare_coefficient(integral_type, coefficient, name, mode=None): +class Interface(object): + def __init__(self, integral_type, arguments): + self.integral_type = integral_type + self.interior_facet = integral_type.startswith("interior_facet") + + funarg, prepare, expressions, finalise = prepare_arguments(self.interior_facet, arguments) + + self.return_funarg = funarg + self.funargs = [] + self.prepare = prepare + self.return_variables = expressions # TODO + self.finalise = finalise + + self.count = itertools.count() + self.coefficients = {} + + def argument_indices(self): + return filter(lambda i: isinstance(i, gem.Index), self.return_variables[0].multiindex) + + def preload(self, coefficient, name=None, mode=None): + # Do not add the same coefficient twice + assert coefficient not in self.coefficients + + # Default name with counting + if name is None: + name = "w_{0}".format(next(self.count)) + + funarg, prepare, expression = prepare_coefficient(self.interior_facet, coefficient, name, mode) + self.funargs.append(funarg) + self.prepare.extend(prepare) + self.coefficients[coefficient] = expression + + def gem(self, coefficient): + try: + return self.coefficients[coefficient] + except KeyError: + self.preload(coefficient) + return self.coefficients[coefficient] + + def construct_kernel_function(self, name, body, oriented): + args = [self.return_funarg] + self.funargs + if oriented: + args.insert(2, coffee.Decl("int *restrict *restrict", + coffee.Symbol("cell_orientations"), + qualifiers=["const"])) + + if self.integral_type in ["exterior_facet", "exterior_facet_vert"]: + args.append(coffee.Decl("unsigned int", + coffee.Symbol("facet", rank=(1,)), + qualifiers=["const"])) + elif self.integral_type in ["interior_facet", "interior_facet_vert"]: + args.append(coffee.Decl("unsigned int", + coffee.Symbol("facet", rank=(2,)), + qualifiers=["const"])) + + body_ = coffee.Block(self.prepare + [body] + self.finalise) + return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) + + +def prepare_coefficient(interior_facet, coefficient, name, mode=None): """Bridges the kernel interface and the GEM abstraction for Coefficients. Mixed element Coefficients are rearranged here for interior facet integrals. - :arg integral_type: integral type + :arg interior_facet: interior facet integral? :arg coefficient: UFL Coefficient :arg name: unique name to refer to the Coefficient in the kernel :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with @@ -70,7 +131,7 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): fiat_element = create_element(coefficient.ufl_element()) - if not integral_type.startswith("interior_facet"): + if not interior_facet: # Simple case shape = (fiat_element.space_dimension(),) @@ -172,12 +233,12 @@ def prepare_coefficient(integral_type, coefficient, name, mode=None): return funarg, [], expression -def prepare_arguments(integral_type, arguments): +def prepare_arguments(interior_facet, arguments): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. - :arg integral_type: integral type + :arg interior_facet: interior facet integral? :arg arguments: UFL Arguments :returns: (funarg, prepare, expression, finalise) funarg - :class:`coffee.Decl` function argument @@ -199,7 +260,7 @@ def prepare_arguments(integral_type, arguments): elements = tuple(create_element(arg.ufl_element()) for arg in arguments) indices = tuple(gem.Index(name=name) for i, name in zip(range(len(arguments)), ['j', 'k'])) - if not integral_type.startswith("interior_facet"): + if not interior_facet: # Not an interior facet integral shape = tuple(element.space_dimension() for element in elements) From 6f1b2ee31d4bbab5b8e9e26c18a6c99d099a1ff2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 16 Mar 2016 17:25:20 +0000 Subject: [PATCH 089/809] slightly change fem.py <-> driver.py interface --- tsfc/driver.py | 25 +++++++++++-------------- tsfc/fem.py | 25 ++++++++++++------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b04646dc93..f0fe308527 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -102,7 +102,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): kernel.coefficient_numbers = tuple(coefficient_numbers) - nonfem_ = [] + expressions = [] quadrature_indices = [] cell = integral_data.domain.ufl_cell() # Map from UFL FiniteElement objects to Index instances. This is @@ -130,23 +130,20 @@ def compile_integral(integral_data, form_data, prefix, parameters): raise ValueError("Expected to find a QuadratureRule object, not a %s" % type(quad_rule)) - tabulation_manager = fem.TabulationManager(integral_type, cell, - quad_rule.points) - integrand = fem.replace_coordinates(integral.integrand(), coordinates) - quadrature_index = gem.Index(name="ip%d" % i) + expression, quadrature_index = fem.process(integral_type, + cell, quad_rule, + integrand, + interface, + index_cache) quadrature_indices.append(quadrature_index) - nonfem = fem.process(integral_type, integrand, - tabulation_manager, quad_rule.weights, - quadrature_index, interface.argument_indices(), # TODO - interface.coefficients, index_cache) # TODO if parameters["unroll_indexsum"]: - nonfem = opt.unroll_indexsum(nonfem, max_extent=parameters["unroll_indexsum"]) - nonfem_.append([(gem.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) - for e in nonfem]) + expression = opt.unroll_indexsum(expression, max_extent=parameters["unroll_indexsum"]) + expressions.append([(gem.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) + for e in expression]) # Sum the expressions that are part of the same restriction - nonfem = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*nonfem_)) + expression = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*expressions)) index_names = zip(interface.argument_indices(), ['j', 'k']) # TODO if len(quadrature_indices) == 1: @@ -155,7 +152,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body, kernel.oriented = build_kernel_body(interface.return_variables, nonfem, # TODO + body, kernel.oriented = build_kernel_body(interface.return_variables, expression, # TODO quadrature_indices + list(interface.argument_indices()), # TODO coffee_licm=parameters["coffee_licm"], index_names=index_names) diff --git a/tsfc/fem.py b/tsfc/fem.py index 98a4fcf041..f564705e22 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -383,17 +383,15 @@ def __getitem__(self, key): class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): """Contains all the context necessary to translate UFL into GEM.""" - def __init__(self, weights, quadrature_index, argument_indices, tabulation_manager, - coefficient_map, index_cache): + def __init__(self, tabulation_manager, weights, quadrature_index, interface, index_cache): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) integral_type = tabulation_manager.integral_type self.weights = gem.Literal(weights) self.quadrature_index = quadrature_index - self.argument_indices = argument_indices self.tabulation_manager = tabulation_manager self.integral_type = integral_type - self.coefficient_map = coefficient_map + self.interface = interface self.index_cache = index_cache if integral_type in ['exterior_facet', 'exterior_facet_vert']: @@ -495,7 +493,7 @@ def _(terminal, mt, params): @translate.register(Argument) # noqa: Not actually redefinition def _(terminal, mt, params): - argument_index = params.argument_indices[terminal.number()] + argument_index = params.interface.argument_indices()[terminal.number()] def callback(key): table = params.tabulation_manager[key] @@ -512,7 +510,7 @@ def callback(key): @translate.register(Coefficient) # noqa: Not actually redefinition def _(terminal, mt, params): - kernel_arg = params.coefficient_map[terminal] + kernel_arg = params.interface.gem(terminal) if terminal.ufl_element().family() == 'Real': assert mt.local_derivatives == 0 @@ -552,8 +550,7 @@ def replace_coordinates(integrand, coordinate_coefficient): return map_expr_dag(ReplaceSpatialCoordinates(coordinate_coefficient), integrand) -def process(integral_type, integrand, tabulation_manager, quadrature_weights, quadrature_index, - argument_indices, coefficient_map, index_cache): +def process(integral_type, cell, quadrature_rule, integrand, interface, index_cache): # Abs-simplification integrand = simplify_abs(integrand) @@ -570,20 +567,22 @@ def process(integral_type, integrand, tabulation_manager, quadrature_weights, qu max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives + tabulation_manager = TabulationManager(integral_type, cell, quadrature_rule.points) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) if integral_type.startswith("interior_facet"): expressions = [] - for rs in itertools.product(("+", "-"), repeat=len(argument_indices)): + for rs in itertools.product(("+", "-"), repeat=len(interface.argument_indices())): expressions.append(map_expr_dag(PickRestriction(*rs), integrand)) else: expressions = [integrand] # Translate UFL to Einstein's notation, # lowering finite element specific nodes - translator = Translator(quadrature_weights, quadrature_index, - argument_indices, tabulation_manager, - coefficient_map, index_cache) - return map_expr_dags(translator, expressions) + quadrature_index = gem.Index(name='ip') + translator = Translator(tabulation_manager, + quadrature_rule.weights, quadrature_index, + interface, index_cache) + return map_expr_dags(translator, expressions), quadrature_index From 4ee96594ee8a9b4a26299c92eba2aa31117aafa1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 16 Mar 2016 17:44:33 +0000 Subject: [PATCH 090/809] separate impero_utils.compile_gem (GEM -> ImperoC) --- tests/test_codegen.py | 8 +-- tsfc/driver.py | 142 +++++++++++++++------------------------ tsfc/impero_utils.py | 107 +++++++++++++++++++++++------ tsfc/kernel_interface.py | 23 ------- tsfc/scheduling.py | 5 -- 5 files changed, 140 insertions(+), 145 deletions(-) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index f3967f91ae..d7889f162e 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1,4 +1,4 @@ -from tsfc import driver, impero_utils, scheduling +from tsfc import impero_utils from tsfc.gem import Index, Indexed, IndexSum, Product, Variable @@ -15,12 +15,8 @@ def make_expression(i, j): e1 = make_expression(i, j) e2 = make_expression(i, i) - apply_ordering = driver.make_index_orderer((i, j)) - get_indices = lambda expr: apply_ordering(expr.free_indices) - def gencode(expr): - ops = scheduling.emit_operations([(Ri, expr)], get_indices) - impero_c = impero_utils.process(ops, get_indices) + impero_c = impero_utils.compile_gem([Ri], [expr], (i, j)) return impero_c.tree assert len(gencode(e1).children) == len(gencode(e2).children) diff --git a/tsfc/driver.py b/tsfc/driver.py index f0fe308527..6bfa642447 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -3,19 +3,40 @@ import collections import time -import numpy - from ufl.classes import Form from ufl.algorithms import compute_form_data from ufl.log import GREEN from tsfc.quadrature import create_quadrature, QuadratureRule -from tsfc import fem, gem, scheduling as sch, optimise as opt, impero_utils +from tsfc import fem, gem, optimise as opt, impero_utils from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.node import traversal -from tsfc.kernel_interface import Kernel, Interface +from tsfc.kernel_interface import Interface + + +class Kernel(object): + __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + "coefficient_numbers", "__weakref__") + """A compiled Kernel object. + + :kwarg ast: The COFFEE ast for the kernel. + :kwarg integral_type: The type of integral. + :kwarg oriented: Does the kernel require cell_orientations. + :kwarg subdomain_id: What is the subdomain id for this kernel. + :kwarg coefficient_numbers: A list of which coefficients from the + form the kernel needs. + """ + def __init__(self, ast=None, integral_type=None, oriented=False, + subdomain_id=None, coefficient_numbers=()): + # Defaults + self.ast = ast + self.integral_type = integral_type + self.oriented = oriented + self.subdomain_id = subdomain_id + self.coefficient_numbers = coefficient_numbers + super(Kernel, self).__init__() def compile_form(form, prefix="form", parameters=None): @@ -48,9 +69,10 @@ def compile_form(form, prefix="form", parameters=None): kernels = [] for integral_data in fd.integral_data: start = time.time() - kernel = compile_integral(integral_data, fd, prefix, parameters) - if kernel is not None: - kernels.append(kernel) + try: + kernels.append(compile_integral(integral_data, fd, prefix, parameters)) + except impero_utils.NoopError: + pass print GREEN % ("compile_integral finished in %g seconds." % (time.time() - start)) print GREEN % ("TSFC finished in %g seconds." % (time.time() - cpu_time)) @@ -98,11 +120,11 @@ def compile_integral(integral_data, form_data, prefix, parameters): # Consider f*v*dx + g*v*ds, the full form contains two # coefficients, but each integral only requires one. coefficient_numbers.append(form_data.original_coefficient_positions[i]) - interface.preload(coefficient, "w_%d" % i) # TODO: Remove! + interface.preload(coefficient, "w_%d" % i) kernel.coefficient_numbers = tuple(coefficient_numbers) - expressions = [] + irs = [] quadrature_indices = [] cell = integral_data.domain.ufl_cell() # Map from UFL FiniteElement objects to Index instances. This is @@ -131,20 +153,31 @@ def compile_integral(integral_data, form_data, prefix, parameters): type(quad_rule)) integrand = fem.replace_coordinates(integral.integrand(), coordinates) - expression, quadrature_index = fem.process(integral_type, - cell, quad_rule, - integrand, - interface, - index_cache) + ir, quadrature_index = fem.process(integral_type, cell, + quad_rule, integrand, + interface, index_cache) quadrature_indices.append(quadrature_index) if parameters["unroll_indexsum"]: - expression = opt.unroll_indexsum(expression, max_extent=parameters["unroll_indexsum"]) - expressions.append([(gem.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) - for e in expression]) + ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) + irs.append([(gem.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) + for e in ir]) # Sum the expressions that are part of the same restriction - expression = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*expressions)) + ir = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*irs)) + + # Look for cell orientations in the IR + kernel.oriented = False + for node in traversal(ir): + if isinstance(node, gem.Variable) and node.name == "cell_orientations": + kernel.oriented = True + break + impero_c = impero_utils.compile_gem(interface.return_variables, ir, # TODO + quadrature_indices + list(interface.argument_indices()), # TODO + coffee_licm=parameters["coffee_licm"], + remove_zeros=True) + + # Generate COFFEE index_names = zip(interface.argument_indices(), ['j', 'k']) # TODO if len(quadrature_indices) == 1: index_names.append((quadrature_indices[0], 'ip')) @@ -152,85 +185,16 @@ def compile_integral(integral_data, form_data, prefix, parameters): for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body, kernel.oriented = build_kernel_body(interface.return_variables, expression, # TODO - quadrature_indices + list(interface.argument_indices()), # TODO - coffee_licm=parameters["coffee_licm"], - index_names=index_names) - if body is None: - return None + body = generate_coffee(impero_c, index_names) + body.open_scope = False funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) kernel.ast = interface.construct_kernel_function(funname, body, kernel.oriented) return kernel -def build_kernel_body(return_variables, ir, prefix_ordering, coffee_licm=False, index_names=None): - ir = opt.remove_componenttensors(ir) - - # Look for cell orientations in the simplified GEM - oriented = False - for node in traversal(ir): - if isinstance(node, gem.Variable) and node.name == "cell_orientations": - oriented = True - break - - # Collect indices in a deterministic order - indices = [] - for node in traversal(ir): - if isinstance(node, gem.Indexed): - indices.extend(node.multiindex) - # The next two lines remove duplicate elements from the list, but - # preserve the ordering, i.e. all elements will appear only once, - # in the order of their first occurance in the original list. - _, unique_indices = numpy.unique(indices, return_index=True) - indices = numpy.asarray(indices)[numpy.sort(unique_indices)] - - # Build ordered index map - index_ordering = make_prefix_ordering(indices, prefix_ordering) - apply_ordering = make_index_orderer(index_ordering) - - get_indices = lambda expr: apply_ordering(expr.free_indices) - - # Build operation ordering - ops = sch.emit_operations(zip(return_variables, ir), get_indices) - - # Zero-simplification occurred - if len(ops) == 0: - return None, False - - # Drop unnecessary temporaries - ops = impero_utils.inline_temporaries(ir, ops, coffee_licm=coffee_licm) - - # Prepare ImperoC (Impero AST + other data for code generation) - impero_c = impero_utils.process(ops, get_indices) - - # Generate COFFEE - if index_names is None: - index_names = {} - body = generate_coffee(impero_c, index_names) - body.open_scope = False - return body, oriented - - def is_mesh_affine(mesh): """Tells if a mesh geometry is affine.""" affine_cells = ["interval", "triangle", "tetrahedron"] degree = mesh.ufl_coordinate_element().degree() return mesh.ufl_cell().cellname() in affine_cells and degree == 1 - - -def make_prefix_ordering(indices, prefix_ordering): - """Creates an ordering of ``indices`` which starts with those - indices in ``prefix_ordering``.""" - # Need to return deterministically ordered indices - return tuple(prefix_ordering) + tuple(k for k in indices if k not in prefix_ordering) - - -def make_index_orderer(index_ordering): - """Returns a function which given a set of indices returns those - indices in the order as they appear in ``index_ordering``.""" - idx2pos = {idx: pos for pos, idx in enumerate(index_ordering)} - - def apply_ordering(indices): - return tuple(sorted(indices, key=lambda i: idx2pos[i])) - return apply_ordering diff --git a/tsfc/impero_utils.py b/tsfc/impero_utils.py index 8aa5a92241..710b751d7d 100644 --- a/tsfc/impero_utils.py +++ b/tsfc/impero_utils.py @@ -11,10 +11,11 @@ import collections import itertools +import numpy from singledispatch import singledispatch from tsfc.node import traversal, collect_refcount -from tsfc import impero as imp +from tsfc import gem, impero as imp, optimise, scheduling # ImperoC is named tuple for C code generation. @@ -27,6 +28,89 @@ ImperoC = collections.namedtuple('ImperoC', ['tree', 'temporaries', 'declare', 'indices']) +class NoopError(Exception): + """No operations in the kernel.""" + pass + + +def compile_gem(return_variables, expressions, prefix_ordering, remove_zeros=False, coffee_licm=False): + """Compiles GEM to Impero. + + :arg return_variables: return variables for each root (type: GEM expressions) + :arg expressions: multi-root expression DAG (type: GEM expressions) + :arg prefix_ordering: outermost loop indices + :arg remove_zeros: remove zero assignment to return variables + :arg coffee_licm: trust COFFEE to do loop invariant code motion + """ + expressions = optimise.remove_componenttensors(expressions) + + # Remove zeros + if remove_zeros: + rv = [] + es = [] + for var, expr in zip(return_variables, expressions): + if not isinstance(expr, gem.Zero): + rv.append(var) + es.append(expr) + return_variables, expressions = rv, es + + # Collect indices in a deterministic order + indices = [] + for node in traversal(expressions): + if isinstance(node, gem.Indexed): + indices.extend(node.multiindex) + # The next two lines remove duplicate elements from the list, but + # preserve the ordering, i.e. all elements will appear only once, + # in the order of their first occurance in the original list. + _, unique_indices = numpy.unique(indices, return_index=True) + indices = numpy.asarray(indices)[numpy.sort(unique_indices)] + + # Build ordered index map + index_ordering = make_prefix_ordering(indices, prefix_ordering) + apply_ordering = make_index_orderer(index_ordering) + + get_indices = lambda expr: apply_ordering(expr.free_indices) + + # Build operation ordering + ops = scheduling.emit_operations(zip(return_variables, expressions), get_indices) + + # Empty kernel + if len(ops) == 0: + raise NoopError() + + # Drop unnecessary temporaries + ops = inline_temporaries(expressions, ops, coffee_licm=coffee_licm) + + # Build Impero AST + tree = make_loop_tree(ops, get_indices) + + # Collect temporaries + temporaries = collect_temporaries(ops) + + # Determine declarations + declare, indices = place_declarations(ops, tree, temporaries, get_indices) + + # Prepare ImperoC (Impero AST + other data for code generation) + return ImperoC(tree, temporaries, declare, indices) + + +def make_prefix_ordering(indices, prefix_ordering): + """Creates an ordering of ``indices`` which starts with those + indices in ``prefix_ordering``.""" + # Need to return deterministically ordered indices + return tuple(prefix_ordering) + tuple(k for k in indices if k not in prefix_ordering) + + +def make_index_orderer(index_ordering): + """Returns a function which given a set of indices returns those + indices in the order as they appear in ``index_ordering``.""" + idx2pos = {idx: pos for pos, idx in enumerate(index_ordering)} + + def apply_ordering(indices): + return tuple(sorted(indices, key=lambda i: idx2pos[i])) + return apply_ordering + + def inline_temporaries(expressions, ops, coffee_licm=False): """Inline temporaries which could be inlined without blowing up the code. @@ -59,27 +143,6 @@ def inline_temporaries(expressions, ops, coffee_licm=False): return [op for op in ops if not (isinstance(op, imp.Evaluate) and op.expression in candidates)] -def process(ops, get_indices): - """Process the scheduling of operations, builds an Impero AST + - other data which are directly used for C / COFFEE code generation. - - :arg ops: a list of Impero terminal nodes - :arg get_indices: callable mapping from GEM nodes to an ordering - of free indices - :returns: ImperoC named tuple - """ - # Build Impero AST - tree = make_loop_tree(ops, get_indices) - - # Collect temporaries - temporaries = collect_temporaries(ops) - - # Determine declarations - declare, indices = place_declarations(ops, tree, temporaries, get_indices) - - return ImperoC(tree, temporaries, declare, indices) - - def collect_temporaries(ops): """Collects GEM expressions to assign to temporaries from a list of Impero terminals.""" diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 10f8938bf1..ab7b4c1a9f 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -12,29 +12,6 @@ from tsfc.coffee import SCALAR_TYPE -class Kernel(object): - __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", - "coefficient_numbers", "__weakref__") - """A compiled Kernel object. - - :kwarg ast: The COFFEE ast for the kernel. - :kwarg integral_type: The type of integral. - :kwarg oriented: Does the kernel require cell_orientations. - :kwarg subdomain_id: What is the subdomain id for this kernel. - :kwarg coefficient_numbers: A list of which coefficients from the - form the kernel needs. - """ - def __init__(self, ast=None, integral_type=None, oriented=False, - subdomain_id=None, coefficient_numbers=()): - # Defaults - self.ast = ast - self.integral_type = integral_type - self.oriented = oriented - self.subdomain_id = subdomain_id - self.coefficient_numbers = coefficient_numbers - super(Kernel, self).__init__() - - class Interface(object): def __init__(self, integral_type, arguments): self.integral_type = integral_type diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py index 2466e33eeb..dcda247c0b 100644 --- a/tsfc/scheduling.py +++ b/tsfc/scheduling.py @@ -155,11 +155,6 @@ def emit_operations(assignments, get_indices): :returns: list of Impero terminals correctly ordered to evaluate the assignments """ - # Filter out zeros - assignments = [(variable, expression) - for variable, expression in assignments - if not isinstance(expression, gem.Zero)] - # Prepare reference counts refcount = collect_refcount([e for v, e in assignments]) From 6126ce8cc0d71b9cddacbe3bc0e957284dea0762 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 25 Feb 2016 14:17:21 +0000 Subject: [PATCH 091/809] new file ufl_utils.py --- tsfc/driver.py | 15 +-- tsfc/fem.py | 258 +------------------------------------------- tsfc/ufl_utils.py | 267 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+), 264 deletions(-) create mode 100644 tsfc/ufl_utils.py diff --git a/tsfc/driver.py b/tsfc/driver.py index 6bfa642447..34ea3b9400 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -9,7 +9,7 @@ from tsfc.quadrature import create_quadrature, QuadratureRule -from tsfc import fem, gem, optimise as opt, impero_utils +from tsfc import fem, gem, optimise as opt, impero_utils, ufl_utils from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.node import traversal @@ -99,8 +99,8 @@ def compile_integral(integral_data, form_data, prefix, parameters): interface = Interface(integral_type, form_data.preprocessed_form.arguments()) mesh = integral_data.domain - coordinates = fem.coordinate_coefficient(mesh) - if is_mesh_affine(mesh): + coordinates = ufl_utils.coordinate_coefficient(mesh) + if ufl_utils.is_element_affine(mesh.ufl_coordinate_element()): # For affine mesh geometries we prefer code generation that # composes well with optimisations. interface.preload(coordinates, "coords", mode='list_tensor') @@ -152,7 +152,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): raise ValueError("Expected to find a QuadratureRule object, not a %s" % type(quad_rule)) - integrand = fem.replace_coordinates(integral.integrand(), coordinates) + integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) ir, quadrature_index = fem.process(integral_type, cell, quad_rule, integrand, interface, index_cache) @@ -191,10 +191,3 @@ def compile_integral(integral_data, form_data, prefix, parameters): funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) kernel.ast = interface.construct_kernel_function(funname, body, kernel.oriented) return kernel - - -def is_mesh_affine(mesh): - """Tells if a mesh geometry is affine.""" - affine_cells = ["interval", "triangle", "tetrahedron"] - degree = mesh.ufl_coordinate_element().degree() - return mesh.ufl_cell().cellname() in affine_cells and degree == 1 diff --git a/tsfc/fem.py b/tsfc/fem.py index f564705e22..2466be3e0c 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -6,265 +6,27 @@ import numpy from singledispatch import singledispatch -import ufl from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction from ufl.classes import (Argument, Coefficient, FormArgument, - GeometricQuantity, QuadratureWeight, - ReferenceValue, Zero) -from ufl.classes import (Abs, CellOrientation, Expr, FloatValue, - Division, Product, ScalarValue, Sqrt) + GeometricQuantity, QuadratureWeight) from tsfc.constants import PRECISION from tsfc.fiatinterface import create_element, as_fiat_cell -from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal -from tsfc.node import MemoizerArg +from tsfc.modified_terminals import analyse_modified_terminal from tsfc import compat from tsfc import gem from tsfc import ufl2gem from tsfc import geometric +from tsfc.ufl_utils import (CollectModifiedTerminals, + ModifiedTerminalMixin, PickRestriction, + spanning_degree, simplify_abs) # FFC uses one less digits for rounding than for printing epsilon = eval("1e-%d" % (PRECISION - 1)) -class ReplaceSpatialCoordinates(MultiFunction): - """Replace SpatialCoordinate nodes with the ReferenceValue of a - Coefficient. Assumes that the coordinate element only needs - affine mapping. - - :arg coordinates: the coefficient to replace spatial coordinates with - """ - def __init__(self, coordinates): - self.coordinates = coordinates - MultiFunction.__init__(self) - - expr = MultiFunction.reuse_if_untouched - - def terminal(self, t): - return t - - def spatial_coordinate(self, o): - assert o.ufl_domain().ufl_coordinate_element().mapping() == "identity" - return ReferenceValue(self.coordinates) - - -class ModifiedTerminalMixin(object): - """Mixin to use with MultiFunctions that operate on modified - terminals.""" - - def unexpected(self, o): - assert False, "Not expected %r at this stage." % o - - # global derivates should have been pulled back - grad = unexpected - div = unexpected - curl = unexpected - - # div and curl should have been algebraically lowered - reference_div = unexpected - reference_curl = unexpected - - def _modified_terminal(self, o): - assert is_modified_terminal(o) - return self.modified_terminal(o) - - # Unlike UFL, we do not regard Indexed as a terminal modifier. - # indexed = _modified_terminal - - positive_restricted = _modified_terminal - negative_restricted = _modified_terminal - - cell_avg = _modified_terminal - facet_avg = _modified_terminal - - reference_grad = _modified_terminal - reference_value = _modified_terminal - - terminal = _modified_terminal - - -class CollectModifiedTerminals(MultiFunction, ModifiedTerminalMixin): - """Collect the modified terminals in a UFL expression. - - :arg return_list: modified terminals will be appended to this list - """ - def __init__(self, return_list): - MultiFunction.__init__(self) - self.return_list = return_list - - def expr(self, o, *ops): - pass # operands visited - - def indexed(self, o, *ops): - pass # not a terminal modifier - - def multi_index(self, o): - pass # ignore - - def modified_terminal(self, o): - self.return_list.append(o) - - -class PickRestriction(MultiFunction, ModifiedTerminalMixin): - """Pick out parts of an expression with specified restrictions on - the arguments. - - :arg test: The restriction on the test function. - :arg trial: The restriction on the trial function. - - Returns those parts of the expression that have the requested - restrictions, or else :class:`ufl.classes.Zero` if no such part - exists. - """ - def __init__(self, test=None, trial=None): - self.restrictions = {0: test, 1: trial} - MultiFunction.__init__(self) - - expr = MultiFunction.reuse_if_untouched - - def multi_index(self, o): - return o - - def modified_terminal(self, o): - mt = analyse_modified_terminal(o) - t = mt.terminal - r = mt.restriction - if isinstance(t, Argument) and r != self.restrictions[t.number()]: - return Zero(o.ufl_shape, o.ufl_free_indices, o.ufl_index_dimensions) - else: - return o - - -def _spanning_degree(cell, degree): - if cell is None: - assert degree == 0 - return degree - elif cell.cellname() in ["interval", "triangle", "tetrahedron"]: - return degree - elif cell.cellname() == "quadrilateral": - # TODO: Tensor-product space assumed - return 2 * degree - elif isinstance(cell, ufl.TensorProductCell): - try: - # A component cell might be a quadrilateral, so recurse. - return sum(_spanning_degree(sub_cell, d) - for sub_cell, d in zip(cell.sub_cells(), degree)) - except TypeError: - assert degree == 0 - return 0 - else: - raise ValueError("Unknown cell %s" % cell.cellname()) - - -def spanning_degree(element): - """Determine the degree of the polynomial space spanning an element. - - :arg element: The element to determine the degree of. - - .. warning:: - - For non-simplex elements, this assumes a tensor-product - space. - """ - return _spanning_degree(element.cell(), element.degree()) - - -def ufl_reuse_if_untouched(o, *ops): - """Reuse object if operands are the same objects.""" - if all(a is b for a, b in zip(o.ufl_operands, ops)): - return o - else: - return o._ufl_expr_reconstruct_(*ops) - - -@singledispatch -def _simplify_abs(o, self, in_abs): - """Single-dispatch function to simplify absolute values. - - :arg o: UFL node - :arg self: Callback handler for recursion - :arg in_abs: Is ``o`` inside an absolute value? - - When ``in_abs`` we must return a non-negative value, potentially - by wrapping the returned node with ``Abs``. - """ - raise AssertionError("UFL node expected, not %s" % type(o)) - - -@_simplify_abs.register(Expr) # noqa -def _(o, self, in_abs): - # General case, only wrap the outer expression (if necessary) - operands = [self(op, False) for op in o.ufl_operands] - result = ufl_reuse_if_untouched(o, *operands) - if in_abs: - result = Abs(result) - return result - - -@_simplify_abs.register(Sqrt) # noqa -def _(o, self, in_abs): - # Square root is always non-negative - return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) - - -@_simplify_abs.register(ScalarValue) # noqa -def _(o, self, in_abs): - if not in_abs: - return o - # Inline abs(constant) - return ufl.as_ufl(abs(o._value)) - - -@_simplify_abs.register(CellOrientation) # noqa -def _(o, self, in_abs): - if not in_abs: - return o - # Cell orientation is +-1 - return FloatValue(1) - - -@_simplify_abs.register(Division) # noqa -@_simplify_abs.register(Product) -def _(o, self, in_abs): - if not in_abs: - # Just reconstruct - ops = [self(op, False) for op in o.ufl_operands] - return ufl_reuse_if_untouched(o, *ops) - - # Visit children, distributing Abs - ops = [self(op, True) for op in o.ufl_operands] - - # Strip Abs off again (we will put it outside now) - stripped = False - strip_ops = [] - for op in ops: - if isinstance(op, Abs): - stripped = True - strip_ops.append(op.ufl_operands[0]) - else: - strip_ops.append(op) - - # Rebuild, and wrap with Abs if necessary - result = ufl_reuse_if_untouched(o, *strip_ops) - if stripped: - result = Abs(result) - return result - - -@_simplify_abs.register(Abs) # noqa -def _(o, self, in_abs): - return self(o.ufl_operands[0], True) - - -def simplify_abs(expression): - """Simplify absolute values in a UFL expression. Its primary - purpose is to "neutralise" CellOrientation nodes that are - surrounded by absolute values and thus not at all necessary.""" - return MemoizerArg(_simplify_abs)(expression, False) - - def _tabulate(ufl_element, order, points): """Ask FIAT to tabulate ``points`` up to order ``order``, then rearranges the result into a series of ``(c, D, table)`` tuples, @@ -540,16 +302,6 @@ def callback(key): return iterate_shape(mt, callback) -def coordinate_coefficient(domain): - """Create a fake coordinate coefficient for a domain.""" - return ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) - - -def replace_coordinates(integrand, coordinate_coefficient): - """Replace SpatialCoordinate nodes with Coefficients.""" - return map_expr_dag(ReplaceSpatialCoordinates(coordinate_coefficient), integrand) - - def process(integral_type, cell, quadrature_rule, integrand, interface, index_cache): # Abs-simplification integrand = simplify_abs(integrand) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py new file mode 100644 index 0000000000..b834cb1d28 --- /dev/null +++ b/tsfc/ufl_utils.py @@ -0,0 +1,267 @@ +"""Utilities for preprocessing UFL objects.""" + +from __future__ import absolute_import + +from singledispatch import singledispatch + +import ufl +from ufl.corealg.map_dag import map_expr_dag +from ufl.corealg.multifunction import MultiFunction +from ufl.classes import (Argument, ReferenceValue, Zero) +from ufl.classes import (Abs, CellOrientation, Expr, FloatValue, + Division, Product, ScalarValue, Sqrt) + +from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal +from tsfc.node import MemoizerArg + + +def is_element_affine(ufl_element): + """Tells if a UFL element is affine.""" + affine_cells = ["interval", "triangle", "tetrahedron"] + return ufl_element.cell().cellname() in affine_cells and ufl_element.degree() == 1 + + +class ReplaceSpatialCoordinates(MultiFunction): + """Replace SpatialCoordinate nodes with the ReferenceValue of a + Coefficient. Assumes that the coordinate element only needs + affine mapping. + + :arg coordinates: the coefficient to replace spatial coordinates with + """ + def __init__(self, coordinates): + self.coordinates = coordinates + MultiFunction.__init__(self) + + expr = MultiFunction.reuse_if_untouched + + def terminal(self, t): + return t + + def spatial_coordinate(self, o): + assert o.ufl_domain().ufl_coordinate_element().mapping() == "identity" + return ReferenceValue(self.coordinates) + + +def replace_coordinates(integrand, coordinate_coefficient): + """Replace SpatialCoordinate nodes with Coefficients.""" + return map_expr_dag(ReplaceSpatialCoordinates(coordinate_coefficient), integrand) + + +def coordinate_coefficient(domain): + """Create a fake coordinate coefficient for a domain.""" + return ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) + + +class ModifiedTerminalMixin(object): + """Mixin to use with MultiFunctions that operate on modified + terminals.""" + + def unexpected(self, o): + assert False, "Not expected %r at this stage." % o + + # global derivates should have been pulled back + grad = unexpected + div = unexpected + curl = unexpected + + # div and curl should have been algebraically lowered + reference_div = unexpected + reference_curl = unexpected + + def _modified_terminal(self, o): + assert is_modified_terminal(o) + return self.modified_terminal(o) + + # Unlike UFL, we do not regard Indexed as a terminal modifier. + # indexed = _modified_terminal + + positive_restricted = _modified_terminal + negative_restricted = _modified_terminal + + cell_avg = _modified_terminal + facet_avg = _modified_terminal + + reference_grad = _modified_terminal + reference_value = _modified_terminal + + terminal = _modified_terminal + + +class CollectModifiedTerminals(MultiFunction, ModifiedTerminalMixin): + """Collect the modified terminals in a UFL expression. + + :arg return_list: modified terminals will be appended to this list + """ + def __init__(self, return_list): + MultiFunction.__init__(self) + self.return_list = return_list + + def expr(self, o, *ops): + pass # operands visited + + def indexed(self, o, *ops): + pass # not a terminal modifier + + def multi_index(self, o): + pass # ignore + + def modified_terminal(self, o): + self.return_list.append(o) + + +class PickRestriction(MultiFunction, ModifiedTerminalMixin): + """Pick out parts of an expression with specified restrictions on + the arguments. + + :arg test: The restriction on the test function. + :arg trial: The restriction on the trial function. + + Returns those parts of the expression that have the requested + restrictions, or else :class:`ufl.classes.Zero` if no such part + exists. + """ + def __init__(self, test=None, trial=None): + self.restrictions = {0: test, 1: trial} + MultiFunction.__init__(self) + + expr = MultiFunction.reuse_if_untouched + + def multi_index(self, o): + return o + + def modified_terminal(self, o): + mt = analyse_modified_terminal(o) + t = mt.terminal + r = mt.restriction + if isinstance(t, Argument) and r != self.restrictions[t.number()]: + return Zero(o.ufl_shape, o.ufl_free_indices, o.ufl_index_dimensions) + else: + return o + + +def _spanning_degree(cell, degree): + if cell is None: + assert degree == 0 + return degree + elif cell.cellname() in ["interval", "triangle", "tetrahedron"]: + return degree + elif cell.cellname() == "quadrilateral": + # TODO: Tensor-product space assumed + return 2 * degree + elif isinstance(cell, ufl.TensorProductCell): + try: + # A component cell might be a quadrilateral, so recurse. + return sum(_spanning_degree(sub_cell, d) + for sub_cell, d in zip(cell.sub_cells(), degree)) + except TypeError: + assert degree == 0 + return 0 + else: + raise ValueError("Unknown cell %s" % cell.cellname()) + + +def spanning_degree(element): + """Determine the degree of the polynomial space spanning an element. + + :arg element: The element to determine the degree of. + + .. warning:: + + For non-simplex elements, this assumes a tensor-product + space. + """ + return _spanning_degree(element.cell(), element.degree()) + + +def ufl_reuse_if_untouched(o, *ops): + """Reuse object if operands are the same objects.""" + if all(a is b for a, b in zip(o.ufl_operands, ops)): + return o + else: + return o._ufl_expr_reconstruct_(*ops) + + +@singledispatch +def _simplify_abs(o, self, in_abs): + """Single-dispatch function to simplify absolute values. + + :arg o: UFL node + :arg self: Callback handler for recursion + :arg in_abs: Is ``o`` inside an absolute value? + + When ``in_abs`` we must return a non-negative value, potentially + by wrapping the returned node with ``Abs``. + """ + raise AssertionError("UFL node expected, not %s" % type(o)) + + +@_simplify_abs.register(Expr) +def _simplify_abs_expr(o, self, in_abs): + # General case, only wrap the outer expression (if necessary) + operands = [self(op, False) for op in o.ufl_operands] + result = ufl_reuse_if_untouched(o, *operands) + if in_abs: + result = Abs(result) + return result + + +@_simplify_abs.register(Sqrt) +def _simplify_abs_sqrt(o, self, in_abs): + # Square root is always non-negative + return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) + + +@_simplify_abs.register(ScalarValue) +def _simplify_abs_(o, self, in_abs): + if not in_abs: + return o + # Inline abs(constant) + return ufl.as_ufl(abs(o._value)) + + +@_simplify_abs.register(CellOrientation) +def _simplify_abs_cellorientation(o, self, in_abs): + if not in_abs: + return o + # Cell orientation is +-1 + return FloatValue(1) + + +@_simplify_abs.register(Division) +@_simplify_abs.register(Product) +def _simplify_abs_product(o, self, in_abs): + if not in_abs: + # Just reconstruct + ops = [self(op, False) for op in o.ufl_operands] + return ufl_reuse_if_untouched(o, *ops) + + # Visit children, distributing Abs + ops = [self(op, True) for op in o.ufl_operands] + + # Strip Abs off again (we will put it outside now) + stripped = False + strip_ops = [] + for op in ops: + if isinstance(op, Abs): + stripped = True + strip_ops.append(op.ufl_operands[0]) + else: + strip_ops.append(op) + + # Rebuild, and wrap with Abs if necessary + result = ufl_reuse_if_untouched(o, *strip_ops) + if stripped: + result = Abs(result) + return result + + +@_simplify_abs.register(Abs) +def _simplify_abs_abs(o, self, in_abs): + return self(o.ufl_operands[0], True) + + +def simplify_abs(expression): + """Simplify absolute values in a UFL expression. Its primary + purpose is to "neutralise" CellOrientation nodes that are + surrounded by absolute values and thus not at all necessary.""" + return MemoizerArg(_simplify_abs)(expression, False) From bc082dc5668e965e5b7e8362fb8de38a20b23871 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 18 Mar 2016 11:38:18 +0000 Subject: [PATCH 092/809] further separation of kernel interface --- tsfc/driver.py | 100 ++++++++++++------------------- tsfc/fem.py | 28 ++++----- tsfc/kernel_interface.py | 123 ++++++++++++++++++++++++++------------- 3 files changed, 134 insertions(+), 117 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 34ea3b9400..edb5c2e22e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -13,30 +13,7 @@ from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.node import traversal -from tsfc.kernel_interface import Interface - - -class Kernel(object): - __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", - "coefficient_numbers", "__weakref__") - """A compiled Kernel object. - - :kwarg ast: The COFFEE ast for the kernel. - :kwarg integral_type: The type of integral. - :kwarg oriented: Does the kernel require cell_orientations. - :kwarg subdomain_id: What is the subdomain id for this kernel. - :kwarg coefficient_numbers: A list of which coefficients from the - form the kernel needs. - """ - def __init__(self, ast=None, integral_type=None, oriented=False, - subdomain_id=None, coefficient_numbers=()): - # Defaults - self.ast = ast - self.integral_type = integral_type - self.oriented = oriented - self.subdomain_id = subdomain_id - self.coefficient_numbers = coefficient_numbers - super(Kernel, self).__init__() +from tsfc.kernel_interface import Interface as KernelInterface def compile_form(form, prefix="form", parameters=None): @@ -95,45 +72,36 @@ def compile_integral(integral_data, form_data, prefix, parameters): del parameters["quadrature_rule"] integral_type = integral_data.integral_type - kernel = Kernel(integral_type=integral_type, subdomain_id=integral_data.subdomain_id) - interface = Interface(integral_type, form_data.preprocessed_form.arguments()) - mesh = integral_data.domain + cell = integral_data.domain.ufl_cell() + arguments = form_data.preprocessed_form.arguments() + + argument_indices = tuple(gem.Index(name=name) for arg, name in zip(arguments, ['j', 'k'])) + quadrature_indices = [] + + interface = KernelInterface(integral_type, integral_data.subdomain_id) + return_variables = interface.set_arguments(arguments, argument_indices) + coordinates = ufl_utils.coordinate_coefficient(mesh) if ufl_utils.is_element_affine(mesh.ufl_coordinate_element()): # For affine mesh geometries we prefer code generation that # composes well with optimisations. - interface.preload(coordinates, "coords", mode='list_tensor') + interface.set_coordinates(coordinates, "coords", mode='list_tensor') else: # Otherwise we use the approach that might be faster (?) - interface.preload(coordinates, "coords") - - coefficient_numbers = [] - # enabled_coefficients is a boolean array that indicates which of - # reduced_coefficients the integral requires. - for i, on in enumerate(integral_data.enabled_coefficients): - if not on: - continue - coefficient = form_data.reduced_coefficients[i] - # This is which coefficient in the original form the current - # coefficient is. - # Consider f*v*dx + g*v*ds, the full form contains two - # coefficients, but each integral only requires one. - coefficient_numbers.append(form_data.original_coefficient_positions[i]) - interface.preload(coefficient, "w_%d" % i) - - kernel.coefficient_numbers = tuple(coefficient_numbers) + interface.set_coordinates(coordinates, "coords") + + interface.set_coefficients(integral_data, form_data) - irs = [] - quadrature_indices = [] - cell = integral_data.domain.ufl_cell() # Map from UFL FiniteElement objects to Index instances. This is # so we reuse Index instances when evaluating the same coefficient # multiple times with the same table. Occurs, for example, if we # have multiple integrals here (and the affine coordinate # evaluation can be hoisted). index_cache = collections.defaultdict(gem.Index) - for i, integral in enumerate(integral_data.integrals): + + irs = [] + for integral in integral_data.integrals: params = {} # Record per-integral parameters params.update(integral.metadata()) @@ -153,32 +121,38 @@ def compile_integral(integral_data, form_data, prefix, parameters): type(quad_rule)) integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) - ir, quadrature_index = fem.process(integral_type, cell, - quad_rule, integrand, - interface, index_cache) - quadrature_indices.append(quadrature_index) + ir = fem.process(integral_type, cell, quad_rule.points, + quad_rule.weights, argument_indices, + integrand, interface.coefficient_mapper(), + index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) - irs.append([(gem.IndexSum(e, quadrature_index) if quadrature_index in e.free_indices else e) - for e in ir]) + ir_ = [] + for e in ir: + quadrature_index = set(e.free_indices) - set(argument_indices) + if quadrature_index: + quadrature_index, = quadrature_index + quadrature_indices.append(quadrature_index) + e = gem.IndexSum(e, quadrature_index) + ir_.append(e) + irs.append(ir_) # Sum the expressions that are part of the same restriction ir = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*irs)) # Look for cell orientations in the IR - kernel.oriented = False for node in traversal(ir): if isinstance(node, gem.Variable) and node.name == "cell_orientations": - kernel.oriented = True + interface.require_cell_orientations() break - impero_c = impero_utils.compile_gem(interface.return_variables, ir, # TODO - quadrature_indices + list(interface.argument_indices()), # TODO + impero_c = impero_utils.compile_gem(return_variables, ir, + tuple(quadrature_indices) + argument_indices, coffee_licm=parameters["coffee_licm"], remove_zeros=True) # Generate COFFEE - index_names = zip(interface.argument_indices(), ['j', 'k']) # TODO + index_names = [(index, index.name) for index in argument_indices] if len(quadrature_indices) == 1: index_names.append((quadrature_indices[0], 'ip')) else: @@ -186,8 +160,6 @@ def compile_integral(integral_data, form_data, prefix, parameters): index_names.append((quadrature_index, 'ip_%d' % i)) body = generate_coffee(impero_c, index_names) - body.open_scope = False - funname = "%s_%s_integral_%s" % (prefix, integral_type, integral.subdomain_id()) - kernel.ast = interface.construct_kernel_function(funname, body, kernel.oriented) - return kernel + kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) + return interface.construct_kernel(kernel_name, body) diff --git a/tsfc/fem.py b/tsfc/fem.py index 2466be3e0c..5cd9741677 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -145,15 +145,17 @@ def __getitem__(self, key): class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): """Contains all the context necessary to translate UFL into GEM.""" - def __init__(self, tabulation_manager, weights, quadrature_index, interface, index_cache): + def __init__(self, tabulation_manager, weights, quadrature_index, + argument_indices, coefficient_mapper, index_cache): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) integral_type = tabulation_manager.integral_type + self.integral_type = integral_type + self.tabulation_manager = tabulation_manager self.weights = gem.Literal(weights) self.quadrature_index = quadrature_index - self.tabulation_manager = tabulation_manager - self.integral_type = integral_type - self.interface = interface + self.argument_indices = argument_indices + self.coefficient_mapper = coefficient_mapper self.index_cache = index_cache if integral_type in ['exterior_facet', 'exterior_facet_vert']: @@ -255,7 +257,7 @@ def _(terminal, mt, params): @translate.register(Argument) # noqa: Not actually redefinition def _(terminal, mt, params): - argument_index = params.interface.argument_indices()[terminal.number()] + argument_index = params.argument_indices[terminal.number()] def callback(key): table = params.tabulation_manager[key] @@ -272,7 +274,7 @@ def callback(key): @translate.register(Coefficient) # noqa: Not actually redefinition def _(terminal, mt, params): - kernel_arg = params.interface.gem(terminal) + kernel_arg = params.coefficient_mapper(terminal) if terminal.ufl_element().family() == 'Real': assert mt.local_derivatives == 0 @@ -302,7 +304,7 @@ def callback(key): return iterate_shape(mt, callback) -def process(integral_type, cell, quadrature_rule, integrand, interface, index_cache): +def process(integral_type, cell, points, weights, argument_indices, integrand, coefficient_mapper, index_cache): # Abs-simplification integrand = simplify_abs(integrand) @@ -319,14 +321,14 @@ def process(integral_type, cell, quadrature_rule, integrand, interface, index_ca max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(integral_type, cell, quadrature_rule.points) + tabulation_manager = TabulationManager(integral_type, cell, points) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) if integral_type.startswith("interior_facet"): expressions = [] - for rs in itertools.product(("+", "-"), repeat=len(interface.argument_indices())): + for rs in itertools.product(("+", "-"), repeat=len(argument_indices)): expressions.append(map_expr_dag(PickRestriction(*rs), integrand)) else: expressions = [integrand] @@ -334,7 +336,7 @@ def process(integral_type, cell, quadrature_rule, integrand, interface, index_ca # Translate UFL to Einstein's notation, # lowering finite element specific nodes quadrature_index = gem.Index(name='ip') - translator = Translator(tabulation_manager, - quadrature_rule.weights, quadrature_index, - interface, index_cache) - return map_expr_dags(translator, expressions), quadrature_index + translator = Translator(tabulation_manager, weights, + quadrature_index, argument_indices, + coefficient_mapper, index_cache) + return map_expr_dags(translator, expressions) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index ab7b4c1a9f..9b607ee998 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -1,7 +1,5 @@ from __future__ import absolute_import -import itertools - import numpy import coffee.base as coffee @@ -12,52 +10,95 @@ from tsfc.coffee import SCALAR_TYPE +class Kernel(object): + __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + "coefficient_numbers", "__weakref__") + """A compiled Kernel object. + + :kwarg ast: The COFFEE ast for the kernel. + :kwarg integral_type: The type of integral. + :kwarg oriented: Does the kernel require cell_orientations. + :kwarg subdomain_id: What is the subdomain id for this kernel. + :kwarg coefficient_numbers: A list of which coefficients from the + form the kernel needs. + """ + def __init__(self, ast=None, integral_type=None, oriented=False, + subdomain_id=None, coefficient_numbers=()): + # Defaults + self.ast = ast + self.integral_type = integral_type + self.oriented = oriented + self.subdomain_id = subdomain_id + self.coefficient_numbers = coefficient_numbers + super(Kernel, self).__init__() + + class Interface(object): - def __init__(self, integral_type, arguments): + def __init__(self, integral_type, subdomain_id): + self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id) + self.integral_type = integral_type self.interior_facet = integral_type.startswith("interior_facet") - funarg, prepare, expressions, finalise = prepare_arguments(self.interior_facet, arguments) + self.local_tensor = None + self.coordinates_arg = None + self.coefficient_args = [] - self.return_funarg = funarg - self.funargs = [] - self.prepare = prepare - self.return_variables = expressions # TODO - self.finalise = finalise + self.prepare = [] + self.finalise = [] - self.count = itertools.count() - self.coefficients = {} + self.coefficient_map = {} - def argument_indices(self): - return filter(lambda i: isinstance(i, gem.Index), self.return_variables[0].multiindex) + def set_arguments(self, arguments, argument_indices): + funarg, prepare, expressions, finalise = prepare_arguments( + self.interior_facet, arguments, argument_indices) - def preload(self, coefficient, name=None, mode=None): - # Do not add the same coefficient twice - assert coefficient not in self.coefficients + self.local_tensor = funarg + self.prepare.extend(prepare) + self.finalise.extend(finalise) - # Default name with counting - if name is None: - name = "w_{0}".format(next(self.count)) + return expressions - funarg, prepare, expression = prepare_coefficient(self.interior_facet, coefficient, name, mode) - self.funargs.append(funarg) + def set_coordinates(self, coefficient, name, mode=None): + funarg, prepare, expression = prepare_coefficient( + self.interior_facet, coefficient, name, mode=mode) + self.coordinates_arg = funarg self.prepare.extend(prepare) - self.coefficients[coefficient] = expression - - def gem(self, coefficient): - try: - return self.coefficients[coefficient] - except KeyError: - self.preload(coefficient) - return self.coefficients[coefficient] - - def construct_kernel_function(self, name, body, oriented): - args = [self.return_funarg] + self.funargs - if oriented: - args.insert(2, coffee.Decl("int *restrict *restrict", - coffee.Symbol("cell_orientations"), - qualifiers=["const"])) - + self.coefficient_map[coefficient] = expression + + def set_coefficients(self, integral_data, form_data): + coefficient_numbers = [] + # enabled_coefficients is a boolean array that indicates which + # of reduced_coefficients the integral requires. + for i in range(len(integral_data.enabled_coefficients)): + if integral_data.enabled_coefficients[i]: + coefficient = form_data.reduced_coefficients[i] + funarg, prepare, expression = prepare_coefficient( + self.interior_facet, coefficient, "w_%d" % i) + self.coefficient_args.append(funarg) + self.prepare.extend(prepare) + self.coefficient_map[coefficient] = expression + + # This is which coefficient in the original form the + # current coefficient is. + # Consider f*v*dx + g*v*ds, the full form contains two + # coefficients, but each integral only requires one. + coefficient_numbers.append(form_data.original_coefficient_positions[i]) + self.kernel.coefficient_numbers = tuple(coefficient_numbers) + + def coefficient_mapper(self): + return lambda coefficient: self.coefficient_map[coefficient] + + def require_cell_orientations(self): + self.kernel.oriented = True + + def construct_kernel(self, name, body): + args = [self.local_tensor, self.coordinates_arg] + if self.kernel.oriented: + args.append(coffee.Decl("int *restrict *restrict", + coffee.Symbol("cell_orientations"), + qualifiers=["const"])) + args.extend(self.coefficient_args) if self.integral_type in ["exterior_facet", "exterior_facet_vert"]: args.append(coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(1,)), @@ -67,8 +108,10 @@ def construct_kernel_function(self, name, body, oriented): coffee.Symbol("facet", rank=(2,)), qualifiers=["const"])) + body.open_scope = False body_ = coffee.Block(self.prepare + [body] + self.finalise) - return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) + self.kernel.ast = coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) + return self.kernel def prepare_coefficient(interior_facet, coefficient, name, mode=None): @@ -210,13 +253,14 @@ def prepare_coefficient(interior_facet, coefficient, name, mode=None): return funarg, [], expression -def prepare_arguments(interior_facet, arguments): +def prepare_arguments(interior_facet, arguments, indices): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. :arg interior_facet: interior facet integral? :arg arguments: UFL Arguments + :arg indices: Argument indices :returns: (funarg, prepare, expression, finalise) funarg - :class:`coffee.Decl` function argument prepare - list of COFFEE nodes to be prepended to the @@ -235,7 +279,6 @@ def prepare_arguments(interior_facet, arguments): return funarg, [], [expression], [] elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - indices = tuple(gem.Index(name=name) for i, name in zip(range(len(arguments)), ['j', 'k'])) if not interior_facet: # Not an interior facet integral From ce2137241e7dc527f8da552eea7d360db8465503 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 21 Mar 2016 15:59:12 +0000 Subject: [PATCH 093/809] make interior_facet a keyword argument --- tsfc/kernel_interface.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 9b607ee998..a9226b1805 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -51,7 +51,8 @@ def __init__(self, integral_type, subdomain_id): def set_arguments(self, arguments, argument_indices): funarg, prepare, expressions, finalise = prepare_arguments( - self.interior_facet, arguments, argument_indices) + arguments, argument_indices, + interior_facet=self.interior_facet) self.local_tensor = funarg self.prepare.extend(prepare) @@ -61,7 +62,9 @@ def set_arguments(self, arguments, argument_indices): def set_coordinates(self, coefficient, name, mode=None): funarg, prepare, expression = prepare_coefficient( - self.interior_facet, coefficient, name, mode=mode) + coefficient, name, mode=mode, + interior_facet=self.interior_facet) + self.coordinates_arg = funarg self.prepare.extend(prepare) self.coefficient_map[coefficient] = expression @@ -74,7 +77,8 @@ def set_coefficients(self, integral_data, form_data): if integral_data.enabled_coefficients[i]: coefficient = form_data.reduced_coefficients[i] funarg, prepare, expression = prepare_coefficient( - self.interior_facet, coefficient, "w_%d" % i) + coefficient, "w_%d" % i, + interior_facet=self.interior_facet) self.coefficient_args.append(funarg) self.prepare.extend(prepare) self.coefficient_map[coefficient] = expression @@ -114,16 +118,16 @@ def construct_kernel(self, name, body): return self.kernel -def prepare_coefficient(interior_facet, coefficient, name, mode=None): +def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. Mixed element Coefficients are rearranged here for interior facet integrals. - :arg interior_facet: interior facet integral? :arg coefficient: UFL Coefficient :arg name: unique name to refer to the Coefficient in the kernel :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with interior facet integrals on mixed elements + :arg interior_facet: interior facet integral? :returns: (funarg, prepare, expression) funarg - :class:`coffee.Decl` function argument prepare - list of COFFEE nodes to be prepended to the @@ -135,6 +139,7 @@ def prepare_coefficient(interior_facet, coefficient, name, mode=None): mode = 'manual_loop' assert mode in ['manual_loop', 'list_tensor'] + assert isinstance(interior_facet, bool) if coefficient.ufl_element().family() == 'Real': # Constant @@ -253,14 +258,14 @@ def prepare_coefficient(interior_facet, coefficient, name, mode=None): return funarg, [], expression -def prepare_arguments(interior_facet, arguments, indices): +def prepare_arguments(arguments, indices, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. - :arg interior_facet: interior facet integral? :arg arguments: UFL Arguments :arg indices: Argument indices + :arg interior_facet: interior facet integral? :returns: (funarg, prepare, expression, finalise) funarg - :class:`coffee.Decl` function argument prepare - list of COFFEE nodes to be prepended to the @@ -270,6 +275,7 @@ def prepare_arguments(interior_facet, arguments, indices): kernel body """ from itertools import chain, product + assert isinstance(interior_facet, bool) if len(arguments) == 0: # No arguments From a30a92be84facac5a091d3501ebe1a0804d868ab Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 21 Mar 2016 17:09:24 +0000 Subject: [PATCH 094/809] introduce kernel_interface.InterfaceBase --- tsfc/driver.py | 2 +- tsfc/kernel_interface.py | 97 ++++++++++++++++++++++------------------ 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index edb5c2e22e..ff8715d058 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -123,7 +123,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) ir = fem.process(integral_type, cell, quad_rule.points, quad_rule.weights, argument_indices, - integrand, interface.coefficient_mapper(), + integrand, interface.coefficient_mapper, index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index a9226b1805..4a338e7ded 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -33,41 +33,61 @@ def __init__(self, ast=None, integral_type=None, oriented=False, super(Kernel, self).__init__() -class Interface(object): - def __init__(self, integral_type, subdomain_id): - self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id) - - self.integral_type = integral_type - self.interior_facet = integral_type.startswith("interior_facet") - - self.local_tensor = None - self.coordinates_arg = None - self.coefficient_args = [] +class InterfaceBase(object): + def __init__(self, interior_facet=False): + assert isinstance(interior_facet, bool) + self.interior_facet = interior_facet self.prepare = [] self.finalise = [] self.coefficient_map = {} - def set_arguments(self, arguments, argument_indices): - funarg, prepare, expressions, finalise = prepare_arguments( - arguments, argument_indices, - interior_facet=self.interior_facet) + def apply_glue(self, prepare=None, finalise=None): + if prepare is not None: + self.prepare.extend(prepare) + if finalise is not None: + self.finalise.extend(finalise) - self.local_tensor = funarg - self.prepare.extend(prepare) - self.finalise.extend(finalise) + def construct_kernel(self, name, args, body): + body.open_scope = False + body_ = coffee.Block(self.prepare + [body] + self.finalise) + return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) - return expressions + @property + def coefficient_mapper(self): + return lambda coefficient: self.coefficient_map[coefficient] - def set_coordinates(self, coefficient, name, mode=None): + def arguments(self, arguments, indices): + funarg, prepare, expressions, finalise = prepare_arguments( + arguments, indices, interior_facet=self.interior_facet) + self.apply_glue(prepare, finalise) + return funarg, expressions + + def coefficient(self, coefficient, name, mode=None): funarg, prepare, expression = prepare_coefficient( coefficient, name, mode=mode, interior_facet=self.interior_facet) - - self.coordinates_arg = funarg - self.prepare.extend(prepare) + self.apply_glue(prepare) self.coefficient_map[coefficient] = expression + return funarg + + +class Interface(InterfaceBase): + def __init__(self, integral_type, subdomain_id): + super(Interface, self).__init__(integral_type.startswith("interior_facet")) + + self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id) + self.local_tensor = None + self.coordinates_arg = None + self.coefficient_args = [] + + def set_arguments(self, arguments, indices): + self.local_tensor, expressions = self.arguments(arguments, indices) + return expressions + + def set_coordinates(self, coefficient, name, mode=None): + self.coordinates_arg = self.coefficient(coefficient, name, mode) def set_coefficients(self, integral_data, form_data): coefficient_numbers = [] @@ -76,13 +96,8 @@ def set_coefficients(self, integral_data, form_data): for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: coefficient = form_data.reduced_coefficients[i] - funarg, prepare, expression = prepare_coefficient( - coefficient, "w_%d" % i, - interior_facet=self.interior_facet) - self.coefficient_args.append(funarg) - self.prepare.extend(prepare) - self.coefficient_map[coefficient] = expression - + self.coefficient_args.append( + self.coefficient(coefficient, "w_%d" % i)) # This is which coefficient in the original form the # current coefficient is. # Consider f*v*dx + g*v*ds, the full form contains two @@ -90,9 +105,6 @@ def set_coefficients(self, integral_data, form_data): coefficient_numbers.append(form_data.original_coefficient_positions[i]) self.kernel.coefficient_numbers = tuple(coefficient_numbers) - def coefficient_mapper(self): - return lambda coefficient: self.coefficient_map[coefficient] - def require_cell_orientations(self): self.kernel.oriented = True @@ -103,18 +115,16 @@ def construct_kernel(self, name, body): coffee.Symbol("cell_orientations"), qualifiers=["const"])) args.extend(self.coefficient_args) - if self.integral_type in ["exterior_facet", "exterior_facet_vert"]: + if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: args.append(coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(1,)), qualifiers=["const"])) - elif self.integral_type in ["interior_facet", "interior_facet_vert"]: + elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: args.append(coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(2,)), qualifiers=["const"])) - body.open_scope = False - body_ = coffee.Block(self.prepare + [body] + self.finalise) - self.kernel.ast = coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) + self.kernel.ast = InterfaceBase.construct_kernel(self, name, args, body) return self.kernel @@ -267,12 +277,13 @@ def prepare_arguments(arguments, indices, interior_facet=False): :arg indices: Argument indices :arg interior_facet: interior facet integral? :returns: (funarg, prepare, expression, finalise) - funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expression - GEM expression referring to the argument tensor - finalise - list of COFFEE nodes to be appended to the - kernel body + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions - GEM expressions referring to the argument + tensor + finalise - list of COFFEE nodes to be appended to the + kernel body """ from itertools import chain, product assert isinstance(interior_facet, bool) From 575e5b1ba1ac5d88576de2eed8a25a1f244d38e1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 21 Mar 2016 17:49:50 +0000 Subject: [PATCH 095/809] streamline a few things --- tsfc/driver.py | 30 ++++++++++++------------------ tsfc/fem.py | 4 ++-- tsfc/kernel_interface.py | 18 +++++++++++++++--- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index ff8715d058..f1f24ab3dd 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -12,8 +12,7 @@ from tsfc import fem, gem, optimise as opt, impero_utils, ufl_utils from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters -from tsfc.node import traversal -from tsfc.kernel_interface import Interface as KernelInterface +from tsfc.kernel_interface import Interface as KernelInterface, needs_cell_orientations def compile_form(form, prefix="form", parameters=None): @@ -121,30 +120,25 @@ def compile_integral(integral_data, form_data, prefix, parameters): type(quad_rule)) integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) + quadrature_index = gem.Index(name='ip') + quadrature_indices.append(quadrature_index) ir = fem.process(integral_type, cell, quad_rule.points, - quad_rule.weights, argument_indices, - integrand, interface.coefficient_mapper, - index_cache) + quad_rule.weights, quadrature_index, + argument_indices, integrand, + interface.coefficient_mapper, index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) - ir_ = [] - for e in ir: - quadrature_index = set(e.free_indices) - set(argument_indices) - if quadrature_index: - quadrature_index, = quadrature_index - quadrature_indices.append(quadrature_index) - e = gem.IndexSum(e, quadrature_index) - ir_.append(e) - irs.append(ir_) + irs.append([(gem.IndexSum(expr, quadrature_index) + if quadrature_index in expr.free_indices + else expr) + for expr in ir]) # Sum the expressions that are part of the same restriction ir = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*irs)) # Look for cell orientations in the IR - for node in traversal(ir): - if isinstance(node, gem.Variable) and node.name == "cell_orientations": - interface.require_cell_orientations() - break + if needs_cell_orientations(ir): + interface.require_cell_orientations() impero_c = impero_utils.compile_gem(return_variables, ir, tuple(quadrature_indices) + argument_indices, diff --git a/tsfc/fem.py b/tsfc/fem.py index 5cd9741677..681c5e6f79 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -304,7 +304,8 @@ def callback(key): return iterate_shape(mt, callback) -def process(integral_type, cell, points, weights, argument_indices, integrand, coefficient_mapper, index_cache): +def process(integral_type, cell, points, weights, quadrature_index, + argument_indices, integrand, coefficient_mapper, index_cache): # Abs-simplification integrand = simplify_abs(integrand) @@ -335,7 +336,6 @@ def process(integral_type, cell, points, weights, argument_indices, integrand, c # Translate UFL to Einstein's notation, # lowering finite element specific nodes - quadrature_index = gem.Index(name='ip') translator = Translator(tabulation_manager, weights, quadrature_index, argument_indices, coefficient_mapper, index_cache) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 4a338e7ded..79f94fd652 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -7,6 +7,7 @@ from tsfc import gem from tsfc.fiatinterface import create_element from tsfc.mixedelement import MixedElement +from tsfc.node import traversal from tsfc.coffee import SCALAR_TYPE @@ -111,9 +112,7 @@ def require_cell_orientations(self): def construct_kernel(self, name, body): args = [self.local_tensor, self.coordinates_arg] if self.kernel.oriented: - args.append(coffee.Decl("int *restrict *restrict", - coffee.Symbol("cell_orientations"), - qualifiers=["const"])) + args.append(cell_orientations_coffee_arg) args.extend(self.coefficient_args) if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: args.append(coffee.Decl("unsigned int", @@ -381,3 +380,16 @@ def coffee_for(index, extent, body): coffee.Less(index, extent), coffee.Incr(index, 1), body) + + +def needs_cell_orientations(ir): + for node in traversal(ir): + if isinstance(node, gem.Variable) and node.name == "cell_orientations": + return True + return False + + +cell_orientations_coffee_arg = coffee.Decl( + "int *restrict *restrict", + coffee.Symbol("cell_orientations"), + qualifiers=["const"]) From f3c494badcb4a089a50f932d127f0f4e3e1b3ae5 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 22 Mar 2016 14:07:20 +0000 Subject: [PATCH 096/809] docstrings and renaming --- tsfc/driver.py | 18 ++++----- tsfc/kernel_interface.py | 80 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index f1f24ab3dd..423cf6d7d5 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -12,7 +12,7 @@ from tsfc import fem, gem, optimise as opt, impero_utils, ufl_utils from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters -from tsfc.kernel_interface import Interface as KernelInterface, needs_cell_orientations +from tsfc.kernel_interface import KernelBuilder, needs_cell_orientations def compile_form(form, prefix="form", parameters=None): @@ -78,19 +78,19 @@ def compile_integral(integral_data, form_data, prefix, parameters): argument_indices = tuple(gem.Index(name=name) for arg, name in zip(arguments, ['j', 'k'])) quadrature_indices = [] - interface = KernelInterface(integral_type, integral_data.subdomain_id) - return_variables = interface.set_arguments(arguments, argument_indices) + builder = KernelBuilder(integral_type, integral_data.subdomain_id) + return_variables = builder.set_arguments(arguments, argument_indices) coordinates = ufl_utils.coordinate_coefficient(mesh) if ufl_utils.is_element_affine(mesh.ufl_coordinate_element()): # For affine mesh geometries we prefer code generation that # composes well with optimisations. - interface.set_coordinates(coordinates, "coords", mode='list_tensor') + builder.set_coordinates(coordinates, "coords", mode='list_tensor') else: # Otherwise we use the approach that might be faster (?) - interface.set_coordinates(coordinates, "coords") + builder.set_coordinates(coordinates, "coords") - interface.set_coefficients(integral_data, form_data) + builder.set_coefficients(integral_data, form_data) # Map from UFL FiniteElement objects to Index instances. This is # so we reuse Index instances when evaluating the same coefficient @@ -125,7 +125,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): ir = fem.process(integral_type, cell, quad_rule.points, quad_rule.weights, quadrature_index, argument_indices, integrand, - interface.coefficient_mapper, index_cache) + builder.coefficient_mapper, index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) irs.append([(gem.IndexSum(expr, quadrature_index) @@ -138,7 +138,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): # Look for cell orientations in the IR if needs_cell_orientations(ir): - interface.require_cell_orientations() + builder.require_cell_orientations() impero_c = impero_utils.compile_gem(return_variables, ir, tuple(quadrature_indices) + argument_indices, @@ -156,4 +156,4 @@ def compile_integral(integral_data, form_data, prefix, parameters): body = generate_coffee(impero_c, index_names) kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) - return interface.construct_kernel(kernel_name, body) + return builder.construct_kernel(kernel_name, body) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 79f94fd652..c6731de5b9 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -34,8 +34,14 @@ def __init__(self, ast=None, integral_type=None, oriented=False, super(Kernel, self).__init__() -class InterfaceBase(object): +class KernelBuilderBase(object): + """Helper class for building local assembly kernels.""" + def __init__(self, interior_facet=False): + """Initialise a kernel builder. + + :arg interior_facet: kernel accesses two cells + """ assert isinstance(interior_facet, bool) self.interior_facet = interior_facet @@ -45,27 +51,60 @@ def __init__(self, interior_facet=False): self.coefficient_map = {} def apply_glue(self, prepare=None, finalise=None): + """Append glue code for operations that are not handled in the + GEM abstraction. + + Current uses: mixed interior facet mess + + :arg prepare: code snippets to be prepended to the kernel + :arg finalise: code snippets to be appended to the kernel + """ if prepare is not None: self.prepare.extend(prepare) if finalise is not None: self.finalise.extend(finalise) def construct_kernel(self, name, args, body): + """Construct a COFFEE function declaration with the + accumulated glue code. + + :arg name: function name + :arg args: function argument list + :arg body: function body (:class:`coffee.Block` node) + :returns: :class:`coffee.FunDecl` object + """ body.open_scope = False body_ = coffee.Block(self.prepare + [body] + self.finalise) return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) @property def coefficient_mapper(self): + """A function that maps :class:`ufl.Coefficient`s to GEM + expressions.""" return lambda coefficient: self.coefficient_map[coefficient] def arguments(self, arguments, indices): + """Prepare arguments. Adds glue code for the arguments. + + :arg arguments: :class:`ufl.Argument`s + :arg indices: GEM argument indices + :returns: COFFEE function argument and GEM expression + representing the argument tensor + """ funarg, prepare, expressions, finalise = prepare_arguments( arguments, indices, interior_facet=self.interior_facet) self.apply_glue(prepare, finalise) return funarg, expressions def coefficient(self, coefficient, name, mode=None): + """Prepare a coefficient. Adds glue code for the coefficient + and adds the coefficient to the coefficient map. + + :arg coefficient: :class:`ufl.Coefficient` + :arg name: coefficient name + :arg mode: see :func:`prepare_coefficient` + :returns: COFFEE function argument for the coefficient + """ funarg, prepare, expression = prepare_coefficient( coefficient, name, mode=mode, interior_facet=self.interior_facet) @@ -74,9 +113,12 @@ def coefficient(self, coefficient, name, mode=None): return funarg -class Interface(InterfaceBase): +class KernelBuilder(KernelBuilderBase): + """Helper class for building a :class:`Kernel` object.""" + def __init__(self, integral_type, subdomain_id): - super(Interface, self).__init__(integral_type.startswith("interior_facet")) + """Initialise a kernel builder.""" + super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id) self.local_tensor = None @@ -84,13 +126,30 @@ def __init__(self, integral_type, subdomain_id): self.coefficient_args = [] def set_arguments(self, arguments, indices): + """Process arguments. + + :arg arguments: :class:`ufl.Argument`s + :arg indices: GEM argument indices + :returns: GEM expression representing the return variable + """ self.local_tensor, expressions = self.arguments(arguments, indices) return expressions def set_coordinates(self, coefficient, name, mode=None): + """Prepare the coordinate field. + + :arg coefficient: :class:`ufl.Coefficient` + :arg name: coordinate coefficient name + :arg mode: see :func:`prepare_coefficient` + """ self.coordinates_arg = self.coefficient(coefficient, name, mode) def set_coefficients(self, integral_data, form_data): + """Prepare the coefficients of the form. + + :arg integral_data: UFL integral data + :arg form_data: UFL form data + """ coefficient_numbers = [] # enabled_coefficients is a boolean array that indicates which # of reduced_coefficients the integral requires. @@ -107,9 +166,19 @@ def set_coefficients(self, integral_data, form_data): self.kernel.coefficient_numbers = tuple(coefficient_numbers) def require_cell_orientations(self): + """Set that the kernel requires cell orientations.""" self.kernel.oriented = True def construct_kernel(self, name, body): + """Construct a fully built :class:`Kernel`. + + This function contains the logic for building the argument + list for assembly kernels. + + :arg name: function name + :arg body: function body (:class:`coffee.Block` node) + :returns: :class:`Kernel` object + """ args = [self.local_tensor, self.coordinates_arg] if self.kernel.oriented: args.append(cell_orientations_coffee_arg) @@ -123,7 +192,7 @@ def construct_kernel(self, name, body): coffee.Symbol("facet", rank=(2,)), qualifiers=["const"])) - self.kernel.ast = InterfaceBase.construct_kernel(self, name, args, body) + self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel @@ -383,6 +452,8 @@ def coffee_for(index, extent, body): def needs_cell_orientations(ir): + """Does a multi-root GEM expression DAG references cell + orientations?""" for node in traversal(ir): if isinstance(node, gem.Variable) and node.name == "cell_orientations": return True @@ -393,3 +464,4 @@ def needs_cell_orientations(ir): "int *restrict *restrict", coffee.Symbol("cell_orientations"), qualifiers=["const"]) +"""COFFEE function argument for cell orientations""" From 49947a130908253ffc6dc51cdf2b21e245cb5fd7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 22 Mar 2016 14:45:24 +0000 Subject: [PATCH 097/809] fix for PR review comment --- tsfc/kernel_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index c6731de5b9..65fbc30732 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -73,8 +73,8 @@ def construct_kernel(self, name, args, body): :arg body: function body (:class:`coffee.Block` node) :returns: :class:`coffee.FunDecl` object """ - body.open_scope = False - body_ = coffee.Block(self.prepare + [body] + self.finalise) + assert isinstance(body, coffee.Block) + body_ = coffee.Block(self.prepare + body.children + self.finalise) return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) @property From 79a4ff8cdc2e230ac055dc42f0bb05b30904cd2b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 8 Apr 2016 19:37:49 +0100 Subject: [PATCH 098/809] spin off gem from tsfc --- tests/test_codegen.py | 4 +- tsfc/coffee.py | 3 +- tsfc/driver.py | 5 +- tsfc/fem.py | 3 +- tsfc/gem.py | 506 --------------------------------------- tsfc/geometric.py | 3 +- tsfc/impero.py | 152 ------------ tsfc/impero_utils.py | 323 ------------------------- tsfc/interpreter.py | 305 ----------------------- tsfc/kernel_interface.py | 5 +- tsfc/node.py | 217 ----------------- tsfc/optimise.py | 95 -------- tsfc/scheduling.py | 195 --------------- tsfc/ufl2gem.py | 10 +- tsfc/ufl_utils.py | 3 +- 15 files changed, 21 insertions(+), 1808 deletions(-) delete mode 100644 tsfc/gem.py delete mode 100644 tsfc/impero.py delete mode 100644 tsfc/impero_utils.py delete mode 100644 tsfc/interpreter.py delete mode 100644 tsfc/node.py delete mode 100644 tsfc/optimise.py delete mode 100644 tsfc/scheduling.py diff --git a/tests/test_codegen.py b/tests/test_codegen.py index d7889f162e..3347a07858 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1,5 +1,5 @@ -from tsfc import impero_utils -from tsfc.gem import Index, Indexed, IndexSum, Product, Variable +from gem import impero_utils +from gem.gem import Index, Indexed, IndexSum, Product, Variable def test_loop_fusion(): diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 0b495cd6ec..e81881ae0d 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -13,7 +13,8 @@ import coffee.base as coffee -from tsfc import gem, impero as imp +from gem import gem, impero as imp + from tsfc.constants import SCALAR_TYPE, PRECISION diff --git a/tsfc/driver.py b/tsfc/driver.py index 423cf6d7d5..f4a64d610f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -7,12 +7,13 @@ from ufl.algorithms import compute_form_data from ufl.log import GREEN -from tsfc.quadrature import create_quadrature, QuadratureRule +from gem import gem, optimise as opt, impero_utils -from tsfc import fem, gem, optimise as opt, impero_utils, ufl_utils +from tsfc import fem, ufl_utils from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.kernel_interface import KernelBuilder, needs_cell_orientations +from tsfc.quadrature import create_quadrature, QuadratureRule def compile_form(form, prefix="form", parameters=None): diff --git a/tsfc/fem.py b/tsfc/fem.py index 681c5e6f79..de5af5b860 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -11,11 +11,12 @@ from ufl.classes import (Argument, Coefficient, FormArgument, GeometricQuantity, QuadratureWeight) +from gem import gem + from tsfc.constants import PRECISION from tsfc.fiatinterface import create_element, as_fiat_cell from tsfc.modified_terminals import analyse_modified_terminal from tsfc import compat -from tsfc import gem from tsfc import ufl2gem from tsfc import geometric from tsfc.ufl_utils import (CollectModifiedTerminals, diff --git a/tsfc/gem.py b/tsfc/gem.py deleted file mode 100644 index 38798a1a7a..0000000000 --- a/tsfc/gem.py +++ /dev/null @@ -1,506 +0,0 @@ -"""GEM is the intermediate language of TSFC for describing -tensor-valued mathematical expressions and tensor operations. -It is similar to Einstein's notation. - -Its design was heavily inspired by UFL, with some major differences: - - GEM has got nothing FEM-specific. - - In UFL free indices are just unrolled shape, thus UFL is very - restrictive about operations on expressions with different sets of - free indices. GEM is much more relaxed about free indices. - -Similarly to UFL, all GEM nodes have 'shape' and 'free_indices' -attributes / properties. Unlike UFL, however, index extents live on -the Index objects in GEM, not on all the nodes that have those free -indices. -""" - -from __future__ import absolute_import - -from itertools import chain -from numpy import asarray, unique - -from tsfc.node import Node as NodeBase - - -class NodeMeta(type): - """Metaclass of GEM nodes. - - When a GEM node is constructed, this metaclass automatically - collects its free indices if 'free_indices' has not been set yet. - """ - - def __call__(self, *args, **kwargs): - # Create and initialise object - obj = super(NodeMeta, self).__call__(*args, **kwargs) - - # Set free_indices if not set already - if not hasattr(obj, 'free_indices'): - cfi = list(chain(*[c.free_indices for c in obj.children])) - obj.free_indices = tuple(unique(cfi)) - - return obj - - -class Node(NodeBase): - """Abstract GEM node class.""" - - __metaclass__ = NodeMeta - - __slots__ = ('free_indices') - - def is_equal(self, other): - """Common subexpression eliminating equality predicate. - - When two (sub)expressions are equal, the children of one - object are reassigned to the children of the other, so some - duplicated subexpressions are eliminated. - """ - result = NodeBase.is_equal(self, other) - if result: - self.children = other.children - return result - - -class Terminal(Node): - """Abstract class for terminal GEM nodes.""" - - __slots__ = () - - children = () - - is_equal = NodeBase.is_equal - - -class Scalar(Node): - """Abstract class for scalar-valued GEM nodes.""" - - __slots__ = () - - shape = () - - -class Zero(Terminal): - """Symbolic zero tensor""" - - __slots__ = ('shape',) - __front__ = ('shape',) - - def __init__(self, shape=()): - self.shape = shape - - @property - def value(self): - assert not self.shape - return 0.0 - - -class Literal(Terminal): - """Tensor-valued constant""" - - __slots__ = ('array',) - __front__ = ('array',) - - def __new__(cls, array): - array = asarray(array) - if (array == 0).all(): - # All zeros, make symbolic zero - return Zero(array.shape) - else: - return super(Literal, cls).__new__(cls) - - def __init__(self, array): - self.array = asarray(array, dtype=float) - - def is_equal(self, other): - if type(self) != type(other): - return False - if self.shape != other.shape: - return False - return tuple(self.array.flat) == tuple(other.array.flat) - - def get_hash(self): - return hash((type(self), self.shape, tuple(self.array.flat))) - - @property - def value(self): - return float(self.array) - - @property - def shape(self): - return self.array.shape - - -class Variable(Terminal): - """Symbolic variable tensor""" - - __slots__ = ('name', 'shape') - __front__ = ('name', 'shape') - - def __init__(self, name, shape): - self.name = name - self.shape = shape - - -class Sum(Scalar): - __slots__ = ('children',) - - def __new__(cls, a, b): - assert not a.shape - assert not b.shape - - # Zero folding - if isinstance(a, Zero): - return b - elif isinstance(b, Zero): - return a - - self = super(Sum, cls).__new__(cls) - self.children = a, b - return self - - -class Product(Scalar): - __slots__ = ('children',) - - def __new__(cls, a, b): - assert not a.shape - assert not b.shape - - # Zero folding - if isinstance(a, Zero) or isinstance(b, Zero): - return Zero() - - self = super(Product, cls).__new__(cls) - self.children = a, b - return self - - -class Division(Scalar): - __slots__ = ('children',) - - def __new__(cls, a, b): - assert not a.shape - assert not b.shape - - # Zero folding - if isinstance(b, Zero): - raise ValueError("division by zero") - if isinstance(a, Zero): - return Zero() - - self = super(Division, cls).__new__(cls) - self.children = a, b - return self - - -class Power(Scalar): - __slots__ = ('children',) - - def __new__(cls, base, exponent): - assert not base.shape - assert not exponent.shape - - # Zero folding - if isinstance(base, Zero): - if isinstance(exponent, Zero): - raise ValueError("cannot solve 0^0") - return Zero() - elif isinstance(exponent, Zero): - return Literal(1) - - self = super(Power, cls).__new__(cls) - self.children = base, exponent - return self - - -class MathFunction(Scalar): - __slots__ = ('name', 'children') - __front__ = ('name',) - - def __init__(self, name, argument): - assert isinstance(name, str) - assert not argument.shape - - self.name = name - self.children = argument, - - -class MinValue(Scalar): - __slots__ = ('children',) - - def __init__(self, a, b): - assert not a.shape - assert not b.shape - - self.children = a, b - - -class MaxValue(Scalar): - __slots__ = ('children',) - - def __init__(self, a, b): - assert not a.shape - assert not b.shape - - self.children = a, b - - -class Comparison(Scalar): - __slots__ = ('operator', 'children') - __front__ = ('operator',) - - def __init__(self, op, a, b): - assert not a.shape - assert not b.shape - - if op not in [">", ">=", "==", "!=", "<", "<="]: - raise ValueError("invalid operator") - - self.operator = op - self.children = a, b - - -class LogicalNot(Scalar): - __slots__ = ('children',) - - def __init__(self, expression): - assert not expression.shape - - self.children = expression, - - -class LogicalAnd(Scalar): - __slots__ = ('children',) - - def __init__(self, a, b): - assert not a.shape - assert not b.shape - - self.children = a, b - - -class LogicalOr(Scalar): - __slots__ = ('children',) - - def __init__(self, a, b): - assert not a.shape - assert not b.shape - - self.children = a, b - - -class Conditional(Node): - __slots__ = ('children', 'shape') - - def __init__(self, condition, then, else_): - assert not condition.shape - assert then.shape == else_.shape - - self.children = condition, then, else_ - self.shape = then.shape - - -class Index(object): - """Free index""" - - # Not true object count, just for naming purposes - _count = 0 - - __slots__ = ('name', 'extent', 'count') - - def __init__(self, name=None): - self.name = name - Index._count += 1 - self.count = Index._count - # Initialise with indefinite extent - self.extent = None - - def set_extent(self, value): - # Set extent, check for consistency - if self.extent is None: - self.extent = value - elif self.extent != value: - raise ValueError("Inconsistent index extents!") - - def __str__(self): - if self.name is None: - return "i_%d" % self.count - return self.name - - def __repr__(self): - if self.name is None: - return "Index(%r)" % self.count - return "Index(%r)" % self.name - - -class VariableIndex(object): - """An index that is constant during a single execution of the - kernel, but whose value is not known at compile time.""" - - def __init__(self, expression): - assert isinstance(expression, Node) - assert not expression.free_indices - assert not expression.shape - self.expression = expression - - def __eq__(self, other): - if self is other: - return True - if type(self) is not type(other): - return False - return self.expression == other.expression - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - return hash((VariableIndex, self.expression)) - - -class Indexed(Scalar): - __slots__ = ('children', 'multiindex') - __back__ = ('multiindex',) - - def __new__(cls, aggregate, multiindex): - # Set index extents from shape - assert len(aggregate.shape) == len(multiindex) - for index, extent in zip(multiindex, aggregate.shape): - if isinstance(index, Index): - index.set_extent(extent) - - # Zero folding - if isinstance(aggregate, Zero): - return Zero() - - # All indices fixed - if all(isinstance(i, int) for i in multiindex): - if isinstance(aggregate, Literal): - return Literal(aggregate.array[multiindex]) - elif isinstance(aggregate, ListTensor): - return aggregate.array[multiindex] - - self = super(Indexed, cls).__new__(cls) - self.children = (aggregate,) - self.multiindex = multiindex - - new_indices = tuple(i for i in multiindex if isinstance(i, Index)) - self.free_indices = tuple(unique(aggregate.free_indices + new_indices)) - - return self - - -class ComponentTensor(Node): - __slots__ = ('children', 'multiindex', 'shape') - __back__ = ('multiindex',) - - def __new__(cls, expression, multiindex): - assert not expression.shape - - # Collect shape - shape = tuple(index.extent for index in multiindex) - assert all(shape) - - # Zero folding - if isinstance(expression, Zero): - return Zero(shape) - - self = super(ComponentTensor, cls).__new__(cls) - self.children = (expression,) - self.multiindex = multiindex - self.shape = shape - - # Collect free indices - assert set(multiindex) <= set(expression.free_indices) - self.free_indices = tuple(set(expression.free_indices) - set(multiindex)) - - return self - - -class IndexSum(Scalar): - __slots__ = ('children', 'index') - __back__ = ('index',) - - def __new__(cls, summand, index): - # Sum zeros - assert not summand.shape - if isinstance(summand, Zero): - return summand - - # Sum a single expression - if index.extent == 1: - return Indexed(ComponentTensor(summand, (index,)), (0,)) - - self = super(IndexSum, cls).__new__(cls) - self.children = (summand,) - self.index = index - - # Collect shape and free indices - assert index in summand.free_indices - self.free_indices = tuple(set(summand.free_indices) - {index}) - - return self - - -class ListTensor(Node): - __slots__ = ('array',) - - def __new__(cls, array): - array = asarray(array) - - # Zero folding - if all(isinstance(elem, Zero) for elem in array.flat): - assert all(elem.shape == () for elem in array.flat) - return Zero(array.shape) - - self = super(ListTensor, cls).__new__(cls) - self.array = array - return self - - @property - def children(self): - return tuple(self.array.flat) - - @property - def shape(self): - return self.array.shape - - def reconstruct(self, *args): - return ListTensor(asarray(args).reshape(self.array.shape)) - - def __repr__(self): - return "ListTensor(%r)" % self.array.tolist() - - def is_equal(self, other): - """Common subexpression eliminating equality predicate.""" - if type(self) != type(other): - return False - if (self.array == other.array).all(): - self.array = other.array - return True - return False - - def get_hash(self): - return hash((type(self), self.shape, self.children)) - - -def partial_indexed(tensor, indices): - """Generalised indexing into a tensor. The number of indices may - be less than or equal to the rank of the tensor, so the result may - have a non-empty shape. - - :arg tensor: tensor-valued GEM expression - :arg indices: indices, at most as many as the rank of the tensor - :returns: a potentially tensor-valued expression - """ - if len(indices) == 0: - return tensor - elif len(indices) < len(tensor.shape): - rank = len(tensor.shape) - len(indices) - shape_indices = tuple(Index() for i in range(rank)) - return ComponentTensor( - Indexed(tensor, indices + shape_indices), - shape_indices) - elif len(indices) == len(tensor.shape): - return Indexed(tensor, indices) - else: - raise ValueError("More indices than rank!") diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 689ead5e4d..6b8272e0e8 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -12,9 +12,10 @@ CellOrientation, ReferenceCellVolume, ReferenceNormal) +from gem import gem + from tsfc.constants import NUMPY_TYPE from tsfc.fiatinterface import as_fiat_cell -from tsfc import gem interval_x_interval = TensorProductCell(interval, interval) diff --git a/tsfc/impero.py b/tsfc/impero.py deleted file mode 100644 index 0cac80cc82..0000000000 --- a/tsfc/impero.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Impero is a helper AST for generating C code (or equivalent, -e.g. COFFEE) from GEM. An Impero expression is a proper tree, not -directed acyclic graph (DAG). Impero is a helper AST, not a -standalone language; it is incomplete without GEM as its terminals -refer to nodes from GEM expressions. - -Trivia: - - Impero helps translating GEM into an imperative language. - - Byzantine units in Age of Empires II sometimes say 'Impero?' - (Command?) after clicking on them. -""" - -from __future__ import absolute_import - -from abc import ABCMeta, abstractmethod - -from tsfc.node import Node as NodeBase - - -class Node(NodeBase): - """Base class of all Impero nodes""" - - __slots__ = () - - -class Terminal(Node): - """Abstract class for terminal Impero nodes""" - - __metaclass__ = ABCMeta - - __slots__ = () - - children = () - - @abstractmethod - def loop_shape(self, free_indices): - """Gives the loop shape, an ordering of indices for an Impero - terminal. - - :arg free_indices: a callable mapping of GEM expressions to - ordered free indices. - """ - pass - - -class Evaluate(Terminal): - """Assign the value of a GEM expression to a temporary.""" - - __slots__ = ('expression',) - __front__ = ('expression',) - - def __init__(self, expression): - self.expression = expression - - def loop_shape(self, free_indices): - return free_indices(self.expression) - - -class Initialise(Terminal): - """Initialise an :class:`gem.IndexSum`.""" - - __slots__ = ('indexsum',) - __front__ = ('indexsum',) - - def __init__(self, indexsum): - self.indexsum = indexsum - - def loop_shape(self, free_indices): - return free_indices(self.indexsum) - - -class Accumulate(Terminal): - """Accumulate terms into an :class:`gem.IndexSum`.""" - - __slots__ = ('indexsum',) - __front__ = ('indexsum',) - - def __init__(self, indexsum): - self.indexsum = indexsum - - def loop_shape(self, free_indices): - return free_indices(self.indexsum.children[0]) - - -class Noop(Terminal): - """No-op terminal. Does not generate code, but wraps a GEM - expression to have a loop shape, thus affects loop fusion.""" - - __slots__ = ('expression',) - __front__ = ('expression',) - - def __init__(self, expression): - self.expression = expression - - def loop_shape(self, free_indices): - return free_indices(self.expression) - - -class Return(Terminal): - """Save value of GEM expression into an lvalue. Used to "return" - values from a kernel.""" - - __slots__ = ('variable', 'expression') - __front__ = ('variable', 'expression') - - def __init__(self, variable, expression): - assert set(variable.free_indices) >= set(expression.free_indices) - - self.variable = variable - self.expression = expression - - def loop_shape(self, free_indices): - return free_indices(self.variable) - - -class ReturnAccumulate(Terminal): - """Accumulate an :class:`gem.IndexSum` directly into a return - variable.""" - - __slots__ = ('variable', 'indexsum') - __front__ = ('variable', 'indexsum') - - def __init__(self, variable, indexsum): - assert set(variable.free_indices) == set(indexsum.free_indices) - - self.variable = variable - self.indexsum = indexsum - - def loop_shape(self, free_indices): - return free_indices(self.indexsum.children[0]) - - -class Block(Node): - """An ordered set of Impero expressions. Corresponds to a curly - braces block in C.""" - - __slots__ = ('children',) - - def __init__(self, statements): - self.children = tuple(statements) - - -class For(Node): - """For loop with an index which stores its extent, and a loop body - expression which is usually a :class:`Block`.""" - - __slots__ = ('index', 'children') - __front__ = ('index',) - - def __init__(self, index, statement): - self.index = index - self.children = (statement,) diff --git a/tsfc/impero_utils.py b/tsfc/impero_utils.py deleted file mode 100644 index 710b751d7d..0000000000 --- a/tsfc/impero_utils.py +++ /dev/null @@ -1,323 +0,0 @@ -"""Utilities for building an Impero AST from an ordered list of -terminal Impero operations, and for building any additional data -required for straightforward C code generation. - -What this module does is independent of whether we eventually generate -C code or a COFFEE AST. -""" - -from __future__ import absolute_import - -import collections -import itertools - -import numpy -from singledispatch import singledispatch - -from tsfc.node import traversal, collect_refcount -from tsfc import gem, impero as imp, optimise, scheduling - - -# ImperoC is named tuple for C code generation. -# -# Attributes: -# tree - Impero AST describing the loop structure and operations -# temporaries - List of GEM expressions which have assigned temporaries -# declare - Where to declare temporaries to get correct C code -# indices - Indices for declarations and referencing values -ImperoC = collections.namedtuple('ImperoC', ['tree', 'temporaries', 'declare', 'indices']) - - -class NoopError(Exception): - """No operations in the kernel.""" - pass - - -def compile_gem(return_variables, expressions, prefix_ordering, remove_zeros=False, coffee_licm=False): - """Compiles GEM to Impero. - - :arg return_variables: return variables for each root (type: GEM expressions) - :arg expressions: multi-root expression DAG (type: GEM expressions) - :arg prefix_ordering: outermost loop indices - :arg remove_zeros: remove zero assignment to return variables - :arg coffee_licm: trust COFFEE to do loop invariant code motion - """ - expressions = optimise.remove_componenttensors(expressions) - - # Remove zeros - if remove_zeros: - rv = [] - es = [] - for var, expr in zip(return_variables, expressions): - if not isinstance(expr, gem.Zero): - rv.append(var) - es.append(expr) - return_variables, expressions = rv, es - - # Collect indices in a deterministic order - indices = [] - for node in traversal(expressions): - if isinstance(node, gem.Indexed): - indices.extend(node.multiindex) - # The next two lines remove duplicate elements from the list, but - # preserve the ordering, i.e. all elements will appear only once, - # in the order of their first occurance in the original list. - _, unique_indices = numpy.unique(indices, return_index=True) - indices = numpy.asarray(indices)[numpy.sort(unique_indices)] - - # Build ordered index map - index_ordering = make_prefix_ordering(indices, prefix_ordering) - apply_ordering = make_index_orderer(index_ordering) - - get_indices = lambda expr: apply_ordering(expr.free_indices) - - # Build operation ordering - ops = scheduling.emit_operations(zip(return_variables, expressions), get_indices) - - # Empty kernel - if len(ops) == 0: - raise NoopError() - - # Drop unnecessary temporaries - ops = inline_temporaries(expressions, ops, coffee_licm=coffee_licm) - - # Build Impero AST - tree = make_loop_tree(ops, get_indices) - - # Collect temporaries - temporaries = collect_temporaries(ops) - - # Determine declarations - declare, indices = place_declarations(ops, tree, temporaries, get_indices) - - # Prepare ImperoC (Impero AST + other data for code generation) - return ImperoC(tree, temporaries, declare, indices) - - -def make_prefix_ordering(indices, prefix_ordering): - """Creates an ordering of ``indices`` which starts with those - indices in ``prefix_ordering``.""" - # Need to return deterministically ordered indices - return tuple(prefix_ordering) + tuple(k for k in indices if k not in prefix_ordering) - - -def make_index_orderer(index_ordering): - """Returns a function which given a set of indices returns those - indices in the order as they appear in ``index_ordering``.""" - idx2pos = {idx: pos for pos, idx in enumerate(index_ordering)} - - def apply_ordering(indices): - return tuple(sorted(indices, key=lambda i: idx2pos[i])) - return apply_ordering - - -def inline_temporaries(expressions, ops, coffee_licm=False): - """Inline temporaries which could be inlined without blowing up - the code. - - :arg expressions: a multi-root GEM expression DAG, used for - reference counting - :arg ops: ordered list of Impero terminals - :arg coffee_licm: Trust COFFEE to do LICM. If enabled, inlining - can move calculations inside inner loops. - :returns: a filtered ``ops``, without the unnecessary - :class:`impero.Evaluate`s - """ - refcount = collect_refcount(expressions) - - candidates = set() # candidates for inlining - for op in ops: - if isinstance(op, imp.Evaluate): - expr = op.expression - if expr.shape == () and refcount[expr] == 1: - candidates.add(expr) - - if not coffee_licm: - # Prevent inlining that pulls expressions into inner loops - for node in traversal(expressions): - for child in node.children: - if child in candidates and set(child.free_indices) < set(node.free_indices): - candidates.remove(child) - - # Filter out candidates - return [op for op in ops if not (isinstance(op, imp.Evaluate) and op.expression in candidates)] - - -def collect_temporaries(ops): - """Collects GEM expressions to assign to temporaries from a list - of Impero terminals.""" - result = [] - for op in ops: - # IndexSum temporaries should be added either at Initialise or - # at Accumulate. The difference is only in ordering - # (numbering). We chose Accumulate here. - if isinstance(op, imp.Accumulate): - result.append(op.indexsum) - elif isinstance(op, imp.Evaluate): - result.append(op.expression) - return result - - -def make_loop_tree(ops, get_indices, level=0): - """Creates an Impero AST with loops from a list of operations and - their respective free indices. - - :arg ops: a list of Impero terminal nodes - :arg get_indices: callable mapping from GEM nodes to an ordering - of free indices - :arg level: depth of loop nesting - :returns: Impero AST with loops, without declarations - """ - keyfunc = lambda op: op.loop_shape(get_indices)[level:level+1] - statements = [] - for first_index, op_group in itertools.groupby(ops, keyfunc): - if first_index: - inner_block = make_loop_tree(op_group, get_indices, level+1) - statements.append(imp.For(first_index[0], inner_block)) - else: - statements.extend(op_group) - # Remove no-op terminals from the tree - statements = filter(lambda s: not isinstance(s, imp.Noop), statements) - return imp.Block(statements) - - -def place_declarations(ops, tree, temporaries, get_indices): - """Determines where and how to declare temporaries for an Impero AST. - - :arg ops: terminals of ``tree`` - :arg tree: Impero AST to determine the declarations for - :arg temporaries: list of GEM expressions which are assigned to - temporaries - :arg get_indices: callable mapping from GEM nodes to an ordering - of free indices - """ - temporaries_set = set(temporaries) - assert len(temporaries_set) == len(temporaries) - - # Collect the total number of temporary references - total_refcount = collections.Counter() - for op in ops: - total_refcount.update(temp_refcount(temporaries_set, op)) - assert temporaries_set == set(total_refcount) - - # Result - declare = {} - indices = {} - - @singledispatch - def recurse(expr, loop_indices): - """Visit an Impero AST to collect declarations. - - :arg expr: Impero tree node - :arg loop_indices: loop indices (in order) from the outer - loops surrounding ``expr`` - :returns: :class:`collections.Counter` with the reference - counts for each temporary in the subtree whose root - is ``expr`` - """ - return AssertionError("unsupported expression type %s" % type(expr)) - - @recurse.register(imp.Terminal) - def recurse_terminal(expr, loop_indices): - return temp_refcount(temporaries_set, expr) - - @recurse.register(imp.For) - def recurse_for(expr, loop_indices): - return recurse(expr.children[0], loop_indices + (expr.index,)) - - @recurse.register(imp.Block) - def recurse_block(expr, loop_indices): - # Temporaries declared at the beginning of the block are - # collected here - declare[expr] = [] - - # Collect reference counts for the block - refcount = collections.Counter() - for statement in expr.children: - refcount.update(recurse(statement, loop_indices)) - - # Visit :class:`collections.Counter` in deterministic order - for e in sorted(refcount.keys(), key=temporaries.index): - if refcount[e] == total_refcount[e]: - # If all references are within this block, then this - # block is the right place to declare the temporary. - assert loop_indices == get_indices(e)[:len(loop_indices)] - indices[e] = get_indices(e)[len(loop_indices):] - if indices[e]: - # Scalar-valued temporaries are not declared until - # their value is assigned. This does not really - # matter, but produces a more compact and nicer to - # read C code. - declare[expr].append(e) - # Remove expression from the ``refcount`` so it will - # not be declared again. - del refcount[e] - return refcount - - # Populate result - remainder = recurse(tree, ()) - assert not remainder - - # Set in ``declare`` for Impero terminals whether they should - # declare the temporary that they are writing to. - for op in ops: - declare[op] = False - if isinstance(op, imp.Evaluate): - e = op.expression - elif isinstance(op, imp.Initialise): - e = op.indexsum - else: - continue - - if len(indices[e]) == 0: - declare[op] = True - - return declare, indices - - -def temp_refcount(temporaries, op): - """Collects the number of times temporaries are referenced when - generating code for an Impero terminal. - - :arg temporaries: set of temporaries - :arg op: Impero terminal - :returns: :class:`collections.Counter` object mapping some of - elements from ``temporaries`` to the number of times - they will referenced from ``op`` - """ - counter = collections.Counter() - - def recurse(o): - """Traverses expression until reaching temporaries, counting - temporary references.""" - if o in temporaries: - counter[o] += 1 - else: - for c in o.children: - recurse(c) - - def recurse_top(o): - """Traverses expression until reaching temporaries, counting - temporary references. Always descends into children at least - once, even when the root is a temporary.""" - if o in temporaries: - counter[o] += 1 - for c in o.children: - recurse(c) - - if isinstance(op, imp.Initialise): - counter[op.indexsum] += 1 - elif isinstance(op, imp.Accumulate): - recurse_top(op.indexsum) - elif isinstance(op, imp.Evaluate): - recurse_top(op.expression) - elif isinstance(op, imp.Return): - recurse(op.expression) - elif isinstance(op, imp.ReturnAccumulate): - recurse(op.indexsum.children[0]) - elif isinstance(op, imp.Noop): - pass - else: - raise AssertionError("unhandled operation: %s" % type(op)) - - return counter diff --git a/tsfc/interpreter.py b/tsfc/interpreter.py deleted file mode 100644 index fd4598621e..0000000000 --- a/tsfc/interpreter.py +++ /dev/null @@ -1,305 +0,0 @@ -""" -An interpreter for GEM trees. -""" -from __future__ import absolute_import - -import numpy -import operator -import math -from singledispatch import singledispatch -import itertools - -from tsfc import gem, node - -__all__ = ("evaluate", ) - - -class Result(object): - """An array object that tracks which axes of the array correspond to - gem free indices (and what those free indices are). - - :arg arr: The array. - :arg fids: The free indices. - - The first ``len(fids)`` axes of the provided array correspond to - the free indices, the remaining axes are the shape of each entry. - """ - def __init__(self, arr, fids=None): - self.arr = arr - self.fids = fids if fids is not None else () - - def filter(self, idx, fids): - """Given an index tuple and some free indices, return a - "filtered" index tuple which removes entries that correspond - to indices in fids that are not in ``self.fids``. - - :arg idx: The index tuple to filter. - :arg fids: The free indices for the index tuple. - """ - return tuple(idx[fids.index(i)] for i in self.fids) + idx[len(fids):] - - def __getitem__(self, idx): - return self.arr[idx] - - def __setitem__(self, idx, val): - self.arr[idx] = val - - @property - def tshape(self): - """The total shape of the result array.""" - return self.arr.shape - - @property - def fshape(self): - """The shape of the free index part of the result array.""" - return self.tshape[:len(self.fids)] - - @property - def shape(self): - """The shape of the shape part of the result array.""" - return self.tshape[len(self.fids):] - - def __repr__(self): - return "Result(%r, %r)" % (self.arr, self.fids) - - def __str__(self): - return repr(self) - - @classmethod - def empty(cls, *children, **kwargs): - """Build an empty Result object. - - :arg children: The children used to determine the shape and - free indices. - :kwarg dtype: The data type of the result array. - """ - dtype = kwargs.get("dtype", float) - assert all(children[0].shape == c.shape for c in children) - fids = [] - for f in itertools.chain(*(c.fids for c in children)): - if f not in fids: - fids.append(f) - shape = tuple(i.extent for i in fids) + children[0].shape - return cls(numpy.empty(shape, dtype=dtype), tuple(fids)) - - -@singledispatch -def _evaluate(expression, self): - """Evaluate an expression using a provided callback handler. - - :arg expression: The expression to evaluation. - :arg self: The callback handler (should provide bindings). - """ - raise ValueError("Unhandled node type %s" % type(expression)) - - -@_evaluate.register(gem.Zero) # noqa: not actually redefinition -def _(e, self): - """Zeros produce an array of zeros.""" - return Result(numpy.zeros(e.shape, dtype=float)) - - -@_evaluate.register(gem.Literal) # noqa: not actually redefinition -def _(e, self): - """Literals return their array.""" - return Result(e.array) - - -@_evaluate.register(gem.Variable) # noqa: not actually redefinition -def _(e, self): - """Look up variables in the provided bindings.""" - try: - val = self.bindings[e] - except KeyError: - raise ValueError("Binding for %s not found" % e) - if val.shape != e.shape: - raise ValueError("Binding for %s has wrong shape. %s, not %s." % - (e, val.shape, e.shape)) - return Result(val) - - -@_evaluate.register(gem.Power) # noqa: not actually redefinition -@_evaluate.register(gem.Division) -@_evaluate.register(gem.Product) -@_evaluate.register(gem.Sum) -def _(e, self): - op = {gem.Product: operator.mul, - gem.Division: operator.div, - gem.Sum: operator.add, - gem.Power: operator.pow}[type(e)] - - a, b = [self(o) for o in e.children] - result = Result.empty(a, b) - fids = result.fids - for idx in numpy.ndindex(result.tshape): - result[idx] = op(a[a.filter(idx, fids)], b[b.filter(idx, fids)]) - return result - - -@_evaluate.register(gem.MathFunction) # noqa: not actually redefinition -def _(e, self): - ops = [self(o) for o in e.children] - result = Result.empty(*ops) - names = {"abs": abs, - "log": math.log} - op = names[e.name] - for idx in numpy.ndindex(result.tshape): - result[idx] = op(*(o[o.filter(idx, result.fids)] for o in ops)) - return result - - -@_evaluate.register(gem.MaxValue) # noqa: not actually redefinition -@_evaluate.register(gem.MinValue) -def _(e, self): - ops = [self(o) for o in e.children] - result = Result.empty(*ops) - op = {gem.MinValue: min, - gem.MaxValue: max}[type(e)] - for idx in numpy.ndindex(result.tshape): - result[idx] = op(*(o[o.filter(idx, result.fids)] for o in ops)) - return result - - -@_evaluate.register(gem.Comparison) # noqa: not actually redefinition -def _(e, self): - ops = [self(o) for o in e.children] - op = {">": operator.gt, - ">=": operator.ge, - "==": operator.eq, - "!=": operator.ne, - "<": operator.lt, - "<=": operator.le}[e.operator] - result = Result.empty(*ops, dtype=bool) - for idx in numpy.ndindex(result.tshape): - result[idx] = op(*(o[o.filter(idx, result.fids)] for o in ops)) - return result - - -@_evaluate.register(gem.LogicalNot) # noqa: not actually redefinition -def _(e, self): - val = self(e.children[0]) - assert val.arr.dtype == numpy.dtype("bool") - result = Result.empty(val, bool) - for idx in numpy.ndindex(result.tshape): - result[idx] = not val[val.filter(idx, result.fids)] - return result - - -@_evaluate.register(gem.LogicalAnd) # noqa: not actually redefinition -def _(e, self): - a, b = [self(o) for o in e.children] - assert a.arr.dtype == numpy.dtype("bool") - assert b.arr.dtype == numpy.dtype("bool") - result = Result.empty(a, b, bool) - for idx in numpy.ndindex(result.tshape): - result[idx] = a[a.filter(idx, result.fids)] and \ - b[b.filter(idx, result.fids)] - return result - - -@_evaluate.register(gem.LogicalOr) # noqa: not actually redefinition -def _(e, self): - a, b = [self(o) for o in e.children] - assert a.arr.dtype == numpy.dtype("bool") - assert b.arr.dtype == numpy.dtype("bool") - result = Result.empty(a, b, dtype=bool) - for idx in numpy.ndindex(result.tshape): - result[idx] = a[a.filter(idx, result.fids)] or \ - b[b.filter(idx, result.fids)] - return result - - -@_evaluate.register(gem.Conditional) # noqa: not actually redefinition -def _(e, self): - cond, then, else_ = [self(o) for o in e.children] - assert cond.arr.dtype == numpy.dtype("bool") - result = Result.empty(cond, then, else_) - for idx in numpy.ndindex(result.tshape): - if cond[cond.filter(idx, result.fids)]: - result[idx] = then[then.filter(idx, result.fids)] - else: - result[idx] = else_[else_.filter(idx, result.fids)] - return result - - -@_evaluate.register(gem.Indexed) # noqa: not actually redefinition -def _(e, self): - """Indexing maps shape to free indices""" - val = self(e.children[0]) - fids = tuple(i for i in e.multiindex if isinstance(i, gem.Index)) - - idx = [] - # First pick up all the existing free indices - for _ in val.fids: - idx.append(Ellipsis) - # Now grab the shape axes - for i in e.multiindex: - if isinstance(i, gem.Index): - # Free index, want entire extent - idx.append(Ellipsis) - elif isinstance(i, gem.VariableIndex): - # Variable index, evaluate inner expression - result, = self(i.expression) - assert not result.tshape - idx.append(result[()]) - else: - # Fixed index, just pick that value - idx.append(i) - assert len(idx) == len(val.tshape) - return Result(val[idx], val.fids + fids) - - -@_evaluate.register(gem.ComponentTensor) # noqa: not actually redefinition -def _(e, self): - """Component tensors map free indices to shape.""" - val = self(e.children[0]) - axes = [] - fids = [] - # First grab the free indices that aren't bound - for a, f in enumerate(val.fids): - if f not in e.multiindex: - axes.append(a) - fids.append(f) - # Now the bound free indices - for i in e.multiindex: - axes.append(val.fids.index(i)) - # Now the existing shape - axes.extend(range(len(val.fshape), len(val.tshape))) - return Result(numpy.transpose(val.arr, axes=axes), - tuple(fids)) - - -@_evaluate.register(gem.IndexSum) # noqa: not actually redefinition -def _(e, self): - """Index sums reduce over the given axis.""" - val = self(e.children[0]) - idx = val.fids.index(e.index) - return Result(val.arr.sum(axis=idx), - val.fids[:idx] + val.fids[idx+1:]) - - -@_evaluate.register(gem.ListTensor) # noqa: not actually redefinition -def _(e, self): - """List tensors just turn into arrays.""" - ops = [self(o) for o in e.children] - assert all(ops[0].fids == o.fids for o in ops) - return Result(numpy.asarray([o.arr for o in ops]).reshape(e.shape), - ops[0].fids) - - -def evaluate(expressions, bindings=None): - """Evaluate some GEM expressions given variable bindings. - - :arg expressions: A single GEM expression, or iterable of - expressions to evaluate. - :kwarg bindings: An optional dict mapping GEM :class:`gem.Variable` - nodes to data. - :returns: a list of the evaluated expressions. - """ - try: - exprs = tuple(expressions) - except TypeError: - exprs = (expressions, ) - mapper = node.Memoizer(_evaluate) - mapper.bindings = bindings if bindings is not None else {} - return map(mapper, exprs) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 65fbc30732..bbce5a1830 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -4,10 +4,11 @@ import coffee.base as coffee -from tsfc import gem +from gem import gem +from gem.node import traversal + from tsfc.fiatinterface import create_element from tsfc.mixedelement import MixedElement -from tsfc.node import traversal from tsfc.coffee import SCALAR_TYPE diff --git a/tsfc/node.py b/tsfc/node.py deleted file mode 100644 index d1c72cc71c..0000000000 --- a/tsfc/node.py +++ /dev/null @@ -1,217 +0,0 @@ -"""Generic abstract node class and utility functions for creating -expression DAG languages.""" - -from __future__ import absolute_import - -import collections - - -class Node(object): - """Abstract node class. - - Nodes are not meant to be modified. - - A node can reference other nodes; they are called children. A node - might contain data, or reference other objects which are not - themselves nodes; they are not called children. - - Both the children (if any) and non-child data (if any) are - required to create a node, or determine the equality of two - nodes. For reconstruction, however, only the new children are - necessary. - """ - - __slots__ = ('hash_value',) - - # Non-child data as the first arguments of the constructor. - # To be (potentially) overridden by derived node classes. - __front__ = () - - # Non-child data as the last arguments of the constructor. - # To be (potentially) overridden by derived node classes. - __back__ = () - - def __getinitargs__(self, children): - """Constructs an argument list for the constructor with - non-child data from 'self' and children from 'children'. - - Internally used utility function. - """ - front_args = [getattr(self, name) for name in self.__front__] - back_args = [getattr(self, name) for name in self.__back__] - - return tuple(front_args) + tuple(children) + tuple(back_args) - - def reconstruct(self, *args): - """Reconstructs the node with new children from - 'args'. Non-child data are copied from 'self'. - - Returns a new object. - """ - return type(self)(*self.__getinitargs__(args)) - - def __repr__(self): - init_args = self.__getinitargs__(self.children) - return "%s(%s)" % (type(self).__name__, ", ".join(map(repr, init_args))) - - def __eq__(self, other): - """Provides equality testing with quick positive and negative - paths based on :func:`id` and :meth:`__hash__`. - """ - if self is other: - return True - elif hash(self) != hash(other): - return False - else: - return self.is_equal(other) - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - """Provides caching for hash values.""" - try: - return self.hash_value - except AttributeError: - self.hash_value = self.get_hash() - return self.hash_value - - def is_equal(self, other): - """Equality predicate. - - This is the method to potentially override in derived classes, - not :meth:`__eq__` or :meth:`__ne__`. - """ - if type(self) != type(other): - return False - self_initargs = self.__getinitargs__(self.children) - other_initargs = other.__getinitargs__(other.children) - return self_initargs == other_initargs - - def get_hash(self): - """Hash function. - - This is the method to potentially override in derived classes, - not :meth:`__hash__`. - """ - return hash((type(self),) + self.__getinitargs__(self.children)) - - -def traversal(expression_dags): - """Pre-order traversal of the nodes of expression DAGs.""" - seen = set() - lifo = [] - # Some roots might be same, but they must be visited only once. - # Keep the original ordering of roots, for deterministic code - # generation. - for root in expression_dags: - if root not in seen: - seen.add(root) - lifo.append(root) - - while lifo: - node = lifo.pop() - yield node - for child in node.children: - if child not in seen: - seen.add(child) - lifo.append(child) - - -def collect_refcount(expression_dags): - """Collects reference counts for a multi-root expression DAG.""" - result = collections.Counter(expression_dags) - for node in traversal(expression_dags): - result.update(node.children) - return result - - -def noop_recursive(function): - """No-op wrapper for functions with overridable recursive calls. - - :arg function: a function with parameters (value, rec), where - ``rec`` is expected to be a function used for - recursive calls. - :returns: a function with working recursion and nothing fancy - """ - def recursive(node): - return function(node, recursive) - return recursive - - -def noop_recursive_arg(function): - """No-op wrapper for functions with overridable recursive calls - and an argument. - - :arg function: a function with parameters (value, rec, arg), where - ``rec`` is expected to be a function used for - recursive calls. - :returns: a function with working recursion and nothing fancy - """ - def recursive(node, arg): - return function(node, recursive, arg) - return recursive - - -class Memoizer(object): - """Caching wrapper for functions with overridable recursive calls. - The lifetime of the cache is the lifetime of the object instance. - - :arg function: a function with parameters (value, rec), where - ``rec`` is expected to be a function used for - recursive calls. - :returns: a function with working recursion and caching - """ - def __init__(self, function): - self.cache = {} - self.function = function - - def __call__(self, node): - try: - return self.cache[node] - except KeyError: - result = self.function(node, self) - self.cache[node] = result - return result - - -class MemoizerArg(object): - """Caching wrapper for functions with overridable recursive calls - and an argument. The lifetime of the cache is the lifetime of the - object instance. - - :arg function: a function with parameters (value, rec, arg), where - ``rec`` is expected to be a function used for - recursive calls. - :returns: a function with working recursion and caching - """ - def __init__(self, function): - self.cache = {} - self.function = function - - def __call__(self, node, arg): - cache_key = (node, arg) - try: - return self.cache[cache_key] - except KeyError: - result = self.function(node, self, arg) - self.cache[cache_key] = result - return result - - -def reuse_if_untouched(node, self): - """Reuse if untouched recipe""" - new_children = map(self, node.children) - if all(nc == c for nc, c in zip(new_children, node.children)): - return node - else: - return node.reconstruct(*new_children) - - -def reuse_if_untouched_arg(node, self, arg): - """Reuse if touched recipe propagating an extra argument""" - new_children = [self(child, arg) for child in node.children] - if all(nc == c for nc, c in zip(new_children, node.children)): - return node - else: - return node.reconstruct(*new_children) diff --git a/tsfc/optimise.py b/tsfc/optimise.py deleted file mode 100644 index 6a8b449d4f..0000000000 --- a/tsfc/optimise.py +++ /dev/null @@ -1,95 +0,0 @@ -"""A set of routines implementing various transformations on GEM -expressions.""" - -from __future__ import absolute_import - -from singledispatch import singledispatch - -from tsfc.node import Memoizer, MemoizerArg, reuse_if_untouched, reuse_if_untouched_arg -from tsfc.gem import Node, Zero, Sum, Indexed, IndexSum, ComponentTensor - - -@singledispatch -def replace_indices(node, self, subst): - """Replace free indices in a GEM expression. - - :arg node: root of the expression - :arg self: function for recursive calls - :arg subst: tuple of pairs; each pair is a substitution - rule with a free index to replace and an index to - replace with. - """ - raise AssertionError("cannot handle type %s" % type(node)) - - -replace_indices.register(Node)(reuse_if_untouched_arg) - - -@replace_indices.register(Indexed) # noqa -def _(node, self, subst): - child, = node.children - substitute = dict(subst) - multiindex = tuple(substitute.get(i, i) for i in node.multiindex) - if isinstance(child, ComponentTensor): - # Indexing into ComponentTensor - # Inline ComponentTensor and augment the substitution rules - substitute.update(zip(child.multiindex, multiindex)) - return self(child.children[0], tuple(sorted(substitute.items()))) - else: - # Replace indices - new_child = self(child, subst) - if new_child == child and multiindex == node.multiindex: - return node - else: - return Indexed(new_child, multiindex) - - -def filtered_replace_indices(node, self, subst): - """Wrapper for :func:`replace_indices`. At each call removes - substitution rules that do not apply.""" - filtered_subst = tuple((k, v) for k, v in subst if k in node.free_indices) - return replace_indices(node, self, filtered_subst) - - -def remove_componenttensors(expressions): - """Removes all ComponentTensors from a list of expression DAGs.""" - mapper = MemoizerArg(filtered_replace_indices) - return [mapper(expression, ()) for expression in expressions] - - -@singledispatch -def _unroll_indexsum(node, self): - """Unrolls IndexSums below a certain extent. - - :arg node: root of the expression - :arg self: function for recursive calls - """ - raise AssertionError("cannot handle type %s" % type(node)) - - -_unroll_indexsum.register(Node)(reuse_if_untouched) - - -@_unroll_indexsum.register(IndexSum) # noqa -def _(node, self): - if node.index.extent <= self.max_extent: - # Unrolling - summand = self(node.children[0]) - return reduce(Sum, - (Indexed(ComponentTensor(summand, (node.index,)), (i,)) - for i in range(node.index.extent)), - Zero()) - else: - return reuse_if_untouched(node, self) - - -def unroll_indexsum(expressions, max_extent): - """Unrolls IndexSums below a specified extent. - - :arg expressions: list of expression DAGs - :arg max_extent: maximum extent for which IndexSums are unrolled - :returns: list of expression DAGs with some unrolled IndexSums - """ - mapper = Memoizer(_unroll_indexsum) - mapper.max_extent = max_extent - return map(mapper, expressions) diff --git a/tsfc/scheduling.py b/tsfc/scheduling.py deleted file mode 100644 index dcda247c0b..0000000000 --- a/tsfc/scheduling.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Schedules operations to evaluate a multi-root expression DAG, -forming an ordered list of Impero terminals.""" - -from __future__ import absolute_import - -import collections -import functools - -from tsfc import gem, impero -from tsfc.node import collect_refcount - - -class OrderedDefaultDict(collections.OrderedDict): - """A dictionary that provides a default value and ordered iteration. - - :arg factory: The callable used to create the default value. - - See :class:`collections.OrderedDict` for description of the - remaining arguments. - """ - def __init__(self, factory, *args, **kwargs): - self.factory = factory - super(OrderedDefaultDict, self).__init__(*args, **kwargs) - - def __missing__(self, key): - val = self[key] = self.factory() - return val - - -class ReferenceStager(object): - """Provides staging for nodes in reference counted expression - DAGs. A callback function is called once the reference count is - exhausted.""" - - def __init__(self, reference_count, callback): - """Initialises a ReferenceStager. - - :arg reference_count: initial reference counts for all - expected nodes - :arg callback: function to call on each node when - reference count is exhausted - """ - self.waiting = reference_count.copy() - self.callback = callback - - def decref(self, o): - """Decreases the reference count of a node, and possibly - triggering a callback (when the reference count drops to - zero).""" - assert 1 <= self.waiting[o] - - self.waiting[o] -= 1 - if self.waiting[o] == 0: - self.callback(o) - - def empty(self): - """All reference counts exhausted?""" - return not any(self.waiting.values()) - - -class Queue(object): - """Special queue for operation scheduling. GEM / Impero nodes are - inserted when they are ready to be scheduled, i.e. any operation - which depends on the operation to be inserted must have been - scheduled already. This class implements a heuristic for ordering - operations within the constraints in a way which aims to achieve - maximum loop fusion to minimise the size of temporaries which need - to be introduced. - """ - def __init__(self, callback): - """Initialises a Queue. - - :arg callback: function called on each element "popped" from the queue - """ - # Must have deterministic iteration over the queue - self.queue = OrderedDefaultDict(list) - self.callback = callback - - def insert(self, indices, elem): - """Insert element into queue. - - :arg indices: loop indices used by the scheduling heuristic - :arg elem: element to be scheduled - """ - self.queue[indices].append(elem) - - def process(self): - """Pops elements from the queue and calls the callback - function on them until the queue is empty. The callback - function can insert further elements into the queue. - """ - indices = () - while self.queue: - # Find innermost non-empty outer loop - while indices not in (i[:len(indices)] for i in self.queue.keys()): - indices = indices[:-1] - - # Pick a loop - for i in self.queue.keys(): - if i[:len(indices)] == indices: - indices = i - break - - while self.queue[indices]: - self.callback(self.queue[indices].pop()) - del self.queue[indices] - - -def handle(ops, push, decref, node): - """Helper function for scheduling""" - if isinstance(node, gem.Variable): - # Declared in the kernel header - pass - elif isinstance(node, gem.Literal): - # Constant literals inlined, unless tensor-valued - if node.shape: - ops.append(impero.Evaluate(node)) - elif isinstance(node, gem.Zero): # should rarely happen - assert not node.shape - elif isinstance(node, gem.Indexed): - # Indexing always inlined - decref(node.children[0]) - elif isinstance(node, gem.IndexSum): - ops.append(impero.Noop(node)) - push(impero.Accumulate(node)) - elif isinstance(node, gem.Node): - ops.append(impero.Evaluate(node)) - for child in node.children: - decref(child) - elif isinstance(node, impero.Initialise): - ops.append(node) - elif isinstance(node, impero.Accumulate): - ops.append(node) - push(impero.Initialise(node.indexsum)) - decref(node.indexsum.children[0]) - elif isinstance(node, impero.Return): - ops.append(node) - decref(node.expression) - elif isinstance(node, impero.ReturnAccumulate): - ops.append(node) - decref(node.indexsum.children[0]) - else: - raise AssertionError("no handler for node type %s" % type(node)) - - -def emit_operations(assignments, get_indices): - """Makes an ordering of operations to evaluate a multi-root - expression DAG. - - :arg assignments: Iterable of (variable, expression) pairs. - The value of expression is written into variable - upon execution. - :arg get_indices: mapping from GEM nodes to an ordering of free - indices - :returns: list of Impero terminals correctly ordered to evaluate - the assignments - """ - # Prepare reference counts - refcount = collect_refcount([e for v, e in assignments]) - - # Stage return operations - staging = [] - for variable, expression in assignments: - if refcount[expression] == 1 and isinstance(expression, gem.IndexSum) \ - and set(variable.free_indices) == set(expression.free_indices): - staging.append(impero.ReturnAccumulate(variable, expression)) - refcount[expression] -= 1 - else: - staging.append(impero.Return(variable, expression)) - - # Prepare data structures - def push_node(node): - queue.insert(get_indices(node), node) - - def push_op(op): - queue.insert(op.loop_shape(get_indices), op) - - ops = [] - - stager = ReferenceStager(refcount, push_node) - queue = Queue(functools.partial(handle, ops, push_op, stager.decref)) - - # Enqueue return operations - for op in staging: - push_op(op) - - # Schedule operations - queue.process() - - # Assert that nothing left unprocessed - assert stager.empty() - - # Return - ops.reverse() - return ops diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 352ab6e9d4..7ad35b0c1b 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -6,11 +6,11 @@ import numpy import ufl -from tsfc.gem import (Literal, Zero, Sum, Product, Division, Power, - MathFunction, MinValue, MaxValue, Comparison, - LogicalNot, LogicalAnd, LogicalOr, Conditional, - Index, Indexed, ComponentTensor, IndexSum, - ListTensor) +from gem.gem import (Literal, Zero, Sum, Product, Division, Power, + MathFunction, MinValue, MaxValue, Comparison, + LogicalNot, LogicalAnd, LogicalOr, Conditional, + Index, Indexed, ComponentTensor, IndexSum, + ListTensor) class Mixin(object): diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index b834cb1d28..ee8ee87eaa 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -11,8 +11,9 @@ from ufl.classes import (Abs, CellOrientation, Expr, FloatValue, Division, Product, ScalarValue, Sqrt) +from gem.node import MemoizerArg + from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal -from tsfc.node import MemoizerArg def is_element_affine(ufl_element): From 558a2d883dd32b18eabecb09cd76bb87e1709aca Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 8 Apr 2016 20:04:56 +0100 Subject: [PATCH 099/809] make gem package a little more convenient --- tsfc/coffee.py | 3 ++- tsfc/driver.py | 4 +++- tsfc/fem.py | 2 +- tsfc/geometric.py | 2 +- tsfc/kernel_interface.py | 2 +- tsfc/ufl2gem.py | 10 +++++----- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index e81881ae0d..18b762823c 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -13,7 +13,8 @@ import coffee.base as coffee -from gem import gem, impero as imp +import gem +import gem.impero as imp from tsfc.constants import SCALAR_TYPE, PRECISION diff --git a/tsfc/driver.py b/tsfc/driver.py index f4a64d610f..9cdd30d51f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -7,7 +7,9 @@ from ufl.algorithms import compute_form_data from ufl.log import GREEN -from gem import gem, optimise as opt, impero_utils +import gem +import gem.optimise as opt +import gem.impero_utils as impero_utils from tsfc import fem, ufl_utils from tsfc.coffee import generate as generate_coffee diff --git a/tsfc/fem.py b/tsfc/fem.py index de5af5b860..bb281cbbbf 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -11,7 +11,7 @@ from ufl.classes import (Argument, Coefficient, FormArgument, GeometricQuantity, QuadratureWeight) -from gem import gem +import gem from tsfc.constants import PRECISION from tsfc.fiatinterface import create_element, as_fiat_cell diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 6b8272e0e8..6f7b4a883f 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -12,7 +12,7 @@ CellOrientation, ReferenceCellVolume, ReferenceNormal) -from gem import gem +import gem from tsfc.constants import NUMPY_TYPE from tsfc.fiatinterface import as_fiat_cell diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index bbce5a1830..bba7fb7428 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -4,7 +4,7 @@ import coffee.base as coffee -from gem import gem +import gem from gem.node import traversal from tsfc.fiatinterface import create_element diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 7ad35b0c1b..8abfea367e 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -6,11 +6,11 @@ import numpy import ufl -from gem.gem import (Literal, Zero, Sum, Product, Division, Power, - MathFunction, MinValue, MaxValue, Comparison, - LogicalNot, LogicalAnd, LogicalOr, Conditional, - Index, Indexed, ComponentTensor, IndexSum, - ListTensor) +from gem import (Literal, Zero, Sum, Product, Division, Power, + MathFunction, MinValue, MaxValue, Comparison, + LogicalNot, LogicalAnd, LogicalOr, Conditional, + Index, Indexed, ComponentTensor, IndexSum, + ListTensor) class Mixin(object): From ab254019f09b7425821ba795a120d8ae06d632ca Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 5 Feb 2016 13:51:02 +0000 Subject: [PATCH 100/809] attach #pragma coffee expression --- tsfc/coffee.py | 26 ++++++++++++++++++++++---- tsfc/driver.py | 5 ++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 18b762823c..c8e1d2dc9d 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -23,16 +23,19 @@ class Bunch(object): pass -def generate(impero_c, index_names): +def generate(impero_c, index_names, roots=()): """Generates COFFEE code. :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names + :arg roots: list of expression DAG roots for attaching + #pragma coffee expression :returns: COFFEE function body """ parameters = Bunch() parameters.declare = impero_c.declare parameters.indices = impero_c.indices + parameters.roots = roots parameters.names = {} for i, temp in enumerate(impero_c.temporaries): @@ -75,6 +78,15 @@ def _ref_symbol(expr, parameters): return _coffee_symbol(parameters.names[expr], rank=tuple(rank)) +def _root_pragma(expr, parameters): + """Decides whether to annonate the expression with + #pragma coffee expression""" + if expr in parameters.roots: + return "#pragma coffee expression" + else: + return None + + @singledispatch def statement(tree, parameters): """Translates an Impero (sub)tree into a COFFEE AST corresponding @@ -118,20 +130,26 @@ def statement_initialise(leaf, parameters): @statement.register(imp.Accumulate) def statement_accumulate(leaf, parameters): + pragma = _root_pragma(leaf.indexsum, parameters) return coffee.Incr(_ref_symbol(leaf.indexsum, parameters), - expression(leaf.indexsum.children[0], parameters)) + expression(leaf.indexsum.children[0], parameters), + pragma=pragma) @statement.register(imp.Return) def statement_return(leaf, parameters): + pragma = _root_pragma(leaf.expression, parameters) return coffee.Incr(expression(leaf.variable, parameters), - expression(leaf.expression, parameters)) + expression(leaf.expression, parameters), + pragma=pragma) @statement.register(imp.ReturnAccumulate) def statement_returnaccumulate(leaf, parameters): + pragma = _root_pragma(leaf.indexsum, parameters) return coffee.Incr(expression(leaf.variable, parameters), - expression(leaf.indexsum.children[0], parameters)) + expression(leaf.indexsum.children[0], parameters), + pragma=pragma) @statement.register(imp.Evaluate) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9cdd30d51f..9b5e6ecdc3 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -139,6 +139,9 @@ def compile_integral(integral_data, form_data, prefix, parameters): # Sum the expressions that are part of the same restriction ir = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*irs)) + # Need optimised roots for COFFEE + ir = opt.remove_componenttensors(ir) + # Look for cell orientations in the IR if needs_cell_orientations(ir): builder.require_cell_orientations() @@ -156,7 +159,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body = generate_coffee(impero_c, index_names) + body = generate_coffee(impero_c, index_names, ir) kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) return builder.construct_kernel(kernel_name, body) From 47630925fd88400c78de98814191a6f6c652caf2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 25 Feb 2016 16:52:21 +0000 Subject: [PATCH 101/809] adopt new coffee.Decl API --- tsfc/kernel_interface.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index bba7fb7428..821b74f8bb 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -239,7 +239,8 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): # Simple case shape = (fiat_element.space_dimension(),) - funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), + pointers=[("restrict",)], qualifiers=["const"]) i = gem.Index() @@ -255,7 +256,8 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): shape = (2, fiat_element.space_dimension()) - funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), + pointers=[("restrict",)], qualifiers=["const"]) expression = gem.Variable(name, shape + (1,)) @@ -288,7 +290,8 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): name_ = name + "_" shape = (2, fiat_element.space_dimension()) - funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_), + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name_), + pointers=[("restrict",), ("restrict",)], qualifiers=["const"]) prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] expression = gem.Variable(name, shape) @@ -314,7 +317,8 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): # In this case we generate a gem.ListTensor to do the # reordering. Every single element in a E[n]{+,-} block is # referenced separately. - funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name), + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("restrict",), ("restrict",)], qualifiers=["const"]) variable = gem.Variable(name, (2 * fiat_element.space_dimension(), 1)) @@ -461,8 +465,7 @@ def needs_cell_orientations(ir): return False -cell_orientations_coffee_arg = coffee.Decl( - "int *restrict *restrict", - coffee.Symbol("cell_orientations"), - qualifiers=["const"]) +cell_orientations_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), + pointers=[("restrict",), ("restrict",)], + qualifiers=["const"]) """COFFEE function argument for cell orientations""" From 4360194574652e1cafabe1527e256186f4cc1a1c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Mar 2016 12:27:24 +0000 Subject: [PATCH 102/809] Attach COFFEE pragma to argument loops --- tsfc/coffee.py | 13 +++++++++++-- tsfc/driver.py | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index c8e1d2dc9d..66c02698e0 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -23,19 +23,23 @@ class Bunch(object): pass -def generate(impero_c, index_names, roots=()): +def generate(impero_c, index_names, roots=(), argument_indices=()): """Generates COFFEE code. :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names :arg roots: list of expression DAG roots for attaching #pragma coffee expression + :arg argument_indices: argument indices for attaching + #pragma coffee linear loop + to the argument loops :returns: COFFEE function body """ parameters = Bunch() parameters.declare = impero_c.declare parameters.indices = impero_c.indices parameters.roots = roots + parameters.argument_indices = argument_indices parameters.names = {} for i, temp in enumerate(impero_c.temporaries): @@ -110,6 +114,10 @@ def statement_block(tree, parameters): @statement.register(imp.For) def statement_for(tree, parameters): + if tree.index in parameters.argument_indices: + pragma = "#pragma coffee linear loop" + else: + pragma = None extent = tree.index.extent assert extent i = _coffee_symbol(parameters.index_names[tree.index]) @@ -117,7 +125,8 @@ def statement_for(tree, parameters): return coffee.For(coffee.Decl("int", i, init=0), coffee.Less(i, extent), coffee.Incr(i, 1), - statement(tree.children[0], parameters)) + statement(tree.children[0], parameters), + pragma=pragma) @statement.register(imp.Initialise) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9b5e6ecdc3..4f63ad4db8 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -159,7 +159,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body = generate_coffee(impero_c, index_names, ir) + body = generate_coffee(impero_c, index_names, ir, argument_indices) kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) return builder.construct_kernel(kernel_name, body) From 20b894bcfbfe1d79c9b3d44e650e4a3a62c69e6f Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Mar 2016 15:36:26 +0000 Subject: [PATCH 103/809] use coffee.SparseArrayInit and zero tracking --- tsfc/coffee.py | 11 ++++++++++- tsfc/fem.py | 14 ++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 66c02698e0..c3bd63010c 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -179,9 +179,18 @@ def statement_evaluate(leaf, parameters): return coffee.Block(ops, open_scope=False) elif isinstance(expr, gem.Literal): assert parameters.declare[leaf] + if expr.track_zeros: + # Take all axes except the last one + axes = tuple(range(len(expr.array.shape) - 1)) + nz_indices, = expr.array.any(axis=axes).nonzero() + nz_bounds = tuple([(i, 0)] for i in expr.array.shape[:-1]) + nz_bounds += ([(max(nz_indices) - min(nz_indices) + 1, min(nz_indices))],) + init = coffee.SparseArrayInit(expr.array, PRECISION, nz_bounds) + else: + init = coffee.ArrayInit(expr.array, precision=PRECISION) return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), - coffee.ArrayInit(expr.array, precision=PRECISION), + init, qualifiers=["static", "const"]) else: code = expression(expr, parameters, top=True) diff --git a/tsfc/fem.py b/tsfc/fem.py index bb281cbbbf..27a8b20194 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -6,6 +6,7 @@ import numpy from singledispatch import singledispatch +import ufl from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction from ufl.classes import (Argument, Coefficient, FormArgument, @@ -234,6 +235,11 @@ def flat_index(ordered_deriv): return result[0] +def zero_tracking_literal(table, ufl_element): + track_zeros = isinstance(ufl_element, (ufl.EnrichedElement, ufl.MixedElement)) + return gem.Literal(table, track_zeros) + + @singledispatch def translate(terminal, mt, params): """Translates modified terminals into GEM. @@ -264,9 +270,9 @@ def callback(key): table = params.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant - row = gem.Literal(table) + row = zero_tracking_literal(table, key[0]) else: - table = params.select_facet(gem.Literal(table), mt.restriction) + table = params.select_facet(zero_tracking_literal(table, key[0]), mt.restriction) row = gem.partial_indexed(table, (params.quadrature_index,)) return gem.Indexed(row, (argument_index,)) @@ -287,7 +293,7 @@ def callback(key): table = params.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant - row = gem.Literal(table) + row = zero_tracking_literal(table, key[0]) if numpy.count_nonzero(table) <= 2: assert row.shape == ka.shape return reduce(gem.Sum, @@ -295,7 +301,7 @@ def callback(key): for i in range(row.shape[0])], gem.Zero()) else: - table = params.select_facet(gem.Literal(table), mt.restriction) + table = params.select_facet(zero_tracking_literal(table, key[0]), mt.restriction) row = gem.partial_indexed(table, (params.quadrature_index,)) r = params.index_cache[terminal.ufl_element()] From a26745551ecc0b2cf7b2c483d0ad8eec2eb04306 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 5 Apr 2016 16:12:51 +0100 Subject: [PATCH 104/809] always use coffee.SparseArrayInit This partially reverts commit 20b894bcfbfe1d79c9b3d44e650e4a3a62c69e6f. --- tsfc/coffee.py | 15 ++++++--------- tsfc/fem.py | 14 ++++---------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index c3bd63010c..612525a606 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -179,15 +179,12 @@ def statement_evaluate(leaf, parameters): return coffee.Block(ops, open_scope=False) elif isinstance(expr, gem.Literal): assert parameters.declare[leaf] - if expr.track_zeros: - # Take all axes except the last one - axes = tuple(range(len(expr.array.shape) - 1)) - nz_indices, = expr.array.any(axis=axes).nonzero() - nz_bounds = tuple([(i, 0)] for i in expr.array.shape[:-1]) - nz_bounds += ([(max(nz_indices) - min(nz_indices) + 1, min(nz_indices))],) - init = coffee.SparseArrayInit(expr.array, PRECISION, nz_bounds) - else: - init = coffee.ArrayInit(expr.array, precision=PRECISION) + # Take all axes except the last one + axes = tuple(range(len(expr.array.shape) - 1)) + nz_indices, = expr.array.any(axis=axes).nonzero() + nz_bounds = tuple([(i, 0)] for i in expr.array.shape[:-1]) + nz_bounds += ([(max(nz_indices) - min(nz_indices) + 1, min(nz_indices))],) + init = coffee.SparseArrayInit(expr.array, PRECISION, nz_bounds) return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), init, diff --git a/tsfc/fem.py b/tsfc/fem.py index 27a8b20194..bb281cbbbf 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -6,7 +6,6 @@ import numpy from singledispatch import singledispatch -import ufl from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction from ufl.classes import (Argument, Coefficient, FormArgument, @@ -235,11 +234,6 @@ def flat_index(ordered_deriv): return result[0] -def zero_tracking_literal(table, ufl_element): - track_zeros = isinstance(ufl_element, (ufl.EnrichedElement, ufl.MixedElement)) - return gem.Literal(table, track_zeros) - - @singledispatch def translate(terminal, mt, params): """Translates modified terminals into GEM. @@ -270,9 +264,9 @@ def callback(key): table = params.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant - row = zero_tracking_literal(table, key[0]) + row = gem.Literal(table) else: - table = params.select_facet(zero_tracking_literal(table, key[0]), mt.restriction) + table = params.select_facet(gem.Literal(table), mt.restriction) row = gem.partial_indexed(table, (params.quadrature_index,)) return gem.Indexed(row, (argument_index,)) @@ -293,7 +287,7 @@ def callback(key): table = params.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant - row = zero_tracking_literal(table, key[0]) + row = gem.Literal(table) if numpy.count_nonzero(table) <= 2: assert row.shape == ka.shape return reduce(gem.Sum, @@ -301,7 +295,7 @@ def callback(key): for i in range(row.shape[0])], gem.Zero()) else: - table = params.select_facet(zero_tracking_literal(table, key[0]), mt.restriction) + table = params.select_facet(gem.Literal(table), mt.restriction) row = gem.partial_indexed(table, (params.quadrature_index,)) r = params.index_cache[terminal.ufl_element()] From a7d65bf440c9f9ca7a1def3a09cd38eb7373f3d6 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 11 Apr 2016 10:04:57 +0100 Subject: [PATCH 105/809] remove coffee_licm from parameters --- tsfc/constants.py | 6 ------ tsfc/driver.py | 1 - 2 files changed, 7 deletions(-) diff --git a/tsfc/constants.py b/tsfc/constants.py index 56501564fb..deb8309d75 100644 --- a/tsfc/constants.py +++ b/tsfc/constants.py @@ -15,12 +15,6 @@ "quadrature_rule": "auto", "quadrature_degree": "auto", - # Trust COFFEE to do loop-invariant code motion. Disabled by - # default as COFFEE does not work on TSFC-generated code yet. - # When enabled, it allows the inlining of expressions even if that - # pulls calculations into inner loops. - "coffee_licm": False, - # Maximum extent to unroll index sums. Default is 3, so that loops # over geometric dimensions are unrolled; this improves assembly # performance. Can be disabled by setting it to None, False or 0; diff --git a/tsfc/driver.py b/tsfc/driver.py index 4f63ad4db8..45d006f228 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -148,7 +148,6 @@ def compile_integral(integral_data, form_data, prefix, parameters): impero_c = impero_utils.compile_gem(return_variables, ir, tuple(quadrature_indices) + argument_indices, - coffee_licm=parameters["coffee_licm"], remove_zeros=True) # Generate COFFEE From caf8f5b05459b91ea66efac440cc2933f9c555d2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 11 Apr 2016 11:34:11 +0100 Subject: [PATCH 106/809] gem: introduce Constant and add Identity --- tsfc/coffee.py | 8 +++----- tsfc/ufl2gem.py | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 18b762823c..d2b113394a 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -13,8 +13,7 @@ import coffee.base as coffee -import gem -import gem.impero as imp +from gem import gem, impero as imp from tsfc.constants import SCALAR_TYPE, PRECISION @@ -150,7 +149,7 @@ def statement_evaluate(leaf, parameters): coffee_sym = _coffee_symbol(_ref_symbol(expr, parameters), rank=multiindex) ops.append(coffee.Assign(coffee_sym, expression(value, parameters))) return coffee.Block(ops, open_scope=False) - elif isinstance(expr, gem.Literal): + elif isinstance(expr, gem.Constant): assert parameters.declare[leaf] return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), @@ -256,8 +255,7 @@ def _expression_conditional(expr, parameters): return coffee.Ternary(*[expression(c, parameters) for c in expr.children]) -@_expression.register(gem.Literal) -@_expression.register(gem.Zero) +@_expression.register(gem.Constant) def _expression_scalar(expr, parameters): assert not expr.shape if isnan(expr.value): diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 8abfea367e..d20f8b199f 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -6,8 +6,8 @@ import numpy import ufl -from gem import (Literal, Zero, Sum, Product, Division, Power, - MathFunction, MinValue, MaxValue, Comparison, +from gem import (Literal, Zero, Identity, Sum, Product, Division, + Power, MathFunction, MinValue, MaxValue, Comparison, LogicalNot, LogicalAnd, LogicalOr, Conditional, Index, Indexed, ComponentTensor, IndexSum, ListTensor) @@ -28,7 +28,7 @@ def scalar_value(self, o): return Literal(o.value()) def identity(self, o): - return Literal(numpy.eye(*o.ufl_shape)) + return Identity(o._dim) def zero(self, o): return Zero(o.ufl_shape) From baec495c79ede57da7e38602d4c75e81b3a9226d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 20 Apr 2016 14:10:37 +0100 Subject: [PATCH 107/809] allow building ListTensor from non-scalars --- tsfc/ufl2gem.py | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index d20f8b199f..1d9654dfaf 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -3,7 +3,6 @@ from __future__ import absolute_import import collections -import numpy import ufl from gem import (Literal, Zero, Identity, Sum, Product, Division, @@ -101,36 +100,7 @@ def indexed(self, o, aggregate, index): return Indexed(aggregate, index) def list_tensor(self, o, *ops): - # UFL and GEM have a few semantical differences when it comes - # to ListTensor. In UFL, a ListTensor increases the rank by - # one with respect to its constituents. So to build a matrix - # from scalars, one must build a ListTensor of ListTensors in - # UFL, while a GEM ListTensor can directly construct rank two - # tensor from scalars because it has an explicitly specified - # shape. - nesting = [isinstance(op, ListTensor) for op in ops] - if all(nesting): - # ListTensor of ListTensors in UFL, build a ListTensor of - # higher rank in GEM. - return ListTensor(numpy.array([op.array for op in ops])) - elif len(o.ufl_shape) > 1: - # On the other hand, TSFC only builds ListTensors from - # scalars, while the constituents of a UFL ListTensor can - # be non-scalar despite not being ListTensors themselves - # (e.g. ComponentTensor, Zero). - # - # In this case we currently break up the tensor-valued - # constituents into scalars by generating fixed indices to - # access to every single element. - children = [] - for op in ops: - child = numpy.zeros(o.ufl_shape[1:], dtype=object) - for multiindex in numpy.ndindex(child.shape): - child[multiindex] = Indexed(op, multiindex) - children.append(child) - return ListTensor(numpy.array(children)) - else: - return ListTensor(numpy.array(ops)) + return ListTensor(ops) def component_tensor(self, o, expression, index): return ComponentTensor(expression, index) From c93e491fceeb1c218e8d0bb1a2473028511a34a3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 25 Apr 2016 15:14:00 +0100 Subject: [PATCH 108/809] add FInAT interface --- tsfc/finatinterface.py | 134 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 tsfc/finatinterface.py diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py new file mode 100644 index 0000000000..743bd22aaf --- /dev/null +++ b/tsfc/finatinterface.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# +# This file was modified from FFC +# (http://bitbucket.org/fenics-project/ffc), copyright notice +# reproduced below. +# +# Copyright (C) 2009-2013 Kristian B. Oelgaard and Anders Logg +# +# This file is part of FFC. +# +# FFC is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# FFC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with FFC. If not, see . + +from __future__ import absolute_import +from __future__ import print_function + +from singledispatch import singledispatch +import weakref + +import FIAT +import finat + +import ufl + + +__all__ = ("create_element", "supported_elements", "as_fiat_cell") + + +supported_elements = { + # These all map directly to FInAT elements + "Discontinuous Lagrange": finat.DiscontinuousLagrange, + "Lagrange": finat.Lagrange, +} +"""A :class:`.dict` mapping UFL element family names to their +FIAT-equivalent constructors. If the value is ``None``, the UFL +element is supported, but must be handled specially because it doesn't +have a direct FIAT equivalent.""" + + +def as_fiat_cell(cell): + """Convert a ufl cell to a FIAT cell. + + :arg cell: the :class:`ufl.Cell` to convert.""" + if not isinstance(cell, ufl.AbstractCell): + raise ValueError("Expecting a UFL Cell") + return FIAT.ufc_cell(cell) + + +@singledispatch +def convert(element, vector_is_mixed): + """Handler for converting UFL elements to FIAT elements. + + :arg element: The UFL element to convert. + :arg vector_is_mixed: Should Vector and Tensor elements be treated + as Mixed? If ``False``, then just look at the sub-element. + + Do not use this function directly, instead call + :func:`create_element`.""" + if element.family() in supported_elements: + raise ValueError("Element %s supported, but no handler provided" % element) + raise ValueError("Unsupported element type %s" % type(element)) + + +# Base finite elements first +@convert.register(ufl.FiniteElement) # noqa +def _(element, vector_is_mixed): + cell = as_fiat_cell(element.cell()) + lmbda = supported_elements[element.family()] + return lmbda(cell, element.degree()) + + +# MixedElement case +@convert.register(ufl.MixedElement) # noqa +def _(element, vector_is_mixed): + raise NotImplementedError("MixedElement not implemented in FInAT yet.") + + +# VectorElement case +@convert.register(ufl.VectorElement) # noqa +def _(element, vector_is_mixed): + # If we're just trying to get the scalar part of a vector element? + if not vector_is_mixed: + return create_element(element.sub_elements()[0], vector_is_mixed) + + scalar_element = create_element(element.sub_elements()[0], vector_is_mixed) + return finat.VectorFiniteElement(scalar_element, element.num_sub_elements()) + + +# TensorElement case +@convert.register(ufl.TensorElement) # noqa +def _(element, vector_is_mixed): + raise NotImplementedError("TensorElement not implemented in FInAT yet.") + + +_cache = weakref.WeakKeyDictionary() + + +def create_element(element, vector_is_mixed=True): + """Create a FIAT element (suitable for tabulating with) given a UFL element. + + :arg element: The UFL element to create a FIAT element from. + + :arg vector_is_mixed: indicate whether VectorElement (or + TensorElement) should be treated as a MixedElement. Maybe + useful if you want a FIAT element that tells you how many + "nodes" the finite element has. + """ + try: + cache = _cache[element] + except KeyError: + _cache[element] = {} + cache = _cache[element] + + try: + return cache[vector_is_mixed] + except KeyError: + pass + + if element.cell() is None: + raise ValueError("Don't know how to build element when cell is not given") + + fiat_element = convert(element, vector_is_mixed) + cache[vector_is_mixed] = fiat_element + return fiat_element From 1bf2f96297dc0f176983f437f9ff177f9de0700d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 25 Apr 2016 17:19:27 +0100 Subject: [PATCH 109/809] WIP: FInAT integration --- tsfc/driver.py | 21 +++++--- tsfc/fem.py | 112 +++++++++++---------------------------- tsfc/kernel_interface.py | 30 ++++++----- 3 files changed, 60 insertions(+), 103 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9cdd30d51f..7c1b52f787 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -11,11 +11,13 @@ import gem.optimise as opt import gem.impero_utils as impero_utils +from finat.quadrature import QuadratureRule, CollapsedGaussJacobiQuadrature + from tsfc import fem, ufl_utils from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.kernel_interface import KernelBuilder, needs_cell_orientations -from tsfc.quadrature import create_quadrature, QuadratureRule +from tsfc.quadrature import create_quadrature def compile_form(form, prefix="form", parameters=None): @@ -114,9 +116,12 @@ def compile_integral(integral_data, form_data, prefix, parameters): # the estimated polynomial degree attached by compute_form_data quadrature_degree = params.get("quadrature_degree", params["estimated_polynomial_degree"]) - quad_rule = params.get("quadrature_rule", - create_quadrature(cell, integral_type, - quadrature_degree)) + try: + quad_rule = params["quadrature_rule"] + except KeyError: + quad_rule = create_quadrature(cell, integral_type, quadrature_degree) + quad_rule = QuadratureRule(cell, quad_rule.points, quad_rule.weights) + quad_rule.__class__ = CollapsedGaussJacobiQuadrature if not isinstance(quad_rule, QuadratureRule): raise ValueError("Expected to find a QuadratureRule object, not a %s" % @@ -125,10 +130,10 @@ def compile_integral(integral_data, form_data, prefix, parameters): integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='ip') quadrature_indices.append(quadrature_index) - ir = fem.process(integral_type, cell, quad_rule.points, - quad_rule.weights, quadrature_index, - argument_indices, integrand, - builder.coefficient_mapper, index_cache) + ir = fem.process(integral_type, cell, quad_rule, + quadrature_index, argument_indices, + integrand, builder.coefficient_mapper, + index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) irs.append([(gem.IndexSum(expr, quadrature_index) diff --git a/tsfc/fem.py b/tsfc/fem.py index bb281cbbbf..bf8c3a2781 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -14,63 +14,20 @@ import gem from tsfc.constants import PRECISION -from tsfc.fiatinterface import create_element, as_fiat_cell +from tsfc.finatinterface import create_element, as_fiat_cell from tsfc.modified_terminals import analyse_modified_terminal from tsfc import compat from tsfc import ufl2gem from tsfc import geometric from tsfc.ufl_utils import (CollectModifiedTerminals, ModifiedTerminalMixin, PickRestriction, - spanning_degree, simplify_abs) + simplify_abs) # FFC uses one less digits for rounding than for printing epsilon = eval("1e-%d" % (PRECISION - 1)) -def _tabulate(ufl_element, order, points): - """Ask FIAT to tabulate ``points`` up to order ``order``, then - rearranges the result into a series of ``(c, D, table)`` tuples, - where: - - c: component index (for vector-valued and tensor-valued elements) - D: derivative tuple (e.g. (1, 2) means d/dx d^2/dy^2) - table: tabulation matrix for the given component and derivative. - shape: len(points) x space_dimension - - :arg ufl_element: element to tabulate - :arg order: FIAT gives all derivatives up to this order - :arg points: points to tabulate the element on - """ - element = create_element(ufl_element) - phi = element.space_dimension() - C = ufl_element.reference_value_size() - len(ufl_element.symmetry()) - q = len(points) - for D, fiat_table in element.tabulate(order, points).iteritems(): - reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) - for c, table in enumerate(reordered_table): - yield c, D, table - - -def tabulate(ufl_element, order, points): - """Same as the above, but also applies FFC rounding and recognises - cellwise constantness. Cellwise constantness is determined - symbolically, but we also check the numerics to be safe.""" - for c, D, table in _tabulate(ufl_element, order, points): - # Copied from FFC (ffc/quadrature/quadratureutils.py) - table[abs(table) < epsilon] = 0 - table[abs(table - 1.0) < epsilon] = 1.0 - table[abs(table + 1.0) < epsilon] = -1.0 - table[abs(table - 0.5) < epsilon] = 0.5 - table[abs(table + 0.5) < epsilon] = -0.5 - - if spanning_degree(ufl_element) <= sum(D): - assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) - table = table[0] - - yield c, D, table - - def make_tabulator(points): """Creates a tabulator for an array of points.""" return lambda elem, order: tabulate(elem, order, points) @@ -146,14 +103,15 @@ def __getitem__(self, key): class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): """Contains all the context necessary to translate UFL into GEM.""" - def __init__(self, tabulation_manager, weights, quadrature_index, + def __init__(self, tabulation_manager, quad_rule, quadrature_index, argument_indices, coefficient_mapper, index_cache): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) integral_type = tabulation_manager.integral_type self.integral_type = integral_type self.tabulation_manager = tabulation_manager - self.weights = gem.Literal(weights) + self.quad_rule = quad_rule + self.weights = gem.Literal(quad_rule.weights) self.quadrature_index = quadrature_index self.argument_indices = argument_indices self.coefficient_mapper = coefficient_mapper @@ -260,17 +218,14 @@ def _(terminal, mt, params): def _(terminal, mt, params): argument_index = params.argument_indices[terminal.number()] - def callback(key): - table = params.tabulation_manager[key] - if len(table.shape) == 1: - # Cellwise constant - row = gem.Literal(table) - else: - table = params.select_facet(gem.Literal(table), mt.restriction) - row = gem.partial_indexed(table, (params.quadrature_index,)) - return gem.Indexed(row, (argument_index,)) - - return iterate_shape(mt, callback) + element = create_element(terminal.ufl_element()) + M = element.basis_evaluation(params.quad_rule, derivative=mt.local_derivatives) + vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) + result = gem.Indexed(M, (params.quadrature_index, argument_index) + vi) + if vi: + return gem.ComponentTensor(result, vi) + else: + return result @translate.register(Coefficient) # noqa: Not actually redefinition @@ -283,29 +238,21 @@ def _(terminal, mt, params): ka = gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[mt.restriction]) - def callback(key): - table = params.tabulation_manager[key] - if len(table.shape) == 1: - # Cellwise constant - row = gem.Literal(table) - if numpy.count_nonzero(table) <= 2: - assert row.shape == ka.shape - return reduce(gem.Sum, - [gem.Product(gem.Indexed(row, (i,)), gem.Indexed(ka, (i,))) - for i in range(row.shape[0])], - gem.Zero()) - else: - table = params.select_facet(gem.Literal(table), mt.restriction) - row = gem.partial_indexed(table, (params.quadrature_index,)) - - r = params.index_cache[terminal.ufl_element()] - return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), - gem.Indexed(ka, (r,))), r) - - return iterate_shape(mt, callback) + element = create_element(terminal.ufl_element()) + M = element.basis_evaluation(params.quad_rule, derivative=mt.local_derivatives) + alpha = element.get_indices() + vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) + result = gem.Product(gem.Indexed(M, (params.quadrature_index,) + alpha + vi), + gem.Indexed(ka, alpha)) + for i in alpha: + result = gem.IndexSum(result, i) + if vi: + return gem.ComponentTensor(result, vi) + else: + return result -def process(integral_type, cell, points, weights, quadrature_index, +def process(integral_type, cell, quad_rule, quadrature_index, argument_indices, integrand, coefficient_mapper, index_cache): # Abs-simplification integrand = simplify_abs(integrand) @@ -323,10 +270,11 @@ def process(integral_type, cell, points, weights, quadrature_index, max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(integral_type, cell, points) + tabulation_manager = TabulationManager(integral_type, cell, quad_rule.points) # TODO for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': - tabulation_manager.tabulate(ufl_element, max_deriv) + pass # TODO + # tabulation_manager.tabulate(ufl_element, max_deriv) if integral_type.startswith("interior_facet"): expressions = [] @@ -337,7 +285,7 @@ def process(integral_type, cell, points, weights, quadrature_index, # Translate UFL to Einstein's notation, # lowering finite element specific nodes - translator = Translator(tabulation_manager, weights, + translator = Translator(tabulation_manager, quad_rule, quadrature_index, argument_indices, coefficient_mapper, index_cache) return map_expr_dags(translator, expressions) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index bba7fb7428..855e7b5504 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import operator + import numpy import coffee.base as coffee @@ -7,7 +9,7 @@ import gem from gem.node import traversal -from tsfc.fiatinterface import create_element +from tsfc.finatinterface import create_element from tsfc.mixedelement import MixedElement from tsfc.coffee import SCALAR_TYPE @@ -233,27 +235,27 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): return funarg, [], expression - fiat_element = create_element(coefficient.ufl_element()) + finat_element = create_element(coefficient.ufl_element()) if not interior_facet: # Simple case - shape = (fiat_element.space_dimension(),) - funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), + shape = finat_element.index_shape + funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=tuple(reversed(shape))), qualifiers=["const"]) - i = gem.Index() + alpha = tuple(gem.Index() for d in shape) expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, shape + (1,)), - (i, 0)), - (i,)) + gem.Indexed(gem.Variable(name, tuple(reversed(shape)) + (1,)), + tuple(reversed(alpha)) + (0,)), + alpha) return funarg, [], expression - if not isinstance(fiat_element, MixedElement): + if not isinstance(finat_element, MixedElement): # Interior facet integral - shape = (2, fiat_element.space_dimension()) + shape = (2,) + finat_element.index_shape funarg = coffee.Decl("%s *restrict" % SCALAR_TYPE, coffee.Symbol(name, rank=shape), qualifiers=["const"]) @@ -267,6 +269,7 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): return funarg, [], expression + assert False, "Deal with FInAT + mixed + interior facet later." # Interior facet integral + mixed / vector element # Here we need to reorder the coefficient values. @@ -286,7 +289,7 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): # to reorder the values. A whole E[n]{+,-} block is copied by # a single loop. name_ = name + "_" - shape = (2, fiat_element.space_dimension()) + shape = (2,) + fiat_element.index_shape funarg = coffee.Decl("%s *restrict *restrict" % SCALAR_TYPE, coffee.Symbol(name_), qualifiers=["const"]) @@ -368,7 +371,7 @@ def prepare_arguments(arguments, indices, interior_facet=False): if not interior_facet: # Not an interior facet integral - shape = tuple(element.space_dimension() for element in elements) + shape = reduce(operator.add, [element.index_shape for element in elements], ()) funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) expression = gem.Indexed(gem.Variable("A", shape), indices) @@ -379,7 +382,7 @@ def prepare_arguments(arguments, indices, interior_facet=False): # Interior facet integral, but no vector (mixed) arguments shape = [] for element in elements: - shape += [2, element.space_dimension()] + shape += [2] + list(element.index_shape) shape = tuple(shape) funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) @@ -392,6 +395,7 @@ def prepare_arguments(arguments, indices, interior_facet=False): return funarg, [], expressions, [] + assert False, "Deal with FInAT + vector argument + interior facet later." # Interior facet integral + vector (mixed) argument(s) shape = tuple(element.space_dimension() for element in elements) funarg_shape = tuple(s * 2 for s in shape) From c65e1e40f60b08a1f884e10804a0a8c8b6b5f292 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 3 May 2016 11:32:20 +0100 Subject: [PATCH 110/809] refactor and create FacetManager --- tsfc/fem.py | 99 +++++++++++++++++++++++++++++------------------ tsfc/geometric.py | 4 +- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index bb281cbbbf..785bcd81c2 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -91,30 +91,10 @@ def __init__(self, integral_type, cell, points): self.integral_type = integral_type self.points = points - self.tabulators = [] + self.facet_manager = FacetManager(integral_type, cell) + self.tabulators = map(make_tabulator, self.facet_manager.facet_transform(points)) self.tables = {} - if integral_type == 'cell': - self.tabulators.append(make_tabulator(points)) - - elif integral_type in ['exterior_facet', 'interior_facet']: - for entity in range(cell.num_facets()): - t = as_fiat_cell(cell).get_facet_transform(entity) - self.tabulators.append(make_tabulator(numpy.asarray(map(t, points)))) - - elif integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: - for entity in range(2): # top and bottom - t = as_fiat_cell(cell).get_horiz_facet_transform(entity) - self.tabulators.append(make_tabulator(numpy.asarray(map(t, points)))) - - elif integral_type in ['exterior_facet_vert', 'interior_facet_vert']: - for entity in range(cell.sub_cells()[0].num_facets()): # "base cell" facets - t = as_fiat_cell(cell).get_vert_facet_transform(entity) - self.tabulators.append(make_tabulator(numpy.asarray(map(t, points)))) - - else: - raise NotImplementedError("integral type %s not supported" % integral_type) - def tabulate(self, ufl_element, max_deriv): """Prepare the tabulations of a finite element up to a given derivative order. @@ -143,21 +123,17 @@ def __getitem__(self, key): return self.tables[key] -class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): - """Contains all the context necessary to translate UFL into GEM.""" +class FacetManager(object): + """Collection of utilities for facet integrals.""" - def __init__(self, tabulation_manager, weights, quadrature_index, - argument_indices, coefficient_mapper, index_cache): - MultiFunction.__init__(self) - ufl2gem.Mixin.__init__(self) - integral_type = tabulation_manager.integral_type + def __init__(self, integral_type, ufl_cell): + """Constructs a FacetManager. + + :arg integral_type: integral type + :arg ufl_cell: UFL cell + """ self.integral_type = integral_type - self.tabulation_manager = tabulation_manager - self.weights = gem.Literal(weights) - self.quadrature_index = quadrature_index - self.argument_indices = argument_indices - self.coefficient_mapper = coefficient_mapper - self.index_cache = index_cache + self.ufl_cell = ufl_cell if integral_type in ['exterior_facet', 'exterior_facet_vert']: self.facet = {None: gem.VariableIndex(gem.Indexed(gem.Variable('facet', (1,)), (0,)))} @@ -173,10 +149,32 @@ def __init__(self, tabulation_manager, weights, quadrature_index, else: self.facet = None - if self.integral_type.startswith("interior_facet"): - self.cell_orientations = gem.Variable("cell_orientations", (2, 1)) + def facet_transform(self, points): + """Generator function that transforms points in integration cell + coordinates to cell coordinates for each facet. + + :arg points: points in integration cell coordinates + """ + if self.integral_type == 'cell': + yield points + + elif self.integral_type in ['exterior_facet', 'interior_facet']: + for entity in range(self.ufl_cell.num_facets()): + t = as_fiat_cell(self.ufl_cell).get_facet_transform(entity) + yield numpy.asarray(map(t, points)) + + elif self.integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: + for entity in range(2): # top and bottom + t = as_fiat_cell(self.ufl_cell).get_horiz_facet_transform(entity) + yield numpy.asarray(map(t, points)) + + elif self.integral_type in ['exterior_facet_vert', 'interior_facet_vert']: + for entity in range(self.ufl_cell.sub_cells()[0].num_facets()): # "base cell" facets + t = as_fiat_cell(self.ufl_cell).get_vert_facet_transform(entity) + yield numpy.asarray(map(t, points)) + else: - self.cell_orientations = gem.Variable("cell_orientations", (1, 1)) + raise NotImplementedError("integral type %s not supported" % self.integral_type) def select_facet(self, tensor, restriction): """Applies facet selection on a GEM tensor if necessary. @@ -191,6 +189,31 @@ def select_facet(self, tensor, restriction): f = self.facet[restriction] return gem.partial_indexed(tensor, (f,)) + +class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): + """Contains all the context necessary to translate UFL into GEM.""" + + def __init__(self, tabulation_manager, weights, quadrature_index, + argument_indices, coefficient_mapper, index_cache): + MultiFunction.__init__(self) + ufl2gem.Mixin.__init__(self) + integral_type = tabulation_manager.integral_type + facet_manager = tabulation_manager.facet_manager + self.integral_type = integral_type + self.tabulation_manager = tabulation_manager + self.weights = gem.Literal(weights) + self.quadrature_index = quadrature_index + self.argument_indices = argument_indices + self.coefficient_mapper = coefficient_mapper + self.index_cache = index_cache + self.facet_manager = facet_manager + self.select_facet = facet_manager.select_facet + + if self.integral_type.startswith("interior_facet"): + self.cell_orientations = gem.Variable("cell_orientations", (2, 1)) + else: + self.cell_orientations = gem.Variable("cell_orientations", (1, 1)) + def modified_terminal(self, o): """Overrides the modified terminal handler from :class:`ModifiedTerminalMixin`.""" diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 6f7b4a883f..364d038243 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -159,7 +159,7 @@ def _(terminal, mt, params): table = cell_facet_jacobian[reference_cell(terminal)] table = strip_table(table, params.integral_type) table = table.reshape(table.shape[:1] + terminal.ufl_shape) - return gem.partial_indexed(gem.Literal(table), (params.facet[mt.restriction],)) + return params.select_facet(gem.Literal(table), mt.restriction) @translate.register(ReferenceNormal) # noqa @@ -167,7 +167,7 @@ def _(terminal, mt, params): table = reference_normal[reference_cell(terminal)] table = strip_table(table, params.integral_type) table = table.reshape(table.shape[:1] + terminal.ufl_shape) - return gem.partial_indexed(gem.Literal(table), (params.facet[mt.restriction],)) + return params.select_facet(gem.Literal(table), mt.restriction) @translate.register(CellEdgeVectors) # noqa From 37e09f552d2c28889fac4f7ee80f3890b0cea118 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 3 May 2016 11:36:44 +0100 Subject: [PATCH 111/809] remove noqa in geometric.py --- tsfc/geometric.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 364d038243..bd2ec500e4 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -137,8 +137,8 @@ def translate(terminal, mt, params): raise AssertionError("Cannot handle geometric quantity type: %s" % type(terminal)) -@translate.register(CellOrientation) # noqa -def _(terminal, mt, params): +@translate.register(CellOrientation) +def translate_cell_orientation(terminal, mt, params): cell_orientations = params.cell_orientations f = {None: 0, '+': 0, '-': 1}[mt.restriction] co_int = gem.Indexed(cell_orientations, (f, 0)) @@ -149,29 +149,29 @@ def _(terminal, mt, params): gem.Literal(nan))) -@translate.register(ReferenceCellVolume) # noqa -def _(terminal, mt, params): +@translate.register(ReferenceCellVolume) +def translate_reference_cell_volume(terminal, mt, params): return gem.Literal(reference_cell_volume[reference_cell(terminal)]) -@translate.register(CellFacetJacobian) # noqa -def _(terminal, mt, params): +@translate.register(CellFacetJacobian) +def translate_cell_facet_jacobian(terminal, mt, params): table = cell_facet_jacobian[reference_cell(terminal)] table = strip_table(table, params.integral_type) table = table.reshape(table.shape[:1] + terminal.ufl_shape) return params.select_facet(gem.Literal(table), mt.restriction) -@translate.register(ReferenceNormal) # noqa -def _(terminal, mt, params): +@translate.register(ReferenceNormal) +def translate_reference_normal(terminal, mt, params): table = reference_normal[reference_cell(terminal)] table = strip_table(table, params.integral_type) table = table.reshape(table.shape[:1] + terminal.ufl_shape) return params.select_facet(gem.Literal(table), mt.restriction) -@translate.register(CellEdgeVectors) # noqa -def _(terminal, mt, params): +@translate.register(CellEdgeVectors) +def translate_cell_edge_vectors(terminal, mt, params): from FIAT.reference_element import TensorProductCell as fiat_TensorProductCell fiat_cell = as_fiat_cell(terminal.ufl_domain().ufl_cell()) if isinstance(fiat_cell, fiat_TensorProductCell): From 9ca56c7e00501c2612f5fadbe7116718bf0bed4e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 3 May 2016 11:57:07 +0100 Subject: [PATCH 112/809] support CellCoordinate and FacetCoordinate --- tsfc/geometric.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index bd2ec500e4..58e3d77cfc 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -8,8 +8,9 @@ from ufl import interval, triangle, quadrilateral, tetrahedron from ufl import TensorProductCell -from ufl.classes import (CellEdgeVectors, CellFacetJacobian, - CellOrientation, ReferenceCellVolume, +from ufl.classes import (CellCoordinate, CellEdgeVectors, + CellFacetJacobian, CellOrientation, + FacetCoordinate, ReferenceCellVolume, ReferenceNormal) import gem @@ -181,3 +182,20 @@ def translate_cell_edge_vectors(terminal, mt, params): vecs = vstack(map(fiat_cell.compute_edge_tangent, range(nedges))).astype(NUMPY_TYPE) assert vecs.shape == terminal.ufl_shape return gem.Literal(vecs) + + +@translate.register(CellCoordinate) +def translate_cell_coordinate(terminal, mt, params): + points = params.tabulation_manager.points + if params.integral_type != 'cell': + points = list(params.facet_manager.facet_transform(points)) + return gem.partial_indexed(params.select_facet(gem.Literal(points), + mt.restriction), + (params.quadrature_index,)) + + +@translate.register(FacetCoordinate) +def translate_facet_coordinate(terminal, mt, params): + assert params.integral_type != 'cell' + points = params.tabulation_manager.points + return gem.partial_indexed(gem.Literal(points), (params.quadrature_index,)) From f9c95596bf9ebe1510af766ffa06a1b3dc931e1c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 27 Apr 2016 14:19:11 +0100 Subject: [PATCH 113/809] add CoefficientSplitter for mixed coefficients --- tsfc/modified_terminals.py | 25 +++++++++++++++ tsfc/ufl_utils.py | 62 ++++++++++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/tsfc/modified_terminals.py b/tsfc/modified_terminals.py index 6fda005675..f83bee9b59 100644 --- a/tsfc/modified_terminals.py +++ b/tsfc/modified_terminals.py @@ -23,6 +23,7 @@ from __future__ import print_function # used in some debugging from ufl.classes import (ReferenceValue, ReferenceGrad, + NegativeRestricted, PositiveRestricted, Restricted, FacetAvg, CellAvg) @@ -161,3 +162,27 @@ def analyse_modified_terminal(expr): raise ValueError("Local derivatives of non-local value?") return mt + + +def construct_modified_terminal(mt, terminal): + """Construct a modified terminal given terminal modifiers from an + analysed modified terminal and a terminal.""" + expr = terminal + + if mt.reference_value: + expr = ReferenceValue(expr) + + for n in range(mt.local_derivatives): + expr = ReferenceGrad(expr) + + if mt.averaged == "cell": + expr = CellAvg(expr) + elif mt.averaged == "facet": + expr = FacetAvg(expr) + + if mt.restriction == '+': + expr = PositiveRestricted(expr) + elif mt.restriction == '-': + expr = NegativeRestricted(expr) + + return expr diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index ee8ee87eaa..78ae243c05 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -2,18 +2,23 @@ from __future__ import absolute_import +import numpy from singledispatch import singledispatch import ufl +from ufl import indices, as_tensor from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, ReferenceValue, Zero) -from ufl.classes import (Abs, CellOrientation, Expr, FloatValue, - Division, Product, ScalarValue, Sqrt) +from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, + ComponentTensor, Expr, FloatValue, Division, + MixedElement, MultiIndex, Product, + ReferenceValue, ScalarValue, Sqrt, Zero) from gem.node import MemoizerArg -from tsfc.modified_terminals import is_modified_terminal, analyse_modified_terminal +from tsfc.modified_terminals import (is_modified_terminal, + analyse_modified_terminal, + construct_modified_terminal) def is_element_affine(ufl_element): @@ -22,7 +27,7 @@ def is_element_affine(ufl_element): return ufl_element.cell().cellname() in affine_cells and ufl_element.degree() == 1 -class ReplaceSpatialCoordinates(MultiFunction): +class SpatialCoordinateReplacer(MultiFunction): """Replace SpatialCoordinate nodes with the ReferenceValue of a Coefficient. Assumes that the coordinate element only needs affine mapping. @@ -45,7 +50,7 @@ def spatial_coordinate(self, o): def replace_coordinates(integrand, coordinate_coefficient): """Replace SpatialCoordinate nodes with Coefficients.""" - return map_expr_dag(ReplaceSpatialCoordinates(coordinate_coefficient), integrand) + return map_expr_dag(SpatialCoordinateReplacer(coordinate_coefficient), integrand) def coordinate_coefficient(domain): @@ -88,6 +93,51 @@ def _modified_terminal(self, o): terminal = _modified_terminal +class CoefficientSplitter(MultiFunction, ModifiedTerminalMixin): + def __init__(self, split): + MultiFunction.__init__(self) + self._split = split + + expr = MultiFunction.reuse_if_untouched + + def modified_terminal(self, o): + mt = analyse_modified_terminal(o) + terminal = mt.terminal + + if not isinstance(terminal, Coefficient): + # Only split coefficients + return o + + if type(terminal.ufl_element()) != MixedElement: + # Only split mixed coefficients + return o + + # Reference value expected + assert mt.reference_value + + # Derivative indices + beta = indices(mt.local_derivatives) + + components = [] + for subcoeff in self._split[terminal]: + # Apply terminal modifiers onto the subcoefficient + component = construct_modified_terminal(mt, subcoeff) + # Collect components of the subcoefficient + for alpha in numpy.ndindex(subcoeff.ufl_element().reference_value_shape()): + # New modified terminal: component[alpha + beta] + components.append(component[alpha + beta]) + # Repack derivative indices to shape + c, = indices(1) + return ComponentTensor(as_tensor(components)[c], MultiIndex((c,) + beta)) + + +def split_coefficients(expression, split): + """Split mixed coefficients, so mixed elements need not be + implemented.""" + splitter = CoefficientSplitter(split) + return map_expr_dag(splitter, expression) + + class CollectModifiedTerminals(MultiFunction, ModifiedTerminalMixin): """Collect the modified terminals in a UFL expression. From a19f67077ab811d3e4fe330ce64bf21d255ad714 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 28 Apr 2016 10:50:23 +0100 Subject: [PATCH 114/809] split mixed Coefficients (kernel interface) --- tsfc/driver.py | 1 + tsfc/kernel_interface.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 45d006f228..d889cd73b4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -123,6 +123,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): type(quad_rule)) integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) + integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) quadrature_index = gem.Index(name='ip') quadrature_indices.append(quadrature_index) ir = fem.process(integral_type, cell, quad_rule.points, diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 821b74f8bb..8399ecccfc 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -125,6 +125,7 @@ def __init__(self, integral_type, subdomain_id): self.local_tensor = None self.coordinates_arg = None self.coefficient_args = [] + self.coefficient_split = {} def set_arguments(self, arguments, indices): """Process arguments. @@ -151,19 +152,29 @@ def set_coefficients(self, integral_data, form_data): :arg integral_data: UFL integral data :arg form_data: UFL form data """ + from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace + coefficients = [] coefficient_numbers = [] # enabled_coefficients is a boolean array that indicates which # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: coefficient = form_data.reduced_coefficients[i] - self.coefficient_args.append( - self.coefficient(coefficient, "w_%d" % i)) + if type(coefficient.ufl_element()) == ufl_MixedElement: + split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) + for element in coefficient.ufl_element().sub_elements()] + coefficients.extend(split) + self.coefficient_split[coefficient] = split + else: + coefficients.append(coefficient) # This is which coefficient in the original form the # current coefficient is. # Consider f*v*dx + g*v*ds, the full form contains two # coefficients, but each integral only requires one. coefficient_numbers.append(form_data.original_coefficient_positions[i]) + for i, coefficient in enumerate(coefficients): + self.coefficient_args.append( + self.coefficient(coefficient, "w_%d" % i)) self.kernel.coefficient_numbers = tuple(coefficient_numbers) def require_cell_orientations(self): From c3a0900e331275e7878b9b91a19cc563175dcff7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 11 May 2016 22:11:02 +0100 Subject: [PATCH 115/809] use offset= in coffee.Symbol --- tsfc/kernel_interface.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 821b74f8bb..bf0de8c5bd 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -301,12 +301,16 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): for element in fiat_element.elements(): space_dim = element.space_dimension() - loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, coffee.Sum(offset, i))), - coffee.Symbol(name_, rank=(coffee.Sum(2 * offset, i), 0))) + loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, "i"), + offset=((1, 0), (1, offset))), + coffee.Symbol(name_, rank=("i", 0), + offset=((1, 2 * offset), (1, 0)))) prepare.append(coffee_for(i, space_dim, loop_body)) - loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, coffee.Sum(offset, i))), - coffee.Symbol(name_, rank=(coffee.Sum(2 * offset + space_dim, i), 0))) + loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, "i"), + offset=((1, 0), (1, offset))), + coffee.Symbol(name_, rank=("i", 0), + offset=((1, 2 * offset + space_dim), (1, 0)))) prepare.append(coffee_for(i, space_dim, loop_body)) offset += space_dim From 5aa11ea2cbf3195b642b16fed15fd7ad85218603 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 12 May 2016 11:47:12 +0100 Subject: [PATCH 116/809] adopt PyOP2 flatten=False --- tsfc/kernel_interface.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 60859c9098..332bace7e6 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -246,21 +246,26 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): return funarg, [], expression + import ufl + pyop2_scalar = not isinstance(coefficient.ufl_element(), (ufl.VectorElement, ufl.TensorElement)) finat_element = create_element(coefficient.ufl_element()) if not interior_facet: # Simple case shape = finat_element.index_shape - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=tuple(reversed(shape))), + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=(shape if pyop2_scalar else shape[:-1])), pointers=[("restrict",)], qualifiers=["const"]) - alpha = tuple(gem.Index() for d in shape) - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, tuple(reversed(shape)) + (1,)), - tuple(reversed(alpha)) + (0,)), - alpha) + if pyop2_scalar: + alpha = tuple(gem.Index() for d in shape) + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, shape + (1,)), + alpha + (0,)), + alpha) + else: + expression = gem.Variable(name, shape) return funarg, [], expression From e8cbc8c7e1af84aa8c2b50e438067e35b7995297 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 12 May 2016 14:32:14 +0100 Subject: [PATCH 117/809] support arguments with multiple indices --- tsfc/driver.py | 9 +++++++-- tsfc/fem.py | 2 +- tsfc/kernel_interface.py | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 03a0ae5a81..50837da3dd 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -16,6 +16,7 @@ from tsfc import fem, ufl_utils from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters +from tsfc.finatinterface import create_element from tsfc.kernel_interface import KernelBuilder, needs_cell_orientations from tsfc.quadrature import create_quadrature @@ -80,7 +81,9 @@ def compile_integral(integral_data, form_data, prefix, parameters): cell = integral_data.domain.ufl_cell() arguments = form_data.preprocessed_form.arguments() - argument_indices = tuple(gem.Index(name=name) for arg, name in zip(arguments, ['j', 'k'])) + argument_indices = tuple(tuple(gem.Index(extent=e) + for e in create_element(arg.ufl_element()).index_shape) + for arg in arguments) quadrature_indices = [] builder = KernelBuilder(integral_type, integral_data.subdomain_id) @@ -158,7 +161,9 @@ def compile_integral(integral_data, form_data, prefix, parameters): remove_zeros=True) # Generate COFFEE - index_names = [(index, index.name) for index in argument_indices] + index_names = [(si, name + str(n)) + for index, name in zip(argument_indices, ['j', 'k']) + for n, si in enumerate(index)] if len(quadrature_indices) == 1: index_names.append((quadrature_indices[0], 'ip')) else: diff --git a/tsfc/fem.py b/tsfc/fem.py index df1bc42c5b..20305c20cd 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -244,7 +244,7 @@ def _(terminal, mt, params): element = create_element(terminal.ufl_element()) M = element.basis_evaluation(params.quad_rule, derivative=mt.local_derivatives) vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) - result = gem.Indexed(M, (params.quadrature_index, argument_index) + vi) + result = gem.Indexed(M, (params.quadrature_index,) + argument_index + vi) if vi: return gem.ComponentTensor(result, vi) else: diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 332bace7e6..2efa730fe1 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -398,7 +398,7 @@ def prepare_arguments(arguments, indices, interior_facet=False): shape = reduce(operator.add, [element.index_shape for element in elements], ()) funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) - expression = gem.Indexed(gem.Variable("A", shape), indices) + expression = gem.Indexed(gem.Variable("A", shape), tuple(chain(*indices))) return funarg, [], [expression], [] From 4e429f6680c3f3203226c47b24be8e77860ecfa0 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 12 May 2016 14:32:49 +0100 Subject: [PATCH 118/809] finatinterface: RT, BDM, BDFM --- tsfc/finatinterface.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 743bd22aaf..4fb61dfaea 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -38,8 +38,11 @@ supported_elements = { # These all map directly to FInAT elements + "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, + "Brezzi-Douglas-Fortin-Marini": finat.BrezziDouglasFortinMarini, "Discontinuous Lagrange": finat.DiscontinuousLagrange, "Lagrange": finat.Lagrange, + "Raviart-Thomas": finat.RaviartThomas, } """A :class:`.dict` mapping UFL element family names to their FIAT-equivalent constructors. If the value is ``None``, the UFL From 3733f672bf413fde75667f3c1169b715a692f6a1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 12 May 2016 15:21:07 +0100 Subject: [PATCH 119/809] clean up fem.py --- tsfc/compat.py | 25 ---------- tsfc/fem.py | 124 ++-------------------------------------------- tsfc/geometric.py | 4 +- tsfc/ufl_utils.py | 22 -------- 4 files changed, 7 insertions(+), 168 deletions(-) delete mode 100644 tsfc/compat.py diff --git a/tsfc/compat.py b/tsfc/compat.py deleted file mode 100644 index b41be73e52..0000000000 --- a/tsfc/compat.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Backwards compatibility for some functionality. -""" -import numpy -from distutils.version import StrictVersion - - -if StrictVersion(numpy.__version__) < StrictVersion("1.10"): - def allclose(a, b, rtol=1e-5, atol=1e-8, equal_nan=False): - """Wrapper around ``numpy.allclose``, which see. - - If ``equal_nan`` is ``True``, consider nan values to be equal - in comparisons. - """ - if not equal_nan: - return numpy.allclose(a, b, rtol=rtol, atol=atol) - nana = numpy.isnan(a) - if not nana.any(): - return numpy.allclose(a, b, rtol=rtol, atol=atol) - nanb = numpy.isnan(b) - equal_nan = numpy.allclose(nana, nanb) - return equal_nan and numpy.allclose(a[~nana], b[~nanb], - rtol=rtol, atol=atol) -else: - allclose = numpy.allclose diff --git a/tsfc/fem.py b/tsfc/fem.py index 20305c20cd..f891ca13fe 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -1,6 +1,5 @@ from __future__ import absolute_import -import collections import itertools import numpy @@ -8,78 +7,22 @@ from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, Coefficient, FormArgument, - GeometricQuantity, QuadratureWeight) +from ufl.classes import Argument, Coefficient, GeometricQuantity, QuadratureWeight import gem from tsfc.constants import PRECISION from tsfc.finatinterface import create_element, as_fiat_cell from tsfc.modified_terminals import analyse_modified_terminal -from tsfc import compat from tsfc import ufl2gem from tsfc import geometric -from tsfc.ufl_utils import (CollectModifiedTerminals, - ModifiedTerminalMixin, PickRestriction, - simplify_abs) +from tsfc.ufl_utils import ModifiedTerminalMixin, PickRestriction, simplify_abs # FFC uses one less digits for rounding than for printing epsilon = eval("1e-%d" % (PRECISION - 1)) -def make_tabulator(points): - """Creates a tabulator for an array of points.""" - return lambda elem, order: tabulate(elem, order, points) - - -class TabulationManager(object): - """Manages the generation of tabulation matrices for the different - integral types.""" - - def __init__(self, integral_type, cell, points): - """Constructs a TabulationManager. - - :arg integral_type: integral type - :arg cell: UFL cell - :arg points: points on the integration entity (e.g. points on - an interval for facet integrals on a triangle) - """ - self.integral_type = integral_type - self.points = points - - self.facet_manager = FacetManager(integral_type, cell) - self.tabulators = map(make_tabulator, self.facet_manager.facet_transform(points)) - self.tables = {} - - def tabulate(self, ufl_element, max_deriv): - """Prepare the tabulations of a finite element up to a given - derivative order. - - :arg ufl_element: UFL element to tabulate - :arg max_deriv: tabulate derivatives up this order - """ - store = collections.defaultdict(list) - for tabulator in self.tabulators: - for c, D, table in tabulator(ufl_element, max_deriv): - store[(ufl_element, c, D)].append(table) - - if self.integral_type == 'cell': - for key, (table,) in store.iteritems(): - self.tables[key] = table - else: - for key, tables in store.iteritems(): - table = numpy.array(tables) - if len(table.shape) == 2: - # Cellwise constant; must not depend on the facet - assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) - table = table[0] - self.tables[key] = table - - def __getitem__(self, key): - return self.tables[key] - - class FacetManager(object): """Collection of utilities for facet integrals.""" @@ -150,14 +93,11 @@ def select_facet(self, tensor, restriction): class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): """Contains all the context necessary to translate UFL into GEM.""" - def __init__(self, tabulation_manager, quad_rule, quadrature_index, + def __init__(self, integral_type, facet_manager, quad_rule, quadrature_index, argument_indices, coefficient_mapper, index_cache): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) - integral_type = tabulation_manager.integral_type - facet_manager = tabulation_manager.facet_manager self.integral_type = integral_type - self.tabulation_manager = tabulation_manager self.quad_rule = quad_rule self.weights = gem.Literal(quad_rule.weights) self.quadrature_index = quadrature_index @@ -179,42 +119,6 @@ def modified_terminal(self, o): return translate(mt.terminal, mt, self) -def iterate_shape(mt, callback): - """Iterates through the components of a modified terminal, and - calls ``callback`` with ``(ufl_element, c, D)`` keys which are - used to look up tabulation matrix for that component. Then - assembles the result into a GEM tensor (if tensor-valued) - corresponding to the modified terminal. - - :arg mt: analysed modified terminal - :arg callback: callback to get the GEM translation of a component - :returns: GEM translation of the modified terminal - - This is a helper for translating Arguments and Coefficients. - """ - ufl_element = mt.terminal.ufl_element() - dim = ufl_element.cell().topological_dimension() - - def flat_index(ordered_deriv): - return tuple((numpy.asarray(ordered_deriv) == d).sum() for d in range(dim)) - - ordered_derivs = itertools.product(range(dim), repeat=mt.local_derivatives) - flat_derivs = map(flat_index, ordered_derivs) - - result = [] - for c in range(ufl_element.reference_value_size()): - for flat_deriv in flat_derivs: - result.append(callback((ufl_element, c, flat_deriv))) - - shape = mt.expr.ufl_shape - assert len(result) == numpy.prod(shape) - - if shape: - return gem.ListTensor(numpy.asarray(result).reshape(shape)) - else: - return result[0] - - @singledispatch def translate(terminal, mt, params): """Translates modified terminals into GEM. @@ -280,25 +184,6 @@ def process(integral_type, cell, quad_rule, quadrature_index, # Abs-simplification integrand = simplify_abs(integrand) - # Collect modified terminals - modified_terminals = [] - map_expr_dag(CollectModifiedTerminals(modified_terminals), integrand) - - # Collect maximal derivatives that needs tabulation - max_derivs = collections.defaultdict(int) - - for mt in map(analyse_modified_terminal, modified_terminals): - if isinstance(mt.terminal, FormArgument): - ufl_element = mt.terminal.ufl_element() - max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) - - # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(integral_type, cell, quad_rule.points) # TODO - for ufl_element, max_deriv in max_derivs.items(): - if ufl_element.family() != 'Real': - pass # TODO - # tabulation_manager.tabulate(ufl_element, max_deriv) - if integral_type.startswith("interior_facet"): expressions = [] for rs in itertools.product(("+", "-"), repeat=len(argument_indices)): @@ -308,7 +193,8 @@ def process(integral_type, cell, quad_rule, quadrature_index, # Translate UFL to Einstein's notation, # lowering finite element specific nodes - translator = Translator(tabulation_manager, quad_rule, + facet_manager = FacetManager(integral_type, cell) + translator = Translator(integral_type, facet_manager, quad_rule, quadrature_index, argument_indices, coefficient_mapper, index_cache) return map_expr_dags(translator, expressions) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 58e3d77cfc..7d352bcc64 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -186,7 +186,7 @@ def translate_cell_edge_vectors(terminal, mt, params): @translate.register(CellCoordinate) def translate_cell_coordinate(terminal, mt, params): - points = params.tabulation_manager.points + points = params.quad_rule.points if params.integral_type != 'cell': points = list(params.facet_manager.facet_transform(points)) return gem.partial_indexed(params.select_facet(gem.Literal(points), @@ -197,5 +197,5 @@ def translate_cell_coordinate(terminal, mt, params): @translate.register(FacetCoordinate) def translate_facet_coordinate(terminal, mt, params): assert params.integral_type != 'cell' - points = params.tabulation_manager.points + points = params.quad_rule.points return gem.partial_indexed(gem.Literal(points), (params.quadrature_index,)) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 78ae243c05..22d2312677 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -138,28 +138,6 @@ def split_coefficients(expression, split): return map_expr_dag(splitter, expression) -class CollectModifiedTerminals(MultiFunction, ModifiedTerminalMixin): - """Collect the modified terminals in a UFL expression. - - :arg return_list: modified terminals will be appended to this list - """ - def __init__(self, return_list): - MultiFunction.__init__(self) - self.return_list = return_list - - def expr(self, o, *ops): - pass # operands visited - - def indexed(self, o, *ops): - pass # not a terminal modifier - - def multi_index(self, o): - pass # ignore - - def modified_terminal(self, o): - self.return_list.append(o) - - class PickRestriction(MultiFunction, ModifiedTerminalMixin): """Pick out parts of an expression with specified restrictions on the arguments. From 380853816cb705249a1270f5f85bef407c61d2a9 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 12 May 2016 15:36:31 +0100 Subject: [PATCH 120/809] purge mixed elements --- tsfc/driver.py | 8 +- tsfc/finatinterface.py | 2 +- tsfc/kernel_interface.py | 252 ++++++--------------------------------- tsfc/mixedelement.py | 112 ----------------- tsfc/ufl_utils.py | 6 - 5 files changed, 38 insertions(+), 342 deletions(-) delete mode 100644 tsfc/mixedelement.py diff --git a/tsfc/driver.py b/tsfc/driver.py index 50837da3dd..29f6b1f483 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -90,13 +90,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): return_variables = builder.set_arguments(arguments, argument_indices) coordinates = ufl_utils.coordinate_coefficient(mesh) - if ufl_utils.is_element_affine(mesh.ufl_coordinate_element()): - # For affine mesh geometries we prefer code generation that - # composes well with optimisations. - builder.set_coordinates(coordinates, "coords", mode='list_tensor') - else: - # Otherwise we use the approach that might be faster (?) - builder.set_coordinates(coordinates, "coords") + builder.set_coordinates(coordinates, "coords") builder.set_coefficients(integral_data, form_data) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 4fb61dfaea..05c03b03d3 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -85,7 +85,7 @@ def _(element, vector_is_mixed): # MixedElement case @convert.register(ufl.MixedElement) # noqa def _(element, vector_is_mixed): - raise NotImplementedError("MixedElement not implemented in FInAT yet.") + raise ValueError("FInAT does not implement generic mixed element.") # VectorElement case diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 2efa730fe1..4a75279cab 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -2,15 +2,12 @@ import operator -import numpy - import coffee.base as coffee import gem from gem.node import traversal from tsfc.finatinterface import create_element -from tsfc.mixedelement import MixedElement from tsfc.coffee import SCALAR_TYPE @@ -48,25 +45,8 @@ def __init__(self, interior_facet=False): assert isinstance(interior_facet, bool) self.interior_facet = interior_facet - self.prepare = [] - self.finalise = [] - self.coefficient_map = {} - def apply_glue(self, prepare=None, finalise=None): - """Append glue code for operations that are not handled in the - GEM abstraction. - - Current uses: mixed interior facet mess - - :arg prepare: code snippets to be prepended to the kernel - :arg finalise: code snippets to be appended to the kernel - """ - if prepare is not None: - self.prepare.extend(prepare) - if finalise is not None: - self.finalise.extend(finalise) - def construct_kernel(self, name, args, body): """Construct a COFFEE function declaration with the accumulated glue code. @@ -77,8 +57,7 @@ def construct_kernel(self, name, args, body): :returns: :class:`coffee.FunDecl` object """ assert isinstance(body, coffee.Block) - body_ = coffee.Block(self.prepare + body.children + self.finalise) - return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) + return coffee.FunDecl("void", name, args, body, pred=["static", "inline"]) @property def coefficient_mapper(self): @@ -94,24 +73,18 @@ def arguments(self, arguments, indices): :returns: COFFEE function argument and GEM expression representing the argument tensor """ - funarg, prepare, expressions, finalise = prepare_arguments( - arguments, indices, interior_facet=self.interior_facet) - self.apply_glue(prepare, finalise) + funarg, expressions = prepare_arguments(arguments, indices, interior_facet=self.interior_facet) return funarg, expressions - def coefficient(self, coefficient, name, mode=None): + def coefficient(self, coefficient, name): """Prepare a coefficient. Adds glue code for the coefficient and adds the coefficient to the coefficient map. :arg coefficient: :class:`ufl.Coefficient` :arg name: coefficient name - :arg mode: see :func:`prepare_coefficient` :returns: COFFEE function argument for the coefficient """ - funarg, prepare, expression = prepare_coefficient( - coefficient, name, mode=mode, - interior_facet=self.interior_facet) - self.apply_glue(prepare) + funarg, expression = prepare_coefficient(coefficient, name, interior_facet=self.interior_facet) self.coefficient_map[coefficient] = expression return funarg @@ -139,14 +112,13 @@ def set_arguments(self, arguments, indices): self.local_tensor, expressions = self.arguments(arguments, indices) return expressions - def set_coordinates(self, coefficient, name, mode=None): + def set_coordinates(self, coefficient, name): """Prepare the coordinate field. :arg coefficient: :class:`ufl.Coefficient` :arg name: coordinate coefficient name - :arg mode: see :func:`prepare_coefficient` """ - self.coordinates_arg = self.coefficient(coefficient, name, mode) + self.coordinates_arg = self.coefficient(coefficient, name) def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -210,27 +182,18 @@ def construct_kernel(self, name, body): return self.kernel -def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): +def prepare_coefficient(coefficient, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for - Coefficients. Mixed element Coefficients are rearranged here for - interior facet integrals. + Coefficients. :arg coefficient: UFL Coefficient :arg name: unique name to refer to the Coefficient in the kernel - :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with - interior facet integrals on mixed elements :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expression) + :returns: (funarg, expression) funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body expression - GEM expression referring to the Coefficient values """ - if mode is None: - mode = 'manual_loop' - - assert mode in ['manual_loop', 'list_tensor'] assert isinstance(interior_facet, bool) if coefficient.ufl_element().family() == 'Real': @@ -244,7 +207,7 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): if coefficient.ufl_shape == (): expression = gem.Indexed(expression, (0,)) - return funarg, [], expression + return funarg, expression import ufl pyop2_scalar = not isinstance(coefficient.ufl_element(), (ufl.VectorElement, ufl.TensorElement)) @@ -267,101 +230,23 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): else: expression = gem.Variable(name, shape) - return funarg, [], expression - - if not isinstance(finat_element, MixedElement): - # Interior facet integral - - shape = (2,) + finat_element.index_shape - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), - pointers=[("restrict",)], - qualifiers=["const"]) - expression = gem.Variable(name, shape + (1,)) - - f, i = gem.Index(), gem.Index() - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, shape + (1,)), - (f, i, 0)), - (f, i,)) - - return funarg, [], expression - - assert False, "Deal with FInAT + mixed + interior facet later." - # Interior facet integral + mixed / vector element - - # Here we need to reorder the coefficient values. - # - # Incoming ordering: E1+ E1- E2+ E2- E3+ E3- - # Required ordering: E1+ E2+ E3+ E1- E2- E3- - # - # Each of E[n]{+,-} is a vector of basis function coefficients for - # subelement E[n]. - # - # There are two code generation method to reorder the values. - # We have not done extensive research yet as to which way yield - # faster code. - - if mode == 'manual_loop': - # In this case we generate loops outside the GEM abstraction - # to reorder the values. A whole E[n]{+,-} block is copied by - # a single loop. - name_ = name + "_" - shape = (2,) + fiat_element.index_shape - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name_), - pointers=[("restrict",), ("restrict",)], - qualifiers=["const"]) - prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] - expression = gem.Variable(name, shape) - - offset = 0 - i = coffee.Symbol("i") - for element in fiat_element.elements(): - space_dim = element.space_dimension() - - loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, "i"), - offset=((1, 0), (1, offset))), - coffee.Symbol(name_, rank=("i", 0), - offset=((1, 2 * offset), (1, 0)))) - prepare.append(coffee_for(i, space_dim, loop_body)) - - loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, "i"), - offset=((1, 0), (1, offset))), - coffee.Symbol(name_, rank=("i", 0), - offset=((1, 2 * offset + space_dim), (1, 0)))) - prepare.append(coffee_for(i, space_dim, loop_body)) - - offset += space_dim - - return funarg, prepare, expression - - elif mode == 'list_tensor': - # In this case we generate a gem.ListTensor to do the - # reordering. Every single element in a E[n]{+,-} block is - # referenced separately. - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("restrict",), ("restrict",)], - qualifiers=["const"]) + return funarg, expression - variable = gem.Variable(name, (2 * fiat_element.space_dimension(), 1)) + # Interior facet integral + shape = (2,) + finat_element.index_shape - facet_0 = [] - facet_1 = [] - offset = 0 - for element in fiat_element.elements(): - space_dim = element.space_dimension() + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), + pointers=[("restrict",)], + qualifiers=["const"]) + expression = gem.Variable(name, shape + (1,)) - for i in range(offset, offset + space_dim): - facet_0.append(gem.Indexed(variable, (i, 0))) - offset += space_dim + f, i = gem.Index(), gem.Index() + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, shape + (1,)), + (f, i, 0)), + (f, i,)) - for i in range(offset, offset + space_dim): - facet_1.append(gem.Indexed(variable, (i, 0))) - offset += space_dim - - expression = gem.ListTensor(numpy.array([facet_0, facet_1])) - return funarg, [], expression + return funarg, expression def prepare_arguments(arguments, indices, interior_facet=False): @@ -372,14 +257,10 @@ def prepare_arguments(arguments, indices, interior_facet=False): :arg arguments: UFL Arguments :arg indices: Argument indices :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expression, finalise) + :returns: (funarg, expression) funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body expressions - GEM expressions referring to the argument tensor - finalise - list of COFFEE nodes to be appended to the - kernel body """ from itertools import chain, product assert isinstance(interior_facet, bool) @@ -389,7 +270,7 @@ def prepare_arguments(arguments, indices, interior_facet=False): funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=(1,))) expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) - return funarg, [], [expression], [] + return funarg, [expression] elements = tuple(create_element(arg.ufl_element()) for arg in arguments) @@ -400,84 +281,23 @@ def prepare_arguments(arguments, indices, interior_facet=False): funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) expression = gem.Indexed(gem.Variable("A", shape), tuple(chain(*indices))) - return funarg, [], [expression], [] - - if not any(isinstance(element, MixedElement) for element in elements): - # Interior facet integral, but no vector (mixed) arguments - shape = [] - for element in elements: - shape += [2] + list(element.index_shape) - shape = tuple(shape) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) - varexp = gem.Variable("A", shape) - - expressions = [] - for restrictions in product((0, 1), repeat=len(arguments)): - is_ = tuple(chain(*zip(restrictions, indices))) - expressions.append(gem.Indexed(varexp, is_)) + return funarg, [expression] - return funarg, [], expressions, [] + # Interior facet integral + shape = [] + for element in elements: + shape += [2] + list(element.index_shape) + shape = tuple(shape) - assert False, "Deal with FInAT + vector argument + interior facet later." - # Interior facet integral + vector (mixed) argument(s) - shape = tuple(element.space_dimension() for element in elements) - funarg_shape = tuple(s * 2 for s in shape) - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=funarg_shape)) + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) + varexp = gem.Variable("A", shape) - prepare = [] expressions = [] - - references = [] for restrictions in product((0, 1), repeat=len(arguments)): - name = "A" + "".join(map(str, restrictions)) - - prepare.append(coffee.Decl(SCALAR_TYPE, - coffee.Symbol(name, rank=shape), - init=coffee.ArrayInit(numpy.zeros(1)))) - expressions.append(gem.Indexed(gem.Variable(name, shape), indices)) - - for multiindex in numpy.ndindex(shape): - references.append(coffee.Symbol(name, multiindex)) + is_ = tuple(chain(*zip(restrictions, indices))) + expressions.append(gem.Indexed(varexp, is_)) - restriction_shape = [] - for e in elements: - if isinstance(e, MixedElement): - restriction_shape += [len(e.elements()), - e.elements()[0].space_dimension()] - else: - restriction_shape += [1, e.space_dimension()] - restriction_shape = tuple(restriction_shape) - - references = numpy.array(references) - if len(arguments) == 1: - references = references.reshape((2,) + restriction_shape) - references = references.transpose(1, 0, 2) - elif len(arguments) == 2: - references = references.reshape((2, 2) + restriction_shape) - references = references.transpose(2, 0, 3, 4, 1, 5) - references = references.reshape(funarg_shape) - - finalise = [] - for multiindex in numpy.ndindex(funarg_shape): - finalise.append(coffee.Assign(coffee.Symbol("A", rank=multiindex), - references[multiindex])) - - return funarg, prepare, expressions, finalise - - -def coffee_for(index, extent, body): - """Helper function to make a COFFEE loop. - - :arg index: :class:`coffee.Symbol` loop index - :arg extent: loop extent (integer) - :arg body: loop body (COFFEE node) - :returns: COFFEE loop - """ - return coffee.For(coffee.Decl("int", index, init=0), - coffee.Less(index, extent), - coffee.Incr(index, 1), - body) + return funarg, expressions def needs_cell_orientations(ir): diff --git a/tsfc/mixedelement.py b/tsfc/mixedelement.py deleted file mode 100644 index 2ac4a05fa5..0000000000 --- a/tsfc/mixedelement.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file was modified from FFC -# (http://bitbucket.org/fenics-project/ffc), copyright notice -# reproduced below. -# -# Copyright (C) 2005-2010 Anders Logg -# -# This file is part of FFC. -# -# FFC is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# FFC is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with FFC. If not, see . - -from __future__ import absolute_import -from __future__ import print_function - -import numpy - -from collections import defaultdict -from operator import add -from functools import partial - - -class MixedElement(object): - """A FIAT-like representation of a mixed element. - - :arg elements: An iterable of FIAT elements. - - This object offers tabulation of the concatenated basis function - tables along with an entity_dofs dict.""" - def __init__(self, elements): - self._elements = tuple(elements) - self._entity_dofs = None - - def get_reference_element(self): - return self.elements()[0].get_reference_element() - - def elements(self): - return self._elements - - def space_dimension(self): - return sum(e.space_dimension() for e in self.elements()) - - def value_shape(self): - return (sum(numpy.prod(e.value_shape(), dtype=int) for e in self.elements()), ) - - def entity_dofs(self): - if self._entity_dofs is not None: - return self._entity_dofs - - ret = defaultdict(partial(defaultdict, list)) - - dicts = (e.entity_dofs() for e in self.elements()) - - offsets = numpy.cumsum([0] + list(e.space_dimension() - for e in self.elements()), - dtype=int) - for i, d in enumerate(dicts): - for dim, dofs in d.items(): - for ent, off in dofs.items(): - ret[dim][ent] += map(partial(add, offsets[i]), - off) - self._entity_dofs = ret - return self._entity_dofs - - def num_components(self): - return self.value_shape()[0] - - def tabulate(self, order, points): - """Tabulate a mixed element by appropriately splatting - together the tabulation of the individual elements. - """ - # FIXME: Could we reorder the basis functions so that indexing - # in the form compiler for mixed interior facets becomes - # easier? - # Would probably need to redo entity_dofs as well. - shape = (self.space_dimension(), self.num_components(), len(points)) - - output = {} - - sub_dims = [0] + list(e.space_dimension() for e in self.elements()) - sub_cmps = [0] + list(numpy.prod(e.value_shape(), dtype=int) - for e in self.elements()) - irange = numpy.cumsum(sub_dims) - crange = numpy.cumsum(sub_cmps) - - for i, e in enumerate(self.elements()): - table = e.tabulate(order, points) - - for d, tab in table.items(): - try: - arr = output[d] - except KeyError: - arr = numpy.zeros(shape) - output[d] = arr - - ir = irange[i:i+2] - cr = crange[i:i+2] - tab = tab.reshape(ir[1] - ir[0], cr[1] - cr[0], -1) - arr[slice(*ir), slice(*cr)] = tab - - return output diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 22d2312677..f5d03bf15d 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -21,12 +21,6 @@ construct_modified_terminal) -def is_element_affine(ufl_element): - """Tells if a UFL element is affine.""" - affine_cells = ["interval", "triangle", "tetrahedron"] - return ufl_element.cell().cellname() in affine_cells and ufl_element.degree() == 1 - - class SpatialCoordinateReplacer(MultiFunction): """Replace SpatialCoordinate nodes with the ReferenceValue of a Coefficient. Assumes that the coordinate element only needs From aeabb69169793150937579e1bfc0d61031b75e9c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 23 May 2016 11:24:19 +0100 Subject: [PATCH 121/809] WIP: support for facet integrals --- tsfc/fem.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index f891ca13fe..70f23f9fef 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -146,7 +146,16 @@ def _(terminal, mt, params): argument_index = params.argument_indices[terminal.number()] element = create_element(terminal.ufl_element()) - M = element.basis_evaluation(params.quad_rule, derivative=mt.local_derivatives) + if params.integral_type == 'cell': + M = element.basis_evaluation(params.quad_rule, derivative=mt.local_derivatives) + else: + from finat.quadrature import QuadratureRule, CollapsedGaussJacobiQuadrature + Ms = [] + for points in params.facet_manager.facet_transform(params.quad_rule.points): + quad_rule = QuadratureRule(params.facet_manager.ufl_cell, points, params.quad_rule.weights) + quad_rule.__class__ = CollapsedGaussJacobiQuadrature + Ms.append(element.basis_evaluation(quad_rule, derivative=mt.local_derivatives)) + M = params.facet_manager.select_facet(gem.ListTensor(Ms), mt.restriction) vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) result = gem.Indexed(M, (params.quadrature_index,) + argument_index + vi) if vi: @@ -166,7 +175,17 @@ def _(terminal, mt, params): ka = gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[mt.restriction]) element = create_element(terminal.ufl_element()) - M = element.basis_evaluation(params.quad_rule, derivative=mt.local_derivatives) + if params.integral_type == 'cell': + M = element.basis_evaluation(params.quad_rule, derivative=mt.local_derivatives) + else: + from finat.quadrature import QuadratureRule, CollapsedGaussJacobiQuadrature + Ms = [] + for points in params.facet_manager.facet_transform(params.quad_rule.points): + quad_rule = QuadratureRule(params.facet_manager.ufl_cell, points, params.quad_rule.weights) + quad_rule.__class__ = CollapsedGaussJacobiQuadrature + Ms.append(element.basis_evaluation(quad_rule, derivative=mt.local_derivatives)) + M = params.facet_manager.select_facet(gem.ListTensor(Ms), mt.restriction) + alpha = element.get_indices() vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) result = gem.Product(gem.Indexed(M, (params.quadrature_index,) + alpha + vi), From 9185791b4b6071d41bec64245e9b974e33bdaee8 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 23 May 2016 11:52:31 +0100 Subject: [PATCH 122/809] fix kernel interface for interior facet integrals --- tsfc/kernel_interface.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 4a75279cab..b3a2fd0c68 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -235,16 +235,17 @@ def prepare_coefficient(coefficient, name, interior_facet=False): # Interior facet integral shape = (2,) + finat_element.index_shape - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=(shape if pyop2_scalar else shape[:-1])), pointers=[("restrict",)], qualifiers=["const"]) - expression = gem.Variable(name, shape + (1,)) - - f, i = gem.Index(), gem.Index() - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, shape + (1,)), - (f, i, 0)), - (f, i,)) + if pyop2_scalar: + alpha = tuple(gem.Index() for d in shape) + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, shape + (1,)), + alpha + (0,)), + alpha) + else: + expression = gem.Variable(name, shape) return funarg, expression @@ -293,8 +294,8 @@ def prepare_arguments(arguments, indices, interior_facet=False): varexp = gem.Variable("A", shape) expressions = [] - for restrictions in product((0, 1), repeat=len(arguments)): - is_ = tuple(chain(*zip(restrictions, indices))) + for restrictions in product(((0,), (1,)), repeat=len(arguments)): + is_ = tuple(chain(*chain(*zip(restrictions, indices)))) expressions.append(gem.Indexed(varexp, is_)) return funarg, expressions From 0fe5cb96d324974f705df458c37daeb5c0f9dfc3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 23 May 2016 13:04:46 +0100 Subject: [PATCH 123/809] add FIAT compatibility layer --- tsfc/finatinterface.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 05c03b03d3..8bbc9f5126 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -59,6 +59,15 @@ def as_fiat_cell(cell): return FIAT.ufc_cell(cell) +def fiat_compat(element, vector_is_mixed): + from tsfc.fiatinterface import convert + from finat.fiat_elements import ScalarFiatElement + cell = as_fiat_cell(element.cell()) + finat_element = ScalarFiatElement(cell, element.degree()) + finat_element._fiat_element = convert(element, vector_is_mixed=vector_is_mixed) + return finat_element + + @singledispatch def convert(element, vector_is_mixed): """Handler for converting UFL elements to FIAT elements. @@ -71,15 +80,18 @@ def convert(element, vector_is_mixed): :func:`create_element`.""" if element.family() in supported_elements: raise ValueError("Element %s supported, but no handler provided" % element) - raise ValueError("Unsupported element type %s" % type(element)) + return fiat_compat(element, vector_is_mixed) # Base finite elements first @convert.register(ufl.FiniteElement) # noqa def _(element, vector_is_mixed): cell = as_fiat_cell(element.cell()) - lmbda = supported_elements[element.family()] - return lmbda(cell, element.degree()) + lmbda = supported_elements.get(element.family()) + if lmbda: + return lmbda(cell, element.degree()) + else: + return fiat_compat(element, vector_is_mixed) # MixedElement case From 8c272fc5a26d8a805ecf377aabf47738b65c8ceb Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 23 May 2016 14:11:27 +0100 Subject: [PATCH 124/809] fix RTCF & co --- tsfc/finatinterface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 8bbc9f5126..8faebb2c4c 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -61,9 +61,9 @@ def as_fiat_cell(cell): def fiat_compat(element, vector_is_mixed): from tsfc.fiatinterface import convert - from finat.fiat_elements import ScalarFiatElement + from finat.fiat_elements import FiatElementBase cell = as_fiat_cell(element.cell()) - finat_element = ScalarFiatElement(cell, element.degree()) + finat_element = FiatElementBase(cell, element.degree()) finat_element._fiat_element = convert(element, vector_is_mixed=vector_is_mixed) return finat_element From 228dd50f21bb860daeac387228f0e0a1d3f81b2e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 23 May 2016 16:55:56 +0100 Subject: [PATCH 125/809] fix ufl.TensorElement Reference value already has the symmetry subtracted. --- tsfc/fem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 785bcd81c2..319ffe54c4 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -44,7 +44,7 @@ def _tabulate(ufl_element, order, points): """ element = create_element(ufl_element) phi = element.space_dimension() - C = ufl_element.reference_value_size() - len(ufl_element.symmetry()) + C = ufl_element.reference_value_size() q = len(points) for D, fiat_table in element.tabulate(order, points).iteritems(): reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) From bc90e8fbd3c7730ebb4d67fc16a44ede30198375 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Sun, 29 May 2016 10:19:40 +0100 Subject: [PATCH 126/809] fem: Handle ConstantValue in mt translation Could happen if zero-simplification in UFL didn't pick up that we had grad of a cellwise constant sub coefficient in a mixed form. Fixes #41. --- tsfc/fem.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 319ffe54c4..ae1ae8d7c9 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -8,8 +8,9 @@ from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, Coefficient, FormArgument, - GeometricQuantity, QuadratureWeight) +from ufl.classes import (Argument, Coefficient, ConstantValue, + FormArgument, GeometricQuantity, + QuadratureWeight) import gem @@ -328,6 +329,13 @@ def callback(key): return iterate_shape(mt, callback) +@translate.register(ConstantValue) +def _translate_constantvalue(terminal, mt, params): + # Literal in a modified terminal + # Terminal modifiers have no effect, just translate the terminal. + return params(terminal) + + def process(integral_type, cell, points, weights, quadrature_index, argument_indices, integrand, coefficient_mapper, index_cache): # Abs-simplification From 3442bf58eb43ce7e779f5132bb22b4d2ba9b1dc9 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 18 Mar 2016 14:13:36 +0000 Subject: [PATCH 127/809] initial sketch for CellVolume --- tsfc/driver.py | 33 ++++++++++++++++++++++++++++++++- tsfc/fem.py | 20 ++++++++++++++------ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index d889cd73b4..c1806d1a59 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -102,6 +102,36 @@ def compile_integral(integral_data, form_data, prefix, parameters): # evaluation can be hoisted). index_cache = collections.defaultdict(gem.Index) + def cellvolume(): + from ufl import dx + form = 1 * dx(domain=mesh) + fd = compute_form_data(form, + do_apply_function_pullbacks=True, + do_apply_integral_scaling=True, + do_apply_geometry_lowering=True, + do_apply_restrictions=True, + do_estimate_degrees=True) + itg_data, = fd.integral_data + integral, = itg_data.integrals + + # Check if the integral has a quad degree attached, otherwise use + # the estimated polynomial degree attached by compute_form_data + quadrature_degree = integral.metadata()["estimated_polynomial_degree"] + quad_rule = create_quadrature(cell, 'cell', quadrature_degree) + + integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) + quadrature_index = gem.Index(name='q') + ir = fem.process('cell', cell, quad_rule.points, + quad_rule.weights, quadrature_index, (), + integrand, builder.coefficient_mapper, + index_cache, None) + if parameters["unroll_indexsum"]: + ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) + expr, = ir + if quadrature_index in expr.free_indices: + expr = gem.IndexSum(expr, quadrature_index) + return expr + irs = [] for integral in integral_data.integrals: params = {} @@ -129,7 +159,8 @@ def compile_integral(integral_data, form_data, prefix, parameters): ir = fem.process(integral_type, cell, quad_rule.points, quad_rule.weights, quadrature_index, argument_indices, integrand, - builder.coefficient_mapper, index_cache) + builder.coefficient_mapper, index_cache, + cellvolume) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) irs.append([(gem.IndexSum(expr, quadrature_index) diff --git a/tsfc/fem.py b/tsfc/fem.py index ae1ae8d7c9..094807f768 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -8,9 +8,9 @@ from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, Coefficient, ConstantValue, - FormArgument, GeometricQuantity, - QuadratureWeight) +from ufl.classes import (Argument, Coefficient, CellVolume, + ConstantValue, FormArgument, + GeometricQuantity, QuadratureWeight) import gem @@ -195,7 +195,8 @@ class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): """Contains all the context necessary to translate UFL into GEM.""" def __init__(self, tabulation_manager, weights, quadrature_index, - argument_indices, coefficient_mapper, index_cache): + argument_indices, coefficient_mapper, index_cache, + cellvolume): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) integral_type = tabulation_manager.integral_type @@ -209,6 +210,7 @@ def __init__(self, tabulation_manager, weights, quadrature_index, self.index_cache = index_cache self.facet_manager = facet_manager self.select_facet = facet_manager.select_facet + self.cellvolume = cellvolume if self.integral_type.startswith("interior_facet"): self.cell_orientations = gem.Variable("cell_orientations", (2, 1)) @@ -280,6 +282,11 @@ def _(terminal, mt, params): return geometric.translate(terminal, mt, params) +@translate.register(CellVolume) # noqa: Not actually redefinition +def _(terminal, mt, params): + return params.cellvolume() + + @translate.register(Argument) # noqa: Not actually redefinition def _(terminal, mt, params): argument_index = params.argument_indices[terminal.number()] @@ -337,7 +344,8 @@ def _translate_constantvalue(terminal, mt, params): def process(integral_type, cell, points, weights, quadrature_index, - argument_indices, integrand, coefficient_mapper, index_cache): + argument_indices, integrand, coefficient_mapper, + index_cache, cellvolume): # Abs-simplification integrand = simplify_abs(integrand) @@ -370,5 +378,5 @@ def process(integral_type, cell, points, weights, quadrature_index, # lowering finite element specific nodes translator = Translator(tabulation_manager, weights, quadrature_index, argument_indices, - coefficient_mapper, index_cache) + coefficient_mapper, index_cache, cellvolume) return map_expr_dags(translator, expressions) From feeb64018f902331a66a0daa457e1353c84210a5 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 23 Mar 2016 14:06:50 +0000 Subject: [PATCH 128/809] CellVolume with interior facet integrals --- tsfc/driver.py | 10 ++++++++-- tsfc/fem.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index c1806d1a59..c1a7c6b001 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -102,7 +102,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): # evaluation can be hoisted). index_cache = collections.defaultdict(gem.Index) - def cellvolume(): + def cellvolume(restriction): from ufl import dx form = 1 * dx(domain=mesh) fd = compute_form_data(form, @@ -121,9 +121,15 @@ def cellvolume(): integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='q') + if integral_type.startswith("interior_facet"): + def coefficient_mapper(coefficient): + return gem.partial_indexed(builder.coefficient_mapper(coefficient), ({'+': 0, '-': 1}[restriction],)) + else: + assert restriction is None + coefficient_mapper = builder.coefficient_mapper ir = fem.process('cell', cell, quad_rule.points, quad_rule.weights, quadrature_index, (), - integrand, builder.coefficient_mapper, + integrand, coefficient_mapper, index_cache, None) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) diff --git a/tsfc/fem.py b/tsfc/fem.py index 094807f768..607083a344 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -284,7 +284,7 @@ def _(terminal, mt, params): @translate.register(CellVolume) # noqa: Not actually redefinition def _(terminal, mt, params): - return params.cellvolume() + return params.cellvolume(mt.restriction) @translate.register(Argument) # noqa: Not actually redefinition From 2157826781ad9a1da11c2f897b4884cc2d7a5fb1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 23 Mar 2016 14:31:21 +0000 Subject: [PATCH 129/809] support FacetArea --- tsfc/driver.py | 35 +++++++++++++++++++++++++++++++++-- tsfc/fem.py | 14 ++++++++++---- tsfc/geometric.py | 15 ++++++++++++++- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index c1a7c6b001..72b442bfa3 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -130,7 +130,38 @@ def coefficient_mapper(coefficient): ir = fem.process('cell', cell, quad_rule.points, quad_rule.weights, quadrature_index, (), integrand, coefficient_mapper, - index_cache, None) + index_cache, None, None) + if parameters["unroll_indexsum"]: + ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) + expr, = ir + if quadrature_index in expr.free_indices: + expr = gem.IndexSum(expr, quadrature_index) + return expr + + def facetarea(): + from ufl import Measure + assert integral_type != 'cell' + form = 1 * Measure(integral_type, domain=mesh) + fd = compute_form_data(form, + do_apply_function_pullbacks=True, + do_apply_integral_scaling=True, + do_apply_geometry_lowering=True, + do_apply_restrictions=True, + do_estimate_degrees=True) + itg_data, = fd.integral_data + integral, = itg_data.integrals + + # Check if the integral has a quad degree attached, otherwise use + # the estimated polynomial degree attached by compute_form_data + quadrature_degree = integral.metadata()["estimated_polynomial_degree"] + quad_rule = create_quadrature(cell, integral_type, quadrature_degree) + + integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) + quadrature_index = gem.Index(name='q') + ir = fem.process(integral_type, cell, quad_rule.points, + quad_rule.weights, quadrature_index, (), + integrand, builder.coefficient_mapper, + index_cache, None, None) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) expr, = ir @@ -166,7 +197,7 @@ def coefficient_mapper(coefficient): quad_rule.weights, quadrature_index, argument_indices, integrand, builder.coefficient_mapper, index_cache, - cellvolume) + cellvolume, facetarea) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) irs.append([(gem.IndexSum(expr, quadrature_index) diff --git a/tsfc/fem.py b/tsfc/fem.py index 607083a344..e0eed74eb0 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -9,7 +9,7 @@ from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction from ufl.classes import (Argument, Coefficient, CellVolume, - ConstantValue, FormArgument, + ConstantValue, FacetArea, FormArgument, GeometricQuantity, QuadratureWeight) import gem @@ -196,7 +196,7 @@ class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): def __init__(self, tabulation_manager, weights, quadrature_index, argument_indices, coefficient_mapper, index_cache, - cellvolume): + cellvolume, facetarea): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) integral_type = tabulation_manager.integral_type @@ -211,6 +211,7 @@ def __init__(self, tabulation_manager, weights, quadrature_index, self.facet_manager = facet_manager self.select_facet = facet_manager.select_facet self.cellvolume = cellvolume + self.facetarea = facetarea if self.integral_type.startswith("interior_facet"): self.cell_orientations = gem.Variable("cell_orientations", (2, 1)) @@ -287,6 +288,11 @@ def _(terminal, mt, params): return params.cellvolume(mt.restriction) +@translate.register(FacetArea) +def translate_facetarea(terminal, mt, params): + return params.facetarea() + + @translate.register(Argument) # noqa: Not actually redefinition def _(terminal, mt, params): argument_index = params.argument_indices[terminal.number()] @@ -345,7 +351,7 @@ def _translate_constantvalue(terminal, mt, params): def process(integral_type, cell, points, weights, quadrature_index, argument_indices, integrand, coefficient_mapper, - index_cache, cellvolume): + index_cache, cellvolume, facetarea): # Abs-simplification integrand = simplify_abs(integrand) @@ -378,5 +384,5 @@ def process(integral_type, cell, points, weights, quadrature_index, # lowering finite element specific nodes translator = Translator(tabulation_manager, weights, quadrature_index, argument_indices, - coefficient_mapper, index_cache, cellvolume) + coefficient_mapper, index_cache, cellvolume, facetarea) return map_expr_dags(translator, expressions) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 58e3d77cfc..6e6f1bfa18 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -11,7 +11,7 @@ from ufl.classes import (CellCoordinate, CellEdgeVectors, CellFacetJacobian, CellOrientation, FacetCoordinate, ReferenceCellVolume, - ReferenceNormal) + ReferenceFacetVolume, ReferenceNormal) import gem @@ -36,6 +36,14 @@ } +# Volume of the reference cells of facets +reference_facet_volume = { + interval: 1.0, + triangle: 1.0, + tetrahedron: 1.0/2.0, +} + + # Jacobian of the mapping from a facet to the cell on the reference cell cell_facet_jacobian = { interval: array([[1.0], @@ -155,6 +163,11 @@ def translate_reference_cell_volume(terminal, mt, params): return gem.Literal(reference_cell_volume[reference_cell(terminal)]) +@translate.register(ReferenceFacetVolume) +def translate_reference_facet_volume(terminal, mt, params): + return gem.Literal(reference_facet_volume[reference_cell(terminal)]) + + @translate.register(CellFacetJacobian) def translate_cell_facet_jacobian(terminal, mt, params): table = cell_facet_jacobian[reference_cell(terminal)] From 20b9bca209d2b88cae7578c30b5b6b38cd443b2a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 6 Jun 2016 14:49:45 +0100 Subject: [PATCH 130/809] refactor tsfc.fem interface --- tsfc/driver.py | 38 +++++++----- tsfc/fem.py | 147 ++++++++++++++++++++++++++++++---------------- tsfc/geometric.py | 4 +- 3 files changed, 123 insertions(+), 66 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 72b442bfa3..2d8488166d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -117,7 +117,6 @@ def cellvolume(restriction): # Check if the integral has a quad degree attached, otherwise use # the estimated polynomial degree attached by compute_form_data quadrature_degree = integral.metadata()["estimated_polynomial_degree"] - quad_rule = create_quadrature(cell, 'cell', quadrature_degree) integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='q') @@ -127,10 +126,12 @@ def coefficient_mapper(coefficient): else: assert restriction is None coefficient_mapper = builder.coefficient_mapper - ir = fem.process('cell', cell, quad_rule.points, - quad_rule.weights, quadrature_index, (), - integrand, coefficient_mapper, - index_cache, None, None) + ir = fem.compile_ufl(integrand, + cell=cell, + quadrature_degree=quadrature_degree, + point_index=quadrature_index, + coefficient_mapper=coefficient_mapper, + index_cache=index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) expr, = ir @@ -154,14 +155,16 @@ def facetarea(): # Check if the integral has a quad degree attached, otherwise use # the estimated polynomial degree attached by compute_form_data quadrature_degree = integral.metadata()["estimated_polynomial_degree"] - quad_rule = create_quadrature(cell, integral_type, quadrature_degree) integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='q') - ir = fem.process(integral_type, cell, quad_rule.points, - quad_rule.weights, quadrature_index, (), - integrand, builder.coefficient_mapper, - index_cache, None, None) + ir = fem.compile_ufl(integrand, + integral_type=integral_type, + cell=cell, + quadrature_degree=quadrature_degree, + point_index=quadrature_index, + coefficient_mapper=builder.coefficient_mapper, + index_cache=index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) expr, = ir @@ -193,11 +196,16 @@ def facetarea(): integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) quadrature_index = gem.Index(name='ip') quadrature_indices.append(quadrature_index) - ir = fem.process(integral_type, cell, quad_rule.points, - quad_rule.weights, quadrature_index, - argument_indices, integrand, - builder.coefficient_mapper, index_cache, - cellvolume, facetarea) + ir = fem.compile_ufl(integrand, + integral_type=integral_type, + cell=cell, + quadrature_rule=quad_rule, + point_index=quadrature_index, + argument_indices=argument_indices, + coefficient_mapper=builder.coefficient_mapper, + index_cache=index_cache, + cellvolume=cellvolume, + facetarea=facetarea) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) irs.append([(gem.IndexSum(expr, quadrature_index) diff --git a/tsfc/fem.py b/tsfc/fem.py index e0eed74eb0..9089d8e9da 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -17,6 +17,7 @@ from tsfc.constants import PRECISION from tsfc.fiatinterface import create_element, as_fiat_cell from tsfc.modified_terminals import analyse_modified_terminal +from tsfc.quadrature import create_quadrature from tsfc import compat from tsfc import ufl2gem from tsfc import geometric @@ -191,38 +192,89 @@ def select_facet(self, tensor, restriction): return gem.partial_indexed(tensor, (f,)) +class cached_property(object): + """A read-only @property that is only evaluated once. The value is cached + on the object itself rather than the function or class; this should prevent + memory leakage.""" + def __init__(self, fget, doc=None): + self.fget = fget + self.__doc__ = doc or fget.__doc__ + self.__name__ = fget.__name__ + self.__module__ = fget.__module__ + + def __get__(self, obj, cls): + if obj is None: + return self + obj.__dict__[self.__name__] = result = self.fget(obj) + return result + + +class Parameters(object): + keywords = ('integral_type', + 'cell', + 'quadrature_degree', + 'quadrature_rule', + 'points', + 'weights', + 'point_index', + 'argument_indices', + 'coefficient_mapper', + 'cellvolume', + 'facetarea', + 'index_cache') + + def __init__(self, **kwargs): + invalid_keywords = set(kwargs.keys()) - set(Parameters.keywords) + if invalid_keywords: + raise ValueError("unexpected keyword argument '{0}'".format(invalid_keywords.pop())) + self.__dict__.update(kwargs) + + # Defaults + integral_type = 'cell' + + @cached_property + def quadrature_rule(self): + return create_quadrature(self.cell, + self.integral_type, + self.quadrature_degree) + + @cached_property + def points(self): + return self.quadrature_rule.points + + @cached_property + def weights(self): + return self.quadrature_rule.weights + + argument_indices = () + + @cached_property + def index_cache(self): + return collections.defaultdict(gem.Index) + + class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): """Contains all the context necessary to translate UFL into GEM.""" - def __init__(self, tabulation_manager, weights, quadrature_index, - argument_indices, coefficient_mapper, index_cache, - cellvolume, facetarea): + def __init__(self, tabulation_manager, parameters): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) - integral_type = tabulation_manager.integral_type - facet_manager = tabulation_manager.facet_manager - self.integral_type = integral_type - self.tabulation_manager = tabulation_manager - self.weights = gem.Literal(weights) - self.quadrature_index = quadrature_index - self.argument_indices = argument_indices - self.coefficient_mapper = coefficient_mapper - self.index_cache = index_cache - self.facet_manager = facet_manager - self.select_facet = facet_manager.select_facet - self.cellvolume = cellvolume - self.facetarea = facetarea - - if self.integral_type.startswith("interior_facet"): - self.cell_orientations = gem.Variable("cell_orientations", (2, 1)) + + if parameters.integral_type.startswith("interior_facet"): + parameters.cell_orientations = gem.Variable("cell_orientations", (2, 1)) else: - self.cell_orientations = gem.Variable("cell_orientations", (1, 1)) + parameters.cell_orientations = gem.Variable("cell_orientations", (1, 1)) + + parameters.tabulation_manager = tabulation_manager + parameters.facet_manager = tabulation_manager.facet_manager + parameters.select_facet = tabulation_manager.facet_manager.select_facet + self.parameters = parameters def modified_terminal(self, o): """Overrides the modified terminal handler from :class:`ModifiedTerminalMixin`.""" mt = analyse_modified_terminal(o) - return translate(mt.terminal, mt, self) + return translate(mt.terminal, mt, self.parameters) def iterate_shape(mt, callback): @@ -273,18 +325,18 @@ def translate(terminal, mt, params): raise AssertionError("Cannot handle terminal type: %s" % type(terminal)) -@translate.register(QuadratureWeight) # noqa: Not actually redefinition -def _(terminal, mt, params): - return gem.Indexed(params.weights, (params.quadrature_index,)) +@translate.register(QuadratureWeight) +def translate_quadratureweight(terminal, mt, params): + return gem.Indexed(gem.Literal(params.weights), (params.point_index,)) -@translate.register(GeometricQuantity) # noqa: Not actually redefinition -def _(terminal, mt, params): +@translate.register(GeometricQuantity) +def translate_geometricquantity(terminal, mt, params): return geometric.translate(terminal, mt, params) -@translate.register(CellVolume) # noqa: Not actually redefinition -def _(terminal, mt, params): +@translate.register(CellVolume) +def translate_cellvolume(terminal, mt, params): return params.cellvolume(mt.restriction) @@ -293,8 +345,8 @@ def translate_facetarea(terminal, mt, params): return params.facetarea() -@translate.register(Argument) # noqa: Not actually redefinition -def _(terminal, mt, params): +@translate.register(Argument) +def translate_argument(terminal, mt, params): argument_index = params.argument_indices[terminal.number()] def callback(key): @@ -304,14 +356,14 @@ def callback(key): row = gem.Literal(table) else: table = params.select_facet(gem.Literal(table), mt.restriction) - row = gem.partial_indexed(table, (params.quadrature_index,)) + row = gem.partial_indexed(table, (params.point_index,)) return gem.Indexed(row, (argument_index,)) return iterate_shape(mt, callback) -@translate.register(Coefficient) # noqa: Not actually redefinition -def _(terminal, mt, params): +@translate.register(Coefficient) +def translate_coefficient(terminal, mt, params): kernel_arg = params.coefficient_mapper(terminal) if terminal.ufl_element().family() == 'Real': @@ -333,7 +385,7 @@ def callback(key): gem.Zero()) else: table = params.select_facet(gem.Literal(table), mt.restriction) - row = gem.partial_indexed(table, (params.quadrature_index,)) + row = gem.partial_indexed(table, (params.point_index,)) r = params.index_cache[terminal.ufl_element()] return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), @@ -349,15 +401,15 @@ def _translate_constantvalue(terminal, mt, params): return params(terminal) -def process(integral_type, cell, points, weights, quadrature_index, - argument_indices, integrand, coefficient_mapper, - index_cache, cellvolume, facetarea): +def compile_ufl(expression, **kwargs): + params = Parameters(**kwargs) + # Abs-simplification - integrand = simplify_abs(integrand) + expression = simplify_abs(expression) # Collect modified terminals modified_terminals = [] - map_expr_dag(CollectModifiedTerminals(modified_terminals), integrand) + map_expr_dag(CollectModifiedTerminals(modified_terminals), expression) # Collect maximal derivatives that needs tabulation max_derivs = collections.defaultdict(int) @@ -368,21 +420,18 @@ def process(integral_type, cell, points, weights, quadrature_index, max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(integral_type, cell, points) + tabulation_manager = TabulationManager(params.integral_type, params.cell, params.points) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) - if integral_type.startswith("interior_facet"): + if params.integral_type.startswith("interior_facet"): expressions = [] - for rs in itertools.product(("+", "-"), repeat=len(argument_indices)): - expressions.append(map_expr_dag(PickRestriction(*rs), integrand)) + for rs in itertools.product(("+", "-"), repeat=len(params.argument_indices)): + expressions.append(map_expr_dag(PickRestriction(*rs), expression)) else: - expressions = [integrand] + expressions = [expression] - # Translate UFL to Einstein's notation, - # lowering finite element specific nodes - translator = Translator(tabulation_manager, weights, - quadrature_index, argument_indices, - coefficient_mapper, index_cache, cellvolume, facetarea) + # Translate UFL to GEM, lowering finite element specific nodes + translator = Translator(tabulation_manager, params) return map_expr_dags(translator, expressions) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 6e6f1bfa18..df47a5d6d1 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -204,11 +204,11 @@ def translate_cell_coordinate(terminal, mt, params): points = list(params.facet_manager.facet_transform(points)) return gem.partial_indexed(params.select_facet(gem.Literal(points), mt.restriction), - (params.quadrature_index,)) + (params.point_index,)) @translate.register(FacetCoordinate) def translate_facet_coordinate(terminal, mt, params): assert params.integral_type != 'cell' points = params.tabulation_manager.points - return gem.partial_indexed(gem.Literal(points), (params.quadrature_index,)) + return gem.partial_indexed(gem.Literal(points), (params.point_index,)) From fd71fd68d0e7bd6339480516bbf50ee44c359358 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 6 Jun 2016 15:44:04 +0100 Subject: [PATCH 131/809] workaround UFL quadrature degree type Closes firedrakeproject/firedrake#744. --- tests/test_create_quadrature.py | 18 ------------------ tsfc/quadrature.py | 13 ++++--------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/tests/test_create_quadrature.py b/tests/test_create_quadrature.py index 66df464282..9b1982dd89 100644 --- a/tests/test_create_quadrature.py +++ b/tests/test_create_quadrature.py @@ -36,24 +36,6 @@ def test_select_degree(cell, degree, itype): assert selected == degree -@pytest.mark.parametrize("degree", - [(1, 1), (2, 2)]) -@pytest.mark.parametrize("itype", - ["interior_facet", "exterior_facet"]) -def test_select_degree_facet_quad(degree, itype): - selected = q.select_degree(degree, ufl.quadrilateral, itype) - assert selected == degree[0] - - -@pytest.mark.parametrize("degree", - [(1, 2), (2,)]) -@pytest.mark.parametrize("itype", - ["interior_facet", "exterior_facet"]) -def test_select_invalid_degree_facet_quad(degree, itype): - with pytest.raises(ValueError): - q.select_degree(degree, ufl.quadrilateral, itype) - - @pytest.mark.parametrize("degree", [(1, 2), (2, 3)]) @pytest.mark.parametrize("itype", diff --git a/tsfc/quadrature.py b/tsfc/quadrature.py index 5e74304841..8b08a865e2 100644 --- a/tsfc/quadrature.py +++ b/tsfc/quadrature.py @@ -391,19 +391,14 @@ def select_degree(degree, cell, integral_type): raise ValueError("Integral type '%s' invalid for cell '%s'" % (integral_type, cell.cellname())) if cell.cellname() == "quadrilateral": - try: - d1, d2 = degree - if len(degree) != 2: - raise ValueError("Expected tuple degree of length 2") - if d1 != d2: - raise ValueError("tuple degree must have matching values") - return d1 - except TypeError: - return degree + assert isinstance(degree, int) return degree if not isinstance(cell, ufl.TensorProductCell): raise ValueError("Integral type '%s' invalid for cell '%s'" % (integral_type, cell.cellname())) + # Fix degree on TensorProductCell when not tuple + if degree == 0: + degree = (0, 0) if integral_type in ("exterior_facet_top", "exterior_facet_bottom", "interior_facet_horiz"): return degree[0] From 90f68e30640afd395074c95d63d687b005450734 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 6 Jun 2016 17:05:23 +0100 Subject: [PATCH 132/809] disable UFL pipeline even for affine TSFC handles CellVolume and FacetArea more robustly. --- tsfc/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2d8488166d..b7180a1ae6 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -3,7 +3,7 @@ import collections import time -from ufl.classes import Form +from ufl.classes import Form, CellVolume, FacetArea from ufl.algorithms import compute_form_data from ufl.log import GREEN @@ -42,6 +42,7 @@ def compile_form(form, prefix="form", parameters=None): do_apply_integral_scaling=True, do_apply_geometry_lowering=True, do_apply_restrictions=True, + preserve_geometry_types=(CellVolume, FacetArea), do_estimate_degrees=True) print GREEN % ("compute_form_data finished in %g seconds." % (time.time() - cpu_time)) From 2e81a2fae695e82cf8c0c6153352b62349cdd8b0 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 13 Jun 2016 11:11:46 +0100 Subject: [PATCH 133/809] update TODO --- tsfc/driver.py | 2 ++ tsfc/fem.py | 1 + 2 files changed, 3 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index b7180a1ae6..7d64ffdb97 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -103,6 +103,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): # evaluation can be hoisted). index_cache = collections.defaultdict(gem.Index) + # TODO: refactor this! def cellvolume(restriction): from ufl import dx form = 1 * dx(domain=mesh) @@ -140,6 +141,7 @@ def coefficient_mapper(coefficient): expr = gem.IndexSum(expr, quadrature_index) return expr + # TODO: refactor this! def facetarea(): from ufl import Measure assert integral_type != 'cell' diff --git a/tsfc/fem.py b/tsfc/fem.py index 9089d8e9da..76f4373fe8 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -192,6 +192,7 @@ def select_facet(self, tensor, restriction): return gem.partial_indexed(tensor, (f,)) +# FIXME: copy-paste from PyOP2 class cached_property(object): """A read-only @property that is only evaluated once. The value is cached on the object itself rather than the function or class; this should prevent From 9de1fc1789d913018c54636a850d23b91599842c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 13 Jun 2016 14:40:47 +0100 Subject: [PATCH 134/809] support Python logging Closes #43. --- tsfc/driver.py | 7 ++++--- tsfc/logging.py | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 tsfc/logging.py diff --git a/tsfc/driver.py b/tsfc/driver.py index 7d64ffdb97..48fd1c45e1 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -15,6 +15,7 @@ from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.kernel_interface import KernelBuilder, needs_cell_orientations +from tsfc.logging import logger from tsfc.quadrature import create_quadrature, QuadratureRule @@ -44,7 +45,7 @@ def compile_form(form, prefix="form", parameters=None): do_apply_restrictions=True, preserve_geometry_types=(CellVolume, FacetArea), do_estimate_degrees=True) - print GREEN % ("compute_form_data finished in %g seconds." % (time.time() - cpu_time)) + logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) kernels = [] for integral_data in fd.integral_data: @@ -53,9 +54,9 @@ def compile_form(form, prefix="form", parameters=None): kernels.append(compile_integral(integral_data, fd, prefix, parameters)) except impero_utils.NoopError: pass - print GREEN % ("compile_integral finished in %g seconds." % (time.time() - start)) + logger.info(GREEN % "compile_integral finished in %g seconds.", time.time() - start) - print GREEN % ("TSFC finished in %g seconds." % (time.time() - cpu_time)) + logger.info(GREEN % "TSFC finished in %g seconds.", time.time() - cpu_time) return kernels diff --git a/tsfc/logging.py b/tsfc/logging.py new file mode 100644 index 0000000000..1ba89abf1a --- /dev/null +++ b/tsfc/logging.py @@ -0,0 +1,8 @@ +"""Logging for TSFC.""" + +from __future__ import absolute_import + +import logging + +logger = logging.getLogger('tsfc') +logger.addHandler(logging.StreamHandler()) From ecb3336e937b666deb53885b5c93af9141a17a20 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 20 Jul 2016 15:37:48 +0100 Subject: [PATCH 135/809] move quadrature schemes to FIAT --- tests/test_create_quadrature.py | 34 ----- tsfc/quadrature.py | 262 +------------------------------- 2 files changed, 5 insertions(+), 291 deletions(-) diff --git a/tests/test_create_quadrature.py b/tests/test_create_quadrature.py index 9b1982dd89..f5a42646da 100644 --- a/tests/test_create_quadrature.py +++ b/tests/test_create_quadrature.py @@ -79,40 +79,6 @@ def test_invalid_integral_type_tensor_prod(tensor_product_cell, itype): q.select_degree((1, 1), tensor_product_cell, itype) -@pytest.mark.parametrize("itype", - ["interior_facet", - "exterior_facet", - "cell"]) -@pytest.mark.parametrize("scheme", - ["default", - "canonical"]) -def test_invalid_quadrature_degree(cell, itype, scheme): - with pytest.raises(ValueError): - q.create_quadrature(cell, itype, -1, scheme) - - -@pytest.mark.parametrize("itype", - ["interior_facet_horiz", - "interior_facet_vert", - "exterior_facet_vert", - "exterior_facet_top", - "exterior_facet_bottom", - "cell"]) -def test_invalid_quadrature_degree_tensor_prod(tensor_product_cell, itype): - with pytest.raises(ValueError): - q.create_quadrature(tensor_product_cell, itype, (-1, -1)) - - -def test_high_degree_runtime_error(cell): - with pytest.raises(RuntimeError): - q.create_quadrature(cell, "cell", 60) - - -def test_high_degree_runtime_error_tensor_prod(tensor_product_cell): - with pytest.raises(RuntimeError): - q.create_quadrature(tensor_product_cell, "cell", (60, 60)) - - if __name__ == "__main__": import os import sys diff --git a/tsfc/quadrature.py b/tsfc/quadrature.py index 8b08a865e2..9b0a538b7d 100644 --- a/tsfc/quadrature.py +++ b/tsfc/quadrature.py @@ -23,13 +23,9 @@ from __future__ import absolute_import from __future__ import print_function -from __future__ import division -from singledispatch import singledispatch import numpy import FIAT -from FIAT.reference_element import UFCInterval, UFCTriangle, UFCTetrahedron, \ - FiredrakeQuadrilateral, TensorProductCell import ufl from tsfc.fiatinterface import as_fiat_cell @@ -55,252 +51,6 @@ def __init__(self, points, weights): self.weights = weights -def fiat_scheme(cell, degree): - """Create a quadrature rule using FIAT. - - On simplexes, this is a collapsed Guass scheme, on tensor-product - cells, it is a tensor-product quadrature rule of the subcells. - - :arg cell: The FIAT cell to create the quadrature for. - :arg degree: The degree of polynomial that the rule should - integrate exactly.""" - try: - points = tuple((d + 2) // 2 for d in degree) - except TypeError: - points = (degree + 2) // 2 - tdim = cell.get_spatial_dimension() - if points > 30: - raise RuntimeError("Requested a quadrature rule with %d points per direction (%d points)" % - (points, points**tdim)) - if numpy.prod(points) <= 0: - raise ValueError("Requested a quadrature rule with a negative number of points") - if numpy.prod(points) > 900: - raise RuntimeError("Requested a quadrature rule with %d points" % numpy.prod(points)) - quad = FIAT.make_quadrature(cell, points) - return QuadratureRule(quad.get_points(), quad.get_weights()) - - -@singledispatch -def default_scheme(cell, degree): - """Create a quadrature rule. - - For low-degree (<=6) polynomials on triangles and tetrahedra, this - uses hard-coded rules, otherwise it falls back to the schemes that - FIAT provides (see :func:`fiat_scheme`). - - :arg cell: The FIAT cell to create the quadrature for. - :arg degree: The degree of polynomial that the rule should - integrate exactly.""" - raise ValueError("No scheme handler defined for %s" % cell) - - -@default_scheme.register(TensorProductCell) # noqa -@default_scheme.register(FiredrakeQuadrilateral) -@default_scheme.register(UFCInterval) -def _(cell, degree): - return fiat_scheme(cell, degree) - - -@default_scheme.register(UFCTriangle) # noqa -def _(cell, degree): - if degree < 0: - raise ValueError("Need positive degree, not %d" % degree) - if degree > 6: - return fiat_scheme(cell, degree) - if degree == 0 or degree == 1: - # Scheme from Zienkiewicz and Taylor, 1 point, degree of precision 1 - x = numpy.array([[1.0/3.0, 1.0/3.0]], dtype=numpy.float64) - w = numpy.array([0.5], dtype=numpy.float64) - if degree == 2: - # Scheme from Strang and Fix, 3 points, degree of precision 2 - x = numpy.array([[1.0/6.0, 1.0/6.0], - [1.0/6.0, 2.0/3.0], - [2.0/3.0, 1.0/6.0]], - dtype=numpy.float64) - w = numpy.full(3, 1.0/6.0, dtype=numpy.float64) - if degree == 3: - # Scheme from Strang and Fix, 6 points, degree of precision 3 - x = numpy.array([[0.659027622374092, 0.231933368553031], - [0.659027622374092, 0.109039009072877], - [0.231933368553031, 0.659027622374092], - [0.231933368553031, 0.109039009072877], - [0.109039009072877, 0.659027622374092], - [0.109039009072877, 0.231933368553031]], - dtype=numpy.float64) - w = numpy.full(6, 1.0/12.0, dtype=numpy.float64) - if degree == 4: - # Scheme from Strang and Fix, 6 points, degree of precision 4 - x = numpy.array([[0.816847572980459, 0.091576213509771], - [0.091576213509771, 0.816847572980459], - [0.091576213509771, 0.091576213509771], - [0.108103018168070, 0.445948490915965], - [0.445948490915965, 0.108103018168070], - [0.445948490915965, 0.445948490915965]], - dtype=numpy.float64) - w = numpy.empty(6, dtype=numpy.float64) - w[0:3] = 0.109951743655322 - w[3:6] = 0.223381589678011 - w /= 2.0 - if degree == 5: - # Scheme from Strang and Fix, 7 points, degree of precision 5 - x = numpy.array([[0.33333333333333333, 0.33333333333333333], - [0.79742698535308720, 0.10128650732345633], - [0.10128650732345633, 0.79742698535308720], - [0.10128650732345633, 0.10128650732345633], - [0.05971587178976981, 0.47014206410511505], - [0.47014206410511505, 0.05971587178976981], - [0.47014206410511505, 0.47014206410511505]], - dtype=numpy.float64) - w = numpy.empty(7, dtype=numpy.float64) - w[0] = 0.22500000000000000 - w[1:4] = 0.12593918054482717 - w[4:7] = 0.13239415278850616 - w = w/2.0 - if degree == 6: - # Scheme from Strang and Fix, 12 points, degree of precision 6 - x = numpy.array([[0.873821971016996, 0.063089014491502], - [0.063089014491502, 0.873821971016996], - [0.063089014491502, 0.063089014491502], - [0.501426509658179, 0.249286745170910], - [0.249286745170910, 0.501426509658179], - [0.249286745170910, 0.249286745170910], - [0.636502499121399, 0.310352451033785], - [0.636502499121399, 0.053145049844816], - [0.310352451033785, 0.636502499121399], - [0.310352451033785, 0.053145049844816], - [0.053145049844816, 0.636502499121399], - [0.053145049844816, 0.310352451033785]], - dtype=numpy.float64) - w = numpy.empty(12, dtype=numpy.float64) - w[0:3] = 0.050844906370207 - w[3:6] = 0.116786275726379 - w[6:12] = 0.082851075618374 - w = w/2.0 - - return QuadratureRule(x, w) - - -@default_scheme.register(UFCTetrahedron) # noqa -def _(cell, degree): - if degree < 0: - raise ValueError("Need positive degree, not %d" % degree) - if degree > 6: - return fiat_scheme(cell, degree) - if degree == 0 or degree == 1: - # Scheme from Zienkiewicz and Taylor, 1 point, degree of precision 1 - x = numpy.array([[1.0/4.0, 1.0/4.0, 1.0/4.0]], dtype=numpy.float64) - w = numpy.array([1.0/6.0], dtype=numpy.float64) - elif degree == 2: - # Scheme from Zienkiewicz and Taylor, 4 points, degree of precision 2 - a, b = 0.585410196624969, 0.138196601125011 - x = numpy.array([[a, b, b], - [b, a, b], - [b, b, a], - [b, b, b]], - dtype=numpy.float64) - w = numpy.full(4, 1.0/24.0, dtype=numpy.float64) - elif degree == 3: - # Scheme from Zienkiewicz and Taylor, 5 points, degree of precision 3 - # Note: this scheme has a negative weight - x = numpy.array([[0.2500000000000000, 0.2500000000000000, 0.2500000000000000], - [0.5000000000000000, 0.1666666666666666, 0.1666666666666666], - [0.1666666666666666, 0.5000000000000000, 0.1666666666666666], - [0.1666666666666666, 0.1666666666666666, 0.5000000000000000], - [0.1666666666666666, 0.1666666666666666, 0.1666666666666666]], - dtype=numpy.float64) - w = numpy.empty(5, dtype=numpy.float64) - w[0] = -0.8 - w[1:5] = 0.45 - w = w/6.0 - elif degree == 4: - # Keast rule, 14 points, degree of precision 4 - # Values taken from http://people.sc.fsu.edu/~jburkardt/datasets/quadrature_rules_tet/quadrature_rules_tet.html - # (KEAST5) - x = numpy.array([[0.0000000000000000, 0.5000000000000000, 0.5000000000000000], - [0.5000000000000000, 0.0000000000000000, 0.5000000000000000], - [0.5000000000000000, 0.5000000000000000, 0.0000000000000000], - [0.5000000000000000, 0.0000000000000000, 0.0000000000000000], - [0.0000000000000000, 0.5000000000000000, 0.0000000000000000], - [0.0000000000000000, 0.0000000000000000, 0.5000000000000000], - [0.6984197043243866, 0.1005267652252045, 0.1005267652252045], - [0.1005267652252045, 0.1005267652252045, 0.1005267652252045], - [0.1005267652252045, 0.1005267652252045, 0.6984197043243866], - [0.1005267652252045, 0.6984197043243866, 0.1005267652252045], - [0.0568813795204234, 0.3143728734931922, 0.3143728734931922], - [0.3143728734931922, 0.3143728734931922, 0.3143728734931922], - [0.3143728734931922, 0.3143728734931922, 0.0568813795204234], - [0.3143728734931922, 0.0568813795204234, 0.3143728734931922]], - dtype=numpy.float64) - w = numpy.empty(14, dtype=numpy.float64) - w[0:6] = 0.0190476190476190 - w[6:10] = 0.0885898247429807 - w[10:14] = 0.1328387466855907 - w = w/6.0 - elif degree == 5: - # Keast rule, 15 points, degree of precision 5 - # Values taken from http://people.sc.fsu.edu/~jburkardt/datasets/quadrature_rules_tet/quadrature_rules_tet.html - # (KEAST6) - x = numpy.array([[0.2500000000000000, 0.2500000000000000, 0.2500000000000000], - [0.0000000000000000, 0.3333333333333333, 0.3333333333333333], - [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], - [0.3333333333333333, 0.3333333333333333, 0.0000000000000000], - [0.3333333333333333, 0.0000000000000000, 0.3333333333333333], - [0.7272727272727273, 0.0909090909090909, 0.0909090909090909], - [0.0909090909090909, 0.0909090909090909, 0.0909090909090909], - [0.0909090909090909, 0.0909090909090909, 0.7272727272727273], - [0.0909090909090909, 0.7272727272727273, 0.0909090909090909], - [0.4334498464263357, 0.0665501535736643, 0.0665501535736643], - [0.0665501535736643, 0.4334498464263357, 0.0665501535736643], - [0.0665501535736643, 0.0665501535736643, 0.4334498464263357], - [0.0665501535736643, 0.4334498464263357, 0.4334498464263357], - [0.4334498464263357, 0.0665501535736643, 0.4334498464263357], - [0.4334498464263357, 0.4334498464263357, 0.0665501535736643]], - dtype=numpy.float64) - w = numpy.empty(15, dtype=numpy.float64) - w[0] = 0.1817020685825351 - w[1:5] = 0.0361607142857143 - w[5:9] = 0.0698714945161738 - w[9:15] = 0.0656948493683187 - w = w/6.0 - elif degree == 6: - # Keast rule, 24 points, degree of precision 6 - # Values taken from http://people.sc.fsu.edu/~jburkardt/datasets/quadrature_rules_tet/quadrature_rules_tet.html - # (KEAST7) - x = numpy.array([[0.3561913862225449, 0.2146028712591517, 0.2146028712591517], - [0.2146028712591517, 0.2146028712591517, 0.2146028712591517], - [0.2146028712591517, 0.2146028712591517, 0.3561913862225449], - [0.2146028712591517, 0.3561913862225449, 0.2146028712591517], - [0.8779781243961660, 0.0406739585346113, 0.0406739585346113], - [0.0406739585346113, 0.0406739585346113, 0.0406739585346113], - [0.0406739585346113, 0.0406739585346113, 0.8779781243961660], - [0.0406739585346113, 0.8779781243961660, 0.0406739585346113], - [0.0329863295731731, 0.3223378901422757, 0.3223378901422757], - [0.3223378901422757, 0.3223378901422757, 0.3223378901422757], - [0.3223378901422757, 0.3223378901422757, 0.0329863295731731], - [0.3223378901422757, 0.0329863295731731, 0.3223378901422757], - [0.2696723314583159, 0.0636610018750175, 0.0636610018750175], - [0.0636610018750175, 0.2696723314583159, 0.0636610018750175], - [0.0636610018750175, 0.0636610018750175, 0.2696723314583159], - [0.6030056647916491, 0.0636610018750175, 0.0636610018750175], - [0.0636610018750175, 0.6030056647916491, 0.0636610018750175], - [0.0636610018750175, 0.0636610018750175, 0.6030056647916491], - [0.0636610018750175, 0.2696723314583159, 0.6030056647916491], - [0.2696723314583159, 0.6030056647916491, 0.0636610018750175], - [0.6030056647916491, 0.0636610018750175, 0.2696723314583159], - [0.0636610018750175, 0.6030056647916491, 0.2696723314583159], - [0.2696723314583159, 0.0636610018750175, 0.6030056647916491], - [0.6030056647916491, 0.2696723314583159, 0.0636610018750175]], - dtype=numpy.float64) - w = numpy.empty(24, dtype=numpy.float64) - w[0:4] = 0.0399227502581679 - w[4:8] = 0.0100772110553207 - w[8:12] = 0.0553571815436544 - w[12:24] = 0.0482142857142857 - w = w/6.0 - - return QuadratureRule(x, w) - - def create_quadrature_rule(cell, degree, scheme="default"): """Create a quadrature rule. @@ -331,16 +81,14 @@ def create_quadrature_rule(cell, degree, scheme="default"): degree = (degree, degree) if cell.cellname() == "vertex": - if degree < 0: - raise ValueError("Need positive degree, not %d" % degree) return QuadratureRule(numpy.zeros((1, 0), dtype=numpy.float64), numpy.ones(1, dtype=numpy.float64)) - cell = as_fiat_cell(cell) - if scheme == "canonical": - return fiat_scheme(cell, degree) - - return default_scheme(cell, degree) + cell = as_fiat_cell(cell) + fiat_rule = FIAT.create_quadrature(cell, degree, scheme) + if len(fiat_rule.get_points()) > 900: + raise RuntimeError("Requested a quadrature rule with %d points" % len(fiat_rule.get_points())) + return QuadratureRule(fiat_rule.get_points(), fiat_rule.get_weights()) def integration_cell(cell, integral_type): From f1049b99d3ae9868d01417f95f64dbff5f13f467 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 14 Jul 2016 18:59:08 +0100 Subject: [PATCH 136/809] adopt new FIAT API --- tsfc/fem.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 76f4373fe8..a18b925e75 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -157,22 +157,24 @@ def facet_transform(self, points): :arg points: points in integration cell coordinates """ + dim = self.ufl_cell.topological_dimension() + if self.integral_type == 'cell': yield points elif self.integral_type in ['exterior_facet', 'interior_facet']: for entity in range(self.ufl_cell.num_facets()): - t = as_fiat_cell(self.ufl_cell).get_facet_transform(entity) + t = as_fiat_cell(self.ufl_cell).get_entity_transform(dim-1, entity) yield numpy.asarray(map(t, points)) elif self.integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: for entity in range(2): # top and bottom - t = as_fiat_cell(self.ufl_cell).get_horiz_facet_transform(entity) + t = as_fiat_cell(self.ufl_cell).get_entity_transform((dim-1, 0), entity) yield numpy.asarray(map(t, points)) elif self.integral_type in ['exterior_facet_vert', 'interior_facet_vert']: for entity in range(self.ufl_cell.sub_cells()[0].num_facets()): # "base cell" facets - t = as_fiat_cell(self.ufl_cell).get_vert_facet_transform(entity) + t = as_fiat_cell(self.ufl_cell).get_entity_transform((dim-2, 1), entity) yield numpy.asarray(map(t, points)) else: From 2f3ad6c5b1387eeb55dc734860cd89493de531c0 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 22 Jul 2016 11:17:45 +0100 Subject: [PATCH 137/809] simplify quadrature creation --- tests/test_create_quadrature.py | 72 -------------------- tsfc/quadrature.py | 117 ++++++-------------------------- 2 files changed, 21 insertions(+), 168 deletions(-) diff --git a/tests/test_create_quadrature.py b/tests/test_create_quadrature.py index f5a42646da..4ab09f95a1 100644 --- a/tests/test_create_quadrature.py +++ b/tests/test_create_quadrature.py @@ -1,5 +1,4 @@ from tsfc import quadrature as q -import ufl import pytest @@ -8,77 +7,6 @@ def test_invalid_quadrature_rule(): q.QuadratureRule([[0.5, 0.5]], [0.5, 0.5, 0.5]) -@pytest.fixture(params=["interval", "triangle", - "tetrahedron", "quadrilateral"]) -def cell(request): - cells = {"interval": ufl.interval, - "triangle": ufl.triangle, - "tetrahedron": ufl.tetrahedron, - "quadrilateral": ufl.quadrilateral} - - return cells[request.param] - - -@pytest.fixture -def tensor_product_cell(cell): - if cell.cellname() == "tetrahedron": - pytest.skip("Tensor-producted tet not supported") - - return ufl.TensorProductCell(cell, ufl.interval) - - -@pytest.mark.parametrize("degree", - [1, 2]) -@pytest.mark.parametrize("itype", - ["cell", "interior_facet", "exterior_facet"]) -def test_select_degree(cell, degree, itype): - selected = q.select_degree(degree, cell, itype) - assert selected == degree - - -@pytest.mark.parametrize("degree", - [(1, 2), (2, 3)]) -@pytest.mark.parametrize("itype", - ["interior_facet_horiz", "exterior_facet_top", - "exterior_facet_bottom"]) -def test_select_degree_horiz_facet(tensor_product_cell, degree, itype): - selected = q.select_degree(degree, tensor_product_cell, itype) - assert selected == degree[0] - - -@pytest.mark.parametrize("degree", - [(1, 2), (2, 3)]) -@pytest.mark.parametrize("itype", - ["interior_facet_vert", "exterior_facet_vert"]) -def test_select_degree_vert_facet(tensor_product_cell, degree, itype): - selected = q.select_degree(degree, tensor_product_cell, itype) - if tensor_product_cell.topological_dimension() == 2: - assert selected == degree[1] - else: - assert selected == degree - - -@pytest.mark.parametrize("itype", - ["interior_facet_horiz", - "interior_facet_vert", - "exterior_facet_vert", - "exterior_facet_top", - "exterior_facet_bottom", - "nonsense"]) -def test_invalid_integral_type(cell, itype): - with pytest.raises(ValueError): - q.select_degree(1, cell, itype) - - -@pytest.mark.parametrize("itype", - ["interior_facet", - "exterior_facet", - "nonsense"]) -def test_invalid_integral_type_tensor_prod(tensor_product_cell, itype): - with pytest.raises(ValueError): - q.select_degree((1, 1), tensor_product_cell, itype) - - if __name__ == "__main__": import os import sys diff --git a/tsfc/quadrature.py b/tsfc/quadrature.py index 9b0a538b7d..1367cdedd5 100644 --- a/tsfc/quadrature.py +++ b/tsfc/quadrature.py @@ -51,10 +51,28 @@ def __init__(self, points, weights): self.weights = weights -def create_quadrature_rule(cell, degree, scheme="default"): +def entity_dimension(cell, integral_type): + # TODO TODO TODO + dim = cell.get_spatial_dimension() + if integral_type == 'cell': + if isinstance(cell, FIAT.reference_element.TensorProductCell): + return tuple(c.get_spatial_dimension() for c in cell.cells) + return dim + elif integral_type in ['exterior_facet', 'interior_facet']: + return dim - 1 + elif integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: + return (dim - 1, 0) + elif integral_type in ['exterior_facet_vert', 'interior_facet_vert']: + return (dim - 2, 1) + else: + raise NotImplementedError("integral type %s not supported" % integral_type) + + +def create_quadrature(cell, integral_type, degree, scheme="default"): """Create a quadrature rule. - :arg cell: The UFL cell to create the rule for. + :arg cell: The UFL cell. + :arg integral_type: The integral being performed. :arg degree: The degree of polynomial that should be integrated exactly by the rule. :kwarg scheme: optional scheme to use (either ``"default"``, or @@ -80,102 +98,9 @@ def create_quadrature_rule(cell, degree, scheme="default"): # each direction. degree = (degree, degree) - if cell.cellname() == "vertex": - return QuadratureRule(numpy.zeros((1, 0), dtype=numpy.float64), - numpy.ones(1, dtype=numpy.float64)) - cell = as_fiat_cell(cell) + cell = cell.construct_subelement(entity_dimension(cell, integral_type)) fiat_rule = FIAT.create_quadrature(cell, degree, scheme) if len(fiat_rule.get_points()) > 900: raise RuntimeError("Requested a quadrature rule with %d points" % len(fiat_rule.get_points())) return QuadratureRule(fiat_rule.get_points(), fiat_rule.get_weights()) - - -def integration_cell(cell, integral_type): - """Return the integration cell for a given integral type. - - :arg cell: The "base" cell (that cell integrals are performed - over). - :arg integral_type: The integration type. - """ - if integral_type == "cell": - return cell - if integral_type in ("exterior_facet", "interior_facet"): - return {"interval": ufl.vertex, - "triangle": ufl.interval, - "quadrilateral": ufl.interval, - "tetrahedron": ufl.triangle, - "hexahedron": ufl.quadrilateral}[cell.cellname()] - # Extruded cases - base_cell, interval = cell.sub_cells() - assert interval.cellname() == "interval" - if integral_type in ("exterior_facet_top", "exterior_facet_bottom", - "interior_facet_horiz"): - return base_cell - if integral_type in ("exterior_facet_vert", "interior_facet_vert"): - if base_cell.topological_dimension() == 2: - return ufl.TensorProductCell(ufl.interval, ufl.interval) - elif base_cell.topological_dimension() == 1: - return ufl.interval - raise ValueError("Don't know how to find an integration cell") - - -def select_degree(degree, cell, integral_type): - """Select correct part of degree given an integral type. - - :arg degree: The degree on the cell. - :arg cell: The "base" integration cell (that cell integrals are - performed over). - :arg integral_type: The integration type. - - For non-tensor-product cells, this always just returns the - degree. For tensor-product cells it returns the degree on the - appropriate sub-entity. - """ - if integral_type == "cell": - return degree - if integral_type in ("exterior_facet", "interior_facet"): - if isinstance(cell, ufl.TensorProductCell): - raise ValueError("Integral type '%s' invalid for cell '%s'" % - (integral_type, cell.cellname())) - if cell.cellname() == "quadrilateral": - assert isinstance(degree, int) - return degree - if not isinstance(cell, ufl.TensorProductCell): - raise ValueError("Integral type '%s' invalid for cell '%s'" % - (integral_type, cell.cellname())) - # Fix degree on TensorProductCell when not tuple - if degree == 0: - degree = (0, 0) - if integral_type in ("exterior_facet_top", "exterior_facet_bottom", - "interior_facet_horiz"): - return degree[0] - if integral_type in ("exterior_facet_vert", "interior_facet_vert"): - if cell.topological_dimension() == 2: - return degree[1] - return degree - raise ValueError("Invalid cell, integral_type combination") - - -def create_quadrature(cell, integral_type, degree, scheme="default"): - """Create a quadrature rule. - - :arg cell: The UFL cell. - :arg integral_type: The integral being performed. - :arg degree: The degree of polynomial that should be integrated - exactly by the rule. - :kwarg scheme: optional scheme to use (either ``"default"``, or - ``"canonical"``). These correspond to - :func:`default_scheme` and :func:`fiat_scheme` respectively. - - .. note :: - - If the cell is a tensor product cell, the degree should be a - tuple, indicating the degree in each direction of the tensor - product. - """ - # Pick out correct part of degree for non-cell integrals. - degree = select_degree(degree, cell, integral_type) - # Pick cell to be integrated over for non-cell integrals. - cell = integration_cell(cell, integral_type) - return create_quadrature_rule(cell, degree, scheme=scheme) From a2092bfbaef8325eb1f385875085111d4c6148d8 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 25 Jul 2016 11:03:17 +0100 Subject: [PATCH 138/809] lower integral_type for quadrature rule creation --- tests/test_create_quadrature.py | 13 ---- tsfc/driver.py | 38 +++++++++++- tsfc/fem.py | 22 ++++--- tsfc/fiatinterface.py | 27 +++++++- tsfc/quadrature.py | 106 -------------------------------- 5 files changed, 77 insertions(+), 129 deletions(-) delete mode 100644 tests/test_create_quadrature.py delete mode 100644 tsfc/quadrature.py diff --git a/tests/test_create_quadrature.py b/tests/test_create_quadrature.py deleted file mode 100644 index 4ab09f95a1..0000000000 --- a/tests/test_create_quadrature.py +++ /dev/null @@ -1,13 +0,0 @@ -from tsfc import quadrature as q -import pytest - - -def test_invalid_quadrature_rule(): - with pytest.raises(ValueError): - q.QuadratureRule([[0.5, 0.5]], [0.5, 0.5, 0.5]) - - -if __name__ == "__main__": - import os - import sys - pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) diff --git a/tsfc/driver.py b/tsfc/driver.py index 48fd1c45e1..218a76fab1 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -14,9 +14,9 @@ from tsfc import fem, ufl_utils from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters +from tsfc.fiatinterface import QuadratureRule, as_fiat_cell, create_quadrature from tsfc.kernel_interface import KernelBuilder, needs_cell_orientations from tsfc.logging import logger -from tsfc.quadrature import create_quadrature, QuadratureRule def compile_form(form, prefix="form", parameters=None): @@ -80,6 +80,9 @@ def compile_integral(integral_data, form_data, prefix, parameters): cell = integral_data.domain.ufl_cell() arguments = form_data.preprocessed_form.arguments() + fiat_cell = as_fiat_cell(cell) + integration_dim, entity_ids = lower_integral_type(fiat_cell, integral_type) + argument_indices = tuple(gem.Index(name=name) for arg, name in zip(arguments, ['j', 'k'])) quadrature_indices = [] @@ -164,6 +167,7 @@ def facetarea(): quadrature_index = gem.Index(name='q') ir = fem.compile_ufl(integrand, integral_type=integral_type, + integration_dim=integration_dim, cell=cell, quadrature_degree=quadrature_degree, point_index=quadrature_index, @@ -188,8 +192,9 @@ def facetarea(): # the estimated polynomial degree attached by compute_form_data quadrature_degree = params.get("quadrature_degree", params["estimated_polynomial_degree"]) + integration_cell = fiat_cell.construct_subelement(integration_dim) quad_rule = params.get("quadrature_rule", - create_quadrature(cell, integral_type, + create_quadrature(integration_cell, quadrature_degree)) if not isinstance(quad_rule, QuadratureRule): @@ -203,6 +208,8 @@ def facetarea(): ir = fem.compile_ufl(integrand, integral_type=integral_type, cell=cell, + integration_dim=integration_dim, + entity_ids=entity_ids, quadrature_rule=quad_rule, point_index=quadrature_index, argument_indices=argument_indices, @@ -243,3 +250,30 @@ def facetarea(): kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) return builder.construct_kernel(kernel_name, body) + + +def lower_integral_type(fiat_cell, integral_type): + dim = fiat_cell.get_dimension() + if integral_type == 'cell': + integration_dim = dim + elif integral_type in ['exterior_facet', 'interior_facet']: + integration_dim = dim - 1 + else: + basedim, extrdim = dim + assert extrdim == 1 + + if integral_type in ['exterior_facet_vert', 'interior_facet_vert']: + integration_dim = (basedim - 1, 1) + elif integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: + integration_dim = (basedim, 0) + else: + raise NotImplementedError("integral type %s not supported" % integral_type) + + if integral_type == 'exterior_facet_bottom': + entity_ids = [0] + elif integral_type == 'exterior_facet_top': + entity_ids = [1] + else: + entity_ids = range(len(fiat_cell.get_topology()[integration_dim])) + + return integration_dim, entity_ids diff --git a/tsfc/fem.py b/tsfc/fem.py index a18b925e75..d4bb30b389 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -15,9 +15,8 @@ import gem from tsfc.constants import PRECISION -from tsfc.fiatinterface import create_element, as_fiat_cell +from tsfc.fiatinterface import create_element, create_quadrature, as_fiat_cell from tsfc.modified_terminals import analyse_modified_terminal -from tsfc.quadrature import create_quadrature from tsfc import compat from tsfc import ufl2gem from tsfc import geometric @@ -215,6 +214,8 @@ def __get__(self, obj, cls): class Parameters(object): keywords = ('integral_type', 'cell', + 'integration_dim', + 'entity_ids', 'quadrature_degree', 'quadrature_rule', 'points', @@ -235,19 +236,26 @@ def __init__(self, **kwargs): # Defaults integral_type = 'cell' + @cached_property + def integration_dim(self): + fiat_cell = as_fiat_cell(self.cell) + return fiat_cell.get_dimension() + + entity_ids = [None] + @cached_property def quadrature_rule(self): - return create_quadrature(self.cell, - self.integral_type, - self.quadrature_degree) + fiat_cell = as_fiat_cell(self.cell) + integration_cell = fiat_cell.construct_subelement(self.integration_dim) + return create_quadrature(integration_cell, self.quadrature_degree) @cached_property def points(self): - return self.quadrature_rule.points + return self.quadrature_rule.get_points() @cached_property def weights(self): - return self.quadrature_rule.weights + return self.quadrature_rule.get_weights() argument_indices = () diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index d2b009ee7c..5928b827ae 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -31,6 +31,7 @@ import FIAT from FIAT.reference_element import FiredrakeQuadrilateral from FIAT.dual_set import DualSet +from FIAT.quadrature import QuadratureRule # noqa import ufl from ufl.algorithms.elementtransformations import reconstruct_element @@ -38,7 +39,7 @@ from .mixedelement import MixedElement -__all__ = ("create_element", "supported_elements", "as_fiat_cell") +__all__ = ("create_element", "create_quadrature", "supported_elements", "as_fiat_cell") supported_elements = { @@ -84,6 +85,30 @@ def as_fiat_cell(cell): return FIAT.ufc_cell(cell) +def create_quadrature(cell, degree, scheme="default"): + """Create a quadrature rule. + + :arg cell: The FIAT cell. + :arg degree: The degree of polynomial that should be integrated + exactly by the rule. + :kwarg scheme: optional scheme to use (either ``"default"``, or + ``"canonical"``). + + .. note :: + + If the cell is a tensor product cell, the degree should be a + tuple, indicating the degree in each direction of the tensor + product. + """ + if scheme not in ("default", "canonical"): + raise ValueError("Unknown quadrature scheme '%s'" % scheme) + + rule = FIAT.create_quadrature(cell, degree, scheme) + if len(rule.get_points()) > 900: + raise RuntimeError("Requested a quadrature rule with %d points" % len(rule.get_points())) + return rule + + @singledispatch def convert(element, vector_is_mixed): """Handler for converting UFL elements to FIAT elements. diff --git a/tsfc/quadrature.py b/tsfc/quadrature.py deleted file mode 100644 index 1367cdedd5..0000000000 --- a/tsfc/quadrature.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file was modified from FFC -# (http://bitbucket.org/fenics-project/ffc), copyright notice -# reproduced below. -# -# Copyright (C) 2011 Garth N. Wells -# -# This file is part of FFC. -# -# FFC is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# FFC is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with FFC. If not, see . - -from __future__ import absolute_import -from __future__ import print_function -import numpy - -import FIAT -import ufl - -from tsfc.fiatinterface import as_fiat_cell - - -__all__ = ("create_quadrature", "QuadratureRule") - - -class QuadratureRule(object): - __slots__ = ("points", "weights") - - def __init__(self, points, weights): - """A representation of a quadrature rule. - - :arg points: The quadrature points. - :arg weights: The quadrature point weights.""" - points = numpy.asarray(points, dtype=numpy.float64) - weights = numpy.asarray(weights, dtype=numpy.float64) - if weights.shape != points.shape[:1]: - raise ValueError("Have %d weights, but %d points" % (weights.shape[0], - points.shape[0])) - self.points = points - self.weights = weights - - -def entity_dimension(cell, integral_type): - # TODO TODO TODO - dim = cell.get_spatial_dimension() - if integral_type == 'cell': - if isinstance(cell, FIAT.reference_element.TensorProductCell): - return tuple(c.get_spatial_dimension() for c in cell.cells) - return dim - elif integral_type in ['exterior_facet', 'interior_facet']: - return dim - 1 - elif integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: - return (dim - 1, 0) - elif integral_type in ['exterior_facet_vert', 'interior_facet_vert']: - return (dim - 2, 1) - else: - raise NotImplementedError("integral type %s not supported" % integral_type) - - -def create_quadrature(cell, integral_type, degree, scheme="default"): - """Create a quadrature rule. - - :arg cell: The UFL cell. - :arg integral_type: The integral being performed. - :arg degree: The degree of polynomial that should be integrated - exactly by the rule. - :kwarg scheme: optional scheme to use (either ``"default"``, or - ``"canonical"``). These correspond to - :func:`default_scheme` and :func:`fiat_scheme` respectively. - - .. note :: - - If the cell is a tensor product cell, the degree should be a - tuple, indicating the degree in each direction of the tensor - product. - """ - if scheme not in ("default", "canonical"): - raise ValueError("Unknown quadrature scheme '%s'" % scheme) - - try: - degree = tuple(degree) - if not isinstance(cell, ufl.TensorProductCell): - raise ValueError("Not expecting tuple of degrees") - except TypeError: - if isinstance(cell, ufl.TensorProductCell): - # We got a single degree, assume we meant that degree in - # each direction. - degree = (degree, degree) - - cell = as_fiat_cell(cell) - cell = cell.construct_subelement(entity_dimension(cell, integral_type)) - fiat_rule = FIAT.create_quadrature(cell, degree, scheme) - if len(fiat_rule.get_points()) > 900: - raise RuntimeError("Requested a quadrature rule with %d points" % len(fiat_rule.get_points())) - return QuadratureRule(fiat_rule.get_points(), fiat_rule.get_weights()) From b4ef60f4c2b4dc8a2becd644064f91ffe0ece513 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 25 Jul 2016 15:16:02 +0100 Subject: [PATCH 139/809] add a docstring --- tsfc/driver.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 218a76fab1..7f31b17e8d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -253,12 +253,19 @@ def facetarea(): def lower_integral_type(fiat_cell, integral_type): + """Lower integral type into the dimension of the integration + subentity and a list of entity numbers for that dimension. + + :arg fiat_cell: FIAT reference cell + :arg integral_type: integral type (string) + """ dim = fiat_cell.get_dimension() if integral_type == 'cell': integration_dim = dim elif integral_type in ['exterior_facet', 'interior_facet']: integration_dim = dim - 1 else: + # Extrusion case basedim, extrdim = dim assert extrdim == 1 From 783d924e6d8530527d4e46932abc7ceb6b382ce2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 8 Aug 2016 22:24:29 +0100 Subject: [PATCH 140/809] adopt n-ary ufl.TensorProductElement --- tsfc/fiatinterface.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 5928b827ae..75213e2568 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -192,14 +192,13 @@ def _(element, vector_is_mixed): vector_is_mixed)) -# Now for the OPE-specific stuff +# Now for the TPE-specific stuff @convert.register(ufl.TensorProductElement) # noqa def _(element, vector_is_mixed): cell = element.cell() if type(cell) is not ufl.TensorProductCell: - raise ValueError("OPE not on OPC?") - A = element._A - B = element._B + raise ValueError("TPE not on TPC?") + A, B = element.sub_elements() return FIAT.TensorProductElement(create_element(A, vector_is_mixed), create_element(B, vector_is_mixed)) @@ -227,7 +226,7 @@ def _(element, vector_is_mixed): def rec(eles): for ele in eles: - if ele.num_sub_elements() > 0: + if isinstance(ele, ufl.MixedElement): rec(ele.sub_elements()) else: elements.append(ele) From 02c5688f2c42a0c9305b5b8999178886cc07858e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 26 Jul 2016 14:18:15 +0100 Subject: [PATCH 141/809] WIP: less integral_type in geometric.py Still a bit broken. --- tsfc/driver.py | 1 + tsfc/fem.py | 23 ++++++++---- tsfc/geometric.py | 89 +++++++++++++---------------------------------- 3 files changed, 43 insertions(+), 70 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7f31b17e8d..be650f740e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -168,6 +168,7 @@ def facetarea(): ir = fem.compile_ufl(integrand, integral_type=integral_type, integration_dim=integration_dim, + entity_ids=entity_ids, cell=cell, quadrature_degree=quadrature_degree, point_index=quadrature_index, diff --git a/tsfc/fem.py b/tsfc/fem.py index d4bb30b389..3b7ac189c4 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -144,7 +144,7 @@ def __init__(self, integral_type, ufl_cell): elif integral_type == 'exterior_facet_bottom': self.facet = {None: 0} elif integral_type == 'exterior_facet_top': - self.facet = {None: 1} + self.facet = {None: 0} elif integral_type == 'interior_facet_horiz': self.facet = {'+': 1, '-': 0} else: @@ -166,7 +166,15 @@ def facet_transform(self, points): t = as_fiat_cell(self.ufl_cell).get_entity_transform(dim-1, entity) yield numpy.asarray(map(t, points)) - elif self.integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: + elif self.integral_type == 'exterior_facet_bottom': + t = as_fiat_cell(self.ufl_cell).get_entity_transform((dim-1, 0), 0) + yield numpy.asarray(map(t, points)) + + elif self.integral_type == 'exterior_facet_top': + t = as_fiat_cell(self.ufl_cell).get_entity_transform((dim-1, 0), 1) + yield numpy.asarray(map(t, points)) + + elif self.integral_type == 'interior_facet_horiz': for entity in range(2): # top and bottom t = as_fiat_cell(self.ufl_cell).get_entity_transform((dim-1, 0), entity) yield numpy.asarray(map(t, points)) @@ -214,6 +222,7 @@ def __get__(self, obj, cls): class Parameters(object): keywords = ('integral_type', 'cell', + 'fiat_cell', 'integration_dim', 'entity_ids', 'quadrature_degree', @@ -236,17 +245,19 @@ def __init__(self, **kwargs): # Defaults integral_type = 'cell' + @cached_property + def fiat_cell(self): + return as_fiat_cell(self.cell) + @cached_property def integration_dim(self): - fiat_cell = as_fiat_cell(self.cell) - return fiat_cell.get_dimension() + return self.fiat_cell.get_dimension() entity_ids = [None] @cached_property def quadrature_rule(self): - fiat_cell = as_fiat_cell(self.cell) - integration_cell = fiat_cell.construct_subelement(self.integration_dim) + integration_cell = self.fiat_cell.construct_subelement(self.integration_dim) return create_quadrature(integration_cell, self.quadrature_degree) @cached_property diff --git a/tsfc/geometric.py b/tsfc/geometric.py index df47a5d6d1..c807417970 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -5,6 +5,7 @@ from numpy import array, nan, vstack from singledispatch import singledispatch +import sympy from ufl import interval, triangle, quadrilateral, tetrahedron from ufl import TensorProductCell @@ -24,63 +25,6 @@ quadrilateral_x_interval = TensorProductCell(quadrilateral, interval) -# Volume of the reference cells -reference_cell_volume = { - interval: 1.0, - triangle: 1.0/2.0, - quadrilateral: 1.0, - tetrahedron: 1.0/6.0, - interval_x_interval: 1.0, - triangle_x_interval: 1.0/2.0, - quadrilateral_x_interval: 1.0, -} - - -# Volume of the reference cells of facets -reference_facet_volume = { - interval: 1.0, - triangle: 1.0, - tetrahedron: 1.0/2.0, -} - - -# Jacobian of the mapping from a facet to the cell on the reference cell -cell_facet_jacobian = { - interval: array([[1.0], - [1.0]], dtype=NUMPY_TYPE), - triangle: array([[-1.0, 1.0], - [0.0, 1.0], - [1.0, 0.0]], dtype=NUMPY_TYPE), - quadrilateral: array([[0.0, 1.0], - [0.0, 1.0], - [1.0, 0.0], - [1.0, 0.0]], dtype=NUMPY_TYPE), - tetrahedron: array([[-1.0, -1.0, 1.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]], dtype=NUMPY_TYPE), - # Product cells. Convention: - # Bottom, top, then vertical facets in the order of the base cell - interval_x_interval: array([[1.0, 0.0], - [1.0, 0.0], - [0.0, 1.0], - [0.0, 1.0]], dtype=NUMPY_TYPE), - triangle_x_interval: array([[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [-1.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0]], - dtype=NUMPY_TYPE), - quadrilateral_x_interval: array([[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0]], - dtype=NUMPY_TYPE), -} - - # Facet normals of the reference cells reference_normal = { interval: array([[-1.0], @@ -125,7 +69,11 @@ def reference_cell(terminal): def strip_table(table, integral_type): """Select horizontal or vertical parts for facet integrals on extruded cells. No-op in all other cases.""" - if integral_type in ["exterior_facet_bottom", "exterior_facet_top", "interior_facet_horiz"]: + if integral_type == "exterior_facet_bottom": + return table[0:1] + elif integral_type == "exterior_facet_top": + return table[1:2] + elif integral_type == "interior_facet_horiz": # Bottom and top return table[:2] elif integral_type in ["exterior_facet_vert", "interior_facet_vert"]: @@ -160,20 +108,33 @@ def translate_cell_orientation(terminal, mt, params): @translate.register(ReferenceCellVolume) def translate_reference_cell_volume(terminal, mt, params): - return gem.Literal(reference_cell_volume[reference_cell(terminal)]) + return gem.Literal(params.fiat_cell.volume()) @translate.register(ReferenceFacetVolume) def translate_reference_facet_volume(terminal, mt, params): - return gem.Literal(reference_facet_volume[reference_cell(terminal)]) + # FIXME: simplex only code path + dim = params.fiat_cell.get_spatial_dimension() + facet_cell = params.fiat_cell.construct_subelement(dim - 1) + return gem.Literal(facet_cell.volume()) @translate.register(CellFacetJacobian) def translate_cell_facet_jacobian(terminal, mt, params): - table = cell_facet_jacobian[reference_cell(terminal)] - table = strip_table(table, params.integral_type) - table = table.reshape(table.shape[:1] + terminal.ufl_shape) - return params.select_facet(gem.Literal(table), mt.restriction) + assert params.integration_dim != params.fiat_cell.get_dimension() + dim = params.fiat_cell.construct_subelement(params.integration_dim).get_spatial_dimension() + X = sympy.DeferredVector('X') + point = [X[j] for j in range(dim)] + result = [] + for entity_id in params.entity_ids: + f = params.fiat_cell.get_entity_transform(params.integration_dim, entity_id) + y = f(point) + J = [[sympy.diff(y_i, X[j]) + for j in range(dim)] + for y_i in y] + J = array(J, dtype=float) + result.append(J) + return params.select_facet(gem.Literal(result), mt.restriction) @translate.register(ReferenceNormal) From 89c389fd1ffb96f69512a75b91f25ccaebba5b1f Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 27 Jul 2016 17:18:41 +0100 Subject: [PATCH 142/809] almost no integral_type in geometric.py Works. --- tsfc/geometric.py | 77 ++++------------------------------------------- 1 file changed, 6 insertions(+), 71 deletions(-) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index c807417970..fc7002b552 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -7,8 +7,6 @@ from singledispatch import singledispatch import sympy -from ufl import interval, triangle, quadrilateral, tetrahedron -from ufl import TensorProductCell from ufl.classes import (CellCoordinate, CellEdgeVectors, CellFacetJacobian, CellOrientation, FacetCoordinate, ReferenceCellVolume, @@ -17,70 +15,6 @@ import gem from tsfc.constants import NUMPY_TYPE -from tsfc.fiatinterface import as_fiat_cell - - -interval_x_interval = TensorProductCell(interval, interval) -triangle_x_interval = TensorProductCell(triangle, interval) -quadrilateral_x_interval = TensorProductCell(quadrilateral, interval) - - -# Facet normals of the reference cells -reference_normal = { - interval: array([[-1.0], - [1.0]], dtype=NUMPY_TYPE), - triangle: array([[1.0, 1.0], - [-1.0, 0.0], - [0.0, -1.0]], dtype=NUMPY_TYPE), - quadrilateral: array([[-1.0, 0.0], - [1.0, 0.0], - [0.0, -1.0], - [0.0, 1.0]], dtype=NUMPY_TYPE), - tetrahedron: array([[1.0, 1.0, 1.0], - [-1.0, 0.0, 0.0], - [0.0, -1.0, 0.0], - [0.0, 0.0, -1.0]], dtype=NUMPY_TYPE), - # Product cells. Convention: - # Bottom, top, then vertical facets in the order of the base cell - interval_x_interval: array([[0.0, -1.0], - [0.0, 1.0], - [-1.0, 0.0], - [1.0, 0.0]], dtype=NUMPY_TYPE), - triangle_x_interval: array([[0.0, 0.0, -1.0], - [0.0, 0.0, 1.0], - [1.0, 1.0, 0.0], - [-1.0, 0.0, 0.0], - [0.0, -1.0, 0.0]], dtype=NUMPY_TYPE), - quadrilateral_x_interval: array([[0.0, 0.0, -1.0], - [0.0, 0.0, 1.0], - [-1.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.0, -1.0, 0.0], - [0.0, 1.0, 0.0]], dtype=NUMPY_TYPE), -} - - -def reference_cell(terminal): - """Reference cell of terminal""" - cell = terminal.ufl_domain().ufl_cell() - return cell.reconstruct(geometric_dimension=cell.topological_dimension()) - - -def strip_table(table, integral_type): - """Select horizontal or vertical parts for facet integrals on - extruded cells. No-op in all other cases.""" - if integral_type == "exterior_facet_bottom": - return table[0:1] - elif integral_type == "exterior_facet_top": - return table[1:2] - elif integral_type == "interior_facet_horiz": - # Bottom and top - return table[:2] - elif integral_type in ["exterior_facet_vert", "interior_facet_vert"]: - # Vertical facets in the order of the base cell - return table[2:] - else: - return table @singledispatch @@ -139,16 +73,17 @@ def translate_cell_facet_jacobian(terminal, mt, params): @translate.register(ReferenceNormal) def translate_reference_normal(terminal, mt, params): - table = reference_normal[reference_cell(terminal)] - table = strip_table(table, params.integral_type) - table = table.reshape(table.shape[:1] + terminal.ufl_shape) - return params.select_facet(gem.Literal(table), mt.restriction) + result = [] + for facet_i in params.entity_ids: + n = params.fiat_cell.compute_scaled_outward_normal(params.integration_dim, facet_i) + result.append(n) + return params.select_facet(gem.Literal(result), mt.restriction) @translate.register(CellEdgeVectors) def translate_cell_edge_vectors(terminal, mt, params): from FIAT.reference_element import TensorProductCell as fiat_TensorProductCell - fiat_cell = as_fiat_cell(terminal.ufl_domain().ufl_cell()) + fiat_cell = params.fiat_cell if isinstance(fiat_cell, fiat_TensorProductCell): raise NotImplementedError("CellEdgeVectors not implemented on TensorProductElements yet") From 79c8af8bb51eeccd66abbefd62b9964c78ada884 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 28 Jul 2016 09:30:09 +0100 Subject: [PATCH 143/809] simplify FacetManager.facet_transform --- tsfc/fem.py | 47 +++++++++++------------------------------------ 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 3b7ac189c4..1778d3015e 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -81,7 +81,7 @@ class TabulationManager(object): """Manages the generation of tabulation matrices for the different integral types.""" - def __init__(self, integral_type, cell, points): + def __init__(self, integral_type, integration_dim, entity_ids, cell, points): """Constructs a TabulationManager. :arg integral_type: integral type @@ -92,7 +92,7 @@ def __init__(self, integral_type, cell, points): self.integral_type = integral_type self.points = points - self.facet_manager = FacetManager(integral_type, cell) + self.facet_manager = FacetManager(integral_type, integration_dim, entity_ids, as_fiat_cell(cell)) self.tabulators = map(make_tabulator, self.facet_manager.facet_transform(points)) self.tables = {} @@ -127,14 +127,16 @@ def __getitem__(self, key): class FacetManager(object): """Collection of utilities for facet integrals.""" - def __init__(self, integral_type, ufl_cell): + def __init__(self, integral_type, integration_dim, entity_ids, fiat_cell): """Constructs a FacetManager. :arg integral_type: integral type - :arg ufl_cell: UFL cell + :arg fiat_cell: FIAT cell """ self.integral_type = integral_type - self.ufl_cell = ufl_cell + self.integration_dim = integration_dim + self.entity_ids = entity_ids + self.fiat_cell = fiat_cell if integral_type in ['exterior_facet', 'exterior_facet_vert']: self.facet = {None: gem.VariableIndex(gem.Indexed(gem.Variable('facet', (1,)), (0,)))} @@ -156,37 +158,10 @@ def facet_transform(self, points): :arg points: points in integration cell coordinates """ - dim = self.ufl_cell.topological_dimension() - - if self.integral_type == 'cell': - yield points - - elif self.integral_type in ['exterior_facet', 'interior_facet']: - for entity in range(self.ufl_cell.num_facets()): - t = as_fiat_cell(self.ufl_cell).get_entity_transform(dim-1, entity) - yield numpy.asarray(map(t, points)) - - elif self.integral_type == 'exterior_facet_bottom': - t = as_fiat_cell(self.ufl_cell).get_entity_transform((dim-1, 0), 0) - yield numpy.asarray(map(t, points)) - - elif self.integral_type == 'exterior_facet_top': - t = as_fiat_cell(self.ufl_cell).get_entity_transform((dim-1, 0), 1) + for entity_id in self.entity_ids: + t = self.fiat_cell.get_entity_transform(self.integration_dim, entity_id) yield numpy.asarray(map(t, points)) - elif self.integral_type == 'interior_facet_horiz': - for entity in range(2): # top and bottom - t = as_fiat_cell(self.ufl_cell).get_entity_transform((dim-1, 0), entity) - yield numpy.asarray(map(t, points)) - - elif self.integral_type in ['exterior_facet_vert', 'interior_facet_vert']: - for entity in range(self.ufl_cell.sub_cells()[0].num_facets()): # "base cell" facets - t = as_fiat_cell(self.ufl_cell).get_entity_transform((dim-2, 1), entity) - yield numpy.asarray(map(t, points)) - - else: - raise NotImplementedError("integral type %s not supported" % self.integral_type) - def select_facet(self, tensor, restriction): """Applies facet selection on a GEM tensor if necessary. @@ -253,7 +228,7 @@ def fiat_cell(self): def integration_dim(self): return self.fiat_cell.get_dimension() - entity_ids = [None] + entity_ids = [0] @cached_property def quadrature_rule(self): @@ -442,7 +417,7 @@ def compile_ufl(expression, **kwargs): max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(params.integral_type, params.cell, params.points) + tabulation_manager = TabulationManager(params.integral_type, params.integration_dim, params.entity_ids, params.cell, params.points) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) From bdf019e3cbd832571684aff9f5aee7f6143f8cfd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 28 Jul 2016 11:00:08 +0100 Subject: [PATCH 144/809] dismantle FacetManager --- tsfc/fem.py | 98 +++++++++++++++++++---------------------------- tsfc/geometric.py | 7 +--- 2 files changed, 41 insertions(+), 64 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 1778d3015e..b67114eb1b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -81,7 +81,7 @@ class TabulationManager(object): """Manages the generation of tabulation matrices for the different integral types.""" - def __init__(self, integral_type, integration_dim, entity_ids, cell, points): + def __init__(self, integral_type, entity_points): """Constructs a TabulationManager. :arg integral_type: integral type @@ -90,10 +90,8 @@ def __init__(self, integral_type, integration_dim, entity_ids, cell, points): an interval for facet integrals on a triangle) """ self.integral_type = integral_type - self.points = points - self.facet_manager = FacetManager(integral_type, integration_dim, entity_ids, as_fiat_cell(cell)) - self.tabulators = map(make_tabulator, self.facet_manager.facet_transform(points)) + self.tabulators = map(make_tabulator, entity_points) self.tables = {} def tabulate(self, ufl_element, max_deriv): @@ -124,58 +122,6 @@ def __getitem__(self, key): return self.tables[key] -class FacetManager(object): - """Collection of utilities for facet integrals.""" - - def __init__(self, integral_type, integration_dim, entity_ids, fiat_cell): - """Constructs a FacetManager. - - :arg integral_type: integral type - :arg fiat_cell: FIAT cell - """ - self.integral_type = integral_type - self.integration_dim = integration_dim - self.entity_ids = entity_ids - self.fiat_cell = fiat_cell - - if integral_type in ['exterior_facet', 'exterior_facet_vert']: - self.facet = {None: gem.VariableIndex(gem.Indexed(gem.Variable('facet', (1,)), (0,)))} - elif integral_type in ['interior_facet', 'interior_facet_vert']: - self.facet = {'+': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (0,))), - '-': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (1,)))} - elif integral_type == 'exterior_facet_bottom': - self.facet = {None: 0} - elif integral_type == 'exterior_facet_top': - self.facet = {None: 0} - elif integral_type == 'interior_facet_horiz': - self.facet = {'+': 1, '-': 0} - else: - self.facet = None - - def facet_transform(self, points): - """Generator function that transforms points in integration cell - coordinates to cell coordinates for each facet. - - :arg points: points in integration cell coordinates - """ - for entity_id in self.entity_ids: - t = self.fiat_cell.get_entity_transform(self.integration_dim, entity_id) - yield numpy.asarray(map(t, points)) - - def select_facet(self, tensor, restriction): - """Applies facet selection on a GEM tensor if necessary. - - :arg tensor: GEM tensor - :arg restriction: restriction on the modified terminal - :returns: another GEM tensor - """ - if self.integral_type == 'cell': - return tensor - else: - f = self.facet[restriction] - return gem.partial_indexed(tensor, (f,)) - - # FIXME: copy-paste from PyOP2 class cached_property(object): """A read-only @property that is only evaluated once. The value is cached @@ -243,6 +189,42 @@ def points(self): def weights(self): return self.quadrature_rule.get_weights() + @cached_property + def entity_points(self): + """Points in cell coordinates for each entity.""" + result = [] + for entity_id in self.entity_ids: + t = self.fiat_cell.get_entity_transform(self.integration_dim, entity_id) + result.append(numpy.asarray(map(t, self.points))) + return result + + def select_facet(self, tensor, restriction): + """Applies facet selection on a GEM tensor if necessary. + + :arg tensor: GEM tensor + :arg restriction: restriction on the modified terminal + :returns: another GEM tensor + """ + if self.integral_type in ['exterior_facet', 'exterior_facet_vert']: + facet = {None: gem.VariableIndex(gem.Indexed(gem.Variable('facet', (1,)), (0,)))} + elif self.integral_type in ['interior_facet', 'interior_facet_vert']: + facet = {'+': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (0,))), + '-': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (1,)))} + elif self.integral_type == 'exterior_facet_bottom': + facet = {None: 0} + elif self.integral_type == 'exterior_facet_top': + facet = {None: 0} + elif self.integral_type == 'interior_facet_horiz': + facet = {'+': 1, '-': 0} + else: + facet = None + + if self.integral_type == 'cell': + return tensor + else: + f = facet[restriction] + return gem.partial_indexed(tensor, (f,)) + argument_indices = () @cached_property @@ -263,8 +245,6 @@ def __init__(self, tabulation_manager, parameters): parameters.cell_orientations = gem.Variable("cell_orientations", (1, 1)) parameters.tabulation_manager = tabulation_manager - parameters.facet_manager = tabulation_manager.facet_manager - parameters.select_facet = tabulation_manager.facet_manager.select_facet self.parameters = parameters def modified_terminal(self, o): @@ -417,7 +397,7 @@ def compile_ufl(expression, **kwargs): max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(params.integral_type, params.integration_dim, params.entity_ids, params.cell, params.points) + tabulation_manager = TabulationManager(params.integral_type, params.entity_points) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index fc7002b552..24c1358bf8 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -95,10 +95,7 @@ def translate_cell_edge_vectors(terminal, mt, params): @translate.register(CellCoordinate) def translate_cell_coordinate(terminal, mt, params): - points = params.tabulation_manager.points - if params.integral_type != 'cell': - points = list(params.facet_manager.facet_transform(points)) - return gem.partial_indexed(params.select_facet(gem.Literal(points), + return gem.partial_indexed(params.select_facet(gem.Literal(params.entity_points), mt.restriction), (params.point_index,)) @@ -106,5 +103,5 @@ def translate_cell_coordinate(terminal, mt, params): @translate.register(FacetCoordinate) def translate_facet_coordinate(terminal, mt, params): assert params.integral_type != 'cell' - points = params.tabulation_manager.points + points = params.points return gem.partial_indexed(gem.Literal(points), (params.point_index,)) From f65673b25b00daec5e7ca11386571861c866a8f7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 28 Jul 2016 14:22:53 +0100 Subject: [PATCH 145/809] simplified entity selection --- tsfc/fem.py | 62 ++++++++++++++++++++--------------------------- tsfc/geometric.py | 20 +++++++-------- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index b67114eb1b..442d7e333e 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -81,16 +81,12 @@ class TabulationManager(object): """Manages the generation of tabulation matrices for the different integral types.""" - def __init__(self, integral_type, entity_points): + def __init__(self, entity_points): """Constructs a TabulationManager. - :arg integral_type: integral type - :arg cell: UFL cell :arg points: points on the integration entity (e.g. points on an interval for facet integrals on a triangle) """ - self.integral_type = integral_type - self.tabulators = map(make_tabulator, entity_points) self.tables = {} @@ -106,17 +102,13 @@ def tabulate(self, ufl_element, max_deriv): for c, D, table in tabulator(ufl_element, max_deriv): store[(ufl_element, c, D)].append(table) - if self.integral_type == 'cell': - for key, (table,) in store.iteritems(): - self.tables[key] = table - else: - for key, tables in store.iteritems(): - table = numpy.array(tables) - if len(table.shape) == 2: - # Cellwise constant; must not depend on the facet - assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) - table = table[0] - self.tables[key] = table + for key, tables in store.iteritems(): + table = numpy.array(tables) + if len(table.shape) == 2: + # Cellwise constant; must not depend on the facet + assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) + table = table[0] + self.tables[key] = table def __getitem__(self, key): return self.tables[key] @@ -198,32 +190,30 @@ def entity_points(self): result.append(numpy.asarray(map(t, self.points))) return result - def select_facet(self, tensor, restriction): - """Applies facet selection on a GEM tensor if necessary. - - :arg tensor: GEM tensor - :arg restriction: restriction on the modified terminal - :returns: another GEM tensor - """ + def facet_number(self, restriction): + # TODO: move to kernel interface if self.integral_type in ['exterior_facet', 'exterior_facet_vert']: facet = {None: gem.VariableIndex(gem.Indexed(gem.Variable('facet', (1,)), (0,)))} elif self.integral_type in ['interior_facet', 'interior_facet_vert']: facet = {'+': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (0,))), '-': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (1,)))} - elif self.integral_type == 'exterior_facet_bottom': - facet = {None: 0} - elif self.integral_type == 'exterior_facet_top': - facet = {None: 0} elif self.integral_type == 'interior_facet_horiz': facet = {'+': 1, '-': 0} - else: - facet = None - if self.integral_type == 'cell': - return tensor + return facet[restriction] + + def _selector(self, callback, opts, restriction): + if len(opts) == 1: + return callback(opts[0]) else: - f = facet[restriction] - return gem.partial_indexed(tensor, (f,)) + results = gem.ListTensor(map(callback, opts)) + return gem.partial_indexed(results, (self.facet_number(restriction),)) + + def entity_selector(self, callback, restriction): + return self._selector(callback, self.entity_ids, restriction) + + def index_selector(self, callback, restriction): + return self._selector(callback, range(len(self.entity_ids)), restriction) argument_indices = () @@ -332,7 +322,7 @@ def callback(key): # Cellwise constant row = gem.Literal(table) else: - table = params.select_facet(gem.Literal(table), mt.restriction) + table = params.index_selector(lambda i: gem.Literal(table[i]), mt.restriction) row = gem.partial_indexed(table, (params.point_index,)) return gem.Indexed(row, (argument_index,)) @@ -361,7 +351,7 @@ def callback(key): for i in range(row.shape[0])], gem.Zero()) else: - table = params.select_facet(gem.Literal(table), mt.restriction) + table = params.index_selector(lambda i: gem.Literal(table[i]), mt.restriction) row = gem.partial_indexed(table, (params.point_index,)) r = params.index_cache[terminal.ufl_element()] @@ -397,7 +387,7 @@ def compile_ufl(expression, **kwargs): max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(params.integral_type, params.entity_points) + tabulation_manager = TabulationManager(params.entity_points) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 24c1358bf8..bd91248fce 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -59,25 +59,23 @@ def translate_cell_facet_jacobian(terminal, mt, params): dim = params.fiat_cell.construct_subelement(params.integration_dim).get_spatial_dimension() X = sympy.DeferredVector('X') point = [X[j] for j in range(dim)] - result = [] - for entity_id in params.entity_ids: + + def callback(entity_id): f = params.fiat_cell.get_entity_transform(params.integration_dim, entity_id) y = f(point) J = [[sympy.diff(y_i, X[j]) for j in range(dim)] for y_i in y] - J = array(J, dtype=float) - result.append(J) - return params.select_facet(gem.Literal(result), mt.restriction) + return gem.Literal(array(J, dtype=float)) + return params.entity_selector(callback, mt.restriction) @translate.register(ReferenceNormal) def translate_reference_normal(terminal, mt, params): - result = [] - for facet_i in params.entity_ids: + def callback(facet_i): n = params.fiat_cell.compute_scaled_outward_normal(params.integration_dim, facet_i) - result.append(n) - return params.select_facet(gem.Literal(result), mt.restriction) + return gem.Literal(n) + return params.entity_selector(callback, mt.restriction) @translate.register(CellEdgeVectors) @@ -95,8 +93,8 @@ def translate_cell_edge_vectors(terminal, mt, params): @translate.register(CellCoordinate) def translate_cell_coordinate(terminal, mt, params): - return gem.partial_indexed(params.select_facet(gem.Literal(params.entity_points), - mt.restriction), + return gem.partial_indexed(params.index_selector(lambda i: gem.Literal(params.entity_points[i]), + mt.restriction), (params.point_index,)) From ec325e5eff8bb8c7dd3f0776b670bb26fec4b0c6 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 29 Jul 2016 12:49:18 +0100 Subject: [PATCH 146/809] FIAT: compute_reference_normal --- tsfc/geometric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index bd91248fce..142c8dbbb3 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -73,7 +73,7 @@ def callback(entity_id): @translate.register(ReferenceNormal) def translate_reference_normal(terminal, mt, params): def callback(facet_i): - n = params.fiat_cell.compute_scaled_outward_normal(params.integration_dim, facet_i) + n = params.fiat_cell.compute_reference_normal(params.integration_dim, facet_i) return gem.Literal(n) return params.entity_selector(callback, mt.restriction) From 8e9fcaae4a5f61a1328129cf1ee113910ded2ffd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 29 Jul 2016 15:33:18 +0100 Subject: [PATCH 147/809] purge dispatching on integral_type --- tsfc/driver.py | 35 +++++++++++------ tsfc/fem.py | 44 ++++++--------------- tsfc/geometric.py | 13 ++----- tsfc/kernel_interface.py | 83 +++++++++++++++++++++++++++++++++------- 4 files changed, 107 insertions(+), 68 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index be650f740e..01239d68dd 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -15,7 +15,7 @@ from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.fiatinterface import QuadratureRule, as_fiat_cell, create_quadrature -from tsfc.kernel_interface import KernelBuilder, needs_cell_orientations +from tsfc.kernel_interface import KernelInterface, KernelBuilder, needs_cell_orientations from tsfc.logging import logger @@ -76,6 +76,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): del parameters["quadrature_rule"] integral_type = integral_data.integral_type + interior_facet = integral_type.startswith("interior_facet") mesh = integral_data.domain cell = integral_data.domain.ufl_cell() arguments = form_data.preprocessed_form.arguments() @@ -126,17 +127,30 @@ def cellvolume(restriction): integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='q') - if integral_type.startswith("interior_facet"): - def coefficient_mapper(coefficient): - return gem.partial_indexed(builder.coefficient_mapper(coefficient), ({'+': 0, '-': 1}[restriction],)) + if interior_facet: + class KIT(KernelInterface): + def __init__(self, parent): + self.parent = parent + + def coefficient(self, ufl_coefficient, r): + return gem.partial_indexed(self.parent.coefficient(ufl_coefficient, r), + ({'+': 0, '-': 1}[restriction],)) + + def cell_orientation(self, restriction): + return self.parent.cell_orientation(restriction) + + def facet_number(self, restriction): + return self.parent.facet_number(restriction) + + ki = KIT(builder) else: assert restriction is None - coefficient_mapper = builder.coefficient_mapper + ki = builder ir = fem.compile_ufl(integrand, cell=cell, quadrature_degree=quadrature_degree, point_index=quadrature_index, - coefficient_mapper=coefficient_mapper, + kernel_interface=ki, index_cache=index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) @@ -166,13 +180,12 @@ def facetarea(): integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='q') ir = fem.compile_ufl(integrand, - integral_type=integral_type, + cell=cell, integration_dim=integration_dim, entity_ids=entity_ids, - cell=cell, quadrature_degree=quadrature_degree, point_index=quadrature_index, - coefficient_mapper=builder.coefficient_mapper, + kernel_interface=builder, index_cache=index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) @@ -207,14 +220,14 @@ def facetarea(): quadrature_index = gem.Index(name='ip') quadrature_indices.append(quadrature_index) ir = fem.compile_ufl(integrand, - integral_type=integral_type, + interior_facet=interior_facet, cell=cell, integration_dim=integration_dim, entity_ids=entity_ids, quadrature_rule=quad_rule, point_index=quadrature_index, argument_indices=argument_indices, - coefficient_mapper=builder.coefficient_mapper, + kernel_interface=builder, index_cache=index_cache, cellvolume=cellvolume, facetarea=facetarea) diff --git a/tsfc/fem.py b/tsfc/fem.py index 442d7e333e..f7cb559483 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -133,8 +133,7 @@ def __get__(self, obj, cls): class Parameters(object): - keywords = ('integral_type', - 'cell', + keywords = ('cell', 'fiat_cell', 'integration_dim', 'entity_ids', @@ -144,7 +143,7 @@ class Parameters(object): 'weights', 'point_index', 'argument_indices', - 'coefficient_mapper', + 'kernel_interface', 'cellvolume', 'facetarea', 'index_cache') @@ -155,9 +154,6 @@ def __init__(self, **kwargs): raise ValueError("unexpected keyword argument '{0}'".format(invalid_keywords.pop())) self.__dict__.update(kwargs) - # Defaults - integral_type = 'cell' - @cached_property def fiat_cell(self): return as_fiat_cell(self.cell) @@ -190,24 +186,13 @@ def entity_points(self): result.append(numpy.asarray(map(t, self.points))) return result - def facet_number(self, restriction): - # TODO: move to kernel interface - if self.integral_type in ['exterior_facet', 'exterior_facet_vert']: - facet = {None: gem.VariableIndex(gem.Indexed(gem.Variable('facet', (1,)), (0,)))} - elif self.integral_type in ['interior_facet', 'interior_facet_vert']: - facet = {'+': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (0,))), - '-': gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (1,)))} - elif self.integral_type == 'interior_facet_horiz': - facet = {'+': 1, '-': 0} - - return facet[restriction] - def _selector(self, callback, opts, restriction): if len(opts) == 1: return callback(opts[0]) else: results = gem.ListTensor(map(callback, opts)) - return gem.partial_indexed(results, (self.facet_number(restriction),)) + f = self.kernel_interface.facet_number(restriction) + return gem.partial_indexed(results, (f,)) def entity_selector(self, callback, restriction): return self._selector(callback, self.entity_ids, restriction) @@ -229,11 +214,6 @@ def __init__(self, tabulation_manager, parameters): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) - if parameters.integral_type.startswith("interior_facet"): - parameters.cell_orientations = gem.Variable("cell_orientations", (2, 1)) - else: - parameters.cell_orientations = gem.Variable("cell_orientations", (1, 1)) - parameters.tabulation_manager = tabulation_manager self.parameters = parameters @@ -331,13 +311,11 @@ def callback(key): @translate.register(Coefficient) def translate_coefficient(terminal, mt, params): - kernel_arg = params.coefficient_mapper(terminal) + vec = params.kernel_interface.coefficient(terminal, mt.restriction) if terminal.ufl_element().family() == 'Real': assert mt.local_derivatives == 0 - return kernel_arg - - ka = gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[mt.restriction]) + return vec def callback(key): table = params.tabulation_manager[key] @@ -345,9 +323,9 @@ def callback(key): # Cellwise constant row = gem.Literal(table) if numpy.count_nonzero(table) <= 2: - assert row.shape == ka.shape + assert row.shape == vec.shape return reduce(gem.Sum, - [gem.Product(gem.Indexed(row, (i,)), gem.Indexed(ka, (i,))) + [gem.Product(gem.Indexed(row, (i,)), gem.Indexed(vec, (i,))) for i in range(row.shape[0])], gem.Zero()) else: @@ -356,7 +334,7 @@ def callback(key): r = params.index_cache[terminal.ufl_element()] return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), - gem.Indexed(ka, (r,))), r) + gem.Indexed(vec, (r,))), r) return iterate_shape(mt, callback) @@ -368,7 +346,7 @@ def _translate_constantvalue(terminal, mt, params): return params(terminal) -def compile_ufl(expression, **kwargs): +def compile_ufl(expression, interior_facet=False, **kwargs): params = Parameters(**kwargs) # Abs-simplification @@ -392,7 +370,7 @@ def compile_ufl(expression, **kwargs): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) - if params.integral_type.startswith("interior_facet"): + if interior_facet: expressions = [] for rs in itertools.product(("+", "-"), repeat=len(params.argument_indices)): expressions.append(map_expr_dag(PickRestriction(*rs), expression)) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 142c8dbbb3..95607ec1ca 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -3,7 +3,7 @@ from __future__ import absolute_import -from numpy import array, nan, vstack +from numpy import array, vstack from singledispatch import singledispatch import sympy @@ -30,14 +30,7 @@ def translate(terminal, mt, params): @translate.register(CellOrientation) def translate_cell_orientation(terminal, mt, params): - cell_orientations = params.cell_orientations - f = {None: 0, '+': 0, '-': 1}[mt.restriction] - co_int = gem.Indexed(cell_orientations, (f, 0)) - return gem.Conditional(gem.Comparison("==", co_int, gem.Literal(1)), - gem.Literal(-1), - gem.Conditional(gem.Comparison("==", co_int, gem.Zero()), - gem.Literal(1), - gem.Literal(nan))) + return params.kernel_interface.cell_orientation(mt.restriction) @translate.register(ReferenceCellVolume) @@ -100,6 +93,6 @@ def translate_cell_coordinate(terminal, mt, params): @translate.register(FacetCoordinate) def translate_facet_coordinate(terminal, mt, params): - assert params.integral_type != 'cell' + assert params.integration_dim != params.fiat_cell.get_dimension() points = params.points return gem.partial_indexed(gem.Literal(points), (params.point_index,)) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 409127fd42..aefb46a8e5 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +from abc import ABCMeta, abstractmethod + import numpy import coffee.base as coffee @@ -12,6 +14,25 @@ from tsfc.coffee import SCALAR_TYPE +class KernelInterface(object): + """Abstract class for interfacing kernel arguments during the + translation of UFL expressions.""" + + __metaclass__ = ABCMeta + + @abstractmethod + def coefficient(self, ufl_coefficient, restriction): + pass + + @abstractmethod + def cell_orientation(self, restriction): + pass + + @abstractmethod + def facet_number(self, restriction): + pass + + class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", "coefficient_numbers", "__weakref__") @@ -35,22 +56,62 @@ def __init__(self, ast=None, integral_type=None, oriented=False, super(Kernel, self).__init__() -class KernelBuilderBase(object): +class KernelBuilderBase(KernelInterface): """Helper class for building local assembly kernels.""" - def __init__(self, interior_facet=False): + def __init__(self, integral_type): """Initialise a kernel builder. :arg interior_facet: kernel accesses two cells """ - assert isinstance(interior_facet, bool) - self.interior_facet = interior_facet + self.interior_facet = integral_type.startswith("interior_facet") self.prepare = [] self.finalise = [] + # Coefficients self.coefficient_map = {} + # Cell orientation + if integral_type.startswith("interior_facet"): + self._cell_orientations = gem.Variable("cell_orientations", (2, 1)) + else: + self._cell_orientations = gem.Variable("cell_orientations", (1, 1)) + + # Facet number + if integral_type in ['exterior_facet', 'exterior_facet_vert']: + facet = gem.Variable('facet', (1,)) + self._facet_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} + elif integral_type in ['interior_facet', 'interior_facet_vert']: + facet = gem.Variable('facet', (2,)) + self._facet_number = { + '+': gem.VariableIndex(gem.Indexed(facet, (0,))), + '-': gem.VariableIndex(gem.Indexed(facet, (1,))) + } + elif integral_type == 'interior_facet_horiz': + self._facet_number = {'+': 1, '-': 0} + + def coefficient(self, ufl_coefficient, restriction): + """A function that maps :class:`ufl.Coefficient`s to GEM + expressions.""" + kernel_arg = self.coefficient_map[ufl_coefficient] + if ufl_coefficient.ufl_element().family() == 'Real': + return kernel_arg + else: + return gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[restriction]) + + def cell_orientation(self, restriction): + f = {None: 0, '+': 0, '-': 1}[restriction] + co_int = gem.Indexed(self._cell_orientations, (f, 0)) + return gem.Conditional(gem.Comparison("==", co_int, gem.Literal(1)), + gem.Literal(-1), + gem.Conditional(gem.Comparison("==", co_int, gem.Zero()), + gem.Literal(1), + gem.Literal(numpy.nan))) + + def facet_number(self, restriction): + return self._facet_number[restriction] + def apply_glue(self, prepare=None, finalise=None): """Append glue code for operations that are not handled in the GEM abstraction. @@ -78,12 +139,6 @@ def construct_kernel(self, name, args, body): body_ = coffee.Block(self.prepare + body.children + self.finalise) return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) - @property - def coefficient_mapper(self): - """A function that maps :class:`ufl.Coefficient`s to GEM - expressions.""" - return lambda coefficient: self.coefficient_map[coefficient] - def arguments(self, arguments, indices): """Prepare arguments. Adds glue code for the arguments. @@ -97,7 +152,7 @@ def arguments(self, arguments, indices): self.apply_glue(prepare, finalise) return funarg, expressions - def coefficient(self, coefficient, name, mode=None): + def _coefficient(self, coefficient, name, mode=None): """Prepare a coefficient. Adds glue code for the coefficient and adds the coefficient to the coefficient map. @@ -119,7 +174,7 @@ class KernelBuilder(KernelBuilderBase): def __init__(self, integral_type, subdomain_id): """Initialise a kernel builder.""" - super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) + super(KernelBuilder, self).__init__(integral_type) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id) self.local_tensor = None @@ -144,7 +199,7 @@ def set_coordinates(self, coefficient, name, mode=None): :arg name: coordinate coefficient name :arg mode: see :func:`prepare_coefficient` """ - self.coordinates_arg = self.coefficient(coefficient, name, mode) + self.coordinates_arg = self._coefficient(coefficient, name, mode) def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -174,7 +229,7 @@ def set_coefficients(self, integral_data, form_data): coefficient_numbers.append(form_data.original_coefficient_positions[i]) for i, coefficient in enumerate(coefficients): self.coefficient_args.append( - self.coefficient(coefficient, "w_%d" % i)) + self._coefficient(coefficient, "w_%d" % i)) self.kernel.coefficient_numbers = tuple(coefficient_numbers) def require_cell_orientations(self): From 72e1a49c070e4cf2f0b360bab44129f7fc0f8abc Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 12 Aug 2016 17:13:42 +0100 Subject: [PATCH 148/809] no sympy :) Thanks Rob for make_affine_mapping. --- tsfc/geometric.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 95607ec1ca..6a42941448 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -3,15 +3,16 @@ from __future__ import absolute_import -from numpy import array, vstack +from numpy import allclose, vstack from singledispatch import singledispatch -import sympy from ufl.classes import (CellCoordinate, CellEdgeVectors, CellFacetJacobian, CellOrientation, FacetCoordinate, ReferenceCellVolume, ReferenceFacetVolume, ReferenceNormal) +from FIAT.reference_element import make_affine_mapping + import gem from tsfc.constants import NUMPY_TYPE @@ -48,18 +49,22 @@ def translate_reference_facet_volume(terminal, mt, params): @translate.register(CellFacetJacobian) def translate_cell_facet_jacobian(terminal, mt, params): - assert params.integration_dim != params.fiat_cell.get_dimension() - dim = params.fiat_cell.construct_subelement(params.integration_dim).get_spatial_dimension() - X = sympy.DeferredVector('X') - point = [X[j] for j in range(dim)] + cell = params.fiat_cell + dim = cell.get_spatial_dimension() + facet_dim = params.integration_dim + facet_cell = cell.construct_subelement(facet_dim) + assert facet_dim != cell.get_dimension() + xs = facet_cell.get_vertices() def callback(entity_id): - f = params.fiat_cell.get_entity_transform(params.integration_dim, entity_id) - y = f(point) - J = [[sympy.diff(y_i, X[j]) - for j in range(dim)] - for y_i in y] - return gem.Literal(array(J, dtype=float)) + ys = cell.get_vertices_of_subcomplex(cell.get_topology()[facet_dim][entity_id]) + # Use first 'dim' points to make an affine mapping + A, b = make_affine_mapping(xs[:dim], ys[:dim]) + for x, y in zip(xs[dim:], ys[dim:]): + # The rest of the points are checked to make sure the + # mapping really *is* affine. + assert allclose(y, A.dot(x) + b) + return gem.Literal(A) return params.entity_selector(callback, mt.restriction) From 4f04a2ca2d8c721a81a529c58fd97a2bfeeb9c7c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 12 Aug 2016 17:36:29 +0100 Subject: [PATCH 149/809] remove KernelInterface for now Complicated the CellVolume implementation and I do not like that. --- tsfc/driver.py | 31 +++++++++++-------------------- tsfc/fem.py | 8 +++++--- tsfc/geometric.py | 2 +- tsfc/kernel_interface.py | 23 +---------------------- 4 files changed, 18 insertions(+), 46 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 01239d68dd..f372d4fec3 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -15,7 +15,7 @@ from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.fiatinterface import QuadratureRule, as_fiat_cell, create_quadrature -from tsfc.kernel_interface import KernelInterface, KernelBuilder, needs_cell_orientations +from tsfc.kernel_interface import KernelBuilder, needs_cell_orientations from tsfc.logging import logger @@ -128,29 +128,17 @@ def cellvolume(restriction): integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='q') if interior_facet: - class KIT(KernelInterface): - def __init__(self, parent): - self.parent = parent - - def coefficient(self, ufl_coefficient, r): - return gem.partial_indexed(self.parent.coefficient(ufl_coefficient, r), - ({'+': 0, '-': 1}[restriction],)) - - def cell_orientation(self, restriction): - return self.parent.cell_orientation(restriction) - - def facet_number(self, restriction): - return self.parent.facet_number(restriction) - - ki = KIT(builder) + def coefficient(ufl_coefficient, r): + return gem.partial_indexed(builder.coefficient(ufl_coefficient, r), + ({'+': 0, '-': 1}[restriction],)) else: assert restriction is None - ki = builder + coefficient = builder.coefficient ir = fem.compile_ufl(integrand, cell=cell, quadrature_degree=quadrature_degree, point_index=quadrature_index, - kernel_interface=ki, + coefficient=coefficient, index_cache=index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) @@ -185,7 +173,8 @@ def facetarea(): entity_ids=entity_ids, quadrature_degree=quadrature_degree, point_index=quadrature_index, - kernel_interface=builder, + coefficient=builder.coefficient, + facet_number=builder.facet_number, index_cache=index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) @@ -227,7 +216,9 @@ def facetarea(): quadrature_rule=quad_rule, point_index=quadrature_index, argument_indices=argument_indices, - kernel_interface=builder, + coefficient=builder.coefficient, + cell_orientation=builder.cell_orientation, + facet_number=builder.facet_number, index_cache=index_cache, cellvolume=cellvolume, facetarea=facetarea) diff --git a/tsfc/fem.py b/tsfc/fem.py index f7cb559483..a0566e9ac1 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -143,7 +143,9 @@ class Parameters(object): 'weights', 'point_index', 'argument_indices', - 'kernel_interface', + 'coefficient', + 'cell_orientation', + 'facet_number', 'cellvolume', 'facetarea', 'index_cache') @@ -191,7 +193,7 @@ def _selector(self, callback, opts, restriction): return callback(opts[0]) else: results = gem.ListTensor(map(callback, opts)) - f = self.kernel_interface.facet_number(restriction) + f = self.facet_number(restriction) return gem.partial_indexed(results, (f,)) def entity_selector(self, callback, restriction): @@ -311,7 +313,7 @@ def callback(key): @translate.register(Coefficient) def translate_coefficient(terminal, mt, params): - vec = params.kernel_interface.coefficient(terminal, mt.restriction) + vec = params.coefficient(terminal, mt.restriction) if terminal.ufl_element().family() == 'Real': assert mt.local_derivatives == 0 diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 6a42941448..c73752d73a 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -31,7 +31,7 @@ def translate(terminal, mt, params): @translate.register(CellOrientation) def translate_cell_orientation(terminal, mt, params): - return params.kernel_interface.cell_orientation(mt.restriction) + return params.cell_orientation(mt.restriction) @translate.register(ReferenceCellVolume) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index aefb46a8e5..355174fc40 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -1,7 +1,5 @@ from __future__ import absolute_import -from abc import ABCMeta, abstractmethod - import numpy import coffee.base as coffee @@ -14,25 +12,6 @@ from tsfc.coffee import SCALAR_TYPE -class KernelInterface(object): - """Abstract class for interfacing kernel arguments during the - translation of UFL expressions.""" - - __metaclass__ = ABCMeta - - @abstractmethod - def coefficient(self, ufl_coefficient, restriction): - pass - - @abstractmethod - def cell_orientation(self, restriction): - pass - - @abstractmethod - def facet_number(self, restriction): - pass - - class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", "coefficient_numbers", "__weakref__") @@ -56,7 +35,7 @@ def __init__(self, ast=None, integral_type=None, oriented=False, super(Kernel, self).__init__() -class KernelBuilderBase(KernelInterface): +class KernelBuilderBase(object): """Helper class for building local assembly kernels.""" def __init__(self, integral_type): From 6036eedb71eebceaffc9b0805b42cd8242b97d23 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 12 Aug 2016 17:48:58 +0100 Subject: [PATCH 150/809] move facet_number to KernelBuilder Restore old KernelBuilderBase interface. --- tsfc/kernel_interface.py | 41 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 355174fc40..dd688f47b3 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -38,12 +38,13 @@ def __init__(self, ast=None, integral_type=None, oriented=False, class KernelBuilderBase(object): """Helper class for building local assembly kernels.""" - def __init__(self, integral_type): + def __init__(self, interior_facet=False): """Initialise a kernel builder. :arg interior_facet: kernel accesses two cells """ - self.interior_facet = integral_type.startswith("interior_facet") + assert isinstance(interior_facet, bool) + self.interior_facet = interior_facet self.prepare = [] self.finalise = [] @@ -52,24 +53,11 @@ def __init__(self, integral_type): self.coefficient_map = {} # Cell orientation - if integral_type.startswith("interior_facet"): + if interior_facet: self._cell_orientations = gem.Variable("cell_orientations", (2, 1)) else: self._cell_orientations = gem.Variable("cell_orientations", (1, 1)) - # Facet number - if integral_type in ['exterior_facet', 'exterior_facet_vert']: - facet = gem.Variable('facet', (1,)) - self._facet_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} - elif integral_type in ['interior_facet', 'interior_facet_vert']: - facet = gem.Variable('facet', (2,)) - self._facet_number = { - '+': gem.VariableIndex(gem.Indexed(facet, (0,))), - '-': gem.VariableIndex(gem.Indexed(facet, (1,))) - } - elif integral_type == 'interior_facet_horiz': - self._facet_number = {'+': 1, '-': 0} - def coefficient(self, ufl_coefficient, restriction): """A function that maps :class:`ufl.Coefficient`s to GEM expressions.""" @@ -88,9 +76,6 @@ def cell_orientation(self, restriction): gem.Literal(1), gem.Literal(numpy.nan))) - def facet_number(self, restriction): - return self._facet_number[restriction] - def apply_glue(self, prepare=None, finalise=None): """Append glue code for operations that are not handled in the GEM abstraction. @@ -153,7 +138,7 @@ class KernelBuilder(KernelBuilderBase): def __init__(self, integral_type, subdomain_id): """Initialise a kernel builder.""" - super(KernelBuilder, self).__init__(integral_type) + super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id) self.local_tensor = None @@ -161,6 +146,22 @@ def __init__(self, integral_type, subdomain_id): self.coefficient_args = [] self.coefficient_split = {} + # Facet number + if integral_type in ['exterior_facet', 'exterior_facet_vert']: + facet = gem.Variable('facet', (1,)) + self._facet_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} + elif integral_type in ['interior_facet', 'interior_facet_vert']: + facet = gem.Variable('facet', (2,)) + self._facet_number = { + '+': gem.VariableIndex(gem.Indexed(facet, (0,))), + '-': gem.VariableIndex(gem.Indexed(facet, (1,))) + } + elif integral_type == 'interior_facet_horiz': + self._facet_number = {'+': 1, '-': 0} + + def facet_number(self, restriction): + return self._facet_number[restriction] + def set_arguments(self, arguments, indices): """Process arguments. From d9286c884ee1a36d4259ad9a5aea24f7ab237300 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 16 Aug 2016 14:09:03 +0100 Subject: [PATCH 151/809] update docstrings --- tsfc/fem.py | 31 ++++++++++++++++++++++++++++--- tsfc/kernel_interface.py | 2 ++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index a0566e9ac1..ba0abdd5e1 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -84,8 +84,9 @@ class TabulationManager(object): def __init__(self, entity_points): """Constructs a TabulationManager. - :arg points: points on the integration entity (e.g. points on - an interval for facet integrals on a triangle) + :arg entity_points: An array of points in cell coordinates for + each integration entity, i.e. an iterable + of arrays of points. """ self.tabulators = map(make_tabulator, entity_points) self.tables = {} @@ -181,7 +182,8 @@ def weights(self): @cached_property def entity_points(self): - """Points in cell coordinates for each entity.""" + """An array of points in cell coordinates for each entity, + i.e. a list of arrays of points.""" result = [] for entity_id in self.entity_ids: t = self.fiat_cell.get_entity_transform(self.integration_dim, entity_id) @@ -189,6 +191,8 @@ def entity_points(self): return result def _selector(self, callback, opts, restriction): + """Helper function for selecting code for the correct entity + at run-time.""" if len(opts) == 1: return callback(opts[0]) else: @@ -197,9 +201,30 @@ def _selector(self, callback, opts, restriction): return gem.partial_indexed(results, (f,)) def entity_selector(self, callback, restriction): + """Selects code for the correct entity at run-time. Callback + generates code for a specified entity. + + This function passes ``callback`` the entity number. + + :arg callback: A function to be called with an entity number + that generates code for that entity. + :arg restriction: Restriction of the modified terminal, used + for entity selection. + """ return self._selector(callback, self.entity_ids, restriction) def index_selector(self, callback, restriction): + """Selects code for the correct entity at run-time. Callback + generates code for a specified entity. + + This function passes ``callback`` an index of the entity + numbers array. + + :arg callback: A function to be called with an entity index + that generates code for that entity. + :arg restriction: Restriction of the modified terminal, used + for entity selection. + """ return self._selector(callback, range(len(self.entity_ids)), restriction) argument_indices = () diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index dd688f47b3..f0fa3364a1 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -68,6 +68,7 @@ def coefficient(self, ufl_coefficient, restriction): return gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[restriction]) def cell_orientation(self, restriction): + """Cell orientation as a GEM expression.""" f = {None: 0, '+': 0, '-': 1}[restriction] co_int = gem.Indexed(self._cell_orientations, (f, 0)) return gem.Conditional(gem.Comparison("==", co_int, gem.Literal(1)), @@ -160,6 +161,7 @@ def __init__(self, integral_type, subdomain_id): self._facet_number = {'+': 1, '-': 0} def facet_number(self, restriction): + """Facet number as a GEM index.""" return self._facet_number[restriction] def set_arguments(self, arguments, indices): From f73f6e74581e4a31beeaa0fc1297ab96ebc9e267 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 16 Aug 2016 14:16:31 +0100 Subject: [PATCH 152/809] fix consistency of a test file --- tests/test_codegen.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 3347a07858..19b58b8ecc 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1,3 +1,5 @@ +import pytest + from gem import impero_utils from gem.gem import Index, Indexed, IndexSum, Product, Variable @@ -20,3 +22,9 @@ def gencode(expr): return impero_c.tree assert len(gencode(e1).children) == len(gencode(e2).children) + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 55b20259d9a5dc575a46d601b6930374ca199a79 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 16 Aug 2016 16:31:05 +0100 Subject: [PATCH 153/809] add test case for CellFacetJacobian --- tests/test_geometry.py | 78 ++++++++++++++++++++++++++++++++++++++++++ tsfc/geometric.py | 29 ++++++++++------ 2 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 tests/test_geometry.py diff --git a/tests/test_geometry.py b/tests/test_geometry.py new file mode 100644 index 0000000000..95ffd08e31 --- /dev/null +++ b/tests/test_geometry.py @@ -0,0 +1,78 @@ +from __future__ import absolute_import, print_function, division + +import pytest +import numpy as np + +from FIAT.reference_element import UFCInterval, UFCTriangle, UFCTetrahedron +from FIAT.reference_element import FiredrakeQuadrilateral, TensorProductCell + +from tsfc.geometric import make_cell_facet_jacobian + +interval = UFCInterval() +triangle = UFCTriangle() +quadrilateral = FiredrakeQuadrilateral() +tetrahedron = UFCTetrahedron() +interval_x_interval = TensorProductCell(interval, interval) +triangle_x_interval = TensorProductCell(triangle, interval) +quadrilateral_x_interval = TensorProductCell(quadrilateral, interval) + + +@pytest.mark.parametrize(('cell', 'cell_facet_jacobian'), + [(interval, [[], + []]), + (triangle, [[-1, 1], + [0, 1], + [1, 0]]), + (quadrilateral, [[0, 1], + [0, 1], + [1, 0], + [1, 0]]), + (tetrahedron, [[-1, -1, 1, 0, 0, 1], + [0, 0, 1, 0, 0, 1], + [1, 0, 0, 0, 0, 1], + [1, 0, 0, 1, 0, 0]])]) +def test_cell_facet_jacobian(cell, cell_facet_jacobian): + facet_dim = cell.get_spatial_dimension() - 1 + for facet_number in range(len(cell.get_topology()[facet_dim])): + actual = make_cell_facet_jacobian(cell, facet_dim, facet_number) + expected = np.reshape(cell_facet_jacobian[facet_number], actual.shape) + assert np.allclose(expected, actual) + + +@pytest.mark.parametrize(('cell', 'cell_facet_jacobian'), + [(interval_x_interval, [1, 0]), + (triangle_x_interval, [1, 0, 0, 1, 0, 0]), + (quadrilateral_x_interval, [[1, 0, 0, 1, 0, 0]])]) +def test_cell_facet_jacobian_horiz(cell, cell_facet_jacobian): + dim = cell.get_spatial_dimension() + + actual = make_cell_facet_jacobian(cell, (dim - 1, 0), 0) # bottom facet + assert np.allclose(np.reshape(cell_facet_jacobian, actual.shape), actual) + + actual = make_cell_facet_jacobian(cell, (dim - 1, 0), 1) # top facet + assert np.allclose(np.reshape(cell_facet_jacobian, actual.shape), actual) + + +@pytest.mark.parametrize(('cell', 'cell_facet_jacobian'), + [(interval_x_interval, [[0, 1], + [0, 1]]), + (triangle_x_interval, [[-1, 0, 1, 0, 0, 1], + [0, 0, 1, 0, 0, 1], + [1, 0, 0, 0, 0, 1]]), + (quadrilateral_x_interval, [[0, 0, 1, 0, 0, 1], + [0, 0, 1, 0, 0, 1], + [1, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 1]])]) +def test_cell_facet_jacobian_vert(cell, cell_facet_jacobian): + dim = cell.get_spatial_dimension() + vert_dim = (dim - 2, 1) + for facet_number in range(len(cell.get_topology()[vert_dim])): + actual = make_cell_facet_jacobian(cell, vert_dim, facet_number) + expected = np.reshape(cell_facet_jacobian[facet_number], actual.shape) + assert np.allclose(expected, actual) + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index c73752d73a..0451fdf10d 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -50,24 +50,31 @@ def translate_reference_facet_volume(terminal, mt, params): @translate.register(CellFacetJacobian) def translate_cell_facet_jacobian(terminal, mt, params): cell = params.fiat_cell - dim = cell.get_spatial_dimension() facet_dim = params.integration_dim - facet_cell = cell.construct_subelement(facet_dim) assert facet_dim != cell.get_dimension() - xs = facet_cell.get_vertices() def callback(entity_id): - ys = cell.get_vertices_of_subcomplex(cell.get_topology()[facet_dim][entity_id]) - # Use first 'dim' points to make an affine mapping - A, b = make_affine_mapping(xs[:dim], ys[:dim]) - for x, y in zip(xs[dim:], ys[dim:]): - # The rest of the points are checked to make sure the - # mapping really *is* affine. - assert allclose(y, A.dot(x) + b) - return gem.Literal(A) + return gem.Literal(make_cell_facet_jacobian(cell, facet_dim, entity_id)) return params.entity_selector(callback, mt.restriction) +def make_cell_facet_jacobian(cell, facet_dim, facet_i): + facet_cell = cell.construct_subelement(facet_dim) + xs = facet_cell.get_vertices() + ys = cell.get_vertices_of_subcomplex(cell.get_topology()[facet_dim][facet_i]) + + # Use first 'dim' points to make an affine mapping + dim = cell.get_spatial_dimension() + A, b = make_affine_mapping(xs[:dim], ys[:dim]) + + for x, y in zip(xs[dim:], ys[dim:]): + # The rest of the points are checked to make sure the + # mapping really *is* affine. + assert allclose(y, A.dot(x) + b) + + return A + + @translate.register(ReferenceNormal) def translate_reference_normal(terminal, mt, params): def callback(facet_i): From 5f30fe9540695c44f0077e79b6914e525bf4f053 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 17 Aug 2016 11:18:50 +0100 Subject: [PATCH 154/809] better CellVolume restriction propagation --- tsfc/driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index f372d4fec3..2a080ff0b0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -129,8 +129,8 @@ def cellvolume(restriction): quadrature_index = gem.Index(name='q') if interior_facet: def coefficient(ufl_coefficient, r): - return gem.partial_indexed(builder.coefficient(ufl_coefficient, r), - ({'+': 0, '-': 1}[restriction],)) + assert r is None + return builder.coefficient(ufl_coefficient, restriction) else: assert restriction is None coefficient = builder.coefficient From 94f0fcaec7bb7b73e491ef93ebbb5fc0dd3c9bd3 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 15 Jul 2016 13:47:24 +0100 Subject: [PATCH 155/809] Support for forms with multiple domains compute_form_data provides us with the index into form.ufl_domains() that allows us to extract the correct domain for a given integral. We need to remember this in the Kernel and send it back to the outside world. --- tsfc/driver.py | 5 ++++- tsfc/kernel_interface.py | 13 ++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2a080ff0b0..5a7c0a0135 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -87,7 +87,10 @@ def compile_integral(integral_data, form_data, prefix, parameters): argument_indices = tuple(gem.Index(name=name) for arg, name in zip(arguments, ['j', 'k'])) quadrature_indices = [] - builder = KernelBuilder(integral_type, integral_data.subdomain_id) + # Dict mapping domains to index in original_form.ufl_domains() + domain_numbering = form_data.original_form.domain_numbering() + builder = KernelBuilder(integral_type, integral_data.subdomain_id, + domain_numbering[integral_data.domain]) return_variables = builder.set_arguments(arguments, argument_indices) coordinates = ufl_utils.coordinate_coefficient(mesh) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index f0fa3364a1..0d662e238d 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -14,6 +14,7 @@ class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + "domain_number", "coefficient_numbers", "__weakref__") """A compiled Kernel object. @@ -21,15 +22,20 @@ class Kernel(object): :kwarg integral_type: The type of integral. :kwarg oriented: Does the kernel require cell_orientations. :kwarg subdomain_id: What is the subdomain id for this kernel. + :kwarg domain_number: Which domain number in the original form + does this kernel correspond to (can be used to index into + original_form.ufl_domains() to get the correct domain). :kwarg coefficient_numbers: A list of which coefficients from the form the kernel needs. """ def __init__(self, ast=None, integral_type=None, oriented=False, - subdomain_id=None, coefficient_numbers=()): + subdomain_id=None, domain_number=None, + coefficient_numbers=()): # Defaults self.ast = ast self.integral_type = integral_type self.oriented = oriented + self.domain_number = domain_number self.subdomain_id = subdomain_id self.coefficient_numbers = coefficient_numbers super(Kernel, self).__init__() @@ -137,11 +143,12 @@ def _coefficient(self, coefficient, name, mode=None): class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id): + def __init__(self, integral_type, subdomain_id, domain_number): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) - self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id) + self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, + domain_number=domain_number) self.local_tensor = None self.coordinates_arg = None self.coefficient_args = [] From db61ad9ecdf32999ef854cdd049639be14c51a45 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 9 Sep 2016 16:14:26 +0100 Subject: [PATCH 156/809] WIP: FlexiblyIndexed --- tsfc/coffee.py | 36 +++++++++++++++++++++++++++++ tsfc/kernel_interface.py | 49 +++++++++++++++------------------------- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index b58c80bbbc..50dbe8c27b 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -314,3 +314,39 @@ def _expression_indexed(expr, parameters): rank.append(index) return _coffee_symbol(expression(expr.children[0], parameters), rank=tuple(rank)) + + +@_expression.register(gem.FlexiblyIndexed) +def _expression_flexiblyindexed(expr, parameters): + rank = [] + offset = [] + for off, idxs in expr.dim2idxs: + if idxs: + indices, strides = zip(*idxs) + strides = list(reversed(strides[1:])) + strides = list(reversed(numpy.cumprod(strides))) + [1] + else: + indices = () + strides = () + + iss = [] + for i, s in zip(indices, strides): + if isinstance(i, int): + off += i * s + elif isinstance(i, gem.Index): + iss.append((i, s)) + else: + raise AssertionError("Unexpected index type!") + + if len(iss) == 0: + rank.append(off) + offset.append((1, 0)) + elif len(iss) == 1: + (i, s), = iss + rank.append(parameters.index_names[i]) + offset.append((s, off)) + else: + raise NotImplementedError("General case not implemented yet") + + return coffee.Symbol(expression(expr.children[0], parameters), + rank=tuple(rank), offset=tuple(offset)) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 0d662e238d..09ff4addbc 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -277,50 +277,37 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): if coefficient.ufl_element().family() == 'Real': # Constant - - shape = coefficient.ufl_shape or (1,) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("restrict",)], qualifiers=["const"]) - expression = gem.Variable(name, shape) - if coefficient.ufl_shape == (): - expression = gem.Indexed(expression, (0,)) + + expression = gem.reshape(gem.Variable(name, (None,)), + coefficient.ufl_shape) return funarg, [], expression fiat_element = create_element(coefficient.ufl_element()) + size = fiat_element.space_dimension() if not interior_facet: # Simple case - - shape = (fiat_element.space_dimension(),) - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), - pointers=[("restrict",)], + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("const", "restrict"), ("restrict",)], qualifiers=["const"]) - i = gem.Index() - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, shape + (1,)), - (i, 0)), - (i,)) + expression = gem.reshape(gem.Variable(name, (size, 1)), (size,), ()) return funarg, [], expression if not isinstance(fiat_element, MixedElement): # Interior facet integral - - shape = (2, fiat_element.space_dimension()) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape), - pointers=[("restrict",)], + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("const", "restrict"), ("restrict",)], qualifiers=["const"]) - expression = gem.Variable(name, shape + (1,)) - f, i = gem.Index(), gem.Index() - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, shape + (1,)), - (f, i, 0)), - (f, i,)) + expression = gem.reshape( + gem.Variable(name, (2 * size, 1)), (2, size), () + ) return funarg, [], expression @@ -343,10 +330,10 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): # to reorder the values. A whole E[n]{+,-} block is copied by # a single loop. name_ = name + "_" - shape = (2, fiat_element.space_dimension()) + shape = (2, size) funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name_), - pointers=[("restrict",), ("restrict",)], + pointers=[("const", "restrict"), ("restrict",)], qualifiers=["const"]) prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] expression = gem.Variable(name, shape) @@ -377,10 +364,10 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): # reordering. Every single element in a E[n]{+,-} block is # referenced separately. funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("restrict",), ("restrict",)], + pointers=[("const", "restrict"), ("restrict",)], qualifiers=["const"]) - variable = gem.Variable(name, (2 * fiat_element.space_dimension(), 1)) + variable = gem.Variable(name, (2 * size, 1)) facet_0 = [] facet_1 = [] From 70bbe4d7cd932188da5c8012f356eee4932d0b3a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 12 Sep 2016 11:11:48 +0100 Subject: [PATCH 157/809] use flexible indexing for Arguments --- tsfc/coffee.py | 6 +++++- tsfc/kernel_interface.py | 15 ++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 50dbe8c27b..340b7f3a7c 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -348,5 +348,9 @@ def _expression_flexiblyindexed(expr, parameters): else: raise NotImplementedError("General case not implemented yet") - return coffee.Symbol(expression(expr.children[0], parameters), + variable = expression(expr.children[0], parameters) + assert isinstance(variable, coffee.Symbol) + assert not variable.rank + assert not variable.offset + return coffee.Symbol(variable.symbol, rank=tuple(rank), offset=tuple(offset)) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 09ff4addbc..50aabb9de7 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -6,6 +6,7 @@ import gem from gem.node import traversal +from gem.gem import FlexiblyIndexed as gem_FlexiblyIndexed from tsfc.fiatinterface import create_element from tsfc.mixedelement import MixedElement @@ -404,7 +405,7 @@ def prepare_arguments(arguments, indices, interior_facet=False): finalise - list of COFFEE nodes to be appended to the kernel body """ - from itertools import chain, product + from itertools import product assert isinstance(interior_facet, bool) if len(arguments) == 0: @@ -427,18 +428,18 @@ def prepare_arguments(arguments, indices, interior_facet=False): if not any(isinstance(element, MixedElement) for element in elements): # Interior facet integral, but no vector (mixed) arguments - shape = [] - for element in elements: - shape += [2, element.space_dimension()] - shape = tuple(shape) + shape = tuple(2 * element.space_dimension() for element in elements) funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) varexp = gem.Variable("A", shape) expressions = [] for restrictions in product((0, 1), repeat=len(arguments)): - is_ = tuple(chain(*zip(restrictions, indices))) - expressions.append(gem.Indexed(varexp, is_)) + expressions.append(gem_FlexiblyIndexed( + varexp, + tuple((r * e.space_dimension(), ((i, e.space_dimension()),)) + for e, i, r in zip(elements, indices, restrictions)) + )) return funarg, [], expressions, [] From 9b4e9392e8a33e85b6746744b05ad38ec855b2af Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 12 Sep 2016 11:28:35 +0100 Subject: [PATCH 158/809] clean up a little --- tsfc/coffee.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 340b7f3a7c..95daedadeb 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -316,15 +316,24 @@ def _expression_indexed(expr, parameters): rank=tuple(rank)) +def cumulative_strides(strides): + temp = numpy.flipud(numpy.cumprod(numpy.flipud(list(strides)[1:]))) + return list(temp) + [1] + + @_expression.register(gem.FlexiblyIndexed) def _expression_flexiblyindexed(expr, parameters): + var = expression(expr.children[0], parameters) + assert isinstance(var, coffee.Symbol) + assert not var.rank + assert not var.offset + rank = [] offset = [] for off, idxs in expr.dim2idxs: if idxs: indices, strides = zip(*idxs) - strides = list(reversed(strides[1:])) - strides = list(reversed(numpy.cumprod(strides))) + [1] + strides = cumulative_strides(strides) else: indices = () strides = () @@ -346,11 +355,6 @@ def _expression_flexiblyindexed(expr, parameters): rank.append(parameters.index_names[i]) offset.append((s, off)) else: - raise NotImplementedError("General case not implemented yet") - - variable = expression(expr.children[0], parameters) - assert isinstance(variable, coffee.Symbol) - assert not variable.rank - assert not variable.offset - return coffee.Symbol(variable.symbol, - rank=tuple(rank), offset=tuple(offset)) + raise NotImplementedError("COFFEE may break in this case.") + + return coffee.Symbol(var.symbol, rank=tuple(rank), offset=tuple(offset)) From 7c1779204130a3fa8ae6a9ef70a00082b4648ebf Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 12 Sep 2016 13:37:06 +0100 Subject: [PATCH 159/809] add docstrings --- tsfc/coffee.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 95daedadeb..65b5216bf9 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -317,6 +317,13 @@ def _expression_indexed(expr, parameters): def cumulative_strides(strides): + """Calculate cumulative strides from per-dimension capacities. + + For example: + + [2, 3, 4] ==> [12, 4, 1] + + """ temp = numpy.flipud(numpy.cumprod(numpy.flipud(list(strides)[1:]))) return list(temp) + [1] From 8f23643845405ec33ef04ee4992fca0a4749aea5 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 13 Sep 2016 11:07:19 +0100 Subject: [PATCH 160/809] implement potentially COFFEE-breaking case --- tsfc/coffee.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 65b5216bf9..9bdc825809 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -16,6 +16,7 @@ from gem import gem, impero as imp from tsfc.constants import SCALAR_TYPE, PRECISION +from tsfc.logging import logger class Bunch(object): @@ -362,6 +363,16 @@ def _expression_flexiblyindexed(expr, parameters): rank.append(parameters.index_names[i]) offset.append((s, off)) else: - raise NotImplementedError("COFFEE may break in this case.") + # Warn that this might break COFFEE + logger.warning("Multiple free indices in FlexiblyIndexed: might break COFFEE.") + index_expr = reduce( + coffee.Sum, + [coffee.Prod(coffee.Symbol(parameters.index_names[i]), + coffee.Symbol(str(s))) + for i, s in iss], + coffee.Symbol(str(off)) + ) + rank.append(index_expr) + offset.append((1, 0)) return coffee.Symbol(var.symbol, rank=tuple(rank), offset=tuple(offset)) From ecc9212325ee9746c7009bcb3553a9bd30946d10 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 13 Sep 2016 11:19:53 +0100 Subject: [PATCH 161/809] fix ordering of argument indices --- tsfc/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 5f6553a505..f9f4d728f3 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -2,6 +2,7 @@ import collections import time +from itertools import chain from ufl.classes import Form, CellVolume, FacetArea from ufl.algorithms import compute_form_data @@ -246,7 +247,7 @@ def facetarea(): builder.require_cell_orientations() impero_c = impero_utils.compile_gem(return_variables, ir, - tuple(quadrature_indices) + argument_indices, + tuple(quadrature_indices) + tuple(chain(*argument_indices)), remove_zeros=True) # Generate COFFEE From fc30df4751d89346ad9fd66e1e63981c87e712d3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 13 Sep 2016 14:17:10 +0100 Subject: [PATCH 162/809] remove broken references to MixedElement --- tsfc/fiatinterface.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 870b0fa02a..1c9ee4463f 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -25,7 +25,6 @@ from __future__ import print_function from singledispatch import singledispatch -from functools import partial import weakref import FIAT @@ -36,8 +35,6 @@ import ufl from ufl.algorithms.elementtransformations import reconstruct_element -from .mixedelement import MixedElement - __all__ = ("create_element", "create_quadrature", "supported_elements", "as_fiat_cell") @@ -222,19 +219,7 @@ def _(element, vector_is_mixed): ufl.TensorElement)) return create_element(element.sub_elements()[0], vector_is_mixed) - elements = [] - - def rec(eles): - for ele in eles: - if ele.num_sub_elements() > 0: - rec(ele.sub_elements()) - else: - elements.append(ele) - - rec(element.sub_elements()) - fiat_elements = map(partial(create_element, vector_is_mixed=vector_is_mixed), - elements) - return MixedElement(fiat_elements) + raise NotImplementedError("ffc.MixedElement removed.") quad_opc = ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")) From 20f8288eaf5df2b00af7d14ec2c58d9a1c075373 Mon Sep 17 00:00:00 2001 From: Jan Blechta Date: Thu, 15 Sep 2016 17:53:14 +0200 Subject: [PATCH 163/809] Start backporting changes for UFC FFC regression tests now run. Will provide separate kernel interfaces for PyOP2 and UFC later. --- tsfc/driver.py | 28 +-- tsfc/kernel_interface.py | 512 +++++++++++++++++++-------------------- 2 files changed, 264 insertions(+), 276 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 5a7c0a0135..30e174531c 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -69,12 +69,6 @@ def compile_integral(integral_data, form_data, prefix, parameters): :arg parameters: parameters object :returns: a kernel, or None if the integral simplifies to zero """ - # Remove these here, they're handled below. - if parameters.get("quadrature_degree") == "auto": - del parameters["quadrature_degree"] - if parameters.get("quadrature_rule") == "auto": - del parameters["quadrature_rule"] - integral_type = integral_data.integral_type interior_facet = integral_type.startswith("interior_facet") mesh = integral_data.domain @@ -97,13 +91,18 @@ def compile_integral(integral_data, form_data, prefix, parameters): if ufl_utils.is_element_affine(mesh.ufl_coordinate_element()): # For affine mesh geometries we prefer code generation that # composes well with optimisations. - builder.set_coordinates(coordinates, "coords", mode='list_tensor') + #builder.set_coordinates(coordinates, "coords", mode='list_tensor') + builder.set_coordinates(coordinates, "coordinate_dofs", mode='list_tensor') else: # Otherwise we use the approach that might be faster (?) - builder.set_coordinates(coordinates, "coords") + #builder.set_coordinates(coordinates, "coords") + builder.set_coordinates(coordinates, "coordinate_dofs") builder.set_coefficients(integral_data, form_data) + builder.set_cell_orientations() + builder.set_facets() + # Map from UFL FiniteElement objects to Index instances. This is # so we reuse Index instances when evaluating the same coefficient # multiple times with the same table. Occurs, for example, if we @@ -196,12 +195,13 @@ def facetarea(): # Check if the integral has a quad degree attached, otherwise use # the estimated polynomial degree attached by compute_form_data - quadrature_degree = params.get("quadrature_degree", - params["estimated_polynomial_degree"]) + quad_degree = params.get("quadrature_degree") + if quad_degree in [None, "auto", "default", -1, "-1"]: + quad_degree = params["estimated_polynomial_degree"] integration_cell = fiat_cell.construct_subelement(integration_dim) - quad_rule = params.get("quadrature_rule", - create_quadrature(integration_cell, - quadrature_degree)) + quad_rule = params.get("quadrature_rule") + if quad_rule in [None, "auto", "default"]: + quad_rule = create_quadrature(integration_cell, quad_degree) if not isinstance(quad_rule, QuadratureRule): raise ValueError("Expected to find a QuadratureRule object, not a %s" % @@ -225,7 +225,7 @@ def facetarea(): index_cache=index_cache, cellvolume=cellvolume, facetarea=facetarea) - if parameters["unroll_indexsum"]: + if parameters.get("unroll_indexsum"): ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) irs.append([(gem.IndexSum(expr, quadrature_index) if quadrature_index in expr.free_indices diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 50aabb9de7..52e33564d3 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -1,6 +1,9 @@ from __future__ import absolute_import +import os +import six import numpy +from itertools import chain, product import coffee.base as coffee @@ -59,12 +62,6 @@ def __init__(self, interior_facet=False): # Coefficients self.coefficient_map = {} - # Cell orientation - if interior_facet: - self._cell_orientations = gem.Variable("cell_orientations", (2, 1)) - else: - self._cell_orientations = gem.Variable("cell_orientations", (1, 1)) - def coefficient(self, ufl_coefficient, restriction): """A function that maps :class:`ufl.Coefficient`s to GEM expressions.""" @@ -77,7 +74,7 @@ def coefficient(self, ufl_coefficient, restriction): def cell_orientation(self, restriction): """Cell orientation as a GEM expression.""" f = {None: 0, '+': 0, '-': 1}[restriction] - co_int = gem.Indexed(self._cell_orientations, (f, 0)) + co_int = self.cell_orientations_mapper[f] return gem.Conditional(gem.Comparison("==", co_int, gem.Literal(1)), gem.Literal(-1), gem.Conditional(gem.Comparison("==", co_int, gem.Zero()), @@ -109,7 +106,7 @@ def construct_kernel(self, name, args, body): """ assert isinstance(body, coffee.Block) body_ = coffee.Block(self.prepare + body.children + self.finalise) - return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) + return coffee.FunDecl("void", name, args, body_, pred=["virtual"]) def arguments(self, arguments, indices): """Prepare arguments. Adds glue code for the arguments. @@ -124,21 +121,63 @@ def arguments(self, arguments, indices): self.apply_glue(prepare, finalise) return funarg, expressions - def _coefficient(self, coefficient, name, mode=None): + def coefficients(self, coefficients, coefficient_numbers, name, mode=None): """Prepare a coefficient. Adds glue code for the coefficient and adds the coefficient to the coefficient map. - :arg coefficient: :class:`ufl.Coefficient` + :arg coefficient: iterable of :class:`ufl.Coefficient`s + :arg coefficient_numbers: iterable of coefficient indices in the original form :arg name: coefficient name :arg mode: see :func:`prepare_coefficient` :returns: COFFEE function argument for the coefficient """ - funarg, prepare, expression = prepare_coefficient( + funarg, prepare, expressions = prepare_coefficients( + coefficients, coefficient_numbers, name, mode=mode, + interior_facet=self.interior_facet) + self.apply_glue(prepare) + for i, coefficient in enumerate(coefficients): + self.coefficient_map[coefficient] = expressions[i] + return funarg + + def coordinates(self, coefficient, name, mode=None): + """Prepare a coordinates. Adds glue code for the coefficient + and adds the coefficient to the coefficient map. + + :arg coefficient: :class:`ufl.Coefficient` + :arg name: coefficient name + :arg mode: see :func:`prepare_coefficient` + :returns: COFFEE function arguments for the coefficient + """ + funargs, prepare, expression = prepare_coordinates( coefficient, name, mode=mode, interior_facet=self.interior_facet) self.apply_glue(prepare) self.coefficient_map[coefficient] = expression - return funarg + return funargs + + def facets(self, integral_type): + """Prepare facets. Adds glue code for facets + and stores facet expression. + + :arg integral_type + :returns: list of COFFEE function arguments for facets + """ + funargs, prepare, expressions = prepare_facets(integral_type) + self.apply_glue(prepare) + self.facet_mapper = expressions + return funargs + + def cell_orientations(self, integral_type): + """Prepare cell orientations. Adds glue code for cell orienatations + and stores cell orientations expression. + + :arg integral_type + :returns: list of COFFEE function arguments for cell orientations + """ + funargs, prepare, expressions = prepare_cell_orientations(integral_type) + self.apply_glue(prepare) + self.cell_orientations_mapper = expressions + return funargs class KernelBuilder(KernelBuilderBase): @@ -154,23 +193,12 @@ def __init__(self, integral_type, subdomain_id, domain_number): self.coordinates_arg = None self.coefficient_args = [] self.coefficient_split = {} - - # Facet number - if integral_type in ['exterior_facet', 'exterior_facet_vert']: - facet = gem.Variable('facet', (1,)) - self._facet_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} - elif integral_type in ['interior_facet', 'interior_facet_vert']: - facet = gem.Variable('facet', (2,)) - self._facet_number = { - '+': gem.VariableIndex(gem.Indexed(facet, (0,))), - '-': gem.VariableIndex(gem.Indexed(facet, (1,))) - } - elif integral_type == 'interior_facet_horiz': - self._facet_number = {'+': 1, '-': 0} + self.cell_orientations_args = [] def facet_number(self, restriction): """Facet number as a GEM index.""" - return self._facet_number[restriction] + f = {None: 0, '+': 0, '-': 1}[restriction] + return self.facet_mapper[f] def set_arguments(self, arguments, indices): """Process arguments. @@ -189,7 +217,17 @@ def set_coordinates(self, coefficient, name, mode=None): :arg name: coordinate coefficient name :arg mode: see :func:`prepare_coefficient` """ - self.coordinates_arg = self._coefficient(coefficient, name, mode) + self.coordinates_args = self.coordinates(coefficient, name, mode) + + def set_facets(self): + """Prepare the facets. + """ + self.facet_args = self.facets(self.kernel.integral_type) + + def set_cell_orientations(self): + """Prepare the cell orientations. + """ + self.cell_orientations_args = self.cell_orientations(self.kernel.integral_type) def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -197,7 +235,6 @@ def set_coefficients(self, integral_data, form_data): :arg integral_data: UFL integral data :arg form_data: UFL form data """ - from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace coefficients = [] coefficient_numbers = [] # enabled_coefficients is a boolean array that indicates which @@ -205,25 +242,18 @@ def set_coefficients(self, integral_data, form_data): for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: coefficient = form_data.reduced_coefficients[i] - if type(coefficient.ufl_element()) == ufl_MixedElement: - split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) - for element in coefficient.ufl_element().sub_elements()] - coefficients.extend(split) - self.coefficient_split[coefficient] = split - else: - coefficients.append(coefficient) + coefficients.append(coefficient) # This is which coefficient in the original form the # current coefficient is. # Consider f*v*dx + g*v*ds, the full form contains two # coefficients, but each integral only requires one. - coefficient_numbers.append(form_data.original_coefficient_positions[i]) - for i, coefficient in enumerate(coefficients): - self.coefficient_args.append( - self._coefficient(coefficient, "w_%d" % i)) + coefficient_numbers.append(i) + self.coefficient_args.append(self.coefficients(coefficients, coefficient_numbers, "w")) self.kernel.coefficient_numbers = tuple(coefficient_numbers) def require_cell_orientations(self): """Set that the kernel requires cell orientations.""" + # FIXME: Don't need this in UFC self.kernel.oriented = True def construct_kernel(self, name, body): @@ -236,28 +266,96 @@ def construct_kernel(self, name, body): :arg body: function body (:class:`coffee.Block` node) :returns: :class:`Kernel` object """ - args = [self.local_tensor, self.coordinates_arg] - if self.kernel.oriented: - args.append(cell_orientations_coffee_arg) + args = [self.local_tensor] args.extend(self.coefficient_args) - if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: - args.append(coffee.Decl("unsigned int", - coffee.Symbol("facet", rank=(1,)), - qualifiers=["const"])) - elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: - args.append(coffee.Decl("unsigned int", - coffee.Symbol("facet", rank=(2,)), - qualifiers=["const"])) + args.extend(self.coordinates_args) + args.extend(self.facet_args) + args.extend(self.cell_orientations_args) + self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel -def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): + +def prepare_coefficients(coefficients, coefficient_numbers, name, mode=None, + interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. Mixed element Coefficients are rearranged here for interior facet integrals. + :arg coefficient: iterable of UFL Coefficients + :arg coefficient_numbers: iterable of coefficient indices in the original form + :arg name: unique name to refer to the Coefficient in the kernel + :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with + interior facet integrals on mixed elements + :arg interior_facet: interior facet integral? + :returns: (funarg, prepare, expressions) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions- GEM expressions referring to the Coefficient + values + """ + assert len(coefficients) == len(coefficient_numbers) + + # FIXME: hack; is actual number really needed? + num_coefficients = max(coefficient_numbers) + 1 if coefficient_numbers else 0 + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("const",), ()], + qualifiers=["const"]) + + # FIXME for interior facets + expressions = [] + for j, coefficient in enumerate(coefficients): + if coefficient.ufl_element().family() == 'Real': + shape = coefficient.ufl_shape + if shape == (): + # Scalar constant/real - needs one dummy index + expression = gem.Indexed(gem.Variable(name, (num_coefficients,) + (1,)), + (coefficient_numbers[j], 0,)) + # FIXME: It seems that Reals are not restricted in gem but are in UFL. + #if interior_facet: + # i = gem.Index() + # expression = gem.ComponentTensor( + # gem.Indexed(gem.Variable(name, (num_coefficients,) + (2,)), + # (coefficient_numbers[j], i,)), + # (i,)) + else: + # Mixed/vector/tensor constant/real + # FIXME: Tensor case is incorrect. Gem wants shaped expression, UFC requires flattened. + indices = tuple(gem.Index() for i in six.moves.xrange(len(shape))) + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), + (coefficient_numbers[j],) + indices), + indices) + else: + # Everything else + i = gem.Index() + fiat_element = create_element(coefficient.ufl_element()) + shape = (fiat_element.space_dimension(),) + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), + (coefficient_numbers[j], i)), + (i,)) + if interior_facet: + num_dofs = shape[0] + variable = gem.Variable(name, (num_coefficients, 2*num_dofs)) + # TODO: Seems that this reordering could be done using reinterpret_cast + expression = gem.ListTensor([[gem.Indexed(variable, (coefficient_numbers[j], i)) + for i in six.moves.xrange( 0, num_dofs)], + [gem.Indexed(variable, (coefficient_numbers[j], i)) + for i in six.moves.xrange(num_dofs, 2*num_dofs)]]) + + expressions.append(expression) + + return funarg, [], expressions + + +def prepare_coordinates(coefficient, name, mode=None, interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + coordinates. + :arg coefficient: UFL Coefficient :arg name: unique name to refer to the Coefficient in the kernel :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with @@ -270,122 +368,89 @@ def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): expression - GEM expression referring to the Coefficient values """ - if mode is None: - mode = 'manual_loop' + if not interior_facet: + funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("",)], + qualifiers=["const"])] + else: + funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_0"), + pointers=[("",)], + qualifiers=["const"]), + coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_1"), + pointers=[("",)], + qualifiers=["const"])] - assert mode in ['manual_loop', 'list_tensor'] - assert isinstance(interior_facet, bool) + fiat_element = create_element(coefficient.ufl_element()) + shape = (fiat_element.space_dimension(),) + gdim = coefficient.ufl_element().cell().geometric_dimension() + assert len(shape) == 1 and shape[0] % gdim == 0 + num_nodes = shape[0] / gdim + + # Translate coords from XYZXYZXYZXYZ into XXXXYYYYZZZZ + # NOTE: See dolfin/mesh/Cell.h:get_coordinate_dofs for ordering scheme + indices = numpy.arange(num_nodes * gdim).reshape(num_nodes, gdim).transpose().flatten() + if not interior_facet: + variable = gem.Variable(name, shape) + expression = gem.ListTensor([gem.Indexed(variable, (i,)) for i in indices]) + else: + variable0 = gem.Variable(name+"_0", shape) + variable1 = gem.Variable(name+"_1", shape) + expression = gem.ListTensor([[gem.Indexed(variable0, (i,)) for i in indices], + [gem.Indexed(variable1, (i,)) for i in indices]]) - if coefficient.ufl_element().family() == 'Real': - # Constant - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("restrict",)], - qualifiers=["const"]) + return funargs, [], expression - expression = gem.reshape(gem.Variable(name, (None,)), - coefficient.ufl_shape) - return funarg, [], expression +def prepare_facets(integral_type): + """Bridges the kernel interface and the GEM abstraction for + facets. - fiat_element = create_element(coefficient.ufl_element()) - size = fiat_element.space_dimension() + :arg integral_type + :returns: (funarg, prepare, expression) + funargs - list of :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions- list of GEM expressions referring to facets + """ + funargs = [] + expressions = [] - if not interior_facet: - # Simple case - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("const", "restrict"), ("restrict",)], - qualifiers=["const"]) - - expression = gem.reshape(gem.Variable(name, (size, 1)), (size,), ()) - - return funarg, [], expression - - if not isinstance(fiat_element, MixedElement): - # Interior facet integral - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("const", "restrict"), ("restrict",)], - qualifiers=["const"]) - - expression = gem.reshape( - gem.Variable(name, (2 * size, 1)), (2, size), () - ) - - return funarg, [], expression - - # Interior facet integral + mixed / vector element - - # Here we need to reorder the coefficient values. - # - # Incoming ordering: E1+ E1- E2+ E2- E3+ E3- - # Required ordering: E1+ E2+ E3+ E1- E2- E3- - # - # Each of E[n]{+,-} is a vector of basis function coefficients for - # subelement E[n]. - # - # There are two code generation method to reorder the values. - # We have not done extensive research yet as to which way yield - # faster code. - - if mode == 'manual_loop': - # In this case we generate loops outside the GEM abstraction - # to reorder the values. A whole E[n]{+,-} block is copied by - # a single loop. - name_ = name + "_" - shape = (2, size) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name_), - pointers=[("const", "restrict"), ("restrict",)], - qualifiers=["const"]) - prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] - expression = gem.Variable(name, shape) - - offset = 0 - i = coffee.Symbol("i") - for element in fiat_element.elements(): - space_dim = element.space_dimension() - - loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, "i"), - offset=((1, 0), (1, offset))), - coffee.Symbol(name_, rank=("i", 0), - offset=((1, 2 * offset), (1, 0)))) - prepare.append(coffee_for(i, space_dim, loop_body)) - - loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, "i"), - offset=((1, 0), (1, offset))), - coffee.Symbol(name_, rank=("i", 0), - offset=((1, 2 * offset + space_dim), (1, 0)))) - prepare.append(coffee_for(i, space_dim, loop_body)) - - offset += space_dim - - return funarg, prepare, expression - - elif mode == 'list_tensor': - # In this case we generate a gem.ListTensor to do the - # reordering. Every single element in a E[n]{+,-} block is - # referenced separately. - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("const", "restrict"), ("restrict",)], - qualifiers=["const"]) - - variable = gem.Variable(name, (2 * size, 1)) - - facet_0 = [] - facet_1 = [] - offset = 0 - for element in fiat_element.elements(): - space_dim = element.space_dimension() - - for i in range(offset, offset + space_dim): - facet_0.append(gem.Indexed(variable, (i, 0))) - offset += space_dim - - for i in range(offset, offset + space_dim): - facet_1.append(gem.Indexed(variable, (i, 0))) - offset += space_dim - - expression = gem.ListTensor(numpy.array([facet_0, facet_1])) - return funarg, [], expression + if integral_type in ["exterior_facet", "exterior_facet_vert"]: + funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet"))) + expressions.append(gem.VariableIndex(gem.Variable("facet", ()))) + elif integral_type in ["interior_facet", "interior_facet_vert"]: + funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet_0"))) + funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet_1"))) + expressions.append(gem.VariableIndex(gem.Variable("facet_0", ()))) + expressions.append(gem.VariableIndex(gem.Variable("facet_1", ()))) + + return funargs, [], expressions + + +def prepare_cell_orientations(integral_type): + """Bridges the kernel interface and the GEM abstraction for + cell orientations. + + :arg integral_type + :returns: (funarg, prepare, expression) + funargs - list of :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions- list of GEM expressions referring to facets + """ + funargs = [] + expressions = [] + + if integral_type in ["interior_facet", "interior_facet_vert"]: + funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation_0"))) + funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation_1"))) + expressions.append(gem.Variable("cell_orientation_0", ())) + expressions.append(gem.Variable("cell_orientation_1", ())) + else: + funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation"))) + expressions.append(gem.Variable("cell_orientation", ())) + + return funargs, [], expressions def prepare_arguments(arguments, indices, interior_facet=False): @@ -405,114 +470,37 @@ def prepare_arguments(arguments, indices, interior_facet=False): finalise - list of COFFEE nodes to be appended to the kernel body """ - from itertools import product - assert isinstance(interior_facet, bool) - - if len(arguments) == 0: - # No arguments - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=(1,))) - expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) - - return funarg, [], [expression], [] + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A"), pointers=[()]) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - - if not interior_facet: - # Not an interior facet integral - shape = tuple(element.space_dimension() for element in elements) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) - expression = gem.Indexed(gem.Variable("A", shape), indices) - - return funarg, [], [expression], [] - - if not any(isinstance(element, MixedElement) for element in elements): - # Interior facet integral, but no vector (mixed) arguments - shape = tuple(2 * element.space_dimension() for element in elements) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) - varexp = gem.Variable("A", shape) - - expressions = [] - for restrictions in product((0, 1), repeat=len(arguments)): - expressions.append(gem_FlexiblyIndexed( - varexp, - tuple((r * e.space_dimension(), ((i, e.space_dimension()),)) - for e, i, r in zip(elements, indices, restrictions)) - )) - - return funarg, [], expressions, [] - - # Interior facet integral + vector (mixed) argument(s) shape = tuple(element.space_dimension() for element in elements) - funarg_shape = tuple(s * 2 for s in shape) - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=funarg_shape)) - - prepare = [] - expressions = [] - - references = [] - for restrictions in product((0, 1), repeat=len(arguments)): - name = "A" + "".join(map(str, restrictions)) - - prepare.append(coffee.Decl(SCALAR_TYPE, - coffee.Symbol(name, rank=shape), - init=coffee.ArrayInit(numpy.zeros(1)))) - expressions.append(gem.Indexed(gem.Variable(name, shape), indices)) - - for multiindex in numpy.ndindex(shape): - references.append(coffee.Symbol(name, multiindex)) - - restriction_shape = [] - for e in elements: - if isinstance(e, MixedElement): - restriction_shape += [len(e.elements()), - e.elements()[0].space_dimension()] - else: - restriction_shape += [1, e.space_dimension()] - restriction_shape = tuple(restriction_shape) - - references = numpy.array(references) - if len(arguments) == 1: - references = references.reshape((2,) + restriction_shape) - references = references.transpose(1, 0, 2) - elif len(arguments) == 2: - references = references.reshape((2, 2) + restriction_shape) - references = references.transpose(2, 0, 3, 4, 1, 5) - references = references.reshape(funarg_shape) - - finalise = [] - for multiindex in numpy.ndindex(funarg_shape): - finalise.append(coffee.Assign(coffee.Symbol("A", rank=multiindex), - references[multiindex])) + if len(arguments) == 0: + shape = (1,) + indices = (0,) + if interior_facet: + shape = tuple(j for i in zip(len(shape)*(2,), shape) for j in i) + indices = tuple(product(*chain(*(((0, 1), (i,)) for i in indices)))) + else: + indices = (indices,) - return funarg, prepare, expressions, finalise + expressions = [gem.Indexed(gem.Variable("AA", shape), i) for i in indices] + reshape = coffee.Decl(SCALAR_TYPE, + coffee.Symbol("(&%s)" % expressions[0].children[0].name, + rank=shape), + init="*reinterpret_cast<%s (*)%s>(%s)" % + (SCALAR_TYPE, + "".join("[%s]"%i for i in shape), + funarg.sym.gencode() + ) + ) + zero = coffee.FlatBlock("memset(%s, 0, %d * sizeof(*%s));%s" % + (funarg.sym.gencode(), numpy.product(shape), funarg.sym.gencode(), os.linesep)) + prepare = [zero, reshape] -def coffee_for(index, extent, body): - """Helper function to make a COFFEE loop. + return funarg, prepare, expressions, [] - :arg index: :class:`coffee.Symbol` loop index - :arg extent: loop extent (integer) - :arg body: loop body (COFFEE node) - :returns: COFFEE loop - """ - return coffee.For(coffee.Decl("int", index, init=0), - coffee.Less(index, extent), - coffee.Incr(index, 1), - body) def needs_cell_orientations(ir): - """Does a multi-root GEM expression DAG references cell - orientations?""" - for node in traversal(ir): - if isinstance(node, gem.Variable) and node.name == "cell_orientations": - return True - return False - - -cell_orientations_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), - pointers=[("restrict",), ("restrict",)], - qualifiers=["const"]) -"""COFFEE function argument for cell orientations""" + return True From 043b8bf2387b9640368aa53f07962e46630f8631 Mon Sep 17 00:00:00 2001 From: Jan Blechta Date: Sat, 17 Sep 2016 00:34:20 +0200 Subject: [PATCH 164/809] Support PyOP2 and UFC backends UFC works, tested by FFC regression test. PyOP2 needs testing. The codeflow is a mess. Needs some restructuring. --- tsfc/backends/__init__.py | 0 tsfc/backends/pyop2.py | 431 ++++++++++++++++++++++++++++++++++++++ tsfc/backends/ufc.py | 395 ++++++++++++++++++++++++++++++++++ tsfc/driver.py | 20 +- tsfc/kernel_interface.py | 418 ++++-------------------------------- 5 files changed, 878 insertions(+), 386 deletions(-) create mode 100644 tsfc/backends/__init__.py create mode 100644 tsfc/backends/pyop2.py create mode 100644 tsfc/backends/ufc.py diff --git a/tsfc/backends/__init__.py b/tsfc/backends/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tsfc/backends/pyop2.py b/tsfc/backends/pyop2.py new file mode 100644 index 0000000000..1de9020afe --- /dev/null +++ b/tsfc/backends/pyop2.py @@ -0,0 +1,431 @@ +from __future__ import absolute_import + +import numpy +from itertools import product + +from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace + +import coffee.base as coffee + +import gem +from gem.node import traversal +from gem.gem import FlexiblyIndexed as gem_FlexiblyIndexed + +from tsfc.kernel_interface import Kernel, KernelBuilderBase +from tsfc.fiatinterface import create_element +from tsfc.mixedelement import MixedElement +from tsfc.coffee import SCALAR_TYPE + + +class KernelBuilder(KernelBuilderBase): + """Helper class for building a :class:`Kernel` object.""" + + def __init__(self, integral_type, subdomain_id, domain_number): + """Initialise a kernel builder.""" + super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) + + self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, + domain_number=domain_number) + self.local_tensor = None + self.coordinates_arg = None + self.coefficient_args = [] + self.coefficient_split = {} + + # Facet number + if integral_type in ['exterior_facet', 'exterior_facet_vert']: + facet = gem.Variable('facet', (1,)) + self._facet_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} + elif integral_type in ['interior_facet', 'interior_facet_vert']: + facet = gem.Variable('facet', (2,)) + self._facet_number = { + '+': gem.VariableIndex(gem.Indexed(facet, (0,))), + '-': gem.VariableIndex(gem.Indexed(facet, (1,))) + } + elif integral_type == 'interior_facet_horiz': + self._facet_number = {'+': 1, '-': 0} + + # Cell orientation + if self.interior_facet: + self._cell_orientations = gem.Variable("cell_orientations", (2, 1)) + else: + self._cell_orientations = gem.Variable("cell_orientations", (1, 1)) + + def facet_number(self, restriction): + """Facet number as a GEM index.""" + return self._facet_number[restriction] + + def cell_orientations_mapper(self, facet): + return gem.Indexed(self._cell_orientations, (facet, 0)) + + def _coefficient(self, coefficient, name, mode=None): + """Prepare a coefficient. Adds glue code for the coefficient + and adds the coefficient to the coefficient map. + + :arg coefficient: :class:`ufl.Coefficient` + :arg name: coefficient name + :arg mode: see :func:`prepare_coefficient` + :returns: COFFEE function argument for the coefficient + """ + funarg, prepare, expression = _prepare_coefficient( + coefficient, name, mode=mode, + interior_facet=self.interior_facet) + self.apply_glue(prepare) + self.coefficient_map[coefficient] = expression + return funarg + + + def set_arguments(self, arguments, indices): + """Process arguments. + + :arg arguments: :class:`ufl.Argument`s + :arg indices: GEM argument indices + :returns: GEM expression representing the return variable + """ + self.local_tensor, expressions = self.arguments(arguments, indices) + return expressions + + def set_coordinates(self, coefficient, mode=None): + """Prepare the coordinate field. + + :arg coefficient: :class:`ufl.Coefficient` + :arg mode: see :func:`prepare_coefficient` + """ + self.coordinates_arg = self._coefficient(coefficient, "coords", mode) + + def set_facets(self): + """Prepare the facets. + """ + return + + def set_cell_orientations(self): + """Prepare the cell orientations. + """ + return + + def set_coefficients(self, integral_data, form_data): + """Prepare the coefficients of the form. + + :arg integral_data: UFL integral data + :arg form_data: UFL form data + """ + coefficients = [] + coefficient_numbers = [] + # enabled_coefficients is a boolean array that indicates which + # of reduced_coefficients the integral requires. + for i in range(len(integral_data.enabled_coefficients)): + if integral_data.enabled_coefficients[i]: + coefficient = form_data.reduced_coefficients[i] + if type(coefficient.ufl_element()) == ufl_MixedElement: + split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) + for element in coefficient.ufl_element().sub_elements()] + coefficients.extend(split) + self.coefficient_split[coefficient] = split + else: + coefficients.append(coefficient) + # This is which coefficient in the original form the + # current coefficient is. + # Consider f*v*dx + g*v*ds, the full form contains two + # coefficients, but each integral only requires one. + coefficient_numbers.append(form_data.original_coefficient_positions[i]) + for i, coefficient in enumerate(coefficients): + self.coefficient_args.append( + self._coefficient(coefficient, "w_%d" % i)) + self.kernel.coefficient_numbers = tuple(coefficient_numbers) + + def require_cell_orientations(self): + """Set that the kernel requires cell orientations.""" + self.kernel.oriented = True + + def construct_kernel(self, name, body): + """Construct a fully built :class:`Kernel`. + + This function contains the logic for building the argument + list for assembly kernels. + + :arg name: function name + :arg body: function body (:class:`coffee.Block` node) + :returns: :class:`Kernel` object + """ + args = [self.local_tensor, self.coordinates_arg] + if self.kernel.oriented: + args.append(cell_orientations_coffee_arg) + args.extend(self.coefficient_args) + if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: + args.append(coffee.Decl("unsigned int", + coffee.Symbol("facet", rank=(1,)), + qualifiers=["const"])) + elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: + args.append(coffee.Decl("unsigned int", + coffee.Symbol("facet", rank=(2,)), + qualifiers=["const"])) + + self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) + return self.kernel + + + @staticmethod + def needs_cell_orientations(ir): + """Does a multi-root GEM expression DAG references cell + orientations?""" + for node in traversal(ir): + if isinstance(node, gem.Variable) and node.name == "cell_orientations": + return True + return False + + @staticmethod + def prepare_arguments(arguments, indices, interior_facet=False): + return _prepare_arguments(arguments, indices, interior_facet=interior_facet) + +def _prepare_coefficient(coefficient, name, mode=None, interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + Coefficients. Mixed element Coefficients are rearranged here for + interior facet integrals. + + :arg coefficient: UFL Coefficient + :arg name: unique name to refer to the Coefficient in the kernel + :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with + interior facet integrals on mixed elements + :arg interior_facet: interior facet integral? + :returns: (funarg, prepare, expression) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expression - GEM expression referring to the Coefficient + values + """ + if mode is None: + mode = 'manual_loop' + + assert mode in ['manual_loop', 'list_tensor'] + assert isinstance(interior_facet, bool) + + if coefficient.ufl_element().family() == 'Real': + # Constant + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("restrict",)], + qualifiers=["const"]) + + expression = gem.reshape(gem.Variable(name, (None,)), + coefficient.ufl_shape) + + return funarg, [], expression + + fiat_element = create_element(coefficient.ufl_element()) + size = fiat_element.space_dimension() + + if not interior_facet: + # Simple case + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("const", "restrict"), ("restrict",)], + qualifiers=["const"]) + + expression = gem.reshape(gem.Variable(name, (size, 1)), (size,), ()) + + return funarg, [], expression + + if not isinstance(fiat_element, MixedElement): + # Interior facet integral + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("const", "restrict"), ("restrict",)], + qualifiers=["const"]) + + expression = gem.reshape( + gem.Variable(name, (2 * size, 1)), (2, size), () + ) + + return funarg, [], expression + + # Interior facet integral + mixed / vector element + + # Here we need to reorder the coefficient values. + # + # Incoming ordering: E1+ E1- E2+ E2- E3+ E3- + # Required ordering: E1+ E2+ E3+ E1- E2- E3- + # + # Each of E[n]{+,-} is a vector of basis function coefficients for + # subelement E[n]. + # + # There are two code generation method to reorder the values. + # We have not done extensive research yet as to which way yield + # faster code. + + if mode == 'manual_loop': + # In this case we generate loops outside the GEM abstraction + # to reorder the values. A whole E[n]{+,-} block is copied by + # a single loop. + name_ = name + "_" + shape = (2, size) + + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name_), + pointers=[("const", "restrict"), ("restrict",)], + qualifiers=["const"]) + prepare = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name, rank=shape))] + expression = gem.Variable(name, shape) + + offset = 0 + i = coffee.Symbol("i") + for element in fiat_element.elements(): + space_dim = element.space_dimension() + + loop_body = coffee.Assign(coffee.Symbol(name, rank=(0, "i"), + offset=((1, 0), (1, offset))), + coffee.Symbol(name_, rank=("i", 0), + offset=((1, 2 * offset), (1, 0)))) + prepare.append(coffee_for(i, space_dim, loop_body)) + + loop_body = coffee.Assign(coffee.Symbol(name, rank=(1, "i"), + offset=((1, 0), (1, offset))), + coffee.Symbol(name_, rank=("i", 0), + offset=((1, 2 * offset + space_dim), (1, 0)))) + prepare.append(coffee_for(i, space_dim, loop_body)) + + offset += space_dim + + return funarg, prepare, expression + + elif mode == 'list_tensor': + # In this case we generate a gem.ListTensor to do the + # reordering. Every single element in a E[n]{+,-} block is + # referenced separately. + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("const", "restrict"), ("restrict",)], + qualifiers=["const"]) + + variable = gem.Variable(name, (2 * size, 1)) + + facet_0 = [] + facet_1 = [] + offset = 0 + for element in fiat_element.elements(): + space_dim = element.space_dimension() + + for i in range(offset, offset + space_dim): + facet_0.append(gem.Indexed(variable, (i, 0))) + offset += space_dim + + for i in range(offset, offset + space_dim): + facet_1.append(gem.Indexed(variable, (i, 0))) + offset += space_dim + + expression = gem.ListTensor(numpy.array([facet_0, facet_1])) + return funarg, [], expression + + +def _prepare_arguments(arguments, indices, interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + Arguments. Vector Arguments are rearranged here for interior + facet integrals. + + :arg arguments: UFL Arguments + :arg indices: Argument indices + :arg interior_facet: interior facet integral? + :returns: (funarg, prepare, expression, finalise) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions - GEM expressions referring to the argument + tensor + finalise - list of COFFEE nodes to be appended to the + kernel body + """ + assert isinstance(interior_facet, bool) + + if len(arguments) == 0: + # No arguments + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=(1,))) + expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) + + return funarg, [], [expression], [] + + elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + + if not interior_facet: + # Not an interior facet integral + shape = tuple(element.space_dimension() for element in elements) + + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) + expression = gem.Indexed(gem.Variable("A", shape), indices) + + return funarg, [], [expression], [] + + if not any(isinstance(element, MixedElement) for element in elements): + # Interior facet integral, but no vector (mixed) arguments + shape = tuple(2 * element.space_dimension() for element in elements) + + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=shape)) + varexp = gem.Variable("A", shape) + + expressions = [] + for restrictions in product((0, 1), repeat=len(arguments)): + expressions.append(gem_FlexiblyIndexed( + varexp, + tuple((r * e.space_dimension(), ((i, e.space_dimension()),)) + for e, i, r in zip(elements, indices, restrictions)) + )) + + return funarg, [], expressions, [] + + # Interior facet integral + vector (mixed) argument(s) + shape = tuple(element.space_dimension() for element in elements) + funarg_shape = tuple(s * 2 for s in shape) + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=funarg_shape)) + + prepare = [] + expressions = [] + + references = [] + for restrictions in product((0, 1), repeat=len(arguments)): + name = "A" + "".join(map(str, restrictions)) + + prepare.append(coffee.Decl(SCALAR_TYPE, + coffee.Symbol(name, rank=shape), + init=coffee.ArrayInit(numpy.zeros(1)))) + expressions.append(gem.Indexed(gem.Variable(name, shape), indices)) + + for multiindex in numpy.ndindex(shape): + references.append(coffee.Symbol(name, multiindex)) + + restriction_shape = [] + for e in elements: + if isinstance(e, MixedElement): + restriction_shape += [len(e.elements()), + e.elements()[0].space_dimension()] + else: + restriction_shape += [1, e.space_dimension()] + restriction_shape = tuple(restriction_shape) + + references = numpy.array(references) + if len(arguments) == 1: + references = references.reshape((2,) + restriction_shape) + references = references.transpose(1, 0, 2) + elif len(arguments) == 2: + references = references.reshape((2, 2) + restriction_shape) + references = references.transpose(2, 0, 3, 4, 1, 5) + references = references.reshape(funarg_shape) + + finalise = [] + for multiindex in numpy.ndindex(funarg_shape): + finalise.append(coffee.Assign(coffee.Symbol("A", rank=multiindex), + references[multiindex])) + + return funarg, prepare, expressions, finalise + + +def coffee_for(index, extent, body): + """Helper function to make a COFFEE loop. + + :arg index: :class:`coffee.Symbol` loop index + :arg extent: loop extent (integer) + :arg body: loop body (COFFEE node) + :returns: COFFEE loop + """ + return coffee.For(coffee.Decl("int", index, init=0), + coffee.Less(index, extent), + coffee.Incr(index, 1), + body) + + +cell_orientations_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), + pointers=[("restrict",), ("restrict",)], + qualifiers=["const"]) +"""COFFEE function argument for cell orientations""" diff --git a/tsfc/backends/ufc.py b/tsfc/backends/ufc.py new file mode 100644 index 0000000000..aaff2cbefb --- /dev/null +++ b/tsfc/backends/ufc.py @@ -0,0 +1,395 @@ +from __future__ import absolute_import + +import os +import six +import numpy +from itertools import chain, product + +import coffee.base as coffee + +import gem + +from tsfc.kernel_interface import Kernel, KernelBuilderBase +from tsfc.fiatinterface import create_element +from tsfc.coffee import SCALAR_TYPE + + +class KernelBuilder(KernelBuilderBase): + """Helper class for building a :class:`Kernel` object.""" + + def __init__(self, integral_type, subdomain_id, domain_number): + """Initialise a kernel builder.""" + super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) + + self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, + domain_number=domain_number) + self.local_tensor = None + self.coordinates_arg = None + self.coefficient_args = [] + self.coefficient_split = {} + self.cell_orientations_args = [] + + def facet_number(self, restriction): + """Facet number as a GEM index.""" + f = {None: 0, '+': 0, '-': 1}[restriction] + return self.facet_mapper[f] + + def cell_orientations_mapper(self, facet): + return self._cell_orientations[facet] + + def coefficients(self, coefficients, coefficient_numbers, name, mode=None): + """Prepare coefficients. Adds glue code for the coefficients + and adds the coefficients to the coefficient map. + + :arg coefficient: iterable of :class:`ufl.Coefficient`s + :arg coefficient_numbers: iterable of coefficient indices in the original form + :arg name: coefficient name + :arg mode: see :func:`prepare_coefficient` + :returns: COFFEE function argument for the coefficient + """ + funarg, prepare, expressions = _prepare_coefficients( + coefficients, coefficient_numbers, name, mode=mode, + interior_facet=self.interior_facet) + self.apply_glue(prepare) + for i, coefficient in enumerate(coefficients): + self.coefficient_map[coefficient] = expressions[i] + return funarg + + def coordinates(self, coefficient, name, mode=None): + """Prepare a coordinates. Adds glue code for the coefficient + and adds the coefficient to the coefficient map. + + :arg coefficient: :class:`ufl.Coefficient` + :arg name: coefficient name + :arg mode: see :func:`prepare_coefficient` + :returns: COFFEE function arguments for the coefficient + """ + funargs, prepare, expression = _prepare_coordinates( + coefficient, name, mode=mode, + interior_facet=self.interior_facet) + self.apply_glue(prepare) + self.coefficient_map[coefficient] = expression + return funargs + + def facets(self, integral_type): + """Prepare facets. Adds glue code for facets + and stores facet expression. + + :arg integral_type + :returns: list of COFFEE function arguments for facets + """ + funargs, prepare, expressions = _prepare_facets(integral_type) + self.apply_glue(prepare) + self.facet_mapper = expressions + return funargs + + + def set_arguments(self, arguments, indices): + """Process arguments. + + :arg arguments: :class:`ufl.Argument`s + :arg indices: GEM argument indices + :returns: GEM expression representing the return variable + """ + self.local_tensor, expressions = self.arguments(arguments, indices) + return expressions + + def set_coordinates(self, coefficient, mode=None): + """Prepare the coordinate field. + + :arg coefficient: :class:`ufl.Coefficient` + :arg mode: see :func:`prepare_coefficient` + """ + self.coordinates_args = self.coordinates(coefficient, "coordinate_dofs", mode) + + def set_facets(self): + """Prepare the facets. + """ + self.facet_args = self.facets(self.kernel.integral_type) + + def set_cell_orientations(self): + """Prepare the cell orientations. + """ + self.cell_orientations_args = self.cell_orientations(self.kernel.integral_type) + + def set_coefficients(self, integral_data, form_data): + """Prepare the coefficients of the form. + + :arg integral_data: UFL integral data + :arg form_data: UFL form data + """ + coefficients = [] + coefficient_numbers = [] + # enabled_coefficients is a boolean array that indicates which + # of reduced_coefficients the integral requires. + for i in range(len(integral_data.enabled_coefficients)): + if integral_data.enabled_coefficients[i]: + coefficient = form_data.reduced_coefficients[i] + coefficients.append(coefficient) + # This is which coefficient in the original form the + # current coefficient is. + # Consider f*v*dx + g*v*ds, the full form contains two + # coefficients, but each integral only requires one. + coefficient_numbers.append(i) + self.coefficient_args.append(self.coefficients(coefficients, coefficient_numbers, "w")) + self.kernel.coefficient_numbers = tuple(coefficient_numbers) + + def require_cell_orientations(self): + """Set that the kernel requires cell orientations.""" + self.kernel.oriented = True + + def construct_kernel(self, name, body): + """Construct a fully built :class:`Kernel`. + + This function contains the logic for building the argument + list for assembly kernels. + + :arg name: function name + :arg body: function body (:class:`coffee.Block` node) + :returns: :class:`Kernel` object + """ + args = [self.local_tensor] + args.extend(self.coefficient_args) + args.extend(self.coordinates_args) + args.extend(self.facet_args) + args.extend(self.cell_orientations_args) + + self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) + return self.kernel + + + @staticmethod + def needs_cell_orientations(ir): + """UFC requires cell orientations argument(s) everytime""" + return True + + @staticmethod + def prepare_arguments(arguments, indices, interior_facet=False): + return _prepare_arguments(arguments, indices, interior_facet=interior_facet) + + @staticmethod + def prepare_cell_orientations(integral_type): + return _prepare_cell_orientations(integral_type) + + +def _prepare_coefficients(coefficients, coefficient_numbers, name, mode=None, + interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + Coefficients. Mixed element Coefficients are rearranged here for + interior facet integrals. + + :arg coefficient: iterable of UFL Coefficients + :arg coefficient_numbers: iterable of coefficient indices in the original form + :arg name: unique name to refer to the Coefficient in the kernel + :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with + interior facet integrals on mixed elements + :arg interior_facet: interior facet integral? + :returns: (funarg, prepare, expressions) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions- GEM expressions referring to the Coefficient + values + """ + assert len(coefficients) == len(coefficient_numbers) + + # FIXME: hack; is actual number really needed? + num_coefficients = max(coefficient_numbers) + 1 if coefficient_numbers else 0 + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("const",), ()], + qualifiers=["const"]) + + # FIXME for interior facets + expressions = [] + for j, coefficient in enumerate(coefficients): + if coefficient.ufl_element().family() == 'Real': + shape = coefficient.ufl_shape + if shape == (): + # Scalar constant/real - needs one dummy index + expression = gem.Indexed(gem.Variable(name, (num_coefficients,) + (1,)), + (coefficient_numbers[j], 0,)) + # FIXME: It seems that Reals are not restricted in gem but are in UFL. + #if interior_facet: + # i = gem.Index() + # expression = gem.ComponentTensor( + # gem.Indexed(gem.Variable(name, (num_coefficients,) + (2,)), + # (coefficient_numbers[j], i,)), + # (i,)) + else: + # Mixed/vector/tensor constant/real + # FIXME: Tensor case is incorrect. Gem wants shaped expression, UFC requires flattened. + indices = tuple(gem.Index() for i in six.moves.xrange(len(shape))) + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), + (coefficient_numbers[j],) + indices), + indices) + else: + # Everything else + i = gem.Index() + fiat_element = create_element(coefficient.ufl_element()) + shape = (fiat_element.space_dimension(),) + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), + (coefficient_numbers[j], i)), + (i,)) + if interior_facet: + num_dofs = shape[0] + variable = gem.Variable(name, (num_coefficients, 2*num_dofs)) + # TODO: Seems that this reordering could be done using reinterpret_cast + expression = gem.ListTensor([[gem.Indexed(variable, (coefficient_numbers[j], i)) + for i in six.moves.xrange( 0, num_dofs)], + [gem.Indexed(variable, (coefficient_numbers[j], i)) + for i in six.moves.xrange(num_dofs, 2*num_dofs)]]) + + expressions.append(expression) + + return funarg, [], expressions + + +def _prepare_coordinates(coefficient, name, mode=None, interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + coordinates. + + :arg coefficient: UFL Coefficient + :arg name: unique name to refer to the Coefficient in the kernel + :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with + interior facet integrals on mixed elements + :arg interior_facet: interior facet integral? + :returns: (funarg, prepare, expression) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expression - GEM expression referring to the Coefficient + values + """ + if not interior_facet: + funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + pointers=[("",)], + qualifiers=["const"])] + else: + funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_0"), + pointers=[("",)], + qualifiers=["const"]), + coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_1"), + pointers=[("",)], + qualifiers=["const"])] + + fiat_element = create_element(coefficient.ufl_element()) + shape = (fiat_element.space_dimension(),) + gdim = coefficient.ufl_element().cell().geometric_dimension() + assert len(shape) == 1 and shape[0] % gdim == 0 + num_nodes = shape[0] / gdim + + # Translate coords from XYZXYZXYZXYZ into XXXXYYYYZZZZ + # NOTE: See dolfin/mesh/Cell.h:get_coordinate_dofs for ordering scheme + indices = numpy.arange(num_nodes * gdim).reshape(num_nodes, gdim).transpose().flatten() + if not interior_facet: + variable = gem.Variable(name, shape) + expression = gem.ListTensor([gem.Indexed(variable, (i,)) for i in indices]) + else: + variable0 = gem.Variable(name+"_0", shape) + variable1 = gem.Variable(name+"_1", shape) + expression = gem.ListTensor([[gem.Indexed(variable0, (i,)) for i in indices], + [gem.Indexed(variable1, (i,)) for i in indices]]) + + return funargs, [], expression + + +def _prepare_facets(integral_type): + """Bridges the kernel interface and the GEM abstraction for + facets. + + :arg integral_type + :returns: (funarg, prepare, expression) + funargs - list of :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions- list of GEM expressions referring to facets + """ + funargs = [] + expressions = [] + + if integral_type in ["exterior_facet", "exterior_facet_vert"]: + funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet"))) + expressions.append(gem.VariableIndex(gem.Variable("facet", ()))) + elif integral_type in ["interior_facet", "interior_facet_vert"]: + funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet_0"))) + funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet_1"))) + expressions.append(gem.VariableIndex(gem.Variable("facet_0", ()))) + expressions.append(gem.VariableIndex(gem.Variable("facet_1", ()))) + + return funargs, [], expressions + + +def _prepare_cell_orientations(integral_type): + """Bridges the kernel interface and the GEM abstraction for + cell orientations. + + :arg integral_type + :returns: (funarg, prepare, expression) + funargs - list of :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions- list of GEM expressions referring to facets + """ + funargs = [] + expressions = [] + + if integral_type in ["interior_facet", "interior_facet_vert"]: + funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation_0"))) + funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation_1"))) + expressions.append(gem.Variable("cell_orientation_0", ())) + expressions.append(gem.Variable("cell_orientation_1", ())) + else: + funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation"))) + expressions.append(gem.Variable("cell_orientation", ())) + + return funargs, [], expressions + + +def _prepare_arguments(arguments, indices, interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + Arguments. Vector Arguments are rearranged here for interior + facet integrals. + + :arg arguments: UFL Arguments + :arg indices: Argument indices + :arg interior_facet: interior facet integral? + :returns: (funarg, prepare, expression, finalise) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions - GEM expressions referring to the argument + tensor + finalise - list of COFFEE nodes to be appended to the + kernel body + """ + funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A"), pointers=[()]) + + elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + shape = tuple(element.space_dimension() for element in elements) + if len(arguments) == 0: + shape = (1,) + indices = (0,) + if interior_facet: + shape = tuple(j for i in zip(len(shape)*(2,), shape) for j in i) + indices = tuple(product(*chain(*(((0, 1), (i,)) for i in indices)))) + else: + indices = (indices,) + + expressions = [gem.Indexed(gem.Variable("AA", shape), i) for i in indices] + + reshape = coffee.Decl(SCALAR_TYPE, + coffee.Symbol("(&%s)" % expressions[0].children[0].name, + rank=shape), + init="*reinterpret_cast<%s (*)%s>(%s)" % + (SCALAR_TYPE, + "".join("[%s]"%i for i in shape), + funarg.sym.gencode() + ) + ) + zero = coffee.FlatBlock("memset(%s, 0, %d * sizeof(*%s));%s" % + (funarg.sym.gencode(), numpy.product(shape), funarg.sym.gencode(), os.linesep)) + prepare = [zero, reshape] + + return funarg, prepare, expressions, [] diff --git a/tsfc/driver.py b/tsfc/driver.py index 30e174531c..722ec37906 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -15,7 +15,7 @@ from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.fiatinterface import QuadratureRule, as_fiat_cell, create_quadrature -from tsfc.kernel_interface import KernelBuilder, needs_cell_orientations +from tsfc.kernel_interface import KernelBuilder from tsfc.logging import logger @@ -60,13 +60,15 @@ def compile_form(form, prefix="form", parameters=None): return kernels -def compile_integral(integral_data, form_data, prefix, parameters): +def compile_integral(integral_data, form_data, prefix, parameters, + backend="pyop2"): """Compiles a UFL integral into an assembly kernel. :arg integral_data: UFL integral data :arg form_data: UFL form data :arg prefix: kernel name will start with this string :arg parameters: parameters object + :arg backend: output format :returns: a kernel, or None if the integral simplifies to zero """ integral_type = integral_data.integral_type @@ -83,7 +85,7 @@ def compile_integral(integral_data, form_data, prefix, parameters): # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() - builder = KernelBuilder(integral_type, integral_data.subdomain_id, + builder = KernelBuilder(backend, integral_type, integral_data.subdomain_id, domain_numbering[integral_data.domain]) return_variables = builder.set_arguments(arguments, argument_indices) @@ -91,12 +93,10 @@ def compile_integral(integral_data, form_data, prefix, parameters): if ufl_utils.is_element_affine(mesh.ufl_coordinate_element()): # For affine mesh geometries we prefer code generation that # composes well with optimisations. - #builder.set_coordinates(coordinates, "coords", mode='list_tensor') - builder.set_coordinates(coordinates, "coordinate_dofs", mode='list_tensor') + builder.set_coordinates(coordinates, mode='list_tensor') else: # Otherwise we use the approach that might be faster (?) - #builder.set_coordinates(coordinates, "coords") - builder.set_coordinates(coordinates, "coordinate_dofs") + builder.set_coordinates(coordinates) builder.set_coefficients(integral_data, form_data) @@ -142,7 +142,7 @@ def coefficient(ufl_coefficient, r): point_index=quadrature_index, coefficient=coefficient, index_cache=index_cache) - if parameters["unroll_indexsum"]: + if parameters.get("unroll_indexsum"): ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) expr, = ir if quadrature_index in expr.free_indices: @@ -178,7 +178,7 @@ def facetarea(): coefficient=builder.coefficient, facet_number=builder.facet_number, index_cache=index_cache) - if parameters["unroll_indexsum"]: + if parameters.get("unroll_indexsum"): ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) expr, = ir if quadrature_index in expr.free_indices: @@ -239,7 +239,7 @@ def facetarea(): ir = opt.remove_componenttensors(ir) # Look for cell orientations in the IR - if needs_cell_orientations(ir): + if builder.needs_cell_orientations(ir): builder.require_cell_orientations() impero_c = impero_utils.compile_gem(return_variables, ir, diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 52e33564d3..344aa3ca66 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -1,19 +1,10 @@ from __future__ import absolute_import -import os -import six import numpy -from itertools import chain, product import coffee.base as coffee import gem -from gem.node import traversal -from gem.gem import FlexiblyIndexed as gem_FlexiblyIndexed - -from tsfc.fiatinterface import create_element -from tsfc.mixedelement import MixedElement -from tsfc.coffee import SCALAR_TYPE class Kernel(object): @@ -74,7 +65,7 @@ def coefficient(self, ufl_coefficient, restriction): def cell_orientation(self, restriction): """Cell orientation as a GEM expression.""" f = {None: 0, '+': 0, '-': 1}[restriction] - co_int = self.cell_orientations_mapper[f] + co_int = self.cell_orientations_mapper(f) return gem.Conditional(gem.Comparison("==", co_int, gem.Literal(1)), gem.Literal(-1), gem.Conditional(gem.Comparison("==", co_int, gem.Zero()), @@ -106,7 +97,7 @@ def construct_kernel(self, name, args, body): """ assert isinstance(body, coffee.Block) body_ = coffee.Block(self.prepare + body.children + self.finalise) - return coffee.FunDecl("void", name, args, body_, pred=["virtual"]) + return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) def arguments(self, arguments, indices): """Prepare arguments. Adds glue code for the arguments. @@ -116,57 +107,11 @@ def arguments(self, arguments, indices): :returns: COFFEE function argument and GEM expression representing the argument tensor """ - funarg, prepare, expressions, finalise = prepare_arguments( + funarg, prepare, expressions, finalise = self.prepare_arguments( arguments, indices, interior_facet=self.interior_facet) self.apply_glue(prepare, finalise) return funarg, expressions - def coefficients(self, coefficients, coefficient_numbers, name, mode=None): - """Prepare a coefficient. Adds glue code for the coefficient - and adds the coefficient to the coefficient map. - - :arg coefficient: iterable of :class:`ufl.Coefficient`s - :arg coefficient_numbers: iterable of coefficient indices in the original form - :arg name: coefficient name - :arg mode: see :func:`prepare_coefficient` - :returns: COFFEE function argument for the coefficient - """ - funarg, prepare, expressions = prepare_coefficients( - coefficients, coefficient_numbers, name, mode=mode, - interior_facet=self.interior_facet) - self.apply_glue(prepare) - for i, coefficient in enumerate(coefficients): - self.coefficient_map[coefficient] = expressions[i] - return funarg - - def coordinates(self, coefficient, name, mode=None): - """Prepare a coordinates. Adds glue code for the coefficient - and adds the coefficient to the coefficient map. - - :arg coefficient: :class:`ufl.Coefficient` - :arg name: coefficient name - :arg mode: see :func:`prepare_coefficient` - :returns: COFFEE function arguments for the coefficient - """ - funargs, prepare, expression = prepare_coordinates( - coefficient, name, mode=mode, - interior_facet=self.interior_facet) - self.apply_glue(prepare) - self.coefficient_map[coefficient] = expression - return funargs - - def facets(self, integral_type): - """Prepare facets. Adds glue code for facets - and stores facet expression. - - :arg integral_type - :returns: list of COFFEE function arguments for facets - """ - funargs, prepare, expressions = prepare_facets(integral_type) - self.apply_glue(prepare) - self.facet_mapper = expressions - return funargs - def cell_orientations(self, integral_type): """Prepare cell orientations. Adds glue code for cell orienatations and stores cell orientations expression. @@ -174,333 +119,54 @@ def cell_orientations(self, integral_type): :arg integral_type :returns: list of COFFEE function arguments for cell orientations """ - funargs, prepare, expressions = prepare_cell_orientations(integral_type) + funargs, prepare, expressions = self.prepare_cell_orientations(integral_type) self.apply_glue(prepare) - self.cell_orientations_mapper = expressions + self._cell_orientations = expressions return funargs - -class KernelBuilder(KernelBuilderBase): - """Helper class for building a :class:`Kernel` object.""" - - def __init__(self, integral_type, subdomain_id, domain_number): - """Initialise a kernel builder.""" - super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) - - self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, - domain_number=domain_number) - self.local_tensor = None - self.coordinates_arg = None - self.coefficient_args = [] - self.coefficient_split = {} - self.cell_orientations_args = [] - - def facet_number(self, restriction): - """Facet number as a GEM index.""" - f = {None: 0, '+': 0, '-': 1}[restriction] - return self.facet_mapper[f] - - def set_arguments(self, arguments, indices): - """Process arguments. - - :arg arguments: :class:`ufl.Argument`s - :arg indices: GEM argument indices - :returns: GEM expression representing the return variable + @staticmethod + def prepare_arguments(arguments, indices, interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + Arguments. + + :arg arguments: UFL Arguments + :arg indices: Argument indices + :arg interior_facet: interior facet integral? + :returns: (funarg, prepare, expression, finalise) + funarg - :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions - GEM expressions referring to the argument + tensor + finalise - list of COFFEE nodes to be appended to the + kernel body """ - self.local_tensor, expressions = self.arguments(arguments, indices) - return expressions - - def set_coordinates(self, coefficient, name, mode=None): - """Prepare the coordinate field. + raise NotImplementedError("This class is abstract") - :arg coefficient: :class:`ufl.Coefficient` - :arg name: coordinate coefficient name - :arg mode: see :func:`prepare_coefficient` - """ - self.coordinates_args = self.coordinates(coefficient, name, mode) - - def set_facets(self): - """Prepare the facets. - """ - self.facet_args = self.facets(self.kernel.integral_type) + @staticmethod + def prepare_cell_orientations(integral_type): + """Bridges the kernel interface and the GEM abstraction for + cell orientations. - def set_cell_orientations(self): - """Prepare the cell orientations. - """ - self.cell_orientations_args = self.cell_orientations(self.kernel.integral_type) - - def set_coefficients(self, integral_data, form_data): - """Prepare the coefficients of the form. - - :arg integral_data: UFL integral data - :arg form_data: UFL form data - """ - coefficients = [] - coefficient_numbers = [] - # enabled_coefficients is a boolean array that indicates which - # of reduced_coefficients the integral requires. - for i in range(len(integral_data.enabled_coefficients)): - if integral_data.enabled_coefficients[i]: - coefficient = form_data.reduced_coefficients[i] - coefficients.append(coefficient) - # This is which coefficient in the original form the - # current coefficient is. - # Consider f*v*dx + g*v*ds, the full form contains two - # coefficients, but each integral only requires one. - coefficient_numbers.append(i) - self.coefficient_args.append(self.coefficients(coefficients, coefficient_numbers, "w")) - self.kernel.coefficient_numbers = tuple(coefficient_numbers) - - def require_cell_orientations(self): - """Set that the kernel requires cell orientations.""" - # FIXME: Don't need this in UFC - self.kernel.oriented = True - - def construct_kernel(self, name, body): - """Construct a fully built :class:`Kernel`. - - This function contains the logic for building the argument - list for assembly kernels. - - :arg name: function name - :arg body: function body (:class:`coffee.Block` node) - :returns: :class:`Kernel` object + :arg integral_type + :returns: (funarg, prepare, expression) + funargs - list of :class:`coffee.Decl` function argument + prepare - list of COFFEE nodes to be prepended to the + kernel body + expressions- list of GEM expressions referring to facets """ - args = [self.local_tensor] - args.extend(self.coefficient_args) - args.extend(self.coordinates_args) - args.extend(self.facet_args) - args.extend(self.cell_orientations_args) - - self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) - self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) - return self.kernel - - + raise NotImplementedError("This class is abstract") -def prepare_coefficients(coefficients, coefficient_numbers, name, mode=None, - interior_facet=False): - """Bridges the kernel interface and the GEM abstraction for - Coefficients. Mixed element Coefficients are rearranged here for - interior facet integrals. - :arg coefficient: iterable of UFL Coefficients - :arg coefficient_numbers: iterable of coefficient indices in the original form - :arg name: unique name to refer to the Coefficient in the kernel - :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with - interior facet integrals on mixed elements - :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expressions) - funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expressions- GEM expressions referring to the Coefficient - values - """ - assert len(coefficients) == len(coefficient_numbers) +# Avoid circular import +from tsfc.backends import pyop2, ufc - # FIXME: hack; is actual number really needed? - num_coefficients = max(coefficient_numbers) + 1 if coefficient_numbers else 0 - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("const",), ()], - qualifiers=["const"]) - # FIXME for interior facets - expressions = [] - for j, coefficient in enumerate(coefficients): - if coefficient.ufl_element().family() == 'Real': - shape = coefficient.ufl_shape - if shape == (): - # Scalar constant/real - needs one dummy index - expression = gem.Indexed(gem.Variable(name, (num_coefficients,) + (1,)), - (coefficient_numbers[j], 0,)) - # FIXME: It seems that Reals are not restricted in gem but are in UFL. - #if interior_facet: - # i = gem.Index() - # expression = gem.ComponentTensor( - # gem.Indexed(gem.Variable(name, (num_coefficients,) + (2,)), - # (coefficient_numbers[j], i,)), - # (i,)) - else: - # Mixed/vector/tensor constant/real - # FIXME: Tensor case is incorrect. Gem wants shaped expression, UFC requires flattened. - indices = tuple(gem.Index() for i in six.moves.xrange(len(shape))) - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), - (coefficient_numbers[j],) + indices), - indices) +class KernelBuilder(KernelBuilderBase): + def __new__(cls, backend, *args, **kwargs): + if backend == "pyop2": + return pyop2.KernelBuilder(*args, **kwargs) + elif backend == "ufc": + return ufc.KernelBuilder(*args, **kwargs) else: - # Everything else - i = gem.Index() - fiat_element = create_element(coefficient.ufl_element()) - shape = (fiat_element.space_dimension(),) - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), - (coefficient_numbers[j], i)), - (i,)) - if interior_facet: - num_dofs = shape[0] - variable = gem.Variable(name, (num_coefficients, 2*num_dofs)) - # TODO: Seems that this reordering could be done using reinterpret_cast - expression = gem.ListTensor([[gem.Indexed(variable, (coefficient_numbers[j], i)) - for i in six.moves.xrange( 0, num_dofs)], - [gem.Indexed(variable, (coefficient_numbers[j], i)) - for i in six.moves.xrange(num_dofs, 2*num_dofs)]]) - - expressions.append(expression) - - return funarg, [], expressions - - -def prepare_coordinates(coefficient, name, mode=None, interior_facet=False): - """Bridges the kernel interface and the GEM abstraction for - coordinates. - - :arg coefficient: UFL Coefficient - :arg name: unique name to refer to the Coefficient in the kernel - :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with - interior facet integrals on mixed elements - :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expression) - funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expression - GEM expression referring to the Coefficient - values - """ - if not interior_facet: - funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("",)], - qualifiers=["const"])] - else: - funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_0"), - pointers=[("",)], - qualifiers=["const"]), - coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_1"), - pointers=[("",)], - qualifiers=["const"])] - - fiat_element = create_element(coefficient.ufl_element()) - shape = (fiat_element.space_dimension(),) - gdim = coefficient.ufl_element().cell().geometric_dimension() - assert len(shape) == 1 and shape[0] % gdim == 0 - num_nodes = shape[0] / gdim - - # Translate coords from XYZXYZXYZXYZ into XXXXYYYYZZZZ - # NOTE: See dolfin/mesh/Cell.h:get_coordinate_dofs for ordering scheme - indices = numpy.arange(num_nodes * gdim).reshape(num_nodes, gdim).transpose().flatten() - if not interior_facet: - variable = gem.Variable(name, shape) - expression = gem.ListTensor([gem.Indexed(variable, (i,)) for i in indices]) - else: - variable0 = gem.Variable(name+"_0", shape) - variable1 = gem.Variable(name+"_1", shape) - expression = gem.ListTensor([[gem.Indexed(variable0, (i,)) for i in indices], - [gem.Indexed(variable1, (i,)) for i in indices]]) - - return funargs, [], expression - - -def prepare_facets(integral_type): - """Bridges the kernel interface and the GEM abstraction for - facets. - - :arg integral_type - :returns: (funarg, prepare, expression) - funargs - list of :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expressions- list of GEM expressions referring to facets - """ - funargs = [] - expressions = [] - - if integral_type in ["exterior_facet", "exterior_facet_vert"]: - funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet"))) - expressions.append(gem.VariableIndex(gem.Variable("facet", ()))) - elif integral_type in ["interior_facet", "interior_facet_vert"]: - funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet_0"))) - funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet_1"))) - expressions.append(gem.VariableIndex(gem.Variable("facet_0", ()))) - expressions.append(gem.VariableIndex(gem.Variable("facet_1", ()))) - - return funargs, [], expressions - - -def prepare_cell_orientations(integral_type): - """Bridges the kernel interface and the GEM abstraction for - cell orientations. - - :arg integral_type - :returns: (funarg, prepare, expression) - funargs - list of :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expressions- list of GEM expressions referring to facets - """ - funargs = [] - expressions = [] - - if integral_type in ["interior_facet", "interior_facet_vert"]: - funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation_0"))) - funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation_1"))) - expressions.append(gem.Variable("cell_orientation_0", ())) - expressions.append(gem.Variable("cell_orientation_1", ())) - else: - funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation"))) - expressions.append(gem.Variable("cell_orientation", ())) - - return funargs, [], expressions - - -def prepare_arguments(arguments, indices, interior_facet=False): - """Bridges the kernel interface and the GEM abstraction for - Arguments. Vector Arguments are rearranged here for interior - facet integrals. - - :arg arguments: UFL Arguments - :arg indices: Argument indices - :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expression, finalise) - funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expressions - GEM expressions referring to the argument - tensor - finalise - list of COFFEE nodes to be appended to the - kernel body - """ - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A"), pointers=[()]) - - elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - shape = tuple(element.space_dimension() for element in elements) - if len(arguments) == 0: - shape = (1,) - indices = (0,) - if interior_facet: - shape = tuple(j for i in zip(len(shape)*(2,), shape) for j in i) - indices = tuple(product(*chain(*(((0, 1), (i,)) for i in indices)))) - else: - indices = (indices,) - - expressions = [gem.Indexed(gem.Variable("AA", shape), i) for i in indices] - - reshape = coffee.Decl(SCALAR_TYPE, - coffee.Symbol("(&%s)" % expressions[0].children[0].name, - rank=shape), - init="*reinterpret_cast<%s (*)%s>(%s)" % - (SCALAR_TYPE, - "".join("[%s]"%i for i in shape), - funarg.sym.gencode() - ) - ) - zero = coffee.FlatBlock("memset(%s, 0, %d * sizeof(*%s));%s" % - (funarg.sym.gencode(), numpy.product(shape), funarg.sym.gencode(), os.linesep)) - prepare = [zero, reshape] - - return funarg, prepare, expressions, [] - - - -def needs_cell_orientations(ir): - return True + raise ValueError("Unknown backend '%s'" % backend) From bd7719763fd3dcc38381e05846182540a619d907 Mon Sep 17 00:00:00 2001 From: Jan Blechta Date: Sat, 17 Sep 2016 16:55:22 +0200 Subject: [PATCH 165/809] Provide compute_form_data for other frontends --- tsfc/driver.py | 25 ++++--------------------- tsfc/ufl_utils.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 722ec37906..d7db13e69d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -3,8 +3,7 @@ import collections import time -from ufl.classes import Form, CellVolume, FacetArea -from ufl.algorithms import compute_form_data +from ufl.classes import Form from ufl.log import GREEN import gem @@ -38,13 +37,7 @@ def compile_form(form, prefix="form", parameters=None): _.update(parameters) parameters = _ - fd = compute_form_data(form, - do_apply_function_pullbacks=True, - do_apply_integral_scaling=True, - do_apply_geometry_lowering=True, - do_apply_restrictions=True, - preserve_geometry_types=(CellVolume, FacetArea), - do_estimate_degrees=True) + fd = ufl_utils.compute_form_data(form) logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) kernels = [] @@ -114,12 +107,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, def cellvolume(restriction): from ufl import dx form = 1 * dx(domain=mesh) - fd = compute_form_data(form, - do_apply_function_pullbacks=True, - do_apply_integral_scaling=True, - do_apply_geometry_lowering=True, - do_apply_restrictions=True, - do_estimate_degrees=True) + fd = ufl_utils.compute_form_data(form, preserve_geometry_types=()) itg_data, = fd.integral_data integral, = itg_data.integrals @@ -154,12 +142,7 @@ def facetarea(): from ufl import Measure assert integral_type != 'cell' form = 1 * Measure(integral_type, domain=mesh) - fd = compute_form_data(form, - do_apply_function_pullbacks=True, - do_apply_integral_scaling=True, - do_apply_geometry_lowering=True, - do_apply_restrictions=True, - do_estimate_degrees=True) + fd = ufl_utils.compute_form_data(form, preserve_geometry_types=()) itg_data, = fd.integral_data integral, = itg_data.integrals diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 78ae243c05..2aef193052 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -7,12 +7,14 @@ import ufl from ufl import indices, as_tensor +from ufl.algorithms import compute_form_data as ufl_compute_form_data from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, ComponentTensor, Expr, FloatValue, Division, MixedElement, MultiIndex, Product, - ReferenceValue, ScalarValue, Sqrt, Zero) + ReferenceValue, ScalarValue, Sqrt, Zero, + CellVolume, FacetArea) from gem.node import MemoizerArg @@ -21,6 +23,32 @@ construct_modified_terminal) +def compute_form_data(form, + do_apply_function_pullbacks=True, + do_apply_integral_scaling=True, + do_apply_geometry_lowering=True, + preserve_geometry_types=(CellVolume, FacetArea), + do_apply_restrictions=True, + do_estimate_degrees=True): + """Preprocess UFL form in a format suitable for TSFC. Return + form data. + + This is merely a wrapper to UFL compute_form_data with default + kwargs overriden in the way TSFC needs it and is provided for + other form compilers based on TSFC. + """ + fd = ufl_compute_form_data( + form, + do_apply_function_pullbacks=do_apply_function_pullbacks, + do_apply_integral_scaling=do_apply_integral_scaling, + do_apply_geometry_lowering=do_apply_geometry_lowering, + preserve_geometry_types=preserve_geometry_types, + do_apply_restrictions=do_apply_restrictions, + do_estimate_degrees=do_estimate_degrees, + ) + return fd + + def is_element_affine(ufl_element): """Tells if a UFL element is affine.""" affine_cells = ["interval", "triangle", "tetrahedron"] From 2b2809e2d0a07ec2b3882fdc90d3456e11338fc7 Mon Sep 17 00:00:00 2001 From: Jan Blechta Date: Sat, 17 Sep 2016 17:17:59 +0200 Subject: [PATCH 166/809] Flake8 fixes --- tsfc/backends/pyop2.py | 3 +-- tsfc/backends/ufc.py | 39 ++++++++++++++++++--------------------- tsfc/kernel_interface.py | 2 +- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/tsfc/backends/pyop2.py b/tsfc/backends/pyop2.py index 1de9020afe..77e7dd7399 100644 --- a/tsfc/backends/pyop2.py +++ b/tsfc/backends/pyop2.py @@ -73,7 +73,6 @@ def _coefficient(self, coefficient, name, mode=None): self.coefficient_map[coefficient] = expression return funarg - def set_arguments(self, arguments, indices): """Process arguments. @@ -162,7 +161,6 @@ def construct_kernel(self, name, body): self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel - @staticmethod def needs_cell_orientations(ir): """Does a multi-root GEM expression DAG references cell @@ -176,6 +174,7 @@ def needs_cell_orientations(ir): def prepare_arguments(arguments, indices, interior_facet=False): return _prepare_arguments(arguments, indices, interior_facet=interior_facet) + def _prepare_coefficient(coefficient, name, mode=None, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. Mixed element Coefficients are rearranged here for diff --git a/tsfc/backends/ufc.py b/tsfc/backends/ufc.py index aaff2cbefb..3ce8f2c724 100644 --- a/tsfc/backends/ufc.py +++ b/tsfc/backends/ufc.py @@ -83,7 +83,6 @@ def facets(self, integral_type): self.facet_mapper = expressions return funargs - def set_arguments(self, arguments, indices): """Process arguments. @@ -157,7 +156,6 @@ def construct_kernel(self, name, body): self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel - @staticmethod def needs_cell_orientations(ir): """UFC requires cell orientations argument(s) everytime""" @@ -173,7 +171,7 @@ def prepare_cell_orientations(integral_type): def _prepare_coefficients(coefficients, coefficient_numbers, name, mode=None, - interior_facet=False): + interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. Mixed element Coefficients are rearranged here for interior facet integrals. @@ -209,12 +207,12 @@ def _prepare_coefficients(coefficients, coefficient_numbers, name, mode=None, expression = gem.Indexed(gem.Variable(name, (num_coefficients,) + (1,)), (coefficient_numbers[j], 0,)) # FIXME: It seems that Reals are not restricted in gem but are in UFL. - #if interior_facet: - # i = gem.Index() - # expression = gem.ComponentTensor( - # gem.Indexed(gem.Variable(name, (num_coefficients,) + (2,)), - # (coefficient_numbers[j], i,)), - # (i,)) + # if interior_facet: + # i = gem.Index() + # expression = gem.ComponentTensor( + # gem.Indexed(gem.Variable(name, (num_coefficients,) + (2,)), + # (coefficient_numbers[j], i,)), + # (i,)) else: # Mixed/vector/tensor constant/real # FIXME: Tensor case is incorrect. Gem wants shaped expression, UFC requires flattened. @@ -225,21 +223,22 @@ def _prepare_coefficients(coefficients, coefficient_numbers, name, mode=None, indices) else: # Everything else - i = gem.Index() fiat_element = create_element(coefficient.ufl_element()) shape = (fiat_element.space_dimension(),) - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), - (coefficient_numbers[j], i)), - (i,)) if interior_facet: num_dofs = shape[0] variable = gem.Variable(name, (num_coefficients, 2*num_dofs)) # TODO: Seems that this reordering could be done using reinterpret_cast expression = gem.ListTensor([[gem.Indexed(variable, (coefficient_numbers[j], i)) - for i in six.moves.xrange( 0, num_dofs)], + for i in six.moves.xrange(0, num_dofs)], [gem.Indexed(variable, (coefficient_numbers[j], i)) for i in six.moves.xrange(num_dofs, 2*num_dofs)]]) + else: + i = gem.Index() + expression = gem.ComponentTensor( + gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), + (coefficient_numbers[j], i)), + (i,)) expressions.append(expression) @@ -383,13 +382,11 @@ def _prepare_arguments(arguments, indices, interior_facet=False): coffee.Symbol("(&%s)" % expressions[0].children[0].name, rank=shape), init="*reinterpret_cast<%s (*)%s>(%s)" % - (SCALAR_TYPE, - "".join("[%s]"%i for i in shape), - funarg.sym.gencode() - ) - ) + (SCALAR_TYPE, "".join("[%s]" % i for i in shape), + funarg.sym.gencode())) zero = coffee.FlatBlock("memset(%s, 0, %d * sizeof(*%s));%s" % - (funarg.sym.gencode(), numpy.product(shape), funarg.sym.gencode(), os.linesep)) + (funarg.sym.gencode(), numpy.product(shape), + funarg.sym.gencode(), os.linesep)) prepare = [zero, reshape] return funarg, prepare, expressions, [] diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 344aa3ca66..c92ff66f5c 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -159,7 +159,7 @@ def prepare_cell_orientations(integral_type): # Avoid circular import -from tsfc.backends import pyop2, ufc +from tsfc.backends import pyop2, ufc # noqa: E402 class KernelBuilder(KernelBuilderBase): From d554cf3a71b74e1bbaf6defaf8bf84c6b85907f1 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 19 Sep 2016 12:17:21 +0100 Subject: [PATCH 167/809] Don't apply restrictions to MTs that are ConstantValue --- tsfc/fem.py | 9 +-------- tsfc/modified_terminals.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index ba0abdd5e1..9886b14a4b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -9,7 +9,7 @@ from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction from ufl.classes import (Argument, Coefficient, CellVolume, - ConstantValue, FacetArea, FormArgument, + FacetArea, FormArgument, GeometricQuantity, QuadratureWeight) import gem @@ -366,13 +366,6 @@ def callback(key): return iterate_shape(mt, callback) -@translate.register(ConstantValue) -def _translate_constantvalue(terminal, mt, params): - # Literal in a modified terminal - # Terminal modifiers have no effect, just translate the terminal. - return params(terminal) - - def compile_ufl(expression, interior_facet=False, **kwargs): params = Parameters(**kwargs) diff --git a/tsfc/modified_terminals.py b/tsfc/modified_terminals.py index f83bee9b59..db587b697a 100644 --- a/tsfc/modified_terminals.py +++ b/tsfc/modified_terminals.py @@ -24,7 +24,8 @@ from ufl.classes import (ReferenceValue, ReferenceGrad, NegativeRestricted, PositiveRestricted, - Restricted, FacetAvg, CellAvg) + Restricted, FacetAvg, CellAvg, + ConstantValue) class ModifiedTerminal(object): @@ -180,9 +181,11 @@ def construct_modified_terminal(mt, terminal): elif mt.averaged == "facet": expr = FacetAvg(expr) - if mt.restriction == '+': - expr = PositiveRestricted(expr) - elif mt.restriction == '-': - expr = NegativeRestricted(expr) + # No need to apply restrictions to ConstantValue terminals + if not isinstance(expr, ConstantValue): + if mt.restriction == '+': + expr = PositiveRestricted(expr) + elif mt.restriction == '-': + expr = NegativeRestricted(expr) return expr From b153aedd89dfcae1d624b3b00dbd96ac1660909a Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Mon, 6 Jun 2016 16:40:25 +0100 Subject: [PATCH 168/809] Adding wrapper class for quad elements --- tsfc/fiatinterface.py | 108 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 14 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 75213e2568..f6a8cbca2b 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -76,6 +76,99 @@ have a direct FIAT equivalent.""" +class FlattenToQuad(FIAT.FiniteElement): + """A wrapper class that flattens a FIAT quadrilateral element defined + on a TensorProductCell to one with FiredrakeQuadrilateral entities + and tabulation properties.""" + + def __init__(self, element): + """ Constructs a FlattenToQuad element. + :arg element: a fiat element + """ + self.element = element + nodes = element.dual.nodes + self.ref_el = FiredrakeQuadrilateral() + entity_ids = element.dual.entity_ids + flat_entity_ids = {} + flat_entity_ids[0] = entity_ids[(0, 0)] + flat_entity_ids[1] = dict(enumerate(entity_ids[(0, 1)].values() + + entity_ids[(1, 0)].values())) + flat_entity_ids[2] = entity_ids[(1, 1)] + self.dual = DualSet(nodes, self.ref_el, flat_entity_ids) + + def dual_basis(self): + """Return the list of functionals for the finite element.""" + return self.dual.get_nodes() + + def entity_dofs(self): + """Return the map of topological entities to degrees of + freedom for the finite element.""" + return self.dual.get_entity_ids() + + def entity_closure_dofs(self): + """Return the map of topological entities to degrees of + freedom on the closure of those entities for the finite element.""" + return self.dual.get_entity_closure_ids() + + def space_dimension(self): + """Return the dimension of the finite element space.""" + return self.element.space_dimension() + + def degree(self): + """Return the degree of the finite element.""" + return self.element.degree() + + def get_order(self): + """Return the order of the finite element.""" + return self.element.get_order() + + def get_formdegree(self): + """Return the degree of the associated form (FEEC).""" + return self.element.get_formdegree() + + def mapping(self): + """Return the list of appropriate mappings from the reference + element to a physical element for each basis function of the + finite element.""" + return self.element.mapping() + + def tabulate(self, order, points, entity=None): + """Return tabulated values of derivatives up to a given order of + basis functions at given points. + + :arg order: The maximum order of derivative. + :arg points: An iterable of points. + :arg entity: Optional entity indicating which topological entity + of the reference element to tabulate on. + """ + if entity is None: + entity = (2, 0) + + # Entity is provided in flattened form (d, i) + # We factor the entity and construct an appropriate + # entity id for a TensorProductCell: ((d1, d2), i) + entity_dim, entity_id = entity + if entity_dim == 2: + assert entity_id == 0 + product_entity = ((1, 1), 0) + elif entity_dim == 1: + facets = [((0, 1), 0), + ((0, 1), 1), + ((1, 0), 0), + ((1, 0), 1)] + product_entity = facets[entity_id] + elif entity_dim == 0: + raise NotImplementedError("Not implemented for 0 dimension entities") + else: + raise ValueError("Illegal entity dimension %s" % entity_dim) + + return self.element.tabulate(order, points, product_entity) + + def value_shape(self): + """Return the value shape of the finite element functions.""" + return self.element.value_shape() + + def as_fiat_cell(cell): """Convert a ufl cell to a FIAT cell. @@ -145,21 +238,8 @@ def _(element, vector_is_mixed): # Can't use create_element here because we're going to modify # it, so if we pull it from the cache, that's bad. element = convert(element, vector_is_mixed) - # Splat quadrilateral elements that are on TFEs back into - # something with the correct entity dofs. - nodes = element.dual.nodes - ref_el = FiredrakeQuadrilateral() - - entity_ids = element.dual.entity_ids - flat_entity_ids = {} - flat_entity_ids[0] = entity_ids[(0, 0)] - flat_entity_ids[1] = dict(enumerate(entity_ids[(0, 1)].values() + - entity_ids[(1, 0)].values())) - flat_entity_ids[2] = entity_ids[(1, 1)] - element.dual = DualSet(nodes, ref_el, flat_entity_ids) - element.ref_el = ref_el - return element + return FlattenToQuad(element) return lmbda(cell, element.degree()) From 3fa5f151877001218fa266170bde422e3ef482b8 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 19 Sep 2016 14:48:59 +0100 Subject: [PATCH 169/809] remove cruft We do need these methods yet. --- tsfc/fiatinterface.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index f6a8cbca2b..10aeded463 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -96,20 +96,6 @@ def __init__(self, element): flat_entity_ids[2] = entity_ids[(1, 1)] self.dual = DualSet(nodes, self.ref_el, flat_entity_ids) - def dual_basis(self): - """Return the list of functionals for the finite element.""" - return self.dual.get_nodes() - - def entity_dofs(self): - """Return the map of topological entities to degrees of - freedom for the finite element.""" - return self.dual.get_entity_ids() - - def entity_closure_dofs(self): - """Return the map of topological entities to degrees of - freedom on the closure of those entities for the finite element.""" - return self.dual.get_entity_closure_ids() - def space_dimension(self): """Return the dimension of the finite element space.""" return self.element.space_dimension() From 2a85400d7372dfab063fe095539ab5f44be6a1ed Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 19 Sep 2016 14:51:53 +0100 Subject: [PATCH 170/809] improve docstring --- tsfc/fiatinterface.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 10aeded463..5d470102e5 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -83,6 +83,7 @@ class FlattenToQuad(FIAT.FiniteElement): def __init__(self, element): """ Constructs a FlattenToQuad element. + :arg element: a fiat element """ self.element = element @@ -124,8 +125,10 @@ def tabulate(self, order, points, entity=None): :arg order: The maximum order of derivative. :arg points: An iterable of points. - :arg entity: Optional entity indicating which topological entity - of the reference element to tabulate on. + :arg entity: Optional (dimension, entity number) pair + indicating which topological entity of the + reference element to tabulate on. If ``None``, + default cell-wise tabulation is performed. """ if entity is None: entity = (2, 0) From a05d20b58bb07140b509d9937e2d5391c15e88fa Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 20 Sep 2016 13:25:55 +0100 Subject: [PATCH 171/809] re-add another KernelBuilderBase for interpolation --- tsfc/backends/pyop2.py | 80 ++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/tsfc/backends/pyop2.py b/tsfc/backends/pyop2.py index 77e7dd7399..776645f05b 100644 --- a/tsfc/backends/pyop2.py +++ b/tsfc/backends/pyop2.py @@ -11,12 +11,56 @@ from gem.node import traversal from gem.gem import FlexiblyIndexed as gem_FlexiblyIndexed -from tsfc.kernel_interface import Kernel, KernelBuilderBase +from tsfc.kernel_interface import Kernel, KernelBuilderBase as _KernelBuilderBase from tsfc.fiatinterface import create_element from tsfc.mixedelement import MixedElement from tsfc.coffee import SCALAR_TYPE +class KernelBuilderBase(_KernelBuilderBase): + + def __init__(self, interior_facet=False): + """Initialise a kernel builder. + + :arg interior_facet: kernel accesses two cells + """ + super(KernelBuilderBase, self).__init__(interior_facet=interior_facet) + + # Cell orientation + if self.interior_facet: + self._cell_orientations = gem.Variable("cell_orientations", (2, 1)) + else: + self._cell_orientations = gem.Variable("cell_orientations", (1, 1)) + + def cell_orientations_mapper(self, facet): + return gem.Indexed(self._cell_orientations, (facet, 0)) + + def _coefficient(self, coefficient, name, mode=None): + """Prepare a coefficient. Adds glue code for the coefficient + and adds the coefficient to the coefficient map. + + :arg coefficient: :class:`ufl.Coefficient` + :arg name: coefficient name + :arg mode: see :func:`prepare_coefficient` + :returns: COFFEE function argument for the coefficient + """ + funarg, prepare, expression = _prepare_coefficient( + coefficient, name, mode=mode, + interior_facet=self.interior_facet) + self.apply_glue(prepare) + self.coefficient_map[coefficient] = expression + return funarg + + @staticmethod + def needs_cell_orientations(ir): + """Does a multi-root GEM expression DAG references cell + orientations?""" + for node in traversal(ir): + if isinstance(node, gem.Variable) and node.name == "cell_orientations": + return True + return False + + class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" @@ -44,35 +88,10 @@ def __init__(self, integral_type, subdomain_id, domain_number): elif integral_type == 'interior_facet_horiz': self._facet_number = {'+': 1, '-': 0} - # Cell orientation - if self.interior_facet: - self._cell_orientations = gem.Variable("cell_orientations", (2, 1)) - else: - self._cell_orientations = gem.Variable("cell_orientations", (1, 1)) - def facet_number(self, restriction): """Facet number as a GEM index.""" return self._facet_number[restriction] - def cell_orientations_mapper(self, facet): - return gem.Indexed(self._cell_orientations, (facet, 0)) - - def _coefficient(self, coefficient, name, mode=None): - """Prepare a coefficient. Adds glue code for the coefficient - and adds the coefficient to the coefficient map. - - :arg coefficient: :class:`ufl.Coefficient` - :arg name: coefficient name - :arg mode: see :func:`prepare_coefficient` - :returns: COFFEE function argument for the coefficient - """ - funarg, prepare, expression = _prepare_coefficient( - coefficient, name, mode=mode, - interior_facet=self.interior_facet) - self.apply_glue(prepare) - self.coefficient_map[coefficient] = expression - return funarg - def set_arguments(self, arguments, indices): """Process arguments. @@ -161,15 +180,6 @@ def construct_kernel(self, name, body): self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel - @staticmethod - def needs_cell_orientations(ir): - """Does a multi-root GEM expression DAG references cell - orientations?""" - for node in traversal(ir): - if isinstance(node, gem.Variable) and node.name == "cell_orientations": - return True - return False - @staticmethod def prepare_arguments(arguments, indices, interior_facet=False): return _prepare_arguments(arguments, indices, interior_facet=interior_facet) From efa8c7cd9e21625627fdb6e6e7fe012aff26cd1a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 20 Sep 2016 14:56:55 +0100 Subject: [PATCH 172/809] clean up handling parameters --- tsfc/driver.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index d7db13e69d..74a30b5040 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -30,13 +30,6 @@ def compile_form(form, prefix="form", parameters=None): assert isinstance(form, Form) - if parameters is None: - parameters = default_parameters() - else: - _ = default_parameters() - _.update(parameters) - parameters = _ - fd = ufl_utils.compute_form_data(form) logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) @@ -64,6 +57,19 @@ def compile_integral(integral_data, form_data, prefix, parameters, :arg backend: output format :returns: a kernel, or None if the integral simplifies to zero """ + if parameters is None: + parameters = default_parameters() + else: + _ = default_parameters() + _.update(parameters) + parameters = _ + + # Remove these here, they're handled below. + if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: + del parameters["quadrature_degree"] + if parameters.get("quadrature_rule") in ["auto", "default", None]: + del parameters["quadrature_rule"] + integral_type = integral_data.integral_type interior_facet = integral_type.startswith("interior_facet") mesh = integral_data.domain @@ -130,7 +136,7 @@ def coefficient(ufl_coefficient, r): point_index=quadrature_index, coefficient=coefficient, index_cache=index_cache) - if parameters.get("unroll_indexsum"): + if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) expr, = ir if quadrature_index in expr.free_indices: @@ -161,7 +167,7 @@ def facetarea(): coefficient=builder.coefficient, facet_number=builder.facet_number, index_cache=index_cache) - if parameters.get("unroll_indexsum"): + if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) expr, = ir if quadrature_index in expr.free_indices: @@ -178,13 +184,12 @@ def facetarea(): # Check if the integral has a quad degree attached, otherwise use # the estimated polynomial degree attached by compute_form_data - quad_degree = params.get("quadrature_degree") - if quad_degree in [None, "auto", "default", -1, "-1"]: - quad_degree = params["estimated_polynomial_degree"] + quadrature_degree = params.get("quadrature_degree", + params["estimated_polynomial_degree"]) integration_cell = fiat_cell.construct_subelement(integration_dim) - quad_rule = params.get("quadrature_rule") - if quad_rule in [None, "auto", "default"]: - quad_rule = create_quadrature(integration_cell, quad_degree) + quad_rule = params.get("quadrature_rule", + create_quadrature(integration_cell, + quadrature_degree)) if not isinstance(quad_rule, QuadratureRule): raise ValueError("Expected to find a QuadratureRule object, not a %s" % @@ -208,7 +213,7 @@ def facetarea(): index_cache=index_cache, cellvolume=cellvolume, facetarea=facetarea) - if parameters.get("unroll_indexsum"): + if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) irs.append([(gem.IndexSum(expr, quadrature_index) if quadrature_index in expr.free_indices From 53b6e7073fcd12c88ce39b1a70f9752cd9887006 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 20 Sep 2016 16:59:01 +0100 Subject: [PATCH 173/809] unbreak FFC regression tests --- tsfc/driver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 74a30b5040..b1b2e45afe 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -179,6 +179,8 @@ def facetarea(): params = {} # Record per-integral parameters params.update(integral.metadata()) + if params.get("quadrature_rule") == "default": + del params["quadrature_rule"] # parameters override per-integral metadata params.update(parameters) From 5e6d794e04c34f11bb4be72dac2e0326b57d55ad Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 20 Sep 2016 18:20:04 +0100 Subject: [PATCH 174/809] remove set_facets & set_cell_orientations ... and other simplifications. --- tsfc/backends/pyop2.py | 20 ++----- tsfc/backends/ufc.py | 110 ++++++++++----------------------------- tsfc/driver.py | 7 +-- tsfc/kernel_interface.py | 2 +- 4 files changed, 36 insertions(+), 103 deletions(-) diff --git a/tsfc/backends/pyop2.py b/tsfc/backends/pyop2.py index 776645f05b..8ca623208c 100644 --- a/tsfc/backends/pyop2.py +++ b/tsfc/backends/pyop2.py @@ -28,12 +28,12 @@ def __init__(self, interior_facet=False): # Cell orientation if self.interior_facet: - self._cell_orientations = gem.Variable("cell_orientations", (2, 1)) + cell_orientations = gem.Variable("cell_orientations", (2, 1)) + self._cell_orientations = (gem.Indexed(cell_orientations, (0, 0)), + gem.Indexed(cell_orientations, (1, 0))) else: - self._cell_orientations = gem.Variable("cell_orientations", (1, 1)) - - def cell_orientations_mapper(self, facet): - return gem.Indexed(self._cell_orientations, (facet, 0)) + cell_orientations = gem.Variable("cell_orientations", (1, 1)) + self._cell_orientations = (gem.Indexed(cell_orientations, (0, 0)),) def _coefficient(self, coefficient, name, mode=None): """Prepare a coefficient. Adds glue code for the coefficient @@ -110,16 +110,6 @@ def set_coordinates(self, coefficient, mode=None): """ self.coordinates_arg = self._coefficient(coefficient, "coords", mode) - def set_facets(self): - """Prepare the facets. - """ - return - - def set_cell_orientations(self): - """Prepare the cell orientations. - """ - return - def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. diff --git a/tsfc/backends/ufc.py b/tsfc/backends/ufc.py index 3ce8f2c724..f0b3ba7741 100644 --- a/tsfc/backends/ufc.py +++ b/tsfc/backends/ufc.py @@ -27,12 +27,23 @@ def __init__(self, integral_type, subdomain_id, domain_number): self.coordinates_arg = None self.coefficient_args = [] self.coefficient_split = {} - self.cell_orientations_args = [] + + if self.interior_facet: + self._cell_orientations = (gem.Variable("cell_orientation_0", ()), + gem.Variable("cell_orientation_1", ())) + else: + self._cell_orientations = (gem.Variable("cell_orientation", ()),) + + if integral_type == "exterior_facet": + self._facet_number = (gem.VariableIndex(gem.Variable("facet", ())),) + elif integral_type == "interior_facet": + self._facet_number = (gem.VariableIndex(gem.Variable("facet_0", ())), + gem.VariableIndex(gem.Variable("facet_1", ()))) def facet_number(self, restriction): """Facet number as a GEM index.""" f = {None: 0, '+': 0, '-': 1}[restriction] - return self.facet_mapper[f] + return self._facet_number[f] def cell_orientations_mapper(self, facet): return self._cell_orientations[facet] @@ -71,18 +82,6 @@ def coordinates(self, coefficient, name, mode=None): self.coefficient_map[coefficient] = expression return funargs - def facets(self, integral_type): - """Prepare facets. Adds glue code for facets - and stores facet expression. - - :arg integral_type - :returns: list of COFFEE function arguments for facets - """ - funargs, prepare, expressions = _prepare_facets(integral_type) - self.apply_glue(prepare) - self.facet_mapper = expressions - return funargs - def set_arguments(self, arguments, indices): """Process arguments. @@ -101,16 +100,6 @@ def set_coordinates(self, coefficient, mode=None): """ self.coordinates_args = self.coordinates(coefficient, "coordinate_dofs", mode) - def set_facets(self): - """Prepare the facets. - """ - self.facet_args = self.facets(self.kernel.integral_type) - - def set_cell_orientations(self): - """Prepare the cell orientations. - """ - self.cell_orientations_args = self.cell_orientations(self.kernel.integral_type) - def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -150,8 +139,21 @@ def construct_kernel(self, name, body): args = [self.local_tensor] args.extend(self.coefficient_args) args.extend(self.coordinates_args) - args.extend(self.facet_args) - args.extend(self.cell_orientations_args) + + # Facet number(s) + integral_type = self.kernel.integral_type + if integral_type == "exterior_facet": + args.append(coffee.Decl("std::size_t", coffee.Symbol("facet"))) + elif integral_type == "interior_facet": + args.append(coffee.Decl("std::size_t", coffee.Symbol("facet_0"))) + args.append(coffee.Decl("std::size_t", coffee.Symbol("facet_1"))) + + # Cell orientation(s) + if self.interior_facet: + args.append(coffee.Decl("int", coffee.Symbol("cell_orientation_0"))) + args.append(coffee.Decl("int", coffee.Symbol("cell_orientation_1"))) + else: + args.append(coffee.Decl("int", coffee.Symbol("cell_orientation"))) self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel @@ -165,10 +167,6 @@ def needs_cell_orientations(ir): def prepare_arguments(arguments, indices, interior_facet=False): return _prepare_arguments(arguments, indices, interior_facet=interior_facet) - @staticmethod - def prepare_cell_orientations(integral_type): - return _prepare_cell_orientations(integral_type) - def _prepare_coefficients(coefficients, coefficient_numbers, name, mode=None, interior_facet=False): @@ -294,58 +292,6 @@ def _prepare_coordinates(coefficient, name, mode=None, interior_facet=False): return funargs, [], expression -def _prepare_facets(integral_type): - """Bridges the kernel interface and the GEM abstraction for - facets. - - :arg integral_type - :returns: (funarg, prepare, expression) - funargs - list of :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expressions- list of GEM expressions referring to facets - """ - funargs = [] - expressions = [] - - if integral_type in ["exterior_facet", "exterior_facet_vert"]: - funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet"))) - expressions.append(gem.VariableIndex(gem.Variable("facet", ()))) - elif integral_type in ["interior_facet", "interior_facet_vert"]: - funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet_0"))) - funargs.append(coffee.Decl("std::size_t", coffee.Symbol("facet_1"))) - expressions.append(gem.VariableIndex(gem.Variable("facet_0", ()))) - expressions.append(gem.VariableIndex(gem.Variable("facet_1", ()))) - - return funargs, [], expressions - - -def _prepare_cell_orientations(integral_type): - """Bridges the kernel interface and the GEM abstraction for - cell orientations. - - :arg integral_type - :returns: (funarg, prepare, expression) - funargs - list of :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expressions- list of GEM expressions referring to facets - """ - funargs = [] - expressions = [] - - if integral_type in ["interior_facet", "interior_facet_vert"]: - funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation_0"))) - funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation_1"))) - expressions.append(gem.Variable("cell_orientation_0", ())) - expressions.append(gem.Variable("cell_orientation_1", ())) - else: - funargs.append(coffee.Decl("int", coffee.Symbol("cell_orientation"))) - expressions.append(gem.Variable("cell_orientation", ())) - - return funargs, [], expressions - - def _prepare_arguments(arguments, indices, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior diff --git a/tsfc/driver.py b/tsfc/driver.py index b1b2e45afe..ff8769fa55 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -99,9 +99,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, builder.set_coefficients(integral_data, form_data) - builder.set_cell_orientations() - builder.set_facets() - # Map from UFL FiniteElement objects to Index instances. This is # so we reuse Index instances when evaluating the same coefficient # multiple times with the same table. Occurs, for example, if we @@ -113,7 +110,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, def cellvolume(restriction): from ufl import dx form = 1 * dx(domain=mesh) - fd = ufl_utils.compute_form_data(form, preserve_geometry_types=()) + fd = ufl_utils.compute_form_data(form) itg_data, = fd.integral_data integral, = itg_data.integrals @@ -148,7 +145,7 @@ def facetarea(): from ufl import Measure assert integral_type != 'cell' form = 1 * Measure(integral_type, domain=mesh) - fd = ufl_utils.compute_form_data(form, preserve_geometry_types=()) + fd = ufl_utils.compute_form_data(form) itg_data, = fd.integral_data integral, = itg_data.integrals diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index c92ff66f5c..84528c3b85 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -65,7 +65,7 @@ def coefficient(self, ufl_coefficient, restriction): def cell_orientation(self, restriction): """Cell orientation as a GEM expression.""" f = {None: 0, '+': 0, '-': 1}[restriction] - co_int = self.cell_orientations_mapper(f) + co_int = self._cell_orientations[f] return gem.Conditional(gem.Comparison("==", co_int, gem.Literal(1)), gem.Literal(-1), gem.Conditional(gem.Comparison("==", co_int, gem.Zero()), From 8f2c74773e37689f14adbf5a4833df0a9e58d22c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 20 Sep 2016 18:52:17 +0100 Subject: [PATCH 175/809] make backend argument a Python module, not string --- tsfc/driver.py | 8 ++++---- tsfc/kernel_interface.py | 14 -------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index ff8769fa55..216eabb0d0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -11,10 +11,10 @@ import gem.impero_utils as impero_utils from tsfc import fem, ufl_utils +from tsfc.backends import pyop2 as default_backend from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.fiatinterface import QuadratureRule, as_fiat_cell, create_quadrature -from tsfc.kernel_interface import KernelBuilder from tsfc.logging import logger @@ -47,7 +47,7 @@ def compile_form(form, prefix="form", parameters=None): def compile_integral(integral_data, form_data, prefix, parameters, - backend="pyop2"): + backend=default_backend): """Compiles a UFL integral into an assembly kernel. :arg integral_data: UFL integral data @@ -84,8 +84,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() - builder = KernelBuilder(backend, integral_type, integral_data.subdomain_id, - domain_numbering[integral_data.domain]) + builder = backend.KernelBuilder(integral_type, integral_data.subdomain_id, + domain_numbering[integral_data.domain]) return_variables = builder.set_arguments(arguments, argument_indices) coordinates = ufl_utils.coordinate_coefficient(mesh) diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 84528c3b85..13cf57e811 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -156,17 +156,3 @@ def prepare_cell_orientations(integral_type): expressions- list of GEM expressions referring to facets """ raise NotImplementedError("This class is abstract") - - -# Avoid circular import -from tsfc.backends import pyop2, ufc # noqa: E402 - - -class KernelBuilder(KernelBuilderBase): - def __new__(cls, backend, *args, **kwargs): - if backend == "pyop2": - return pyop2.KernelBuilder(*args, **kwargs) - elif backend == "ufc": - return ufc.KernelBuilder(*args, **kwargs) - else: - raise ValueError("Unknown backend '%s'" % backend) From 8452aefefbf92cae6e3f711335528d477d2b6697 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 21 Sep 2016 11:19:08 +0100 Subject: [PATCH 176/809] adopt generic TensorFiniteElement in FInAT --- tsfc/finatinterface.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 8faebb2c4c..2e95ffe382 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -84,8 +84,8 @@ def convert(element, vector_is_mixed): # Base finite elements first -@convert.register(ufl.FiniteElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.FiniteElement) +def convert_finiteelement(element, vector_is_mixed): cell = as_fiat_cell(element.cell()) lmbda = supported_elements.get(element.family()) if lmbda: @@ -95,26 +95,31 @@ def _(element, vector_is_mixed): # MixedElement case -@convert.register(ufl.MixedElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.MixedElement) +def convert_mixedelement(element, vector_is_mixed): raise ValueError("FInAT does not implement generic mixed element.") # VectorElement case -@convert.register(ufl.VectorElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.VectorElement) +def convert_vectorelement(element, vector_is_mixed): # If we're just trying to get the scalar part of a vector element? if not vector_is_mixed: return create_element(element.sub_elements()[0], vector_is_mixed) scalar_element = create_element(element.sub_elements()[0], vector_is_mixed) - return finat.VectorFiniteElement(scalar_element, element.num_sub_elements()) + return finat.TensorFiniteElement(scalar_element, (element.num_sub_elements(),)) # TensorElement case -@convert.register(ufl.TensorElement) # noqa -def _(element, vector_is_mixed): - raise NotImplementedError("TensorElement not implemented in FInAT yet.") +@convert.register(ufl.TensorElement) +def convert_tensorelement(element, vector_is_mixed): + # If we're just trying to get the scalar part of a vector element? + if not vector_is_mixed: + return create_element(element.sub_elements()[0], vector_is_mixed) + + scalar_element = create_element(element.sub_elements()[0], vector_is_mixed) + return finat.TensorFiniteElement(scalar_element, element.reference_value_shape()) _cache = weakref.WeakKeyDictionary() From bfc39a0999aa5e8e6932e867ab75fd9eede66fbd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 21 Sep 2016 14:25:48 +0100 Subject: [PATCH 177/809] facilitate FlexiblyIndexed for UFC coefficients --- tsfc/backends/ufc.py | 76 ++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 56 deletions(-) diff --git a/tsfc/backends/ufc.py b/tsfc/backends/ufc.py index f0b3ba7741..40ff75e845 100644 --- a/tsfc/backends/ufc.py +++ b/tsfc/backends/ufc.py @@ -1,7 +1,6 @@ from __future__ import absolute_import import os -import six import numpy from itertools import chain, product @@ -45,9 +44,6 @@ def facet_number(self, restriction): f = {None: 0, '+': 0, '-': 1}[restriction] return self._facet_number[f] - def cell_orientations_mapper(self, facet): - return self._cell_orientations[facet] - def coefficients(self, coefficients, coefficient_numbers, name, mode=None): """Prepare coefficients. Adds glue code for the coefficients and adds the coefficients to the coefficient map. @@ -58,10 +54,10 @@ def coefficients(self, coefficients, coefficient_numbers, name, mode=None): :arg mode: see :func:`prepare_coefficient` :returns: COFFEE function argument for the coefficient """ - funarg, prepare, expressions = _prepare_coefficients( - coefficients, coefficient_numbers, name, mode=mode, - interior_facet=self.interior_facet) - self.apply_glue(prepare) + funarg, expressions = prepare_coefficients(coefficients, + coefficient_numbers, + name, + interior_facet=self.interior_facet) for i, coefficient in enumerate(coefficients): self.coefficient_map[coefficient] = expressions[i] return funarg @@ -168,8 +164,7 @@ def prepare_arguments(arguments, indices, interior_facet=False): return _prepare_arguments(arguments, indices, interior_facet=interior_facet) -def _prepare_coefficients(coefficients, coefficient_numbers, name, mode=None, - interior_facet=False): +def prepare_coefficients(coefficients, coefficient_numbers, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. Mixed element Coefficients are rearranged here for interior facet integrals. @@ -177,70 +172,39 @@ def _prepare_coefficients(coefficients, coefficient_numbers, name, mode=None, :arg coefficient: iterable of UFL Coefficients :arg coefficient_numbers: iterable of coefficient indices in the original form :arg name: unique name to refer to the Coefficient in the kernel - :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with - interior facet integrals on mixed elements :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expressions) + :returns: (funarg, expressions) funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body expressions- GEM expressions referring to the Coefficient values """ assert len(coefficients) == len(coefficient_numbers) - # FIXME: hack; is actual number really needed? - num_coefficients = max(coefficient_numbers) + 1 if coefficient_numbers else 0 funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), pointers=[("const",), ()], qualifiers=["const"]) - # FIXME for interior facets + varexp = gem.Variable(name, (None, None)) expressions = [] - for j, coefficient in enumerate(coefficients): + for coefficient_number, coefficient in zip(coefficient_numbers, coefficients): if coefficient.ufl_element().family() == 'Real': shape = coefficient.ufl_shape - if shape == (): - # Scalar constant/real - needs one dummy index - expression = gem.Indexed(gem.Variable(name, (num_coefficients,) + (1,)), - (coefficient_numbers[j], 0,)) - # FIXME: It seems that Reals are not restricted in gem but are in UFL. - # if interior_facet: - # i = gem.Index() - # expression = gem.ComponentTensor( - # gem.Indexed(gem.Variable(name, (num_coefficients,) + (2,)), - # (coefficient_numbers[j], i,)), - # (i,)) - else: - # Mixed/vector/tensor constant/real - # FIXME: Tensor case is incorrect. Gem wants shaped expression, UFC requires flattened. - indices = tuple(gem.Index() for i in six.moves.xrange(len(shape))) - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), - (coefficient_numbers[j],) + indices), - indices) else: - # Everything else fiat_element = create_element(coefficient.ufl_element()) shape = (fiat_element.space_dimension(),) if interior_facet: - num_dofs = shape[0] - variable = gem.Variable(name, (num_coefficients, 2*num_dofs)) - # TODO: Seems that this reordering could be done using reinterpret_cast - expression = gem.ListTensor([[gem.Indexed(variable, (coefficient_numbers[j], i)) - for i in six.moves.xrange(0, num_dofs)], - [gem.Indexed(variable, (coefficient_numbers[j], i)) - for i in six.moves.xrange(num_dofs, 2*num_dofs)]]) - else: - i = gem.Index() - expression = gem.ComponentTensor( - gem.Indexed(gem.Variable(name, (num_coefficients,) + shape), - (coefficient_numbers[j], i)), - (i,)) - - expressions.append(expression) - - return funarg, [], expressions + shape = (2,) + shape + + alpha = tuple(gem.Index() for s in shape) + expressions.append(gem.ComponentTensor( + gem.FlexiblyIndexed( + varexp, ((coefficient_number, ()), + (0, tuple(zip(alpha, shape)))) + ), + alpha + )) + + return funarg, expressions def _prepare_coordinates(coefficient, name, mode=None, interior_facet=False): From 699d510c7c5d78c5b6f711f6e75ee19e54c6a947 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 21 Sep 2016 15:36:38 +0100 Subject: [PATCH 178/809] rework Arguments with FlexiblyIndexed Removes ugly reinterpret_cast, although has to use a trick for all 2-forms that might drive COFFEE crazy or make it avoid doing important optimisations (probably even worse). --- tsfc/backends/ufc.py | 54 ++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/tsfc/backends/ufc.py b/tsfc/backends/ufc.py index 40ff75e845..6bdc106c6c 100644 --- a/tsfc/backends/ufc.py +++ b/tsfc/backends/ufc.py @@ -2,7 +2,7 @@ import os import numpy -from itertools import chain, product +from itertools import product import coffee.base as coffee @@ -10,7 +10,7 @@ from tsfc.kernel_interface import Kernel, KernelBuilderBase from tsfc.fiatinterface import create_element -from tsfc.coffee import SCALAR_TYPE +from tsfc.coffee import SCALAR_TYPE, cumulative_strides class KernelBuilder(KernelBuilderBase): @@ -274,29 +274,33 @@ def _prepare_arguments(arguments, indices, interior_facet=False): kernel body """ funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A"), pointers=[()]) + varexp = gem.Variable("A", (None,)) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - shape = tuple(element.space_dimension() for element in elements) - if len(arguments) == 0: - shape = (1,) - indices = (0,) - if interior_facet: - shape = tuple(j for i in zip(len(shape)*(2,), shape) for j in i) - indices = tuple(product(*chain(*(((0, 1), (i,)) for i in indices)))) + space_dimensions = tuple(element.space_dimension() for element in elements) + for i, sd in zip(indices, space_dimensions): + i.set_extent(sd) + + if arguments and interior_facet: + shape = tuple(2 * element.space_dimension() for element in elements) + strides = cumulative_strides(shape) + + expressions = [] + for restrictions in product((0, 1), repeat=len(arguments)): + offset = sum(r * sd * stride + for r, sd, stride in zip(restrictions, space_dimensions, strides)) + expressions.append(gem.FlexiblyIndexed( + varexp, + ((offset, + tuple((i, s) for i, s in zip(indices, shape))),) + )) else: - indices = (indices,) - - expressions = [gem.Indexed(gem.Variable("AA", shape), i) for i in indices] - - reshape = coffee.Decl(SCALAR_TYPE, - coffee.Symbol("(&%s)" % expressions[0].children[0].name, - rank=shape), - init="*reinterpret_cast<%s (*)%s>(%s)" % - (SCALAR_TYPE, "".join("[%s]" % i for i in shape), - funarg.sym.gencode())) - zero = coffee.FlatBlock("memset(%s, 0, %d * sizeof(*%s));%s" % - (funarg.sym.gencode(), numpy.product(shape), - funarg.sym.gencode(), os.linesep)) - prepare = [zero, reshape] - - return funarg, prepare, expressions, [] + shape = space_dimensions + expressions = [gem.FlexiblyIndexed(varexp, ((0, tuple(zip(indices, space_dimensions))),))] + + zero = coffee.FlatBlock( + str.format("memset({name}, 0, {size} * sizeof(*{name}));{nl}", + name=funarg.sym.gencode(), size=numpy.product(shape), + nl=os.linesep) + ) + return funarg, [zero], expressions, [] From 8d0a087775465af96c7f177d8f724401f6cd31d3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 21 Sep 2016 17:17:12 +0100 Subject: [PATCH 179/809] clean up kernel interfaces --- tsfc/backends/pyop2.py | 12 +++--- tsfc/backends/ufc.py | 88 ++++++++++++---------------------------- tsfc/kernel_interface.py | 58 -------------------------- 3 files changed, 32 insertions(+), 126 deletions(-) diff --git a/tsfc/backends/pyop2.py b/tsfc/backends/pyop2.py index 8ca623208c..8e2c8678c2 100644 --- a/tsfc/backends/pyop2.py +++ b/tsfc/backends/pyop2.py @@ -99,7 +99,9 @@ def set_arguments(self, arguments, indices): :arg indices: GEM argument indices :returns: GEM expression representing the return variable """ - self.local_tensor, expressions = self.arguments(arguments, indices) + self.local_tensor, prepare, expressions, finalise = prepare_arguments( + arguments, indices, interior_facet=self.interior_facet) + self.apply_glue(prepare, finalise) return expressions def set_coordinates(self, coefficient, mode=None): @@ -170,10 +172,6 @@ def construct_kernel(self, name, body): self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel - @staticmethod - def prepare_arguments(arguments, indices, interior_facet=False): - return _prepare_arguments(arguments, indices, interior_facet=interior_facet) - def _prepare_coefficient(coefficient, name, mode=None, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for @@ -310,7 +308,7 @@ def _prepare_coefficient(coefficient, name, mode=None, interior_facet=False): return funarg, [], expression -def _prepare_arguments(arguments, indices, interior_facet=False): +def prepare_arguments(arguments, indices, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. @@ -425,6 +423,6 @@ def coffee_for(index, extent, body): cell_orientations_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), - pointers=[("restrict",), ("restrict",)], + pointers=[("restrict", "const"), ("restrict",)], qualifiers=["const"]) """COFFEE function argument for cell orientations""" diff --git a/tsfc/backends/ufc.py b/tsfc/backends/ufc.py index 6bdc106c6c..0ee87126a3 100644 --- a/tsfc/backends/ufc.py +++ b/tsfc/backends/ufc.py @@ -1,6 +1,5 @@ from __future__ import absolute_import -import os import numpy from itertools import product @@ -23,9 +22,9 @@ def __init__(self, integral_type, subdomain_id, domain_number): self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) self.local_tensor = None - self.coordinates_arg = None - self.coefficient_args = [] - self.coefficient_split = {} + self.coordinates_args = None + self.coefficient_args = None + self.coefficient_split = None if self.interior_facet: self._cell_orientations = (gem.Variable("cell_orientation_0", ()), @@ -44,40 +43,6 @@ def facet_number(self, restriction): f = {None: 0, '+': 0, '-': 1}[restriction] return self._facet_number[f] - def coefficients(self, coefficients, coefficient_numbers, name, mode=None): - """Prepare coefficients. Adds glue code for the coefficients - and adds the coefficients to the coefficient map. - - :arg coefficient: iterable of :class:`ufl.Coefficient`s - :arg coefficient_numbers: iterable of coefficient indices in the original form - :arg name: coefficient name - :arg mode: see :func:`prepare_coefficient` - :returns: COFFEE function argument for the coefficient - """ - funarg, expressions = prepare_coefficients(coefficients, - coefficient_numbers, - name, - interior_facet=self.interior_facet) - for i, coefficient in enumerate(coefficients): - self.coefficient_map[coefficient] = expressions[i] - return funarg - - def coordinates(self, coefficient, name, mode=None): - """Prepare a coordinates. Adds glue code for the coefficient - and adds the coefficient to the coefficient map. - - :arg coefficient: :class:`ufl.Coefficient` - :arg name: coefficient name - :arg mode: see :func:`prepare_coefficient` - :returns: COFFEE function arguments for the coefficient - """ - funargs, prepare, expression = _prepare_coordinates( - coefficient, name, mode=mode, - interior_facet=self.interior_facet) - self.apply_glue(prepare) - self.coefficient_map[coefficient] = expression - return funargs - def set_arguments(self, arguments, indices): """Process arguments. @@ -85,16 +50,20 @@ def set_arguments(self, arguments, indices): :arg indices: GEM argument indices :returns: GEM expression representing the return variable """ - self.local_tensor, expressions = self.arguments(arguments, indices) + self.local_tensor, prepare, expressions = prepare_arguments( + arguments, indices, interior_facet=self.interior_facet) + self.apply_glue(prepare) return expressions def set_coordinates(self, coefficient, mode=None): """Prepare the coordinate field. :arg coefficient: :class:`ufl.Coefficient` - :arg mode: see :func:`prepare_coefficient` + :arg mode: (ignored) """ - self.coordinates_args = self.coordinates(coefficient, "coordinate_dofs", mode) + self.coordinates_args, expression = prepare_coordinates( + coefficient, "coordinate_dofs", interior_facet=self.interior_facet) + self.coefficient_map[coefficient] = expression def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -115,7 +84,15 @@ def set_coefficients(self, integral_data, form_data): # Consider f*v*dx + g*v*ds, the full form contains two # coefficients, but each integral only requires one. coefficient_numbers.append(i) - self.coefficient_args.append(self.coefficients(coefficients, coefficient_numbers, "w")) + + funarg, expressions = prepare_coefficients( + coefficients, coefficient_numbers, "w", + interior_facet=self.interior_facet) + + self.coefficient_args = [funarg] + for i, coefficient in enumerate(coefficients): + self.coefficient_map[coefficient] = expressions[i] + self.kernel.coefficient_numbers = tuple(coefficient_numbers) def require_cell_orientations(self): @@ -159,10 +136,6 @@ def needs_cell_orientations(ir): """UFC requires cell orientations argument(s) everytime""" return True - @staticmethod - def prepare_arguments(arguments, indices, interior_facet=False): - return _prepare_arguments(arguments, indices, interior_facet=interior_facet) - def prepare_coefficients(coefficients, coefficient_numbers, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for @@ -207,19 +180,15 @@ def prepare_coefficients(coefficients, coefficient_numbers, name, interior_facet return funarg, expressions -def _prepare_coordinates(coefficient, name, mode=None, interior_facet=False): +def prepare_coordinates(coefficient, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for coordinates. :arg coefficient: UFL Coefficient :arg name: unique name to refer to the Coefficient in the kernel - :arg mode: 'manual_loop' or 'list_tensor'; two ways to deal with - interior facet integrals on mixed elements :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expression) + :returns: (funarg, expression) funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body expression - GEM expression referring to the Coefficient values """ @@ -253,10 +222,10 @@ def _prepare_coordinates(coefficient, name, mode=None, interior_facet=False): expression = gem.ListTensor([[gem.Indexed(variable0, (i,)) for i in indices], [gem.Indexed(variable1, (i,)) for i in indices]]) - return funargs, [], expression + return funargs, expression -def _prepare_arguments(arguments, indices, interior_facet=False): +def prepare_arguments(arguments, indices, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. @@ -264,14 +233,12 @@ def _prepare_arguments(arguments, indices, interior_facet=False): :arg arguments: UFL Arguments :arg indices: Argument indices :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expression, finalise) + :returns: (funarg, prepare, expressions) funarg - :class:`coffee.Decl` function argument prepare - list of COFFEE nodes to be prepended to the kernel body expressions - GEM expressions referring to the argument tensor - finalise - list of COFFEE nodes to be appended to the - kernel body """ funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A"), pointers=[()]) varexp = gem.Variable("A", (None,)) @@ -299,8 +266,7 @@ def _prepare_arguments(arguments, indices, interior_facet=False): expressions = [gem.FlexiblyIndexed(varexp, ((0, tuple(zip(indices, space_dimensions))),))] zero = coffee.FlatBlock( - str.format("memset({name}, 0, {size} * sizeof(*{name}));{nl}", - name=funarg.sym.gencode(), size=numpy.product(shape), - nl=os.linesep) + str.format("memset({name}, 0, {size} * sizeof(*{name}));\n", + name=funarg.sym.gencode(), size=numpy.product(shape)) ) - return funarg, [zero], expressions, [] + return funarg, [zero], expressions diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface.py index 13cf57e811..ac87ea3861 100644 --- a/tsfc/kernel_interface.py +++ b/tsfc/kernel_interface.py @@ -98,61 +98,3 @@ def construct_kernel(self, name, args, body): assert isinstance(body, coffee.Block) body_ = coffee.Block(self.prepare + body.children + self.finalise) return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) - - def arguments(self, arguments, indices): - """Prepare arguments. Adds glue code for the arguments. - - :arg arguments: :class:`ufl.Argument`s - :arg indices: GEM argument indices - :returns: COFFEE function argument and GEM expression - representing the argument tensor - """ - funarg, prepare, expressions, finalise = self.prepare_arguments( - arguments, indices, interior_facet=self.interior_facet) - self.apply_glue(prepare, finalise) - return funarg, expressions - - def cell_orientations(self, integral_type): - """Prepare cell orientations. Adds glue code for cell orienatations - and stores cell orientations expression. - - :arg integral_type - :returns: list of COFFEE function arguments for cell orientations - """ - funargs, prepare, expressions = self.prepare_cell_orientations(integral_type) - self.apply_glue(prepare) - self._cell_orientations = expressions - return funargs - - @staticmethod - def prepare_arguments(arguments, indices, interior_facet=False): - """Bridges the kernel interface and the GEM abstraction for - Arguments. - - :arg arguments: UFL Arguments - :arg indices: Argument indices - :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expression, finalise) - funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expressions - GEM expressions referring to the argument - tensor - finalise - list of COFFEE nodes to be appended to the - kernel body - """ - raise NotImplementedError("This class is abstract") - - @staticmethod - def prepare_cell_orientations(integral_type): - """Bridges the kernel interface and the GEM abstraction for - cell orientations. - - :arg integral_type - :returns: (funarg, prepare, expression) - funargs - list of :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expressions- list of GEM expressions referring to facets - """ - raise NotImplementedError("This class is abstract") From 4326bbac7b9a630bddccdd968199e66c9d5c5e55 Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Wed, 21 Sep 2016 14:01:25 +0100 Subject: [PATCH 180/809] fixed entity dict sorting --- tsfc/fiatinterface.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 5d470102e5..0ccd995002 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -90,10 +90,13 @@ def __init__(self, element): nodes = element.dual.nodes self.ref_el = FiredrakeQuadrilateral() entity_ids = element.dual.entity_ids + flat_entity_ids = {} flat_entity_ids[0] = entity_ids[(0, 0)] - flat_entity_ids[1] = dict(enumerate(entity_ids[(0, 1)].values() + - entity_ids[(1, 0)].values())) + flat_entity_ids[1] = dict(enumerate( + [v for k, v in sorted(entity_ids[(0, 1)].items())] + + [v for k, v in sorted(entity_ids[(1, 0)].items())] + )) flat_entity_ids[2] = entity_ids[(1, 1)] self.dual = DualSet(nodes, self.ref_el, flat_entity_ids) @@ -224,11 +227,7 @@ def _(element, vector_is_mixed): element.family(), quad_opc, element.degree()) - # Can't use create_element here because we're going to modify - # it, so if we pull it from the cache, that's bad. - element = convert(element, vector_is_mixed) - - return FlattenToQuad(element) + return FlattenToQuad(create_element(element, vector_is_mixed)) return lmbda(cell, element.degree()) From 4ce4c5d0aa3036dd0b25a7619b1f98525db9f25a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 22 Sep 2016 13:27:06 +0100 Subject: [PATCH 181/809] do not construct Firedrake Kernel object for UFC --- tsfc/backends/ufc.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/tsfc/backends/ufc.py b/tsfc/backends/ufc.py index 0ee87126a3..a9424d2047 100644 --- a/tsfc/backends/ufc.py +++ b/tsfc/backends/ufc.py @@ -7,7 +7,7 @@ import gem -from tsfc.kernel_interface import Kernel, KernelBuilderBase +from tsfc.kernel_interface import KernelBuilderBase from tsfc.fiatinterface import create_element from tsfc.coffee import SCALAR_TYPE, cumulative_strides @@ -18,9 +18,8 @@ class KernelBuilder(KernelBuilderBase): def __init__(self, integral_type, subdomain_id, domain_number): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) + self.integral_type = integral_type - self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, - domain_number=domain_number) self.local_tensor = None self.coordinates_args = None self.coefficient_args = None @@ -93,12 +92,6 @@ def set_coefficients(self, integral_data, form_data): for i, coefficient in enumerate(coefficients): self.coefficient_map[coefficient] = expressions[i] - self.kernel.coefficient_numbers = tuple(coefficient_numbers) - - def require_cell_orientations(self): - """Set that the kernel requires cell orientations.""" - self.kernel.oriented = True - def construct_kernel(self, name, body): """Construct a fully built :class:`Kernel`. @@ -114,10 +107,9 @@ def construct_kernel(self, name, body): args.extend(self.coordinates_args) # Facet number(s) - integral_type = self.kernel.integral_type - if integral_type == "exterior_facet": + if self.integral_type == "exterior_facet": args.append(coffee.Decl("std::size_t", coffee.Symbol("facet"))) - elif integral_type == "interior_facet": + elif self.integral_type == "interior_facet": args.append(coffee.Decl("std::size_t", coffee.Symbol("facet_0"))) args.append(coffee.Decl("std::size_t", coffee.Symbol("facet_1"))) @@ -128,12 +120,16 @@ def construct_kernel(self, name, body): else: args.append(coffee.Decl("int", coffee.Symbol("cell_orientation"))) - self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) - return self.kernel + return KernelBuilderBase.construct_kernel(self, name, args, body) + + @staticmethod + def require_cell_orientations(): + # Nothing to do + pass @staticmethod def needs_cell_orientations(ir): - """UFC requires cell orientations argument(s) everytime""" + # UFC tabulate_tensor always have cell orientations return True From 127b57f17495bf82c746071089644a32ea8f651a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 22 Sep 2016 14:24:58 +0100 Subject: [PATCH 182/809] rename to packages and modules --- tsfc/backends/__init__.py | 0 tsfc/driver.py | 13 +++++++------ tsfc/kernel_interface/__init__.py | 1 + .../common.py} | 0 .../pyop2.py => kernel_interface/firedrake.py} | 2 +- tsfc/{backends => kernel_interface}/ufc.py | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) delete mode 100644 tsfc/backends/__init__.py create mode 100644 tsfc/kernel_interface/__init__.py rename tsfc/{kernel_interface.py => kernel_interface/common.py} (100%) rename tsfc/{backends/pyop2.py => kernel_interface/firedrake.py} (99%) rename tsfc/{backends => kernel_interface}/ufc.py (99%) diff --git a/tsfc/backends/__init__.py b/tsfc/backends/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tsfc/driver.py b/tsfc/driver.py index 216eabb0d0..c2a77376fe 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -11,12 +11,13 @@ import gem.impero_utils as impero_utils from tsfc import fem, ufl_utils -from tsfc.backends import pyop2 as default_backend from tsfc.coffee import generate as generate_coffee from tsfc.constants import default_parameters from tsfc.fiatinterface import QuadratureRule, as_fiat_cell, create_quadrature from tsfc.logging import logger +import tsfc.kernel_interface.firedrake as firedrake_interface + def compile_form(form, prefix="form", parameters=None): """Compiles a UFL form into a set of assembly kernels. @@ -47,15 +48,15 @@ def compile_form(form, prefix="form", parameters=None): def compile_integral(integral_data, form_data, prefix, parameters, - backend=default_backend): + interface=firedrake_interface): """Compiles a UFL integral into an assembly kernel. :arg integral_data: UFL integral data :arg form_data: UFL form data :arg prefix: kernel name will start with this string :arg parameters: parameters object - :arg backend: output format - :returns: a kernel, or None if the integral simplifies to zero + :arg interface: backend module for the kernel interface + :returns: a kernel constructed by the kernel interface """ if parameters is None: parameters = default_parameters() @@ -84,8 +85,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() - builder = backend.KernelBuilder(integral_type, integral_data.subdomain_id, - domain_numbering[integral_data.domain]) + builder = interface.KernelBuilder(integral_type, integral_data.subdomain_id, + domain_numbering[integral_data.domain]) return_variables = builder.set_arguments(arguments, argument_indices) coordinates = ufl_utils.coordinate_coefficient(mesh) diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py new file mode 100644 index 0000000000..c3961685ab --- /dev/null +++ b/tsfc/kernel_interface/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import diff --git a/tsfc/kernel_interface.py b/tsfc/kernel_interface/common.py similarity index 100% rename from tsfc/kernel_interface.py rename to tsfc/kernel_interface/common.py diff --git a/tsfc/backends/pyop2.py b/tsfc/kernel_interface/firedrake.py similarity index 99% rename from tsfc/backends/pyop2.py rename to tsfc/kernel_interface/firedrake.py index 8e2c8678c2..8be5703886 100644 --- a/tsfc/backends/pyop2.py +++ b/tsfc/kernel_interface/firedrake.py @@ -11,7 +11,7 @@ from gem.node import traversal from gem.gem import FlexiblyIndexed as gem_FlexiblyIndexed -from tsfc.kernel_interface import Kernel, KernelBuilderBase as _KernelBuilderBase +from tsfc.kernel_interface.common import Kernel, KernelBuilderBase as _KernelBuilderBase from tsfc.fiatinterface import create_element from tsfc.mixedelement import MixedElement from tsfc.coffee import SCALAR_TYPE diff --git a/tsfc/backends/ufc.py b/tsfc/kernel_interface/ufc.py similarity index 99% rename from tsfc/backends/ufc.py rename to tsfc/kernel_interface/ufc.py index a9424d2047..192348ed45 100644 --- a/tsfc/backends/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -7,7 +7,7 @@ import gem -from tsfc.kernel_interface import KernelBuilderBase +from tsfc.kernel_interface.common import KernelBuilderBase from tsfc.fiatinterface import create_element from tsfc.coffee import SCALAR_TYPE, cumulative_strides From 2503337409da8b587c0cce2d78a2cc5ba922a95d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 22 Sep 2016 14:50:39 +0100 Subject: [PATCH 183/809] final clean up (hopefully) --- tsfc/kernel_interface/common.py | 35 +++++-------------------- tsfc/kernel_interface/firedrake.py | 42 +++++++++++++++++++++++------- tsfc/kernel_interface/ufc.py | 13 ++++----- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index ac87ea3861..89e5def610 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -7,35 +7,6 @@ import gem -class Kernel(object): - __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", - "domain_number", - "coefficient_numbers", "__weakref__") - """A compiled Kernel object. - - :kwarg ast: The COFFEE ast for the kernel. - :kwarg integral_type: The type of integral. - :kwarg oriented: Does the kernel require cell_orientations. - :kwarg subdomain_id: What is the subdomain id for this kernel. - :kwarg domain_number: Which domain number in the original form - does this kernel correspond to (can be used to index into - original_form.ufl_domains() to get the correct domain). - :kwarg coefficient_numbers: A list of which coefficients from the - form the kernel needs. - """ - def __init__(self, ast=None, integral_type=None, oriented=False, - subdomain_id=None, domain_number=None, - coefficient_numbers=()): - # Defaults - self.ast = ast - self.integral_type = integral_type - self.oriented = oriented - self.domain_number = domain_number - self.subdomain_id = subdomain_id - self.coefficient_numbers = coefficient_numbers - super(Kernel, self).__init__() - - class KernelBuilderBase(object): """Helper class for building local assembly kernels.""" @@ -65,6 +36,7 @@ def coefficient(self, ufl_coefficient, restriction): def cell_orientation(self, restriction): """Cell orientation as a GEM expression.""" f = {None: 0, '+': 0, '-': 1}[restriction] + # Assume self._cell_orientations tuple is set up at this point. co_int = self._cell_orientations[f] return gem.Conditional(gem.Comparison("==", co_int, gem.Literal(1)), gem.Literal(-1), @@ -72,6 +44,11 @@ def cell_orientation(self, restriction): gem.Literal(1), gem.Literal(numpy.nan))) + def facet_number(self, restriction): + """Facet number as a GEM index.""" + # Assume self._facet_number dict is set up at this point. + return self._facet_number[restriction] + def apply_glue(self, prepare=None, finalise=None): """Append glue code for operations that are not handled in the GEM abstraction. diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 8be5703886..172c34a191 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -9,14 +9,42 @@ import gem from gem.node import traversal -from gem.gem import FlexiblyIndexed as gem_FlexiblyIndexed -from tsfc.kernel_interface.common import Kernel, KernelBuilderBase as _KernelBuilderBase +from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase from tsfc.fiatinterface import create_element from tsfc.mixedelement import MixedElement from tsfc.coffee import SCALAR_TYPE +class Kernel(object): + __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + "domain_number", + "coefficient_numbers", "__weakref__") + """A compiled Kernel object. + + :kwarg ast: The COFFEE ast for the kernel. + :kwarg integral_type: The type of integral. + :kwarg oriented: Does the kernel require cell_orientations. + :kwarg subdomain_id: What is the subdomain id for this kernel. + :kwarg domain_number: Which domain number in the original form + does this kernel correspond to (can be used to index into + original_form.ufl_domains() to get the correct domain). + :kwarg coefficient_numbers: A list of which coefficients from the + form the kernel needs. + """ + def __init__(self, ast=None, integral_type=None, oriented=False, + subdomain_id=None, domain_number=None, + coefficient_numbers=()): + # Defaults + self.ast = ast + self.integral_type = integral_type + self.oriented = oriented + self.domain_number = domain_number + self.subdomain_id = subdomain_id + self.coefficient_numbers = coefficient_numbers + super(Kernel, self).__init__() + + class KernelBuilderBase(_KernelBuilderBase): def __init__(self, interior_facet=False): @@ -44,7 +72,7 @@ def _coefficient(self, coefficient, name, mode=None): :arg mode: see :func:`prepare_coefficient` :returns: COFFEE function argument for the coefficient """ - funarg, prepare, expression = _prepare_coefficient( + funarg, prepare, expression = prepare_coefficient( coefficient, name, mode=mode, interior_facet=self.interior_facet) self.apply_glue(prepare) @@ -88,10 +116,6 @@ def __init__(self, integral_type, subdomain_id, domain_number): elif integral_type == 'interior_facet_horiz': self._facet_number = {'+': 1, '-': 0} - def facet_number(self, restriction): - """Facet number as a GEM index.""" - return self._facet_number[restriction] - def set_arguments(self, arguments, indices): """Process arguments. @@ -173,7 +197,7 @@ def construct_kernel(self, name, body): return self.kernel -def _prepare_coefficient(coefficient, name, mode=None, interior_facet=False): +def prepare_coefficient(coefficient, name, mode=None, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. Mixed element Coefficients are rearranged here for interior facet integrals. @@ -354,7 +378,7 @@ def prepare_arguments(arguments, indices, interior_facet=False): expressions = [] for restrictions in product((0, 1), repeat=len(arguments)): - expressions.append(gem_FlexiblyIndexed( + expressions.append(gem.FlexiblyIndexed( varexp, tuple((r * e.space_dimension(), ((i, e.space_dimension()),)) for e, i, r in zip(elements, indices, restrictions)) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 192348ed45..edbd61fa03 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -32,15 +32,12 @@ def __init__(self, integral_type, subdomain_id, domain_number): self._cell_orientations = (gem.Variable("cell_orientation", ()),) if integral_type == "exterior_facet": - self._facet_number = (gem.VariableIndex(gem.Variable("facet", ())),) + self._facet_number = {None: gem.VariableIndex(gem.Variable("facet", ()))} elif integral_type == "interior_facet": - self._facet_number = (gem.VariableIndex(gem.Variable("facet_0", ())), - gem.VariableIndex(gem.Variable("facet_1", ()))) - - def facet_number(self, restriction): - """Facet number as a GEM index.""" - f = {None: 0, '+': 0, '-': 1}[restriction] - return self._facet_number[f] + self._facet_number = { + '+': gem.VariableIndex(gem.Variable("facet_0", ())), + '-': gem.VariableIndex(gem.Variable("facet_1", ())) + } def set_arguments(self, arguments, indices): """Process arguments. From 5782a7dc0f05b1f25a05e72aaa4e901307a53d95 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 22 Sep 2016 14:52:08 +0100 Subject: [PATCH 184/809] do not split mixed coefficients for DOLFIN --- tsfc/ufl_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 2aef193052..a915bb8e0e 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -162,6 +162,9 @@ def modified_terminal(self, o): def split_coefficients(expression, split): """Split mixed coefficients, so mixed elements need not be implemented.""" + if split is None: + # Skip this step for DOLFIN + return expression splitter = CoefficientSplitter(split) return map_expr_dag(splitter, expression) From 335f1d21b2747c30fdbbe0c70b9851947e2fdebc Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 22 Sep 2016 16:09:30 +0100 Subject: [PATCH 185/809] re-add FFC-style rounding TODO: avoid duplication of logic --- tsfc/coffee.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 9bdc825809..9b91e9d2f7 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -184,7 +184,15 @@ def statement_evaluate(leaf, parameters): nz_indices, = expr.array.any(axis=axes).nonzero() nz_bounds = tuple([(i, 0)] for i in expr.array.shape[:-1]) nz_bounds += ([(max(nz_indices) - min(nz_indices) + 1, min(nz_indices))],) - init = coffee.SparseArrayInit(expr.array, PRECISION, nz_bounds) + table = numpy.array(expr.array) + # FFC uses one less digits for rounding than for printing + epsilon = eval("1e-%d" % (PRECISION - 1)) + table[abs(table) < epsilon] = 0 + table[abs(table - 1.0) < epsilon] = 1.0 + table[abs(table + 1.0) < epsilon] = -1.0 + table[abs(table - 0.5) < epsilon] = 0.5 + table[abs(table + 0.5) < epsilon] = -0.5 + init = coffee.SparseArrayInit(table, PRECISION, nz_bounds) return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), init, @@ -295,7 +303,20 @@ def _expression_scalar(expr, parameters): if isnan(expr.value): return coffee.Symbol("NAN") else: - return coffee.Symbol(("%%.%dg" % (PRECISION - 1)) % expr.value) + value = float(expr.value) + # FFC uses one less digits for rounding than for printing + epsilon = eval("1e-%d" % (PRECISION - 1)) + if abs(value) < epsilon: + value = 0 + if abs(value - 1.0) < epsilon: + value = 1.0 + if abs(value + 1.0) < epsilon: + value = -1.0 + if abs(value - 0.5) < epsilon: + value = 0.5 + if abs(value + 0.5) < epsilon: + value = -0.5 + return coffee.Symbol(("%%.%dg" % (PRECISION - 1)) % value) @_expression.register(gem.Variable) From 879af7e53d3fb05dbd8e83401418ba819992039c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Fri, 23 Sep 2016 09:28:52 +0100 Subject: [PATCH 186/809] Fix type of buffer size Credit to Jan Blechta. --- tsfc/kernel_interface/ufc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index edbd61fa03..7e50cc18ee 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -260,6 +260,6 @@ def prepare_arguments(arguments, indices, interior_facet=False): zero = coffee.FlatBlock( str.format("memset({name}, 0, {size} * sizeof(*{name}));\n", - name=funarg.sym.gencode(), size=numpy.product(shape)) + name=funarg.sym.gencode(), size=numpy.product(shape, dtype=int)) ) return funarg, [zero], expressions From 01b196727869c423b60258de4f17bb86edead3d3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 23 Sep 2016 10:15:57 +0100 Subject: [PATCH 187/809] futurize --stage1 --all-imports --- tests/test_codegen.py | 2 ++ tests/test_create_element.py | 1 + tests/test_idempotency.py | 1 + tsfc/__init__.py | 2 +- tsfc/coffee.py | 3 ++- tsfc/compat.py | 1 + tsfc/constants.py | 2 +- tsfc/driver.py | 3 ++- tsfc/fem.py | 3 ++- tsfc/fiatinterface.py | 3 +-- tsfc/geometric.py | 2 +- tsfc/kernel_interface/__init__.py | 2 +- tsfc/kernel_interface/common.py | 2 +- tsfc/kernel_interface/firedrake.py | 2 +- tsfc/kernel_interface/ufc.py | 4 ++-- tsfc/logging.py | 2 +- tsfc/mixedelement.py | 3 +-- tsfc/modified_terminals.py | 2 +- tsfc/ufl2gem.py | 2 +- tsfc/ufl_utils.py | 2 +- 20 files changed, 25 insertions(+), 19 deletions(-) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 19b58b8ecc..af8c2f6366 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, print_function, division + import pytest from gem import impero_utils diff --git a/tests/test_create_element.py b/tests/test_create_element.py index b3b3449994..c56e71dfc1 100644 --- a/tests/test_create_element.py +++ b/tests/test_create_element.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, print_function, division from tsfc import fiatinterface as f import pytest import ufl diff --git a/tests/test_idempotency.py b/tests/test_idempotency.py index 7656592e2b..e5ce04dda7 100644 --- a/tests/test_idempotency.py +++ b/tests/test_idempotency.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, print_function, division import ufl from tsfc import compile_form import pytest diff --git a/tsfc/__init__.py b/tsfc/__init__.py index ca26644e98..020805e6d5 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -1,3 +1,3 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division from tsfc.driver import compile_form # noqa diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 9bdc825809..4b42a56035 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -2,9 +2,10 @@ This is the final stage of code generation in TSFC.""" -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division from collections import defaultdict +from functools import reduce from math import isnan import itertools diff --git a/tsfc/compat.py b/tsfc/compat.py index b41be73e52..dc490b226f 100644 --- a/tsfc/compat.py +++ b/tsfc/compat.py @@ -1,6 +1,7 @@ """ Backwards compatibility for some functionality. """ +from __future__ import absolute_import, print_function, division import numpy from distutils.version import StrictVersion diff --git a/tsfc/constants.py b/tsfc/constants.py index deb8309d75..085d436e6a 100644 --- a/tsfc/constants.py +++ b/tsfc/constants.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import numpy diff --git a/tsfc/driver.py b/tsfc/driver.py index c2a77376fe..1190a74a1e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,7 +1,8 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import collections import time +from functools import reduce from ufl.classes import Form from ufl.log import GREEN diff --git a/tsfc/fem.py b/tsfc/fem.py index 9886b14a4b..bb0e3b009e 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -1,7 +1,8 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import collections import itertools +from functools import reduce import numpy from singledispatch import singledispatch diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 0ccd995002..4905bcca83 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -21,8 +21,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with FFC. If not, see . -from __future__ import absolute_import -from __future__ import print_function +from __future__ import absolute_import, print_function, division from singledispatch import singledispatch from functools import partial diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 0451fdf10d..c4e7c66905 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -1,7 +1,7 @@ """Functions to translate UFL reference geometric quantities into GEM expressions.""" -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division from numpy import allclose, vstack from singledispatch import singledispatch diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index c3961685ab..f298a6112c 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -1 +1 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 89e5def610..9a83c383e5 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import numpy diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 172c34a191..f37f385428 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import numpy from itertools import product diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 7e50cc18ee..84e3073c7d 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import numpy from itertools import product @@ -201,7 +201,7 @@ def prepare_coordinates(coefficient, name, interior_facet=False): shape = (fiat_element.space_dimension(),) gdim = coefficient.ufl_element().cell().geometric_dimension() assert len(shape) == 1 and shape[0] % gdim == 0 - num_nodes = shape[0] / gdim + num_nodes = shape[0] // gdim # Translate coords from XYZXYZXYZXYZ into XXXXYYYYZZZZ # NOTE: See dolfin/mesh/Cell.h:get_coordinate_dofs for ordering scheme diff --git a/tsfc/logging.py b/tsfc/logging.py index 1ba89abf1a..e064838960 100644 --- a/tsfc/logging.py +++ b/tsfc/logging.py @@ -1,6 +1,6 @@ """Logging for TSFC.""" -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import logging diff --git a/tsfc/mixedelement.py b/tsfc/mixedelement.py index 2ac4a05fa5..318743af12 100644 --- a/tsfc/mixedelement.py +++ b/tsfc/mixedelement.py @@ -21,8 +21,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with FFC. If not, see . -from __future__ import absolute_import -from __future__ import print_function +from __future__ import absolute_import, print_function, division import numpy diff --git a/tsfc/modified_terminals.py b/tsfc/modified_terminals.py index db587b697a..85712069d9 100644 --- a/tsfc/modified_terminals.py +++ b/tsfc/modified_terminals.py @@ -20,7 +20,7 @@ """Definitions of 'modified terminals', a core concept in uflacs.""" -from __future__ import print_function # used in some debugging +from __future__ import absolute_import, print_function, division from ufl.classes import (ReferenceValue, ReferenceGrad, NegativeRestricted, PositiveRestricted, diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 1d9654dfaf..673d39c6d9 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -1,6 +1,6 @@ """Translation of UFL tensor-algebra into GEM tensor-algebra.""" -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import collections import ufl diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index a915bb8e0e..d31b9528c2 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -1,6 +1,6 @@ """Utilities for preprocessing UFL objects.""" -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import numpy from singledispatch import singledispatch From 044001d85e650f8652288dc8acb01497da02ceb5 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 23 Sep 2016 11:27:05 +0100 Subject: [PATCH 188/809] TSFC: list/iterable compatibility improvements --- tsfc/driver.py | 3 ++- tsfc/fem.py | 11 ++++++----- tsfc/mixedelement.py | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 1190a74a1e..845ba990c8 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,4 +1,5 @@ from __future__ import absolute_import, print_function, division +from six.moves import range import collections import time @@ -278,6 +279,6 @@ def lower_integral_type(fiat_cell, integral_type): elif integral_type == 'exterior_facet_top': entity_ids = [1] else: - entity_ids = range(len(fiat_cell.get_topology()[integration_dim])) + entity_ids = list(range(len(fiat_cell.get_topology()[integration_dim]))) return integration_dim, entity_ids diff --git a/tsfc/fem.py b/tsfc/fem.py index bb0e3b009e..fd1fd5da7c 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -1,4 +1,5 @@ from __future__ import absolute_import, print_function, division +from six.moves import map, range import collections import itertools @@ -89,7 +90,7 @@ def __init__(self, entity_points): each integration entity, i.e. an iterable of arrays of points. """ - self.tabulators = map(make_tabulator, entity_points) + self.tabulators = list(map(make_tabulator, entity_points)) self.tables = {} def tabulate(self, ufl_element, max_deriv): @@ -188,7 +189,7 @@ def entity_points(self): result = [] for entity_id in self.entity_ids: t = self.fiat_cell.get_entity_transform(self.integration_dim, entity_id) - result.append(numpy.asarray(map(t, self.points))) + result.append(numpy.asarray(list(map(t, self.points)))) return result def _selector(self, callback, opts, restriction): @@ -197,7 +198,7 @@ def _selector(self, callback, opts, restriction): if len(opts) == 1: return callback(opts[0]) else: - results = gem.ListTensor(map(callback, opts)) + results = gem.ListTensor(list(map(callback, opts))) f = self.facet_number(restriction) return gem.partial_indexed(results, (f,)) @@ -226,7 +227,7 @@ def index_selector(self, callback, restriction): :arg restriction: Restriction of the modified terminal, used for entity selection. """ - return self._selector(callback, range(len(self.entity_ids)), restriction) + return self._selector(callback, list(range(len(self.entity_ids))), restriction) argument_indices = () @@ -272,7 +273,7 @@ def flat_index(ordered_deriv): return tuple((numpy.asarray(ordered_deriv) == d).sum() for d in range(dim)) ordered_derivs = itertools.product(range(dim), repeat=mt.local_derivatives) - flat_derivs = map(flat_index, ordered_derivs) + flat_derivs = list(map(flat_index, ordered_derivs)) result = [] for c in range(ufl_element.reference_value_size()): diff --git a/tsfc/mixedelement.py b/tsfc/mixedelement.py index 318743af12..f7fb5ecf77 100644 --- a/tsfc/mixedelement.py +++ b/tsfc/mixedelement.py @@ -22,6 +22,7 @@ # along with FFC. If not, see . from __future__ import absolute_import, print_function, division +from six.moves import map import numpy @@ -67,8 +68,8 @@ def entity_dofs(self): for i, d in enumerate(dicts): for dim, dofs in d.items(): for ent, off in dofs.items(): - ret[dim][ent] += map(partial(add, offsets[i]), - off) + ret[dim][ent] += list(map(partial(add, offsets[i]), + off)) self._entity_dofs = ret return self._entity_dofs From 3e7fa5e19c0a196fecbac9698ca2df5b8675d54f Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 23 Sep 2016 14:21:26 +0100 Subject: [PATCH 189/809] fix create_element testing --- ...element.py => test_create_fiat_element.py} | 39 ------------ tests/test_create_finat_element.py | 59 +++++++++++++++++++ tsfc/finatinterface.py | 59 +++++++------------ 3 files changed, 80 insertions(+), 77 deletions(-) rename tests/{test_create_element.py => test_create_fiat_element.py} (57%) create mode 100644 tests/test_create_finat_element.py diff --git a/tests/test_create_element.py b/tests/test_create_fiat_element.py similarity index 57% rename from tests/test_create_element.py rename to tests/test_create_fiat_element.py index b3b3449994..a93406f569 100644 --- a/tests/test_create_element.py +++ b/tests/test_create_fiat_element.py @@ -25,27 +25,6 @@ def test_triangle_basic(ufl_element): assert isinstance(element, f.supported_elements[ufl_element.family()]) -@pytest.fixture -def ufl_vector_element(triangle_names): - return ufl.VectorElement(triangle_names, ufl.triangle, 2) - - -@pytest.mark.parametrize("mixed", - [False, True]) -def test_triangle_vector(mixed, ufl_element, ufl_vector_element): - scalar = f.create_element(ufl_element) - vector = f.create_element(ufl_vector_element, vector_is_mixed=mixed) - - if not mixed: - assert isinstance(scalar, f.supported_elements[ufl_element.family()]) - assert isinstance(vector, f.supported_elements[ufl_element.family()]) - - else: - assert isinstance(vector, f.MixedElement) - assert isinstance(vector.elements()[0], f.supported_elements[ufl_element.family()]) - assert len(vector.elements()) == ufl_vector_element.num_sub_elements() - - @pytest.fixture(params=["CG", "DG"]) def tensor_name(request): return request.param @@ -83,24 +62,6 @@ def test_cache_hit(ufl_element): assert A is B -def test_cache_hit_vector(ufl_vector_element): - A = f.create_element(ufl_vector_element) - B = f.create_element(ufl_vector_element) - - assert A is B - - assert all(a == A.elements()[0] for a in A.elements()) - - -def test_cache_miss_vector(ufl_vector_element): - A = f.create_element(ufl_vector_element) - B = f.create_element(ufl_vector_element, vector_is_mixed=False) - - assert A is not B - - assert A.elements()[0] is not B - - if __name__ == "__main__": import os import sys diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py new file mode 100644 index 0000000000..3d9ad54ad5 --- /dev/null +++ b/tests/test_create_finat_element.py @@ -0,0 +1,59 @@ +from tsfc import finatinterface as f +import pytest +import ufl +import finat + + +@pytest.fixture(params=["BDM", + "BDFM", + "DRT", + "Lagrange", + "N1curl", + "N2curl", + "RT", + "Regge"]) +def triangle_names(request): + return request.param + + +@pytest.fixture +def ufl_element(triangle_names): + return ufl.FiniteElement(triangle_names, ufl.triangle, 2) + + +def test_triangle_basic(ufl_element): + element = f.create_element(ufl_element) + assert isinstance(element, f.supported_elements[ufl_element.family()]) + + +@pytest.fixture +def ufl_vector_element(triangle_names): + return ufl.VectorElement(triangle_names, ufl.triangle, 2) + + +def test_triangle_vector(ufl_element, ufl_vector_element): + scalar = f.create_element(ufl_element) + vector = f.create_element(ufl_vector_element) + + assert isinstance(vector, finat.TensorFiniteElement) + assert scalar == vector.base_element + + +def test_cache_hit(ufl_element): + A = f.create_element(ufl_element) + B = f.create_element(ufl_element) + + assert A is B + + +def test_cache_hit_vector(ufl_vector_element): + A = f.create_element(ufl_vector_element) + B = f.create_element(ufl_vector_element) + + assert A is B + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 2e95ffe382..d65ef0983f 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -41,8 +41,12 @@ "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": finat.BrezziDouglasFortinMarini, "Discontinuous Lagrange": finat.DiscontinuousLagrange, + "Discontinuous Raviart-Thomas": finat.DiscontinuousRaviartThomas, "Lagrange": finat.Lagrange, + "Nedelec 1st kind H(curl)": finat.Nedelec, + "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, "Raviart-Thomas": finat.RaviartThomas, + "Regge": finat.Regge, } """A :class:`.dict` mapping UFL element family names to their FIAT-equivalent constructors. If the value is ``None``, the UFL @@ -59,96 +63,75 @@ def as_fiat_cell(cell): return FIAT.ufc_cell(cell) -def fiat_compat(element, vector_is_mixed): - from tsfc.fiatinterface import convert +def fiat_compat(element): + from tsfc.fiatinterface import create_element from finat.fiat_elements import FiatElementBase cell = as_fiat_cell(element.cell()) finat_element = FiatElementBase(cell, element.degree()) - finat_element._fiat_element = convert(element, vector_is_mixed=vector_is_mixed) + finat_element._fiat_element = create_element(element) return finat_element @singledispatch -def convert(element, vector_is_mixed): +def convert(element): """Handler for converting UFL elements to FIAT elements. :arg element: The UFL element to convert. - :arg vector_is_mixed: Should Vector and Tensor elements be treated - as Mixed? If ``False``, then just look at the sub-element. Do not use this function directly, instead call :func:`create_element`.""" if element.family() in supported_elements: raise ValueError("Element %s supported, but no handler provided" % element) - return fiat_compat(element, vector_is_mixed) + return fiat_compat(element) # Base finite elements first @convert.register(ufl.FiniteElement) -def convert_finiteelement(element, vector_is_mixed): +def convert_finiteelement(element): cell = as_fiat_cell(element.cell()) lmbda = supported_elements.get(element.family()) if lmbda: return lmbda(cell, element.degree()) else: - return fiat_compat(element, vector_is_mixed) + return fiat_compat(element) # MixedElement case @convert.register(ufl.MixedElement) -def convert_mixedelement(element, vector_is_mixed): +def convert_mixedelement(element): raise ValueError("FInAT does not implement generic mixed element.") # VectorElement case @convert.register(ufl.VectorElement) -def convert_vectorelement(element, vector_is_mixed): - # If we're just trying to get the scalar part of a vector element? - if not vector_is_mixed: - return create_element(element.sub_elements()[0], vector_is_mixed) - - scalar_element = create_element(element.sub_elements()[0], vector_is_mixed) +def convert_vectorelement(element): + scalar_element = create_element(element.sub_elements()[0]) return finat.TensorFiniteElement(scalar_element, (element.num_sub_elements(),)) # TensorElement case @convert.register(ufl.TensorElement) -def convert_tensorelement(element, vector_is_mixed): - # If we're just trying to get the scalar part of a vector element? - if not vector_is_mixed: - return create_element(element.sub_elements()[0], vector_is_mixed) - - scalar_element = create_element(element.sub_elements()[0], vector_is_mixed) +def convert_tensorelement(element): + scalar_element = create_element(element.sub_elements()[0]) return finat.TensorFiniteElement(scalar_element, element.reference_value_shape()) _cache = weakref.WeakKeyDictionary() -def create_element(element, vector_is_mixed=True): +def create_element(element): """Create a FIAT element (suitable for tabulating with) given a UFL element. :arg element: The UFL element to create a FIAT element from. - - :arg vector_is_mixed: indicate whether VectorElement (or - TensorElement) should be treated as a MixedElement. Maybe - useful if you want a FIAT element that tells you how many - "nodes" the finite element has. """ try: - cache = _cache[element] - except KeyError: - _cache[element] = {} - cache = _cache[element] - - try: - return cache[vector_is_mixed] + return _cache[element] except KeyError: pass if element.cell() is None: raise ValueError("Don't know how to build element when cell is not given") - fiat_element = convert(element, vector_is_mixed) - cache[vector_is_mixed] = fiat_element - return fiat_element + finat_element = convert(element) + _cache[element] = finat_element + return finat_element From ce4038704ad91d29a57910de19181a1adf2f641a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Sat, 24 Sep 2016 13:55:46 +0100 Subject: [PATCH 190/809] TSFC: use six.iteritems --- tsfc/fem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index fd1fd5da7c..c1b62e305e 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -1,4 +1,5 @@ from __future__ import absolute_import, print_function, division +from six import iteritems from six.moves import map, range import collections @@ -49,7 +50,7 @@ def _tabulate(ufl_element, order, points): phi = element.space_dimension() C = ufl_element.reference_value_size() q = len(points) - for D, fiat_table in element.tabulate(order, points).iteritems(): + for D, fiat_table in iteritems(element.tabulate(order, points)): reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) for c, table in enumerate(reordered_table): yield c, D, table @@ -105,7 +106,7 @@ def tabulate(self, ufl_element, max_deriv): for c, D, table in tabulator(ufl_element, max_deriv): store[(ufl_element, c, D)].append(table) - for key, tables in store.iteritems(): + for key, tables in iteritems(store): table = numpy.array(tables) if len(table.shape) == 2: # Cellwise constant; must not depend on the facet From bba0be49ece46a3ab3ba9f3a4d020e7088cb3f2a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 26 Sep 2016 12:19:38 +0100 Subject: [PATCH 191/809] fix facet integral performance issue --- tsfc/fem.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 9e59c03bf3..fc10a20ccf 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -112,9 +112,8 @@ def _selector(self, callback, opts, restriction): if len(opts) == 1: return callback(opts[0]) else: - results = gem.ListTensor(map(callback, opts)) f = self.facet_number(restriction) - return gem.partial_indexed(results, (f,)) + return gem.select_expression(map(callback, opts), f) def entity_selector(self, callback, restriction): """Selects code for the correct entity at run-time. Callback From 3139c099dbdd2dbad31dd3ad73968899a1d018ec Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 10 Oct 2016 15:57:56 +0100 Subject: [PATCH 192/809] import compile_ufl_kernel from Firedrake --- tsfc/interpolate.py | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tsfc/interpolate.py diff --git a/tsfc/interpolate.py b/tsfc/interpolate.py new file mode 100644 index 0000000000..c2416ad763 --- /dev/null +++ b/tsfc/interpolate.py @@ -0,0 +1,97 @@ +"""Kernel generation for interpolating UFL expressions onto finite +element function spaces with only point evaluation nodes, using the +existing TSFC infrastructure.""" + +from __future__ import absolute_import, print_function, division + +from collections import namedtuple + +import ufl +import coffee.base as ast + +from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks +from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering +from ufl.algorithms.apply_derivatives import apply_derivatives +from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering +from ufl.algorithms import extract_arguments, extract_coefficients +from gem import gem, impero_utils +from tsfc import fem, ufl_utils +from tsfc.coffee import generate as generate_coffee +from tsfc.kernel_interface.firedrake import KernelBuilderBase, cell_orientations_coffee_arg + + +# Expression kernel description type +Kernel = namedtuple('Kernel', ['ast', 'oriented', 'coefficients']) + + +def compile_ufl_kernel(expression, to_pts, to_element, fs): + + # Imitate the compute_form_data processing pipeline + # + # Unfortunately, we cannot call compute_form_data here, since + # we only have an expression, not a form + expression = apply_algebra_lowering(expression) + expression = apply_derivatives(expression) + expression = apply_function_pullbacks(expression) + expression = apply_geometry_lowering(expression) + expression = apply_derivatives(expression) + expression = apply_geometry_lowering(expression) + expression = apply_derivatives(expression) + + # Replace coordinates (if any) + if expression.ufl_domain(): + assert fs.mesh() == expression.ufl_domain() + expression = ufl_utils.replace_coordinates(expression, fs.mesh().coordinates) + + if extract_arguments(expression): + return ValueError("Cannot interpolate UFL expression with Arguments!") + + builder = KernelBuilderBase() + coefficient_split = {} + + coefficients = [] + args = [] + for i, coefficient in enumerate(extract_coefficients(expression)): + if type(coefficient.ufl_element()) == ufl.MixedElement: + coefficient_split[coefficient] = [] + for j, element in enumerate(coefficient.ufl_element().sub_elements()): + subcoeff = ufl.Coefficient(ufl.FunctionSpace(coefficient.ufl_domain(), element)) + coefficient_split[coefficient].append(subcoeff) + args.append(builder._coefficient(subcoeff, "w_%d_%d" % (i, j))) + coefficients.extend(coefficient.split()) + else: + args.append(builder._coefficient(coefficient, "w_%d" % (i,))) + coefficients.append(coefficient) + + expression = ufl_utils.split_coefficients(expression, coefficient_split) + + point_index = gem.Index(name='p') + ir = fem.compile_ufl(expression, + cell=fs.mesh().ufl_cell(), + points=to_pts, + point_index=point_index, + coefficient=builder.coefficient, + cell_orientation=builder.cell_orientation) + assert len(ir) == 1 + + # Deal with non-scalar expressions + tensor_indices = () + if fs.shape: + tensor_indices = tuple(gem.Index() for s in fs.shape) + ir = [gem.Indexed(ir[0], tensor_indices)] + + # Build kernel body + return_var = gem.Variable('A', (len(to_pts),) + fs.shape) + return_expr = gem.Indexed(return_var, (point_index,) + tensor_indices) + impero_c = impero_utils.compile_gem([return_expr], ir, (point_index,) + tensor_indices) + body = generate_coffee(impero_c, index_names={point_index: 'p'}) + + oriented = KernelBuilderBase.needs_cell_orientations(ir) + if oriented: + args.insert(0, cell_orientations_coffee_arg) + + # Build kernel + args.insert(0, ast.Decl("double", ast.Symbol('A', rank=(len(to_pts),) + fs.shape))) + kernel_code = builder.construct_kernel("expression_kernel", args, body) + + return Kernel(kernel_code, oriented, coefficients) From 60d2f35485f1dbe2707e4fbd1796a3e548e094bf Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 10 Oct 2016 17:17:19 +0100 Subject: [PATCH 193/809] refactor external API --- tsfc/__init__.py | 3 ++- tsfc/interpolate.py | 45 +++++++++++++++++---------------------------- tsfc/ufl_utils.py | 20 ++++++++++++++++++++ 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/tsfc/__init__.py b/tsfc/__init__.py index 020805e6d5..3d431ba2b8 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -1,3 +1,4 @@ from __future__ import absolute_import, print_function, division -from tsfc.driver import compile_form # noqa +from tsfc.driver import compile_form # noqa: F401 +from tsfc.interpolate import compile_expression_at_points # noqa: F401 diff --git a/tsfc/interpolate.py b/tsfc/interpolate.py index c2416ad763..0e389f5e8b 100644 --- a/tsfc/interpolate.py +++ b/tsfc/interpolate.py @@ -9,10 +9,6 @@ import ufl import coffee.base as ast -from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks -from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering -from ufl.algorithms.apply_derivatives import apply_derivatives -from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.algorithms import extract_arguments, extract_coefficients from gem import gem, impero_utils from tsfc import fem, ufl_utils @@ -24,27 +20,19 @@ Kernel = namedtuple('Kernel', ['ast', 'oriented', 'coefficients']) -def compile_ufl_kernel(expression, to_pts, to_element, fs): +def compile_expression_at_points(expression, refpoints, coordinates): + # No arguments, please! + if extract_arguments(expression): + return ValueError("Cannot interpolate UFL expression with Arguments!") - # Imitate the compute_form_data processing pipeline - # - # Unfortunately, we cannot call compute_form_data here, since - # we only have an expression, not a form - expression = apply_algebra_lowering(expression) - expression = apply_derivatives(expression) - expression = apply_function_pullbacks(expression) - expression = apply_geometry_lowering(expression) - expression = apply_derivatives(expression) - expression = apply_geometry_lowering(expression) - expression = apply_derivatives(expression) + # Apply UFL preprocessing + expression = ufl_utils.preprocess_expression(expression) # Replace coordinates (if any) - if expression.ufl_domain(): - assert fs.mesh() == expression.ufl_domain() - expression = ufl_utils.replace_coordinates(expression, fs.mesh().coordinates) - - if extract_arguments(expression): - return ValueError("Cannot interpolate UFL expression with Arguments!") + domain = expression.ufl_domain() + if domain: + assert coordinates.ufl_domain() == domain + expression = ufl_utils.replace_coordinates(expression, coordinates) builder = KernelBuilderBase() coefficient_split = {} @@ -67,8 +55,8 @@ def compile_ufl_kernel(expression, to_pts, to_element, fs): point_index = gem.Index(name='p') ir = fem.compile_ufl(expression, - cell=fs.mesh().ufl_cell(), - points=to_pts, + cell=coordinates.ufl_domain().ufl_cell(), + points=refpoints, point_index=point_index, coefficient=builder.coefficient, cell_orientation=builder.cell_orientation) @@ -76,12 +64,13 @@ def compile_ufl_kernel(expression, to_pts, to_element, fs): # Deal with non-scalar expressions tensor_indices = () - if fs.shape: - tensor_indices = tuple(gem.Index() for s in fs.shape) + fs_shape = ir[0].shape + if fs_shape: + tensor_indices = tuple(gem.Index() for s in fs_shape) ir = [gem.Indexed(ir[0], tensor_indices)] # Build kernel body - return_var = gem.Variable('A', (len(to_pts),) + fs.shape) + return_var = gem.Variable('A', (len(refpoints),) + fs_shape) return_expr = gem.Indexed(return_var, (point_index,) + tensor_indices) impero_c = impero_utils.compile_gem([return_expr], ir, (point_index,) + tensor_indices) body = generate_coffee(impero_c, index_names={point_index: 'p'}) @@ -91,7 +80,7 @@ def compile_ufl_kernel(expression, to_pts, to_element, fs): args.insert(0, cell_orientations_coffee_arg) # Build kernel - args.insert(0, ast.Decl("double", ast.Symbol('A', rank=(len(to_pts),) + fs.shape))) + args.insert(0, ast.Decl("double", ast.Symbol('A', rank=(len(refpoints),) + fs_shape))) kernel_code = builder.construct_kernel("expression_kernel", args, body) return Kernel(kernel_code, oriented, coefficients) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index d31b9528c2..e9f5320bc2 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -8,6 +8,10 @@ import ufl from ufl import indices, as_tensor from ufl.algorithms import compute_form_data as ufl_compute_form_data +from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks +from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering +from ufl.algorithms.apply_derivatives import apply_derivatives +from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, @@ -49,6 +53,22 @@ def compute_form_data(form, return fd +def preprocess_expression(expression): + """Imitates the compute_form_data processing pipeline. + + Useful, for example, to preprocess non-scalar expressions, which + are not and cannot be forms. + """ + expression = apply_algebra_lowering(expression) + expression = apply_derivatives(expression) + expression = apply_function_pullbacks(expression) + expression = apply_geometry_lowering(expression) + expression = apply_derivatives(expression) + expression = apply_geometry_lowering(expression) + expression = apply_derivatives(expression) + return expression + + def is_element_affine(ufl_element): """Tells if a UFL element is affine.""" affine_cells = ["interval", "triangle", "tetrahedron"] From 068ee59f8770f08f7d0ef02b2f6468f1126373a4 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 10 Oct 2016 18:01:53 +0100 Subject: [PATCH 194/809] refactor kernel interface for interpolation --- tsfc/interpolate.py | 83 ++++++++++++------------------ tsfc/kernel_interface/firedrake.py | 40 ++++++++++++++ 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/tsfc/interpolate.py b/tsfc/interpolate.py index 0e389f5e8b..7cbfe5f78a 100644 --- a/tsfc/interpolate.py +++ b/tsfc/interpolate.py @@ -4,23 +4,18 @@ from __future__ import absolute_import, print_function, division -from collections import namedtuple +from ufl.algorithms import extract_arguments, extract_coefficients -import ufl import coffee.base as ast -from ufl.algorithms import extract_arguments, extract_coefficients from gem import gem, impero_utils -from tsfc import fem, ufl_utils -from tsfc.coffee import generate as generate_coffee -from tsfc.kernel_interface.firedrake import KernelBuilderBase, cell_orientations_coffee_arg - -# Expression kernel description type -Kernel = namedtuple('Kernel', ['ast', 'oriented', 'coefficients']) +from tsfc import fem, ufl_utils +from tsfc.coffee import generate as generate_coffee, SCALAR_TYPE +from tsfc.kernel_interface.firedrake import ExpressionKernelBuilder -def compile_expression_at_points(expression, refpoints, coordinates): +def compile_expression_at_points(expression, points, coordinates): # No arguments, please! if extract_arguments(expression): return ValueError("Cannot interpolate UFL expression with Arguments!") @@ -34,53 +29,41 @@ def compile_expression_at_points(expression, refpoints, coordinates): assert coordinates.ufl_domain() == domain expression = ufl_utils.replace_coordinates(expression, coordinates) - builder = KernelBuilderBase() - coefficient_split = {} - - coefficients = [] - args = [] - for i, coefficient in enumerate(extract_coefficients(expression)): - if type(coefficient.ufl_element()) == ufl.MixedElement: - coefficient_split[coefficient] = [] - for j, element in enumerate(coefficient.ufl_element().sub_elements()): - subcoeff = ufl.Coefficient(ufl.FunctionSpace(coefficient.ufl_domain(), element)) - coefficient_split[coefficient].append(subcoeff) - args.append(builder._coefficient(subcoeff, "w_%d_%d" % (i, j))) - coefficients.extend(coefficient.split()) - else: - args.append(builder._coefficient(coefficient, "w_%d" % (i,))) - coefficients.append(coefficient) - - expression = ufl_utils.split_coefficients(expression, coefficient_split) + # Initialise kernel builder + builder = ExpressionKernelBuilder() + builder.set_coefficients(extract_coefficients(expression)) + + # Split mixed coefficients + expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) + # Translate to GEM point_index = gem.Index(name='p') - ir = fem.compile_ufl(expression, - cell=coordinates.ufl_domain().ufl_cell(), - points=refpoints, - point_index=point_index, - coefficient=builder.coefficient, - cell_orientation=builder.cell_orientation) - assert len(ir) == 1 + ir, = fem.compile_ufl(expression, + cell=coordinates.ufl_domain().ufl_cell(), + points=points, + point_index=point_index, + coefficient=builder.coefficient, + cell_orientation=builder.cell_orientation) # Deal with non-scalar expressions tensor_indices = () - fs_shape = ir[0].shape - if fs_shape: - tensor_indices = tuple(gem.Index() for s in fs_shape) - ir = [gem.Indexed(ir[0], tensor_indices)] + value_shape = ir.shape + if value_shape: + tensor_indices = tuple(gem.Index() for s in value_shape) + ir = gem.Indexed(ir, tensor_indices) # Build kernel body - return_var = gem.Variable('A', (len(refpoints),) + fs_shape) - return_expr = gem.Indexed(return_var, (point_index,) + tensor_indices) - impero_c = impero_utils.compile_gem([return_expr], ir, (point_index,) + tensor_indices) + return_shape = (len(points),) + value_shape + return_indices = (point_index,) + tensor_indices + return_var = gem.Variable('A', return_shape) + return_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('A', rank=return_shape)) + return_expr = gem.Indexed(return_var, return_indices) + impero_c = impero_utils.compile_gem([return_expr], [ir], return_indices) body = generate_coffee(impero_c, index_names={point_index: 'p'}) - oriented = KernelBuilderBase.needs_cell_orientations(ir) - if oriented: - args.insert(0, cell_orientations_coffee_arg) - - # Build kernel - args.insert(0, ast.Decl("double", ast.Symbol('A', rank=(len(refpoints),) + fs_shape))) - kernel_code = builder.construct_kernel("expression_kernel", args, body) + # Handle cell orientations + if builder.needs_cell_orientations([ir]): + builder.require_cell_orientations() - return Kernel(kernel_code, oriented, coefficients) + # Build kernel tuple + return builder.construct_kernel(return_arg, body) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index f37f385428..c8f115b5aa 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, print_function, division import numpy +from collections import namedtuple from itertools import product from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace @@ -16,6 +17,10 @@ from tsfc.coffee import SCALAR_TYPE +# Expression kernel description type +ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'coefficients']) + + class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", "domain_number", @@ -89,6 +94,41 @@ def needs_cell_orientations(ir): return False +class ExpressionKernelBuilder(KernelBuilderBase): + + def __init__(self): + super(ExpressionKernelBuilder, self).__init__() + self.oriented = False + + def set_coefficients(self, coefficients): + self.coefficients = [] # Firedrake coefficients for calling the kernel + self.coefficient_split = {} + self.kernel_args = [] + + for i, coefficient in enumerate(coefficients): + if type(coefficient.ufl_element()) == ufl_MixedElement: + subcoeffs = coefficient.split() # Firedrake-specific + self.coefficients.extend(subcoeffs) + self.coefficient_split[coefficient] = subcoeffs + self.kernel_args += [self._coefficient(subcoeff, "w_%d_%d" % (i, j)) + for j, subcoeff in enumerate(subcoeffs)] + else: + self.coefficients.append(coefficient) + self.kernel_args.append(self._coefficient(coefficient, "w_%d" % (i,))) + + def require_cell_orientations(self): + """Set that the kernel requires cell orientations.""" + self.oriented = True + + def construct_kernel(self, return_arg, body): + args = [return_arg] + self.kernel_args + if self.oriented: + args.insert(1, cell_orientations_coffee_arg) + + kernel_code = super(ExpressionKernelBuilder, self).construct_kernel("expression_kernel", args, body) + return ExpressionKernel(kernel_code, self.oriented, self.coefficients) + + class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" From 12a4652e1c6d560ad60cb026150b2873f01111d2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 10 Oct 2016 18:07:58 +0100 Subject: [PATCH 195/809] merge interpolate.py into driver.py --- tsfc/__init__.py | 3 +- tsfc/driver.py | 59 +++++++++++++++++++++++++++++++++++++- tsfc/interpolate.py | 69 --------------------------------------------- 3 files changed, 59 insertions(+), 72 deletions(-) delete mode 100644 tsfc/interpolate.py diff --git a/tsfc/__init__.py b/tsfc/__init__.py index 3d431ba2b8..961fab442c 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -1,4 +1,3 @@ from __future__ import absolute_import, print_function, division -from tsfc.driver import compile_form # noqa: F401 -from tsfc.interpolate import compile_expression_at_points # noqa: F401 +from tsfc.driver import compile_form, compile_expression_at_points # noqa: F401 diff --git a/tsfc/driver.py b/tsfc/driver.py index 845ba990c8..342cf0d6b5 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -5,6 +5,7 @@ import time from functools import reduce +from ufl.algorithms import extract_arguments, extract_coefficients from ufl.classes import Form from ufl.log import GREEN @@ -14,7 +15,7 @@ from tsfc import fem, ufl_utils from tsfc.coffee import generate as generate_coffee -from tsfc.constants import default_parameters +from tsfc.constants import SCALAR_TYPE, default_parameters from tsfc.fiatinterface import QuadratureRule, as_fiat_cell, create_quadrature from tsfc.logging import logger @@ -250,6 +251,62 @@ def facetarea(): return builder.construct_kernel(kernel_name, body) +def compile_expression_at_points(expression, points, coordinates): + import coffee.base as ast + + # No arguments, please! + if extract_arguments(expression): + return ValueError("Cannot interpolate UFL expression with Arguments!") + + # Apply UFL preprocessing + expression = ufl_utils.preprocess_expression(expression) + + # Replace coordinates (if any) + domain = expression.ufl_domain() + if domain: + assert coordinates.ufl_domain() == domain + expression = ufl_utils.replace_coordinates(expression, coordinates) + + # Initialise kernel builder + builder = firedrake_interface.ExpressionKernelBuilder() + builder.set_coefficients(extract_coefficients(expression)) + + # Split mixed coefficients + expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) + + # Translate to GEM + point_index = gem.Index(name='p') + ir, = fem.compile_ufl(expression, + cell=coordinates.ufl_domain().ufl_cell(), + points=points, + point_index=point_index, + coefficient=builder.coefficient, + cell_orientation=builder.cell_orientation) + + # Deal with non-scalar expressions + tensor_indices = () + value_shape = ir.shape + if value_shape: + tensor_indices = tuple(gem.Index() for s in value_shape) + ir = gem.Indexed(ir, tensor_indices) + + # Build kernel body + return_shape = (len(points),) + value_shape + return_indices = (point_index,) + tensor_indices + return_var = gem.Variable('A', return_shape) + return_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('A', rank=return_shape)) + return_expr = gem.Indexed(return_var, return_indices) + impero_c = impero_utils.compile_gem([return_expr], [ir], return_indices) + body = generate_coffee(impero_c, index_names={point_index: 'p'}) + + # Handle cell orientations + if builder.needs_cell_orientations([ir]): + builder.require_cell_orientations() + + # Build kernel tuple + return builder.construct_kernel(return_arg, body) + + def lower_integral_type(fiat_cell, integral_type): """Lower integral type into the dimension of the integration subentity and a list of entity numbers for that dimension. diff --git a/tsfc/interpolate.py b/tsfc/interpolate.py deleted file mode 100644 index 7cbfe5f78a..0000000000 --- a/tsfc/interpolate.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Kernel generation for interpolating UFL expressions onto finite -element function spaces with only point evaluation nodes, using the -existing TSFC infrastructure.""" - -from __future__ import absolute_import, print_function, division - -from ufl.algorithms import extract_arguments, extract_coefficients - -import coffee.base as ast - -from gem import gem, impero_utils - -from tsfc import fem, ufl_utils -from tsfc.coffee import generate as generate_coffee, SCALAR_TYPE -from tsfc.kernel_interface.firedrake import ExpressionKernelBuilder - - -def compile_expression_at_points(expression, points, coordinates): - # No arguments, please! - if extract_arguments(expression): - return ValueError("Cannot interpolate UFL expression with Arguments!") - - # Apply UFL preprocessing - expression = ufl_utils.preprocess_expression(expression) - - # Replace coordinates (if any) - domain = expression.ufl_domain() - if domain: - assert coordinates.ufl_domain() == domain - expression = ufl_utils.replace_coordinates(expression, coordinates) - - # Initialise kernel builder - builder = ExpressionKernelBuilder() - builder.set_coefficients(extract_coefficients(expression)) - - # Split mixed coefficients - expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) - - # Translate to GEM - point_index = gem.Index(name='p') - ir, = fem.compile_ufl(expression, - cell=coordinates.ufl_domain().ufl_cell(), - points=points, - point_index=point_index, - coefficient=builder.coefficient, - cell_orientation=builder.cell_orientation) - - # Deal with non-scalar expressions - tensor_indices = () - value_shape = ir.shape - if value_shape: - tensor_indices = tuple(gem.Index() for s in value_shape) - ir = gem.Indexed(ir, tensor_indices) - - # Build kernel body - return_shape = (len(points),) + value_shape - return_indices = (point_index,) + tensor_indices - return_var = gem.Variable('A', return_shape) - return_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('A', rank=return_shape)) - return_expr = gem.Indexed(return_var, return_indices) - impero_c = impero_utils.compile_gem([return_expr], [ir], return_indices) - body = generate_coffee(impero_c, index_names={point_index: 'p'}) - - # Handle cell orientations - if builder.needs_cell_orientations([ir]): - builder.require_cell_orientations() - - # Build kernel tuple - return builder.construct_kernel(return_arg, body) From 3be0488e5da8853dafed75ebead3384f6480600d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 10 Oct 2016 18:19:57 +0100 Subject: [PATCH 196/809] add docstrings --- tsfc/driver.py | 11 +++++++++-- tsfc/kernel_interface/firedrake.py | 11 +++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 342cf0d6b5..6c2b7a2cde 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -252,6 +252,14 @@ def facetarea(): def compile_expression_at_points(expression, points, coordinates): + """Compiles a UFL expression to be evaluated at compile-time known + reference points. Useful for interpolating UFL expressions onto + function spaces with only point evaluation nodes. + + :arg expression: UFL expression + :arg points: reference coordinates of the evaluation points + :arg coordinates: the coordinate function + """ import coffee.base as ast # No arguments, please! @@ -284,10 +292,9 @@ def compile_expression_at_points(expression, points, coordinates): cell_orientation=builder.cell_orientation) # Deal with non-scalar expressions - tensor_indices = () value_shape = ir.shape + tensor_indices = tuple(gem.Index() for s in value_shape) if value_shape: - tensor_indices = tuple(gem.Index() for s in value_shape) ir = gem.Indexed(ir, tensor_indices) # Build kernel body diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index c8f115b5aa..2cbd175654 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -95,12 +95,17 @@ def needs_cell_orientations(ir): class ExpressionKernelBuilder(KernelBuilderBase): + """Builds expression kernels for UFL interpolation in Firedrake.""" def __init__(self): super(ExpressionKernelBuilder, self).__init__() self.oriented = False def set_coefficients(self, coefficients): + """Prepare the coefficients of the expression. + + :arg coefficients: UFL coefficients from Firedrake + """ self.coefficients = [] # Firedrake coefficients for calling the kernel self.coefficient_split = {} self.kernel_args = [] @@ -121,6 +126,12 @@ def require_cell_orientations(self): self.oriented = True def construct_kernel(self, return_arg, body): + """Constructs an :class:`ExpressionKernel`. + + :arg return_arg: COFFEE argument for the return value + :arg body: function body (:class:`coffee.Block` node) + :returns: :class:`ExpressionKernel` object + """ args = [return_arg] + self.kernel_args if self.oriented: args.insert(1, cell_orientations_coffee_arg) From 6234e8681591c8b82bb21b70a20122bed6bff59f Mon Sep 17 00:00:00 2001 From: Jan Blechta Date: Thu, 13 Oct 2016 14:35:35 +0200 Subject: [PATCH 197/809] Add precision and epsilon to parameter system --- tsfc/coffee.py | 33 ++++++++++++++++++--------------- tsfc/constants.py | 10 +++++++++- tsfc/driver.py | 8 ++++++-- tsfc/fem.py | 31 +++++++++++++++---------------- 4 files changed, 48 insertions(+), 34 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 4b42a56035..8084424011 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -16,7 +16,7 @@ from gem import gem, impero as imp -from tsfc.constants import SCALAR_TYPE, PRECISION +from tsfc.constants import SCALAR_TYPE from tsfc.logging import logger @@ -24,11 +24,12 @@ class Bunch(object): pass -def generate(impero_c, index_names, roots=(), argument_indices=()): +def generate(impero_c, index_names, parameters, roots=(), argument_indices=()): """Generates COFFEE code. :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names + :arg parameters: TSFC parameters :arg roots: list of expression DAG roots for attaching #pragma coffee expression :arg argument_indices: argument indices for attaching @@ -36,21 +37,23 @@ def generate(impero_c, index_names, roots=(), argument_indices=()): to the argument loops :returns: COFFEE function body """ - parameters = Bunch() - parameters.declare = impero_c.declare - parameters.indices = impero_c.indices - parameters.roots = roots - parameters.argument_indices = argument_indices + params = Bunch() + params.declare = impero_c.declare + params.indices = impero_c.indices + params.roots = roots + params.argument_indices = argument_indices - parameters.names = {} + params.names = {} for i, temp in enumerate(impero_c.temporaries): - parameters.names[temp] = "t%d" % i + params.names[temp] = "t%d" % i counter = itertools.count() - parameters.index_names = defaultdict(lambda: "i_%d" % next(counter)) - parameters.index_names.update(index_names) + params.index_names = defaultdict(lambda: "i_%d" % next(counter)) + params.index_names.update(index_names) - return statement(impero_c.tree, parameters) + params.precision = parameters["precision"] + + return statement(impero_c.tree, params) def _coffee_symbol(symbol, rank=()): @@ -171,7 +174,7 @@ def statement_evaluate(leaf, parameters): return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), coffee.ArrayInit(array_expression(expr.array), - precision=PRECISION)) + precision=parameters.precision)) else: ops = [] for multiindex, value in numpy.ndenumerate(expr.array): @@ -185,7 +188,7 @@ def statement_evaluate(leaf, parameters): nz_indices, = expr.array.any(axis=axes).nonzero() nz_bounds = tuple([(i, 0)] for i in expr.array.shape[:-1]) nz_bounds += ([(max(nz_indices) - min(nz_indices) + 1, min(nz_indices))],) - init = coffee.SparseArrayInit(expr.array, PRECISION, nz_bounds) + init = coffee.SparseArrayInit(expr.array, parameters.precision, nz_bounds) return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), init, @@ -296,7 +299,7 @@ def _expression_scalar(expr, parameters): if isnan(expr.value): return coffee.Symbol("NAN") else: - return coffee.Symbol(("%%.%dg" % (PRECISION - 1)) % expr.value) + return coffee.Symbol(("%%.%dg" % (parameters.precision - 1)) % expr.value) @_expression.register(gem.Variable) diff --git a/tsfc/constants.py b/tsfc/constants.py index 085d436e6a..c59eb6272e 100644 --- a/tsfc/constants.py +++ b/tsfc/constants.py @@ -5,7 +5,9 @@ NUMPY_TYPE = numpy.dtype("double") -PRECISION = numpy.finfo(NUMPY_TYPE).precision +_PRECISION = numpy.finfo(NUMPY_TYPE).precision + +_EPSILON = eval("1e-%d" % (_PRECISION - 1)) SCALAR_TYPE = {numpy.dtype("double"): "double", numpy.dtype("float32"): "float"}[NUMPY_TYPE] @@ -20,6 +22,12 @@ # performance. Can be disabled by setting it to None, False or 0; # that makes compilation time much shorter. "unroll_indexsum": 3, + + # Precision of float printing (number of digits) + "precision": _PRECISION, + + # Threshold for rounding FE tables to 0, +/- 1/2, +/- 1 + "epsilon": _EPSILON, } diff --git a/tsfc/driver.py b/tsfc/driver.py index 6c2b7a2cde..c2c9995c05 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -132,6 +132,7 @@ def coefficient(ufl_coefficient, r): assert restriction is None coefficient = builder.coefficient ir = fem.compile_ufl(integrand, + parameters, cell=cell, quadrature_degree=quadrature_degree, point_index=quadrature_index, @@ -160,6 +161,7 @@ def facetarea(): integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='q') ir = fem.compile_ufl(integrand, + parameters, cell=cell, integration_dim=integration_dim, entity_ids=entity_ids, @@ -203,6 +205,7 @@ def facetarea(): quadrature_index = gem.Index(name='ip') quadrature_indices.append(quadrature_index) ir = fem.compile_ufl(integrand, + parameters, interior_facet=interior_facet, cell=cell, integration_dim=integration_dim, @@ -245,7 +248,7 @@ def facetarea(): for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body = generate_coffee(impero_c, index_names, ir, argument_indices) + body = generate_coffee(impero_c, index_names, parameters, ir, argument_indices) kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) return builder.construct_kernel(kernel_name, body) @@ -285,6 +288,7 @@ def compile_expression_at_points(expression, points, coordinates): # Translate to GEM point_index = gem.Index(name='p') ir, = fem.compile_ufl(expression, + parameters, cell=coordinates.ufl_domain().ufl_cell(), points=points, point_index=point_index, @@ -304,7 +308,7 @@ def compile_expression_at_points(expression, points, coordinates): return_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('A', rank=return_shape)) return_expr = gem.Indexed(return_var, return_indices) impero_c = impero_utils.compile_gem([return_expr], [ir], return_indices) - body = generate_coffee(impero_c, index_names={point_index: 'p'}) + body = generate_coffee(impero_c, {point_index: 'p'}, parameters) # Handle cell orientations if builder.needs_cell_orientations([ir]): diff --git a/tsfc/fem.py b/tsfc/fem.py index c1b62e305e..4b4771a63f 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -17,7 +17,6 @@ import gem -from tsfc.constants import PRECISION from tsfc.fiatinterface import create_element, create_quadrature, as_fiat_cell from tsfc.modified_terminals import analyse_modified_terminal from tsfc import compat @@ -28,10 +27,6 @@ spanning_degree, simplify_abs) -# FFC uses one less digits for rounding than for printing -epsilon = eval("1e-%d" % (PRECISION - 1)) - - def _tabulate(ufl_element, order, points): """Ask FIAT to tabulate ``points`` up to order ``order``, then rearranges the result into a series of ``(c, D, table)`` tuples, @@ -56,10 +51,11 @@ def _tabulate(ufl_element, order, points): yield c, D, table -def tabulate(ufl_element, order, points): - """Same as the above, but also applies FFC rounding and recognises - cellwise constantness. Cellwise constantness is determined - symbolically, but we also check the numerics to be safe.""" +def tabulate(ufl_element, order, points, epsilon): + """Same as the above, but also applies FFC rounding with + threshold epsilon and recognises cellwise constantness. + Cellwise constantness is determined symbolically, but we + also check the numerics to be safe.""" for c, D, table in _tabulate(ufl_element, order, points): # Copied from FFC (ffc/quadrature/quadratureutils.py) table[abs(table) < epsilon] = 0 @@ -75,23 +71,26 @@ def tabulate(ufl_element, order, points): yield c, D, table -def make_tabulator(points): - """Creates a tabulator for an array of points.""" - return lambda elem, order: tabulate(elem, order, points) +def make_tabulator(points, epsilon): + """Creates a tabulator for an array of points with rounding + parameter epsilon.""" + return lambda elem, order: tabulate(elem, order, points, epsilon) class TabulationManager(object): """Manages the generation of tabulation matrices for the different integral types.""" - def __init__(self, entity_points): + def __init__(self, entity_points, epsilon): """Constructs a TabulationManager. :arg entity_points: An array of points in cell coordinates for each integration entity, i.e. an iterable of arrays of points. + :arg epsilon: precision for rounding FE tables to 0, +-1/2, +-1 """ - self.tabulators = list(map(make_tabulator, entity_points)) + epsilons = itertools.repeat(epsilon, len(entity_points)) + self.tabulators = list(map(make_tabulator, entity_points, epsilons)) self.tables = {} def tabulate(self, ufl_element, max_deriv): @@ -369,7 +368,7 @@ def callback(key): return iterate_shape(mt, callback) -def compile_ufl(expression, interior_facet=False, **kwargs): +def compile_ufl(expression, parameters, interior_facet=False, **kwargs): params = Parameters(**kwargs) # Abs-simplification @@ -388,7 +387,7 @@ def compile_ufl(expression, interior_facet=False, **kwargs): max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(params.entity_points) + tabulation_manager = TabulationManager(params.entity_points, parameters["epsilon"]) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) From 503fd2aab5b0127c2c2ff76527bb1f524dcdece1 Mon Sep 17 00:00:00 2001 From: Jan Blechta Date: Thu, 13 Oct 2016 14:39:42 +0200 Subject: [PATCH 198/809] Add HHJ element to FIAT interface --- tsfc/fiatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 4905bcca83..34b1a4feb7 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -60,6 +60,7 @@ "Raviart-Thomas": FIAT.RaviartThomas, "TraceElement": FIAT.HDivTrace, "Regge": FIAT.Regge, + "Hellan-Herrmann-Johnson": FIAT.HellanHerrmannJohnson, # These require special treatment below "DQ": None, "FacetElement": None, From 776fc9a338de87b2435d479424cea568fc929cc9 Mon Sep 17 00:00:00 2001 From: Jan Blechta Date: Thu, 13 Oct 2016 15:52:11 +0200 Subject: [PATCH 199/809] Init missing parameters in compile_expression_at_points --- tsfc/driver.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index c2c9995c05..604f58965a 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -254,7 +254,7 @@ def facetarea(): return builder.construct_kernel(kernel_name, body) -def compile_expression_at_points(expression, points, coordinates): +def compile_expression_at_points(expression, points, coordinates, parameters=None): """Compiles a UFL expression to be evaluated at compile-time known reference points. Useful for interpolating UFL expressions onto function spaces with only point evaluation nodes. @@ -262,9 +262,17 @@ def compile_expression_at_points(expression, points, coordinates): :arg expression: UFL expression :arg points: reference coordinates of the evaluation points :arg coordinates: the coordinate function + :arg parameters: parameters object """ import coffee.base as ast + if parameters is None: + parameters = default_parameters() + else: + _ = default_parameters() + _.update(parameters) + parameters = _ + # No arguments, please! if extract_arguments(expression): return ValueError("Cannot interpolate UFL expression with Arguments!") From 4c6fd90d8b619f2e525129a9db2b68d108ea46a1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 14 Oct 2016 11:08:05 +0100 Subject: [PATCH 200/809] Fix 'epsilon' numerical tolerance --- tsfc/constants.py | 5 ----- tsfc/fem.py | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tsfc/constants.py b/tsfc/constants.py index c59eb6272e..dc1c7374e5 100644 --- a/tsfc/constants.py +++ b/tsfc/constants.py @@ -7,8 +7,6 @@ _PRECISION = numpy.finfo(NUMPY_TYPE).precision -_EPSILON = eval("1e-%d" % (_PRECISION - 1)) - SCALAR_TYPE = {numpy.dtype("double"): "double", numpy.dtype("float32"): "float"}[NUMPY_TYPE] @@ -25,9 +23,6 @@ # Precision of float printing (number of digits) "precision": _PRECISION, - - # Threshold for rounding FE tables to 0, +/- 1/2, +/- 1 - "epsilon": _EPSILON, } diff --git a/tsfc/fem.py b/tsfc/fem.py index 4b4771a63f..283ef199c9 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -386,8 +386,11 @@ def compile_ufl(expression, parameters, interior_facet=False, **kwargs): ufl_element = mt.terminal.ufl_element() max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) + # Rounding tolerance mimicking FFC + epsilon = 10.0 * eval("1e-%d" % parameters["precision"]) + # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(params.entity_points, parameters["epsilon"]) + tabulation_manager = TabulationManager(params.entity_points, epsilon) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) From 0a785d2b6116293d61956da3deb136983a808aa2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 14 Oct 2016 11:45:59 +0100 Subject: [PATCH 201/809] cast numpy indices to int --- tsfc/kernel_interface/ufc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 84e3073c7d..6dcb9e9af0 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -1,4 +1,5 @@ from __future__ import absolute_import, print_function, division +from six.moves import map import numpy from itertools import product @@ -205,7 +206,7 @@ def prepare_coordinates(coefficient, name, interior_facet=False): # Translate coords from XYZXYZXYZXYZ into XXXXYYYYZZZZ # NOTE: See dolfin/mesh/Cell.h:get_coordinate_dofs for ordering scheme - indices = numpy.arange(num_nodes * gdim).reshape(num_nodes, gdim).transpose().flatten() + indices = list(map(int, numpy.arange(num_nodes * gdim).reshape(num_nodes, gdim).transpose().flat)) if not interior_facet: variable = gem.Variable(name, shape) expression = gem.ListTensor([gem.Indexed(variable, (i,)) for i in indices]) From c220c345a2705691c4e63d8e31a4a5388e1c7234 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 11 Oct 2016 14:04:33 +0100 Subject: [PATCH 202/809] create gem.utils and add OrderedSet --- tsfc/fem.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 283ef199c9..c56725004c 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -16,6 +16,7 @@ GeometricQuantity, QuadratureWeight) import gem +from gem.utils import cached_property from tsfc.fiatinterface import create_element, create_quadrature, as_fiat_cell from tsfc.modified_terminals import analyse_modified_terminal @@ -117,24 +118,6 @@ def __getitem__(self, key): return self.tables[key] -# FIXME: copy-paste from PyOP2 -class cached_property(object): - """A read-only @property that is only evaluated once. The value is cached - on the object itself rather than the function or class; this should prevent - memory leakage.""" - def __init__(self, fget, doc=None): - self.fget = fget - self.__doc__ = doc or fget.__doc__ - self.__name__ = fget.__name__ - self.__module__ = fget.__module__ - - def __get__(self, obj, cls): - if obj is None: - return self - obj.__dict__[self.__name__] = result = self.fget(obj) - return result - - class Parameters(object): keywords = ('cell', 'fiat_cell', From 85e97f4209e61d8b081837eb31bdd0dc1f3e2dc9 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 12 Oct 2016 13:47:34 +0100 Subject: [PATCH 203/809] add proxy class infrastructure --- tsfc/fem.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index c56725004c..84da7da1d6 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -16,7 +16,7 @@ GeometricQuantity, QuadratureWeight) import gem -from gem.utils import cached_property +from gem.utils import cached_property, unset_attribute from tsfc.fiatinterface import create_element, create_quadrature, as_fiat_cell from tsfc.modified_terminals import analyse_modified_terminal @@ -142,6 +142,10 @@ def __init__(self, **kwargs): raise ValueError("unexpected keyword argument '{0}'".format(invalid_keywords.pop())) self.__dict__.update(kwargs) + @unset_attribute + def cell(self): + pass + @cached_property def fiat_cell(self): return as_fiat_cell(self.cell) @@ -152,6 +156,10 @@ def integration_dim(self): entity_ids = [0] + @unset_attribute + def quadrature_degree(self): + pass + @cached_property def quadrature_rule(self): integration_cell = self.fiat_cell.construct_subelement(self.integration_dim) @@ -212,8 +220,32 @@ def index_selector(self, callback, restriction): """ return self._selector(callback, list(range(len(self.entity_ids))), restriction) + @unset_attribute + def point_index(self): + pass + argument_indices = () + @unset_attribute + def coefficient(self): + pass + + @unset_attribute + def cell_orientation(self): + pass + + @unset_attribute + def facet_number(self): + pass + + @unset_attribute + def cellvolume(self): + pass + + @unset_attribute + def facetarea(self): + pass + @cached_property def index_cache(self): return collections.defaultdict(gem.Index) From bd4e310afaa46496d3596fd0f067d00180ab119d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 12 Oct 2016 15:19:54 +0100 Subject: [PATCH 204/809] add KernelInterface abstract base class --- tsfc/driver.py | 23 +++++++++++------------ tsfc/fem.py | 22 ++++++---------------- tsfc/kernel_interface/__init__.py | 25 +++++++++++++++++++++++++ tsfc/kernel_interface/common.py | 4 +++- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 604f58965a..2ccdbaab86 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -19,6 +19,7 @@ from tsfc.fiatinterface import QuadratureRule, as_fiat_cell, create_quadrature from tsfc.logging import logger +from tsfc.kernel_interface import ProxyKernelInterface import tsfc.kernel_interface.firedrake as firedrake_interface @@ -125,18 +126,20 @@ def cellvolume(restriction): integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='q') if interior_facet: - def coefficient(ufl_coefficient, r): - assert r is None - return builder.coefficient(ufl_coefficient, restriction) + class CellVolumeKernelInterface(ProxyKernelInterface): + def coefficient(self, ufl_coefficient, r): + assert r is None + return builder.coefficient(ufl_coefficient, restriction) + kernel_interface = CellVolumeKernelInterface(builder) else: assert restriction is None - coefficient = builder.coefficient + kernel_interface = builder ir = fem.compile_ufl(integrand, parameters, cell=cell, quadrature_degree=quadrature_degree, point_index=quadrature_index, - coefficient=coefficient, + kernel_interface=kernel_interface, index_cache=index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) @@ -167,8 +170,7 @@ def facetarea(): entity_ids=entity_ids, quadrature_degree=quadrature_degree, point_index=quadrature_index, - coefficient=builder.coefficient, - facet_number=builder.facet_number, + kernel_interface=builder, index_cache=index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) @@ -213,9 +215,7 @@ def facetarea(): quadrature_rule=quad_rule, point_index=quadrature_index, argument_indices=argument_indices, - coefficient=builder.coefficient, - cell_orientation=builder.cell_orientation, - facet_number=builder.facet_number, + kernel_interface=builder, index_cache=index_cache, cellvolume=cellvolume, facetarea=facetarea) @@ -300,8 +300,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non cell=coordinates.ufl_domain().ufl_cell(), points=points, point_index=point_index, - coefficient=builder.coefficient, - cell_orientation=builder.cell_orientation) + kernel_interface=builder) # Deal with non-scalar expressions value_shape = ir.shape diff --git a/tsfc/fem.py b/tsfc/fem.py index 84da7da1d6..90dcd1b5ea 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -19,6 +19,7 @@ from gem.utils import cached_property, unset_attribute from tsfc.fiatinterface import create_element, create_quadrature, as_fiat_cell +from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import analyse_modified_terminal from tsfc import compat from tsfc import ufl2gem @@ -118,7 +119,7 @@ def __getitem__(self, key): return self.tables[key] -class Parameters(object): +class Parameters(ProxyKernelInterface): keywords = ('cell', 'fiat_cell', 'integration_dim', @@ -129,14 +130,15 @@ class Parameters(object): 'weights', 'point_index', 'argument_indices', - 'coefficient', - 'cell_orientation', - 'facet_number', 'cellvolume', 'facetarea', 'index_cache') def __init__(self, **kwargs): + # Initialise superclass + ProxyKernelInterface.__init__(self, kwargs["kernel_interface"]) + del kwargs["kernel_interface"] + invalid_keywords = set(kwargs.keys()) - set(Parameters.keywords) if invalid_keywords: raise ValueError("unexpected keyword argument '{0}'".format(invalid_keywords.pop())) @@ -226,18 +228,6 @@ def point_index(self): argument_indices = () - @unset_attribute - def coefficient(self): - pass - - @unset_attribute - def cell_orientation(self): - pass - - @unset_attribute - def facet_number(self): - pass - @unset_attribute def cellvolume(self): pass diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index f298a6112c..6eb5f59554 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -1 +1,26 @@ from __future__ import absolute_import, print_function, division +from six import with_metaclass + +from abc import ABCMeta, abstractmethod + +from gem.utils import make_proxy_class + + +class KernelInterface(with_metaclass(ABCMeta)): + """Abstract interface for accessing the GEM expressions corresponding + to kernel arguments.""" + + @abstractmethod + def coefficient(self, ufl_coefficient, restriction): + """A function that maps :class:`ufl.Coefficient`s to GEM + expressions.""" + + @abstractmethod + def cell_orientation(self, restriction): + """Cell orientation as a GEM expression.""" + + @abstractmethod + def facet_number(self, restriction): + """Facet number as a GEM index.""" + +ProxyKernelInterface = make_proxy_class('ProxyKernelInterface', KernelInterface) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 9a83c383e5..548138f6a2 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -6,8 +6,10 @@ import gem +from tsfc.kernel_interface import KernelInterface -class KernelBuilderBase(object): + +class KernelBuilderBase(KernelInterface): """Helper class for building local assembly kernels.""" def __init__(self, interior_facet=False): From 26215912b09a19d9451bb90472b9ac447bf0815c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 13 Oct 2016 14:33:28 +0100 Subject: [PATCH 205/809] configuration inheritance by .copy() + .update() --- tsfc/driver.py | 48 +++++++++++++++++++++--------------------------- tsfc/fem.py | 10 +++++----- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2ccdbaab86..84f4ba7e7f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -111,6 +111,13 @@ def compile_integral(integral_data, form_data, prefix, parameters, # evaluation can be hoisted). index_cache = collections.defaultdict(gem.Index) + kernel_cfg = dict(interface=builder, + ufl_cell=cell, + integration_dim=integration_dim, + entity_ids=entity_ids, + argument_indices=argument_indices, + index_cache=index_cache) + # TODO: refactor this! def cellvolume(restriction): from ufl import dx @@ -136,10 +143,10 @@ def coefficient(self, ufl_coefficient, r): kernel_interface = builder ir = fem.compile_ufl(integrand, parameters, - cell=cell, + interface=kernel_interface, + ufl_cell=cell, quadrature_degree=quadrature_degree, point_index=quadrature_index, - kernel_interface=kernel_interface, index_cache=index_cache) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) @@ -163,15 +170,10 @@ def facetarea(): integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) quadrature_index = gem.Index(name='q') - ir = fem.compile_ufl(integrand, - parameters, - cell=cell, - integration_dim=integration_dim, - entity_ids=entity_ids, - quadrature_degree=quadrature_degree, - point_index=quadrature_index, - kernel_interface=builder, - index_cache=index_cache) + config = kernel_cfg.copy() + config.update(quadrature_degree=quadrature_degree, + point_index=quadrature_index) + ir = fem.compile_ufl(integrand, parameters, **config) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) expr, = ir @@ -179,6 +181,8 @@ def facetarea(): expr = gem.IndexSum(expr, quadrature_index) return expr + kernel_cfg.update(cellvolume=cellvolume, facetarea=facetarea) + irs = [] for integral in integral_data.integrals: params = {} @@ -206,19 +210,9 @@ def facetarea(): integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) quadrature_index = gem.Index(name='ip') quadrature_indices.append(quadrature_index) - ir = fem.compile_ufl(integrand, - parameters, - interior_facet=interior_facet, - cell=cell, - integration_dim=integration_dim, - entity_ids=entity_ids, - quadrature_rule=quad_rule, - point_index=quadrature_index, - argument_indices=argument_indices, - kernel_interface=builder, - index_cache=index_cache, - cellvolume=cellvolume, - facetarea=facetarea) + config = kernel_cfg.copy() + config.update(quadrature_rule=quad_rule, point_index=quadrature_index) + ir = fem.compile_ufl(integrand, parameters, interior_facet=interior_facet, **config) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) irs.append([(gem.IndexSum(expr, quadrature_index) @@ -297,10 +291,10 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non point_index = gem.Index(name='p') ir, = fem.compile_ufl(expression, parameters, - cell=coordinates.ufl_domain().ufl_cell(), + interface=builder, + ufl_cell=coordinates.ufl_domain().ufl_cell(), points=points, - point_index=point_index, - kernel_interface=builder) + point_index=point_index) # Deal with non-scalar expressions value_shape = ir.shape diff --git a/tsfc/fem.py b/tsfc/fem.py index 90dcd1b5ea..934e9cfeb9 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -120,7 +120,7 @@ def __getitem__(self, key): class Parameters(ProxyKernelInterface): - keywords = ('cell', + keywords = ('ufl_cell', 'fiat_cell', 'integration_dim', 'entity_ids', @@ -136,8 +136,8 @@ class Parameters(ProxyKernelInterface): def __init__(self, **kwargs): # Initialise superclass - ProxyKernelInterface.__init__(self, kwargs["kernel_interface"]) - del kwargs["kernel_interface"] + ProxyKernelInterface.__init__(self, kwargs["interface"]) + del kwargs["interface"] invalid_keywords = set(kwargs.keys()) - set(Parameters.keywords) if invalid_keywords: @@ -145,12 +145,12 @@ def __init__(self, **kwargs): self.__dict__.update(kwargs) @unset_attribute - def cell(self): + def ufl_cell(self): pass @cached_property def fiat_cell(self): - return as_fiat_cell(self.cell) + return as_fiat_cell(self.ufl_cell) @cached_property def integration_dim(self): From 21dfff8cbae15f1932a3cdc330b321e42b0597f6 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 17 Oct 2016 11:21:55 +0100 Subject: [PATCH 206/809] replace parameters argument of compile_ufl --- tsfc/driver.py | 9 +++++---- tsfc/fem.py | 16 +++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 84f4ba7e7f..197c4d23f5 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -113,6 +113,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, kernel_cfg = dict(interface=builder, ufl_cell=cell, + precision=parameters["precision"], integration_dim=integration_dim, entity_ids=entity_ids, argument_indices=argument_indices, @@ -142,7 +143,7 @@ def coefficient(self, ufl_coefficient, r): assert restriction is None kernel_interface = builder ir = fem.compile_ufl(integrand, - parameters, + precision=parameters["precision"], interface=kernel_interface, ufl_cell=cell, quadrature_degree=quadrature_degree, @@ -173,7 +174,7 @@ def facetarea(): config = kernel_cfg.copy() config.update(quadrature_degree=quadrature_degree, point_index=quadrature_index) - ir = fem.compile_ufl(integrand, parameters, **config) + ir = fem.compile_ufl(integrand, **config) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) expr, = ir @@ -212,7 +213,7 @@ def facetarea(): quadrature_indices.append(quadrature_index) config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule, point_index=quadrature_index) - ir = fem.compile_ufl(integrand, parameters, interior_facet=interior_facet, **config) + ir = fem.compile_ufl(integrand, interior_facet=interior_facet, **config) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) irs.append([(gem.IndexSum(expr, quadrature_index) @@ -290,9 +291,9 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non # Translate to GEM point_index = gem.Index(name='p') ir, = fem.compile_ufl(expression, - parameters, interface=builder, ufl_cell=coordinates.ufl_domain().ufl_cell(), + precision=parameters["precision"], points=points, point_index=point_index) diff --git a/tsfc/fem.py b/tsfc/fem.py index 934e9cfeb9..1ebc76ead2 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -18,6 +18,7 @@ import gem from gem.utils import cached_property, unset_attribute +from tsfc.constants import _PRECISION as PRECISION from tsfc.fiatinterface import create_element, create_quadrature, as_fiat_cell from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import analyse_modified_terminal @@ -128,6 +129,7 @@ class Parameters(ProxyKernelInterface): 'quadrature_rule', 'points', 'weights', + 'precision', 'point_index', 'argument_indices', 'cellvolume', @@ -175,6 +177,13 @@ def points(self): def weights(self): return self.quadrature_rule.get_weights() + precision = PRECISION + + @cached_property + def epsilon(self): + # Rounding tolerance mimicking FFC + return 10.0 * eval("1e-%d" % self.precision) + @cached_property def entity_points(self): """An array of points in cell coordinates for each entity, @@ -373,7 +382,7 @@ def callback(key): return iterate_shape(mt, callback) -def compile_ufl(expression, parameters, interior_facet=False, **kwargs): +def compile_ufl(expression, interior_facet=False, **kwargs): params = Parameters(**kwargs) # Abs-simplification @@ -391,11 +400,8 @@ def compile_ufl(expression, parameters, interior_facet=False, **kwargs): ufl_element = mt.terminal.ufl_element() max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) - # Rounding tolerance mimicking FFC - epsilon = 10.0 * eval("1e-%d" % parameters["precision"]) - # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(params.entity_points, epsilon) + tabulation_manager = TabulationManager(params.entity_points, params.epsilon) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) From 0191199bd5f31bfea895d8a8a580a80c6b285939 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 13 Oct 2016 15:25:13 +0100 Subject: [PATCH 207/809] pull cellvolume and facetarea out of compile_integral --- tsfc/driver.py | 138 ++++++++++++++++++++++++++----------------------- 1 file changed, 74 insertions(+), 64 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 197c4d23f5..9a9b6d8828 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -119,70 +119,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, argument_indices=argument_indices, index_cache=index_cache) - # TODO: refactor this! - def cellvolume(restriction): - from ufl import dx - form = 1 * dx(domain=mesh) - fd = ufl_utils.compute_form_data(form) - itg_data, = fd.integral_data - integral, = itg_data.integrals - - # Check if the integral has a quad degree attached, otherwise use - # the estimated polynomial degree attached by compute_form_data - quadrature_degree = integral.metadata()["estimated_polynomial_degree"] - - integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) - quadrature_index = gem.Index(name='q') - if interior_facet: - class CellVolumeKernelInterface(ProxyKernelInterface): - def coefficient(self, ufl_coefficient, r): - assert r is None - return builder.coefficient(ufl_coefficient, restriction) - kernel_interface = CellVolumeKernelInterface(builder) - else: - assert restriction is None - kernel_interface = builder - ir = fem.compile_ufl(integrand, - precision=parameters["precision"], - interface=kernel_interface, - ufl_cell=cell, - quadrature_degree=quadrature_degree, - point_index=quadrature_index, - index_cache=index_cache) - if parameters["unroll_indexsum"]: - ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) - expr, = ir - if quadrature_index in expr.free_indices: - expr = gem.IndexSum(expr, quadrature_index) - return expr - - # TODO: refactor this! - def facetarea(): - from ufl import Measure - assert integral_type != 'cell' - form = 1 * Measure(integral_type, domain=mesh) - fd = ufl_utils.compute_form_data(form) - itg_data, = fd.integral_data - integral, = itg_data.integrals - - # Check if the integral has a quad degree attached, otherwise use - # the estimated polynomial degree attached by compute_form_data - quadrature_degree = integral.metadata()["estimated_polynomial_degree"] - - integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) - quadrature_index = gem.Index(name='q') - config = kernel_cfg.copy() - config.update(quadrature_degree=quadrature_degree, - point_index=quadrature_index) - ir = fem.compile_ufl(integrand, **config) - if parameters["unroll_indexsum"]: - ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) - expr, = ir - if quadrature_index in expr.free_indices: - expr = gem.IndexSum(expr, quadrature_index) - return expr - - kernel_cfg.update(cellvolume=cellvolume, facetarea=facetarea) + kernel_cfg["facetarea"] = facetarea_generator(mesh, coordinates, kernel_cfg, integral_type) + kernel_cfg["cellvolume"] = cellvolume_generator(mesh, coordinates, kernel_cfg) irs = [] for integral in integral_data.integrals: @@ -249,6 +187,78 @@ def facetarea(): return builder.construct_kernel(kernel_name, body) +class CellVolumeKernelInterface(ProxyKernelInterface): + # Since CellVolume is evaluated as a cell integral, we must ensure + # that the right restriction is applied when it is used in an + # interior facet integral. This proxy diverts coefficient + # translation to use a specified restriction. + + def __init__(self, wrapee, restriction): + ProxyKernelInterface.__init__(self, wrapee) + self.restriction = restriction + + def coefficient(self, ufl_coefficient, r): + assert r is None + return self._wrapee.coefficient(ufl_coefficient, self.restriction) + + +def cellvolume_generator(domain, coordinate_coefficient, kernel_config): + def cellvolume(restriction): + from ufl import dx + form = 1 * dx(domain=domain) + fd = ufl_utils.compute_form_data(form) + itg_data, = fd.integral_data + integral, = itg_data.integrals + + integrand = ufl_utils.replace_coordinates(integral.integrand(), + coordinate_coefficient) + + # Check if the integral has a quad degree attached, otherwise use + # the estimated polynomial degree attached by compute_form_data + quadrature_degree = integral.metadata()["estimated_polynomial_degree"] + quadrature_index = gem.Index(name='q') + + interface = CellVolumeKernelInterface(kernel_config["interface"], restriction) + expr, = fem.compile_ufl(integrand, + interface=interface, + ufl_cell=kernel_config["ufl_cell"], + quadrature_degree=quadrature_degree, + precision=kernel_config["precision"], + point_index=quadrature_index, + index_cache=kernel_config["index_cache"]) + if quadrature_index in expr.free_indices: + expr = gem.IndexSum(expr, quadrature_index) + return expr + return cellvolume + + +def facetarea_generator(domain, coordinate_coefficient, kernel_config, integral_type): + def facetarea(): + from ufl import Measure + assert integral_type != 'cell' + form = 1 * Measure(integral_type, domain=domain) + fd = ufl_utils.compute_form_data(form) + itg_data, = fd.integral_data + integral, = itg_data.integrals + + integrand = ufl_utils.replace_coordinates(integral.integrand(), + coordinate_coefficient) + + # Check if the integral has a quad degree attached, otherwise use + # the estimated polynomial degree attached by compute_form_data + quadrature_degree = integral.metadata()["estimated_polynomial_degree"] + quadrature_index = gem.Index(name='q') + + config = kernel_config.copy() + config.update(quadrature_degree=quadrature_degree, + point_index=quadrature_index) + expr, = fem.compile_ufl(integrand, **config) + if quadrature_index in expr.free_indices: + expr = gem.IndexSum(expr, quadrature_index) + return expr + return facetarea + + def compile_expression_at_points(expression, points, coordinates, parameters=None): """Compiles a UFL expression to be evaluated at compile-time known reference points. Useful for interpolating UFL expressions onto From 50c5985acec274eb8bc4c1c1676c273495a42d95 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 17 Oct 2016 11:38:51 +0100 Subject: [PATCH 208/809] use diff-style for cellvolume configuration --- tsfc/driver.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9a9b6d8828..30bb69e2af 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -218,14 +218,13 @@ def cellvolume(restriction): quadrature_degree = integral.metadata()["estimated_polynomial_degree"] quadrature_index = gem.Index(name='q') + config = {k: v for k, v in kernel_config.items() + if k in ["ufl_cell", "precision", "index_cache"]} interface = CellVolumeKernelInterface(kernel_config["interface"], restriction) - expr, = fem.compile_ufl(integrand, - interface=interface, - ufl_cell=kernel_config["ufl_cell"], - quadrature_degree=quadrature_degree, - precision=kernel_config["precision"], - point_index=quadrature_index, - index_cache=kernel_config["index_cache"]) + config.update(interface=interface, + quadrature_degree=quadrature_degree, + point_index=quadrature_index) + expr, = fem.compile_ufl(integrand, **config) if quadrature_index in expr.free_indices: expr = gem.IndexSum(expr, quadrature_index) return expr From c90452757ffbfe837e128cb1c1e096f6b21d6f08 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 17 Oct 2016 13:01:10 +0100 Subject: [PATCH 209/809] Parameters -> Context --- tsfc/fem.py | 90 ++++++++++++++++++++--------------------------------- 1 file changed, 34 insertions(+), 56 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 1ebc76ead2..c2b675b4f5 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -16,7 +16,7 @@ GeometricQuantity, QuadratureWeight) import gem -from gem.utils import cached_property, unset_attribute +from gem.utils import cached_property from tsfc.constants import _PRECISION as PRECISION from tsfc.fiatinterface import create_element, create_quadrature, as_fiat_cell @@ -120,7 +120,7 @@ def __getitem__(self, key): return self.tables[key] -class Parameters(ProxyKernelInterface): +class Context(ProxyKernelInterface): keywords = ('ufl_cell', 'fiat_cell', 'integration_dim', @@ -136,20 +136,14 @@ class Parameters(ProxyKernelInterface): 'facetarea', 'index_cache') - def __init__(self, **kwargs): - # Initialise superclass - ProxyKernelInterface.__init__(self, kwargs["interface"]) - del kwargs["interface"] + def __init__(self, interface, **kwargs): + ProxyKernelInterface.__init__(self, interface) - invalid_keywords = set(kwargs.keys()) - set(Parameters.keywords) + invalid_keywords = set(kwargs.keys()) - set(Context.keywords) if invalid_keywords: raise ValueError("unexpected keyword argument '{0}'".format(invalid_keywords.pop())) self.__dict__.update(kwargs) - @unset_attribute - def ufl_cell(self): - pass - @cached_property def fiat_cell(self): return as_fiat_cell(self.ufl_cell) @@ -160,10 +154,6 @@ def integration_dim(self): entity_ids = [0] - @unset_attribute - def quadrature_degree(self): - pass - @cached_property def quadrature_rule(self): integration_cell = self.fiat_cell.construct_subelement(self.integration_dim) @@ -231,20 +221,8 @@ def index_selector(self, callback, restriction): """ return self._selector(callback, list(range(len(self.entity_ids))), restriction) - @unset_attribute - def point_index(self): - pass - argument_indices = () - @unset_attribute - def cellvolume(self): - pass - - @unset_attribute - def facetarea(self): - pass - @cached_property def index_cache(self): return collections.defaultdict(gem.Index) @@ -253,18 +231,18 @@ def index_cache(self): class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): """Contains all the context necessary to translate UFL into GEM.""" - def __init__(self, tabulation_manager, parameters): + def __init__(self, tabulation_manager, context): MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) - parameters.tabulation_manager = tabulation_manager - self.parameters = parameters + context.tabulation_manager = tabulation_manager + self.context = context def modified_terminal(self, o): """Overrides the modified terminal handler from :class:`ModifiedTerminalMixin`.""" mt = analyse_modified_terminal(o) - return translate(mt.terminal, mt, self.parameters) + return translate(mt.terminal, mt, self.context) def iterate_shape(mt, callback): @@ -304,64 +282,64 @@ def flat_index(ordered_deriv): @singledispatch -def translate(terminal, mt, params): +def translate(terminal, mt, ctx): """Translates modified terminals into GEM. :arg terminal: terminal, for dispatching :arg mt: analysed modified terminal - :arg params: translator context + :arg ctx: translator context :returns: GEM translation of the modified terminal """ raise AssertionError("Cannot handle terminal type: %s" % type(terminal)) @translate.register(QuadratureWeight) -def translate_quadratureweight(terminal, mt, params): - return gem.Indexed(gem.Literal(params.weights), (params.point_index,)) +def translate_quadratureweight(terminal, mt, ctx): + return gem.Indexed(gem.Literal(ctx.weights), (ctx.point_index,)) @translate.register(GeometricQuantity) -def translate_geometricquantity(terminal, mt, params): - return geometric.translate(terminal, mt, params) +def translate_geometricquantity(terminal, mt, ctx): + return geometric.translate(terminal, mt, ctx) @translate.register(CellVolume) -def translate_cellvolume(terminal, mt, params): - return params.cellvolume(mt.restriction) +def translate_cellvolume(terminal, mt, ctx): + return ctx.cellvolume(mt.restriction) @translate.register(FacetArea) -def translate_facetarea(terminal, mt, params): - return params.facetarea() +def translate_facetarea(terminal, mt, ctx): + return ctx.facetarea() @translate.register(Argument) -def translate_argument(terminal, mt, params): - argument_index = params.argument_indices[terminal.number()] +def translate_argument(terminal, mt, ctx): + argument_index = ctx.argument_indices[terminal.number()] def callback(key): - table = params.tabulation_manager[key] + table = ctx.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant row = gem.Literal(table) else: - table = params.index_selector(lambda i: gem.Literal(table[i]), mt.restriction) - row = gem.partial_indexed(table, (params.point_index,)) + table = ctx.index_selector(lambda i: gem.Literal(table[i]), mt.restriction) + row = gem.partial_indexed(table, (ctx.point_index,)) return gem.Indexed(row, (argument_index,)) return iterate_shape(mt, callback) @translate.register(Coefficient) -def translate_coefficient(terminal, mt, params): - vec = params.coefficient(terminal, mt.restriction) +def translate_coefficient(terminal, mt, ctx): + vec = ctx.coefficient(terminal, mt.restriction) if terminal.ufl_element().family() == 'Real': assert mt.local_derivatives == 0 return vec def callback(key): - table = params.tabulation_manager[key] + table = ctx.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant row = gem.Literal(table) @@ -372,10 +350,10 @@ def callback(key): for i in range(row.shape[0])], gem.Zero()) else: - table = params.index_selector(lambda i: gem.Literal(table[i]), mt.restriction) - row = gem.partial_indexed(table, (params.point_index,)) + table = ctx.index_selector(lambda i: gem.Literal(table[i]), mt.restriction) + row = gem.partial_indexed(table, (ctx.point_index,)) - r = params.index_cache[terminal.ufl_element()] + r = ctx.index_cache[terminal.ufl_element()] return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)), gem.Indexed(vec, (r,))), r) @@ -383,7 +361,7 @@ def callback(key): def compile_ufl(expression, interior_facet=False, **kwargs): - params = Parameters(**kwargs) + context = Context(**kwargs) # Abs-simplification expression = simplify_abs(expression) @@ -401,18 +379,18 @@ def compile_ufl(expression, interior_facet=False, **kwargs): max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(params.entity_points, params.epsilon) + tabulation_manager = TabulationManager(context.entity_points, context.epsilon) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) if interior_facet: expressions = [] - for rs in itertools.product(("+", "-"), repeat=len(params.argument_indices)): + for rs in itertools.product(("+", "-"), repeat=len(context.argument_indices)): expressions.append(map_expr_dag(PickRestriction(*rs), expression)) else: expressions = [expression] # Translate UFL to GEM, lowering finite element specific nodes - translator = Translator(tabulation_manager, params) + translator = Translator(tabulation_manager, context) return map_expr_dags(translator, expressions) From 9586bed782eb61d176b0293f96e0d393497a2f31 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 17 Oct 2016 13:55:14 +0100 Subject: [PATCH 210/809] workaround UFL issue #80 --- tsfc/driver.py | 69 ++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 30bb69e2af..430309e8ec 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -205,24 +205,16 @@ def coefficient(self, ufl_coefficient, r): def cellvolume_generator(domain, coordinate_coefficient, kernel_config): def cellvolume(restriction): from ufl import dx - form = 1 * dx(domain=domain) - fd = ufl_utils.compute_form_data(form) - itg_data, = fd.integral_data - integral, = itg_data.integrals + integrand, degree = one_times(dx(domain=domain)) + integrand = ufl_utils.replace_coordinates(integrand, coordinate_coefficient) - integrand = ufl_utils.replace_coordinates(integral.integrand(), - coordinate_coefficient) - - # Check if the integral has a quad degree attached, otherwise use - # the estimated polynomial degree attached by compute_form_data - quadrature_degree = integral.metadata()["estimated_polynomial_degree"] + interface = CellVolumeKernelInterface(kernel_config["interface"], restriction) quadrature_index = gem.Index(name='q') config = {k: v for k, v in kernel_config.items() if k in ["ufl_cell", "precision", "index_cache"]} - interface = CellVolumeKernelInterface(kernel_config["interface"], restriction) config.update(interface=interface, - quadrature_degree=quadrature_degree, + quadrature_degree=degree, point_index=quadrature_index) expr, = fem.compile_ufl(integrand, **config) if quadrature_index in expr.free_indices: @@ -235,22 +227,13 @@ def facetarea_generator(domain, coordinate_coefficient, kernel_config, integral_ def facetarea(): from ufl import Measure assert integral_type != 'cell' - form = 1 * Measure(integral_type, domain=domain) - fd = ufl_utils.compute_form_data(form) - itg_data, = fd.integral_data - integral, = itg_data.integrals - - integrand = ufl_utils.replace_coordinates(integral.integrand(), - coordinate_coefficient) + integrand, degree = one_times(Measure(integral_type, domain=domain)) + integrand = ufl_utils.replace_coordinates(integrand, coordinate_coefficient) - # Check if the integral has a quad degree attached, otherwise use - # the estimated polynomial degree attached by compute_form_data - quadrature_degree = integral.metadata()["estimated_polynomial_degree"] quadrature_index = gem.Index(name='q') config = kernel_config.copy() - config.update(quadrature_degree=quadrature_degree, - point_index=quadrature_index) + config.update(quadrature_degree=degree, point_index=quadrature_index) expr, = fem.compile_ufl(integrand, **config) if quadrature_index in expr.free_indices: expr = gem.IndexSum(expr, quadrature_index) @@ -258,6 +241,31 @@ def facetarea(): return facetarea +def one_times(measure): + # Workaround for UFL issue #80: + # https://bitbucket.org/fenics-project/ufl/issues/80 + from ufl import replace + from ufl.algorithms import estimate_total_polynomial_degree + from ufl.geometry import QuadratureWeight + + form = 1 * measure + fd = ufl_utils.compute_form_data(form, do_estimate_degrees=False) + itg_data, = fd.integral_data + integral, = itg_data.integrals + integrand = integral.integrand() + + # UFL considers QuadratureWeight a geometric quantity, and the + # general handler for geometric quantities estimates the degree of + # the coordinate element. This would unnecessarily increase the + # estimated degree, so we drop QuadratureWeight instead. + expression = replace(integrand, {QuadratureWeight(itg_data.domain): 1}) + + # Now estimate degree for the preprocessed form + degree = estimate_total_polynomial_degree(expression) + + return integrand, degree + + def compile_expression_at_points(expression, points, coordinates, parameters=None): """Compiles a UFL expression to be evaluated at compile-time known reference points. Useful for interpolating UFL expressions onto @@ -299,12 +307,13 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non # Translate to GEM point_index = gem.Index(name='p') - ir, = fem.compile_ufl(expression, - interface=builder, - ufl_cell=coordinates.ufl_domain().ufl_cell(), - precision=parameters["precision"], - points=points, - point_index=point_index) + config = dict(interface=builder, + ufl_cell=coordinates.ufl_domain().ufl_cell(), + precision=parameters["precision"], + points=points, + point_index=point_index) + config["cellvolume"] = cellvolume_generator(coordinates.ufl_domain(), coordinates, config) + ir, = fem.compile_ufl(expression, **config) # Deal with non-scalar expressions value_shape = ir.shape From 4c6072587949d2125b639b134d2d4316908e89f8 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 17 Oct 2016 13:58:17 +0100 Subject: [PATCH 211/809] move one_times to ufl_utils.py --- tsfc/driver.py | 29 ++--------------------------- tsfc/ufl_utils.py | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 430309e8ec..d27f5210dd 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -205,7 +205,7 @@ def coefficient(self, ufl_coefficient, r): def cellvolume_generator(domain, coordinate_coefficient, kernel_config): def cellvolume(restriction): from ufl import dx - integrand, degree = one_times(dx(domain=domain)) + integrand, degree = ufl_utils.one_times(dx(domain=domain)) integrand = ufl_utils.replace_coordinates(integrand, coordinate_coefficient) interface = CellVolumeKernelInterface(kernel_config["interface"], restriction) @@ -227,7 +227,7 @@ def facetarea_generator(domain, coordinate_coefficient, kernel_config, integral_ def facetarea(): from ufl import Measure assert integral_type != 'cell' - integrand, degree = one_times(Measure(integral_type, domain=domain)) + integrand, degree = ufl_utils.one_times(Measure(integral_type, domain=domain)) integrand = ufl_utils.replace_coordinates(integrand, coordinate_coefficient) quadrature_index = gem.Index(name='q') @@ -241,31 +241,6 @@ def facetarea(): return facetarea -def one_times(measure): - # Workaround for UFL issue #80: - # https://bitbucket.org/fenics-project/ufl/issues/80 - from ufl import replace - from ufl.algorithms import estimate_total_polynomial_degree - from ufl.geometry import QuadratureWeight - - form = 1 * measure - fd = ufl_utils.compute_form_data(form, do_estimate_degrees=False) - itg_data, = fd.integral_data - integral, = itg_data.integrals - integrand = integral.integrand() - - # UFL considers QuadratureWeight a geometric quantity, and the - # general handler for geometric quantities estimates the degree of - # the coordinate element. This would unnecessarily increase the - # estimated degree, so we drop QuadratureWeight instead. - expression = replace(integrand, {QuadratureWeight(itg_data.domain): 1}) - - # Now estimate degree for the preprocessed form - degree = estimate_total_polynomial_degree(expression) - - return integrand, degree - - def compile_expression_at_points(expression, points, coordinates, parameters=None): """Compiles a UFL expression to be evaluated at compile-time known reference points. Useful for interpolating UFL expressions onto diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index e9f5320bc2..98f86ca2fd 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -6,14 +6,16 @@ from singledispatch import singledispatch import ufl -from ufl import indices, as_tensor +from ufl import as_tensor, indices, replace from ufl.algorithms import compute_form_data as ufl_compute_form_data +from ufl.algorithms import estimate_total_polynomial_degree from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction +from ufl.geometry import QuadratureWeight from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, ComponentTensor, Expr, FloatValue, Division, MixedElement, MultiIndex, Product, @@ -53,6 +55,27 @@ def compute_form_data(form, return fd +def one_times(measure): + # Workaround for UFL issue #80: + # https://bitbucket.org/fenics-project/ufl/issues/80 + form = 1 * measure + fd = compute_form_data(form, do_estimate_degrees=False) + itg_data, = fd.integral_data + integral, = itg_data.integrals + integrand = integral.integrand() + + # UFL considers QuadratureWeight a geometric quantity, and the + # general handler for geometric quantities estimates the degree of + # the coordinate element. This would unnecessarily increase the + # estimated degree, so we drop QuadratureWeight instead. + expression = replace(integrand, {QuadratureWeight(itg_data.domain): 1}) + + # Now estimate degree for the preprocessed form + degree = estimate_total_polynomial_degree(expression) + + return integrand, degree + + def preprocess_expression(expression): """Imitates the compute_form_data processing pipeline. From 59f59df4ef6b523e01afa58495883b7637a608d5 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 17 Oct 2016 15:22:46 +0100 Subject: [PATCH 212/809] fix coordinates for CellVolume interpolation --- tsfc/driver.py | 10 ++++++++-- tsfc/ufl_utils.py | 9 ++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index d27f5210dd..a33cb54597 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -6,7 +6,8 @@ from functools import reduce from ufl.algorithms import extract_arguments, extract_coefficients -from ufl.classes import Form +from ufl.algorithms.analysis import has_type +from ufl.classes import Form, CellVolume from ufl.log import GREEN import gem @@ -273,9 +274,14 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non assert coordinates.ufl_domain() == domain expression = ufl_utils.replace_coordinates(expression, coordinates) + # Collect required coefficients + coefficients = extract_coefficients(expression) + if coordinates not in coefficients and has_type(expression, CellVolume): + coefficients = [coordinates] + coefficients + # Initialise kernel builder builder = firedrake_interface.ExpressionKernelBuilder() - builder.set_coefficients(extract_coefficients(expression)) + builder.set_coefficients(coefficients) # Split mixed coefficients expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 98f86ca2fd..691117599f 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -29,11 +29,14 @@ construct_modified_terminal) +preserve_geometry_types = (CellVolume, FacetArea) + + def compute_form_data(form, do_apply_function_pullbacks=True, do_apply_integral_scaling=True, do_apply_geometry_lowering=True, - preserve_geometry_types=(CellVolume, FacetArea), + preserve_geometry_types=preserve_geometry_types, do_apply_restrictions=True, do_estimate_degrees=True): """Preprocess UFL form in a format suitable for TSFC. Return @@ -85,9 +88,9 @@ def preprocess_expression(expression): expression = apply_algebra_lowering(expression) expression = apply_derivatives(expression) expression = apply_function_pullbacks(expression) - expression = apply_geometry_lowering(expression) + expression = apply_geometry_lowering(expression, preserve_geometry_types) expression = apply_derivatives(expression) - expression = apply_geometry_lowering(expression) + expression = apply_geometry_lowering(expression, preserve_geometry_types) expression = apply_derivatives(expression) return expression From 13d88388efbfc33b17bb938319037f0d67169b96 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 17 Oct 2016 15:55:36 +0100 Subject: [PATCH 213/809] post-#63 cleanup --- tsfc/__init__.py | 1 + tsfc/coffee.py | 9 ++++----- tsfc/driver.py | 8 ++++---- tsfc/fem.py | 8 +++----- tsfc/geometric.py | 2 +- tsfc/{constants.py => parameters.py} | 4 +--- 6 files changed, 14 insertions(+), 18 deletions(-) rename tsfc/{constants.py => parameters.py} (90%) diff --git a/tsfc/__init__.py b/tsfc/__init__.py index 961fab442c..54e44b8a69 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -1,3 +1,4 @@ from __future__ import absolute_import, print_function, division from tsfc.driver import compile_form, compile_expression_at_points # noqa: F401 +from tsfc.parameters import default_parameters # noqa: F401 diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 8084424011..9dd7f2caae 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -16,7 +16,7 @@ from gem import gem, impero as imp -from tsfc.constants import SCALAR_TYPE +from tsfc.parameters import SCALAR_TYPE from tsfc.logging import logger @@ -24,12 +24,12 @@ class Bunch(object): pass -def generate(impero_c, index_names, parameters, roots=(), argument_indices=()): +def generate(impero_c, index_names, precision, roots=(), argument_indices=()): """Generates COFFEE code. :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names - :arg parameters: TSFC parameters + :arg precision: floating-point precision for printing :arg roots: list of expression DAG roots for attaching #pragma coffee expression :arg argument_indices: argument indices for attaching @@ -40,6 +40,7 @@ def generate(impero_c, index_names, parameters, roots=(), argument_indices=()): params = Bunch() params.declare = impero_c.declare params.indices = impero_c.indices + params.precision = precision params.roots = roots params.argument_indices = argument_indices @@ -51,8 +52,6 @@ def generate(impero_c, index_names, parameters, roots=(), argument_indices=()): params.index_names = defaultdict(lambda: "i_%d" % next(counter)) params.index_names.update(index_names) - params.precision = parameters["precision"] - return statement(impero_c.tree, params) diff --git a/tsfc/driver.py b/tsfc/driver.py index a33cb54597..535ad8fa76 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -15,10 +15,10 @@ import gem.impero_utils as impero_utils from tsfc import fem, ufl_utils -from tsfc.coffee import generate as generate_coffee -from tsfc.constants import SCALAR_TYPE, default_parameters +from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee from tsfc.fiatinterface import QuadratureRule, as_fiat_cell, create_quadrature from tsfc.logging import logger +from tsfc.parameters import default_parameters from tsfc.kernel_interface import ProxyKernelInterface import tsfc.kernel_interface.firedrake as firedrake_interface @@ -182,7 +182,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body = generate_coffee(impero_c, index_names, parameters, ir, argument_indices) + body = generate_coffee(impero_c, index_names, parameters["precision"], ir, argument_indices) kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) return builder.construct_kernel(kernel_name, body) @@ -309,7 +309,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non return_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('A', rank=return_shape)) return_expr = gem.Indexed(return_var, return_indices) impero_c = impero_utils.compile_gem([return_expr], [ir], return_indices) - body = generate_coffee(impero_c, {point_index: 'p'}, parameters) + body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"]) # Handle cell orientations if builder.needs_cell_orientations([ir]): diff --git a/tsfc/fem.py b/tsfc/fem.py index c2b675b4f5..96568f6a2d 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -18,13 +18,11 @@ import gem from gem.utils import cached_property -from tsfc.constants import _PRECISION as PRECISION +from tsfc import compat, ufl2gem, geometric from tsfc.fiatinterface import create_element, create_quadrature, as_fiat_cell from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import analyse_modified_terminal -from tsfc import compat -from tsfc import ufl2gem -from tsfc import geometric +from tsfc.parameters import PARAMETERS from tsfc.ufl_utils import (CollectModifiedTerminals, ModifiedTerminalMixin, PickRestriction, spanning_degree, simplify_abs) @@ -167,7 +165,7 @@ def points(self): def weights(self): return self.quadrature_rule.get_weights() - precision = PRECISION + precision = PARAMETERS["precision"] @cached_property def epsilon(self): diff --git a/tsfc/geometric.py b/tsfc/geometric.py index c4e7c66905..5e49e6242e 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -15,7 +15,7 @@ import gem -from tsfc.constants import NUMPY_TYPE +from tsfc.parameters import NUMPY_TYPE @singledispatch diff --git a/tsfc/constants.py b/tsfc/parameters.py similarity index 90% rename from tsfc/constants.py rename to tsfc/parameters.py index dc1c7374e5..df977435ff 100644 --- a/tsfc/constants.py +++ b/tsfc/parameters.py @@ -5,8 +5,6 @@ NUMPY_TYPE = numpy.dtype("double") -_PRECISION = numpy.finfo(NUMPY_TYPE).precision - SCALAR_TYPE = {numpy.dtype("double"): "double", numpy.dtype("float32"): "float"}[NUMPY_TYPE] @@ -22,7 +20,7 @@ "unroll_indexsum": 3, # Precision of float printing (number of digits) - "precision": _PRECISION, + "precision": numpy.finfo(NUMPY_TYPE).precision, } From 6b181da7883fd8a4e17b5bc102ca7a6c83f992b2 Mon Sep 17 00:00:00 2001 From: David Ham Date: Thu, 11 Aug 2016 14:14:05 +0100 Subject: [PATCH 214/809] Use FInAT quadrature --- tsfc/driver.py | 26 +++++++++++++------------- tsfc/fem.py | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b2b2277a31..755af074db 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -15,11 +15,11 @@ import gem.optimise as opt import gem.impero_utils as impero_utils -from finat.quadrature import QuadratureRule, CollapsedGaussJacobiQuadrature +from finat.quadrature import QuadratureRule, make_quadrature from tsfc import fem, ufl_utils from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee -from tsfc.fiatinterface import as_fiat_cell, create_quadrature +from tsfc.fiatinterface import as_fiat_cell from tsfc.finatinterface import create_element from tsfc.logging import logger from tsfc.parameters import default_parameters @@ -133,6 +133,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, # parameters override per-integral metadata params.update(parameters) + integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) + integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) + # Check if the integral has a quad degree attached, otherwise use # the estimated polynomial degree attached by compute_form_data quadrature_degree = params.get("quadrature_degree", @@ -141,27 +144,24 @@ def compile_integral(integral_data, form_data, prefix, parameters, quad_rule = params["quadrature_rule"] except KeyError: integration_cell = fiat_cell.construct_subelement(integration_dim) - quad_rule = create_quadrature(integration_cell, quadrature_degree) - quad_rule = QuadratureRule(cell, quad_rule.get_points(), quad_rule.get_weights()) - quad_rule.__class__ = CollapsedGaussJacobiQuadrature + quad_rule = make_quadrature(integration_cell, quadrature_degree) if not isinstance(quad_rule, QuadratureRule): raise ValueError("Expected to find a QuadratureRule object, not a %s" % type(quad_rule)) - integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) - integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) - quadrature_index = gem.Index(name='ip') - quadrature_indices.append(quadrature_index) + quadrature_index = quad_rule.get_indices() + quadrature_indices += quadrature_index + config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule, point_index=quadrature_index) ir = fem.compile_ufl(integrand, interior_facet=interior_facet, **config) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) - irs.append([(gem.IndexSum(expr, quadrature_index) - if quadrature_index in expr.free_indices - else expr) - for expr in ir]) + for q in quadrature_index: + ir = [gem.IndexSum(expr, q) if q in expr.free_indices else expr + for expr in ir] + irs.append(ir) # Sum the expressions that are part of the same restriction ir = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*irs)) diff --git a/tsfc/fem.py b/tsfc/fem.py index f0ef7daad4..4cefb4d4dc 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -166,7 +166,7 @@ def translate(terminal, mt, ctx): @translate.register(QuadratureWeight) def translate_quadratureweight(terminal, mt, ctx): - return gem.Indexed(gem.Literal(ctx.weights), (ctx.point_index,)) + return gem.Indexed(gem.Literal(ctx.weights), ctx.point_index) @translate.register(GeometricQuantity) @@ -195,7 +195,7 @@ def callback(entity_index): M = ctx.index_selector(callback, mt.restriction) vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) argument_index = ctx.argument_indices[terminal.number()] - result = gem.Indexed(M, (ctx.point_index,) + argument_index + vi) + result = gem.Indexed(M, ctx.point_index + argument_index + vi) if vi: return gem.ComponentTensor(result, vi) else: @@ -220,7 +220,7 @@ def callback(entity_index): alpha = element.get_indices() vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) - result = gem.Product(gem.Indexed(M, (ctx.point_index,) + alpha + vi), + result = gem.Product(gem.Indexed(M, ctx.point_index + alpha + vi), gem.Indexed(vec, alpha)) for i in alpha: result = gem.IndexSum(result, i) From 369192821ba5aa8beb2025c168728cf63037421e Mon Sep 17 00:00:00 2001 From: David Ham Date: Fri, 12 Aug 2016 14:05:44 +0100 Subject: [PATCH 215/809] remove facet quadrature hack --- tsfc/fem.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 4cefb4d4dc..2ab2fb3380 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -15,10 +15,9 @@ import gem from gem.utils import cached_property -from finat.quadrature import QuadratureRule, CollapsedGaussJacobiQuadrature +from finat.quadrature import make_quadrature from tsfc import ufl2gem, geometric -from tsfc.fiatinterface import create_quadrature from tsfc.finatinterface import create_element, as_fiat_cell from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import analyse_modified_terminal @@ -63,10 +62,7 @@ def integration_dim(self): @cached_property def quadrature_rule(self): integration_cell = self.fiat_cell.construct_subelement(self.integration_dim) - quad_rule = create_quadrature(integration_cell, self.quadrature_degree) - quad_rule = QuadratureRule(integration_cell, quad_rule.get_points(), quad_rule.get_weights()) - quad_rule.__class__ = CollapsedGaussJacobiQuadrature - return quad_rule + return make_quadrature(integration_cell, self.quadrature_degree) @cached_property def points(self): @@ -188,11 +184,11 @@ def translate_facetarea(terminal, mt, ctx): def translate_argument(terminal, mt, ctx): element = create_element(terminal.ufl_element()) - def callback(entity_index): - quad_rule = QuadratureRule(ctx.fiat_cell, ctx.entity_points[entity_index], ctx.weights) - quad_rule.__class__ = CollapsedGaussJacobiQuadrature - return element.basis_evaluation(quad_rule, derivative=mt.local_derivatives) - M = ctx.index_selector(callback, mt.restriction) + def callback(entity_id): + return element.basis_evaluation(ctx.quadrature_rule, + derivative=mt.local_derivatives, + entity=(ctx.integration_dim, entity_id)) + M = ctx.entity_selector(callback, mt.restriction) vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) argument_index = ctx.argument_indices[terminal.number()] result = gem.Indexed(M, ctx.point_index + argument_index + vi) @@ -212,11 +208,11 @@ def translate_coefficient(terminal, mt, ctx): element = create_element(terminal.ufl_element()) - def callback(entity_index): - quad_rule = QuadratureRule(ctx.fiat_cell, ctx.entity_points[entity_index], ctx.weights) - quad_rule.__class__ = CollapsedGaussJacobiQuadrature - return element.basis_evaluation(quad_rule, derivative=mt.local_derivatives) - M = ctx.index_selector(callback, mt.restriction) + def callback(entity_id): + return element.basis_evaluation(ctx.quadrature_rule, + derivative=mt.local_derivatives, + entity=(ctx.integration_dim, entity_id)) + M = ctx.entity_selector(callback, mt.restriction) alpha = element.get_indices() vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) From 15433ffb587a3c29b52bc65e2cdef49746633d3b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 18 Oct 2016 12:53:45 +0100 Subject: [PATCH 216/809] WIP: FInAT API redesign --- tsfc/driver.py | 15 +++++++-------- tsfc/fem.py | 6 +++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 755af074db..754ab7f5c0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -15,7 +15,7 @@ import gem.optimise as opt import gem.impero_utils as impero_utils -from finat.quadrature import QuadratureRule, make_quadrature +from finat.quadrature import AbstractQuadratureRule, QuadratureRule, make_quadrature from tsfc import fem, ufl_utils from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee @@ -146,11 +146,11 @@ def compile_integral(integral_data, form_data, prefix, parameters, integration_cell = fiat_cell.construct_subelement(integration_dim) quad_rule = make_quadrature(integration_cell, quadrature_degree) - if not isinstance(quad_rule, QuadratureRule): + if not isinstance(quad_rule, AbstractQuadratureRule): raise ValueError("Expected to find a QuadratureRule object, not a %s" % type(quad_rule)) - quadrature_index = quad_rule.get_indices() + quadrature_index = quad_rule.point_set.indices quadrature_indices += quadrature_index config = kernel_cfg.copy() @@ -222,7 +222,7 @@ def cellvolume(restriction): if k in ["ufl_cell", "precision", "index_cache"]} config.update(interface=interface, quadrature_degree=degree, - point_index=quadrature_index) + point_index=(quadrature_index,)) expr, = fem.compile_ufl(integrand, **config) if quadrature_index in expr.free_indices: expr = gem.IndexSum(expr, quadrature_index) @@ -240,7 +240,7 @@ def facetarea(): quadrature_index = gem.Index(name='q') config = kernel_config.copy() - config.update(quadrature_degree=degree, point_index=quadrature_index) + config.update(quadrature_degree=degree, point_index=(quadrature_index,)) expr, = fem.compile_ufl(integrand, **config) if quadrature_index in expr.free_indices: expr = gem.IndexSum(expr, quadrature_index) @@ -297,9 +297,8 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non config = dict(interface=builder, ufl_cell=coordinates.ufl_domain().ufl_cell(), precision=parameters["precision"], - weights=None, - points=points, - point_index=point_index) + quadrature_rule=QuadratureRule(points, [0]*len(points)), + point_index=(point_index,)) config["cellvolume"] = cellvolume_generator(coordinates.ufl_domain(), coordinates, config) ir, = fem.compile_ufl(expression, **config) diff --git a/tsfc/fem.py b/tsfc/fem.py index 2ab2fb3380..364d5c5b00 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -162,7 +162,7 @@ def translate(terminal, mt, ctx): @translate.register(QuadratureWeight) def translate_quadratureweight(terminal, mt, ctx): - return gem.Indexed(gem.Literal(ctx.weights), ctx.point_index) + return ctx.quadrature_rule.weight_expression @translate.register(GeometricQuantity) @@ -185,7 +185,7 @@ def translate_argument(terminal, mt, ctx): element = create_element(terminal.ufl_element()) def callback(entity_id): - return element.basis_evaluation(ctx.quadrature_rule, + return element.basis_evaluation(ctx.quadrature_rule.point_set, derivative=mt.local_derivatives, entity=(ctx.integration_dim, entity_id)) M = ctx.entity_selector(callback, mt.restriction) @@ -209,7 +209,7 @@ def translate_coefficient(terminal, mt, ctx): element = create_element(terminal.ufl_element()) def callback(entity_id): - return element.basis_evaluation(ctx.quadrature_rule, + return element.basis_evaluation(ctx.quadrature_rule.point_set, derivative=mt.local_derivatives, entity=(ctx.integration_dim, entity_id)) M = ctx.entity_selector(callback, mt.restriction) From a5ab8e27fcdf052bfcef6a1697ed7001253afff0 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 18 Oct 2016 18:48:44 +0100 Subject: [PATCH 217/809] FInAT works? --- tsfc/driver.py | 30 +++++++----------- tsfc/fem.py | 81 ++++++++++++++++++++--------------------------- tsfc/geometric.py | 15 +++++---- 3 files changed, 55 insertions(+), 71 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 754ab7f5c0..5fd8d3d337 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -15,7 +15,8 @@ import gem.optimise as opt import gem.impero_utils as impero_utils -from finat.quadrature import AbstractQuadratureRule, QuadratureRule, make_quadrature +from finat.point_set import PointSet +from finat.quadrature import AbstractQuadratureRule, make_quadrature from tsfc import fem, ufl_utils from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee @@ -154,7 +155,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, quadrature_indices += quadrature_index config = kernel_cfg.copy() - config.update(quadrature_rule=quad_rule, point_index=quadrature_index) + config.update(quadrature_rule=quad_rule, point_multiindex=quadrature_index) ir = fem.compile_ufl(integrand, interior_facet=interior_facet, **config) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) @@ -214,18 +215,13 @@ def cellvolume(restriction): from ufl import dx integrand, degree = ufl_utils.one_times(dx(domain=domain)) integrand = ufl_utils.replace_coordinates(integrand, coordinate_coefficient) - interface = CellVolumeKernelInterface(kernel_config["interface"], restriction) - quadrature_index = gem.Index(name='q') config = {k: v for k, v in kernel_config.items() if k in ["ufl_cell", "precision", "index_cache"]} config.update(interface=interface, - quadrature_degree=degree, - point_index=(quadrature_index,)) - expr, = fem.compile_ufl(integrand, **config) - if quadrature_index in expr.free_indices: - expr = gem.IndexSum(expr, quadrature_index) + quadrature_degree=degree) + expr, = fem.compile_ufl(integrand, point_sum=True, **config) return expr return cellvolume @@ -237,13 +233,9 @@ def facetarea(): integrand, degree = ufl_utils.one_times(Measure(integral_type, domain=domain)) integrand = ufl_utils.replace_coordinates(integrand, coordinate_coefficient) - quadrature_index = gem.Index(name='q') - config = kernel_config.copy() - config.update(quadrature_degree=degree, point_index=(quadrature_index,)) - expr, = fem.compile_ufl(integrand, **config) - if quadrature_index in expr.free_indices: - expr = gem.IndexSum(expr, quadrature_index) + config.update(quadrature_degree=degree) + expr, = fem.compile_ufl(integrand, point_sum=True, **config) return expr return facetarea @@ -293,12 +285,11 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) # Translate to GEM - point_index = gem.Index(name='p') + point_set = PointSet(points) config = dict(interface=builder, ufl_cell=coordinates.ufl_domain().ufl_cell(), precision=parameters["precision"], - quadrature_rule=QuadratureRule(points, [0]*len(points)), - point_index=(point_index,)) + point_set=point_set) config["cellvolume"] = cellvolume_generator(coordinates.ufl_domain(), coordinates, config) ir, = fem.compile_ufl(expression, **config) @@ -310,11 +301,12 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non # Build kernel body return_shape = (len(points),) + value_shape - return_indices = (point_index,) + tensor_indices + return_indices = point_set.indices + tensor_indices return_var = gem.Variable('A', return_shape) return_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('A', rank=return_shape)) return_expr = gem.Indexed(return_var, return_indices) impero_c = impero_utils.compile_gem([return_expr], [ir], return_indices) + point_index, = point_set.indices body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"]) # Handle cell orientations diff --git a/tsfc/fem.py b/tsfc/fem.py index 364d5c5b00..9eed2ea09a 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -1,10 +1,9 @@ from __future__ import absolute_import, print_function, division -from six.moves import map, range +from six.moves import map import collections import itertools -import numpy from singledispatch import singledispatch from ufl.corealg.map_dag import map_expr_dag, map_expr_dags @@ -32,10 +31,10 @@ class Context(ProxyKernelInterface): 'entity_ids', 'quadrature_degree', 'quadrature_rule', - 'points', - 'weights', + 'point_set', + 'weight_expr', 'precision', - 'point_index', + 'point_multiindex', 'argument_indices', 'cellvolume', 'facetarea', @@ -65,12 +64,16 @@ def quadrature_rule(self): return make_quadrature(integration_cell, self.quadrature_degree) @cached_property - def points(self): - return self.quadrature_rule.points + def point_set(self): + return self.quadrature_rule.point_set @cached_property - def weights(self): - return self.quadrature_rule.weights + def point_multiindex(self): + return self.point_set.indices + + @cached_property + def weight_expr(self): + return self.quadrature_rule.weight_expression precision = PARAMETERS["precision"] @@ -79,24 +82,9 @@ def epsilon(self): # Rounding tolerance mimicking FFC return 10.0 * eval("1e-%d" % self.precision) - @cached_property - def entity_points(self): - """An array of points in cell coordinates for each entity, - i.e. a list of arrays of points.""" - result = [] - for entity_id in self.entity_ids: - t = self.fiat_cell.get_entity_transform(self.integration_dim, entity_id) - result.append(numpy.asarray(list(map(t, self.points)))) - return result - def _selector(self, callback, opts, restriction): """Helper function for selecting code for the correct entity at run-time.""" - if len(opts) == 1: - return callback(opts[0]) - else: - f = self.facet_number(restriction) - return gem.select_expression(list(map(callback, opts)), f) def entity_selector(self, callback, restriction): """Selects code for the correct entity at run-time. Callback @@ -109,21 +97,11 @@ def entity_selector(self, callback, restriction): :arg restriction: Restriction of the modified terminal, used for entity selection. """ - return self._selector(callback, self.entity_ids, restriction) - - def index_selector(self, callback, restriction): - """Selects code for the correct entity at run-time. Callback - generates code for a specified entity. - - This function passes ``callback`` an index of the entity - numbers array. - - :arg callback: A function to be called with an entity index - that generates code for that entity. - :arg restriction: Restriction of the modified terminal, used - for entity selection. - """ - return self._selector(callback, list(range(len(self.entity_ids))), restriction) + if len(self.entity_ids) == 1: + return callback(self.entity_ids[0]) + else: + f = self.facet_number(restriction) + return gem.select_expression(list(map(callback, self.entity_ids)), f) argument_indices = () @@ -162,7 +140,7 @@ def translate(terminal, mt, ctx): @translate.register(QuadratureWeight) def translate_quadratureweight(terminal, mt, ctx): - return ctx.quadrature_rule.weight_expression + return ctx.weight_expr @translate.register(GeometricQuantity) @@ -185,13 +163,13 @@ def translate_argument(terminal, mt, ctx): element = create_element(terminal.ufl_element()) def callback(entity_id): - return element.basis_evaluation(ctx.quadrature_rule.point_set, + return element.basis_evaluation(ctx.point_set, derivative=mt.local_derivatives, entity=(ctx.integration_dim, entity_id)) M = ctx.entity_selector(callback, mt.restriction) vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) argument_index = ctx.argument_indices[terminal.number()] - result = gem.Indexed(M, ctx.point_index + argument_index + vi) + result = gem.Indexed(M, ctx.point_multiindex + argument_index + vi) if vi: return gem.ComponentTensor(result, vi) else: @@ -209,14 +187,14 @@ def translate_coefficient(terminal, mt, ctx): element = create_element(terminal.ufl_element()) def callback(entity_id): - return element.basis_evaluation(ctx.quadrature_rule.point_set, + return element.basis_evaluation(ctx.point_set, derivative=mt.local_derivatives, entity=(ctx.integration_dim, entity_id)) M = ctx.entity_selector(callback, mt.restriction) alpha = element.get_indices() vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) - result = gem.Product(gem.Indexed(M, ctx.point_index + alpha + vi), + result = gem.Product(gem.Indexed(M, ctx.point_multiindex + alpha + vi), gem.Indexed(vec, alpha)) for i in alpha: result = gem.IndexSum(result, i) @@ -226,7 +204,7 @@ def callback(entity_id): return result -def compile_ufl(expression, interior_facet=False, **kwargs): +def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): context = Context(**kwargs) # Abs-simplification @@ -240,4 +218,15 @@ def compile_ufl(expression, interior_facet=False, **kwargs): # Translate UFL to GEM, lowering finite element specific nodes translator = Translator(context) - return map_expr_dags(translator, expressions) + result = map_expr_dags(translator, expressions) + if point_sum: + for index in context.point_multiindex: + result = [index_sum(expr, index) for expr in result] + return result + + +def index_sum(expression, index): + if index in expression.free_indices: + return gem.IndexSum(expression, index) + else: + return expression diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 5e49e6242e..26de1a6bba 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -3,7 +3,7 @@ from __future__ import absolute_import, print_function, division -from numpy import allclose, vstack +from numpy import allclose, asarray, vstack from singledispatch import singledispatch from ufl.classes import (CellCoordinate, CellEdgeVectors, @@ -98,13 +98,16 @@ def translate_cell_edge_vectors(terminal, mt, params): @translate.register(CellCoordinate) def translate_cell_coordinate(terminal, mt, params): - return gem.partial_indexed(params.index_selector(lambda i: gem.Literal(params.entity_points[i]), - mt.restriction), - (params.point_index,)) + def callback(entity_id): + # FIXME: simplex only code, and other cases are untested! :( + t = params.fiat_cell.get_entity_transform(params.integration_dim, entity_id) + return gem.Literal(asarray(list(map(t, params.point_set.points)))) + + return gem.partial_indexed(params.entity_selector(callback, mt.restriction), + params.point_multiindex) @translate.register(FacetCoordinate) def translate_facet_coordinate(terminal, mt, params): assert params.integration_dim != params.fiat_cell.get_dimension() - points = params.points - return gem.partial_indexed(gem.Literal(points), (params.point_index,)) + return params.point_set.expression From 7f41eb437bbedcfdce535e414bc93689ce77aa61 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 19 Oct 2016 11:27:59 +0100 Subject: [PATCH 218/809] use restore_shape in CellCoordinate --- tsfc/geometric.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 26de1a6bba..7765ea7a5f 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -15,6 +15,8 @@ import gem +from finat.point_set import restore_shape + from tsfc.parameters import NUMPY_TYPE @@ -98,10 +100,16 @@ def translate_cell_edge_vectors(terminal, mt, params): @translate.register(CellCoordinate) def translate_cell_coordinate(terminal, mt, params): + if params.integration_dim == params.fiat_cell.get_dimension(): + return params.point_set.expression + + # This destroys the structure of the quadrature points, but since + # this code path is only used to implement CellCoordinate in facet + # integrals, hopefully it does not matter much. def callback(entity_id): - # FIXME: simplex only code, and other cases are untested! :( + ps = params.point_set t = params.fiat_cell.get_entity_transform(params.integration_dim, entity_id) - return gem.Literal(asarray(list(map(t, params.point_set.points)))) + return gem.Literal(restore_shape(asarray(list(map(t, ps.points))), ps)) return gem.partial_indexed(params.entity_selector(callback, mt.restriction), params.point_multiindex) From 3d404854e407d353993c0edbb02558ba554f4665 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 19 Oct 2016 11:31:34 +0100 Subject: [PATCH 219/809] add gem.index_sum --- tsfc/driver.py | 16 +++++++--------- tsfc/fem.py | 9 +-------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 5fd8d3d337..e2d8b8c05f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -151,17 +151,16 @@ def compile_integral(integral_data, form_data, prefix, parameters, raise ValueError("Expected to find a QuadratureRule object, not a %s" % type(quad_rule)) - quadrature_index = quad_rule.point_set.indices - quadrature_indices += quadrature_index + quadrature_multiindex = quad_rule.point_set.indices + quadrature_indices += quadrature_multiindex config = kernel_cfg.copy() - config.update(quadrature_rule=quad_rule, point_multiindex=quadrature_index) + config.update(quadrature_rule=quad_rule) ir = fem.compile_ufl(integrand, interior_facet=interior_facet, **config) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) - for q in quadrature_index: - ir = [gem.IndexSum(expr, q) if q in expr.free_indices else expr - for expr in ir] + for quadrature_index in quadrature_multiindex: + ir = [gem.index_sum(expr, quadrature_index) for expr in ir] irs.append(ir) # Sum the expressions that are part of the same restriction @@ -219,8 +218,7 @@ def cellvolume(restriction): config = {k: v for k, v in kernel_config.items() if k in ["ufl_cell", "precision", "index_cache"]} - config.update(interface=interface, - quadrature_degree=degree) + config.update(interface=interface, quadrature_degree=degree) expr, = fem.compile_ufl(integrand, point_sum=True, **config) return expr return cellvolume @@ -291,7 +289,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non precision=parameters["precision"], point_set=point_set) config["cellvolume"] = cellvolume_generator(coordinates.ufl_domain(), coordinates, config) - ir, = fem.compile_ufl(expression, **config) + ir, = fem.compile_ufl(expression, point_sum=False, **config) # Deal with non-scalar expressions value_shape = ir.shape diff --git a/tsfc/fem.py b/tsfc/fem.py index 9eed2ea09a..cccd5502f3 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -221,12 +221,5 @@ def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): result = map_expr_dags(translator, expressions) if point_sum: for index in context.point_multiindex: - result = [index_sum(expr, index) for expr in result] + result = [gem.index_sum(expr, index) for expr in result] return result - - -def index_sum(expression, index): - if index in expression.free_indices: - return gem.IndexSum(expression, index) - else: - return expression From 6c9ab1b765c22ba1cf4ce1aec71b55307fe3a063 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 19 Oct 2016 12:26:00 +0100 Subject: [PATCH 220/809] remove dangling method --- tsfc/fem.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index cccd5502f3..2092eddbdb 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -82,10 +82,6 @@ def epsilon(self): # Rounding tolerance mimicking FFC return 10.0 * eval("1e-%d" % self.precision) - def _selector(self, callback, opts, restriction): - """Helper function for selecting code for the correct entity - at run-time.""" - def entity_selector(self, callback, restriction): """Selects code for the correct entity at run-time. Callback generates code for a specified entity. From 76c234fa0db9b1467d3c7ff4670e78460b05caf0 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 20 Oct 2016 10:04:12 +0100 Subject: [PATCH 221/809] adopt new return value convention --- tsfc/fem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 2092eddbdb..afe293b68b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -165,7 +165,7 @@ def callback(entity_id): M = ctx.entity_selector(callback, mt.restriction) vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) argument_index = ctx.argument_indices[terminal.number()] - result = gem.Indexed(M, ctx.point_multiindex + argument_index + vi) + result = gem.Indexed(M, argument_index + vi) if vi: return gem.ComponentTensor(result, vi) else: @@ -190,7 +190,7 @@ def callback(entity_id): alpha = element.get_indices() vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) - result = gem.Product(gem.Indexed(M, ctx.point_multiindex + alpha + vi), + result = gem.Product(gem.Indexed(M, alpha + vi), gem.Indexed(vec, alpha)) for i in alpha: result = gem.IndexSum(result, i) From 6b23c96f0e7f5a43fb683c8149f6ad657f162021 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 20 Oct 2016 10:48:15 +0100 Subject: [PATCH 222/809] remove/inline Context.point_multiindex --- tsfc/fem.py | 7 +------ tsfc/geometric.py | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index afe293b68b..fc8dc958ee 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -34,7 +34,6 @@ class Context(ProxyKernelInterface): 'point_set', 'weight_expr', 'precision', - 'point_multiindex', 'argument_indices', 'cellvolume', 'facetarea', @@ -67,10 +66,6 @@ def quadrature_rule(self): def point_set(self): return self.quadrature_rule.point_set - @cached_property - def point_multiindex(self): - return self.point_set.indices - @cached_property def weight_expr(self): return self.quadrature_rule.weight_expression @@ -216,6 +211,6 @@ def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): translator = Translator(context) result = map_expr_dags(translator, expressions) if point_sum: - for index in context.point_multiindex: + for index in context.point_set.indices: result = [gem.index_sum(expr, index) for expr in result] return result diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 7765ea7a5f..4d4e8bb7c8 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -100,19 +100,19 @@ def translate_cell_edge_vectors(terminal, mt, params): @translate.register(CellCoordinate) def translate_cell_coordinate(terminal, mt, params): + ps = params.point_set if params.integration_dim == params.fiat_cell.get_dimension(): - return params.point_set.expression + return ps.expression # This destroys the structure of the quadrature points, but since # this code path is only used to implement CellCoordinate in facet # integrals, hopefully it does not matter much. def callback(entity_id): - ps = params.point_set t = params.fiat_cell.get_entity_transform(params.integration_dim, entity_id) return gem.Literal(restore_shape(asarray(list(map(t, ps.points))), ps)) return gem.partial_indexed(params.entity_selector(callback, mt.restriction), - params.point_multiindex) + ps.indices) @translate.register(FacetCoordinate) From f05403ace791acb7e7c1f728604843183c7d6a90 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 20 Oct 2016 11:44:22 +0100 Subject: [PATCH 223/809] be more backwards compatible --- tsfc/finatinterface.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index b6d193f399..fe7fc9df7b 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -31,6 +31,8 @@ import ufl +from tsfc.ufl_utils import spanning_degree + __all__ = ("create_element", "supported_elements", "as_fiat_cell") @@ -66,7 +68,7 @@ def fiat_compat(element): from tsfc.fiatinterface import create_element from finat.fiat_elements import FiatElementBase cell = as_fiat_cell(element.cell()) - finat_element = FiatElementBase(cell, element.degree()) + finat_element = FiatElementBase(cell, spanning_degree(element)) finat_element._fiat_element = create_element(element) return finat_element From cf3fc24dede57183bc31cc71a1a920aed9348ef4 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 20 Oct 2016 11:55:02 +0100 Subject: [PATCH 224/809] no more restore_shape --- tsfc/geometric.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tsfc/geometric.py b/tsfc/geometric.py index 4d4e8bb7c8..004218295e 100644 --- a/tsfc/geometric.py +++ b/tsfc/geometric.py @@ -15,8 +15,6 @@ import gem -from finat.point_set import restore_shape - from tsfc.parameters import NUMPY_TYPE @@ -107,9 +105,12 @@ def translate_cell_coordinate(terminal, mt, params): # This destroys the structure of the quadrature points, but since # this code path is only used to implement CellCoordinate in facet # integrals, hopefully it does not matter much. + point_shape = tuple(index.extent for index in ps.indices) + def callback(entity_id): t = params.fiat_cell.get_entity_transform(params.integration_dim, entity_id) - return gem.Literal(restore_shape(asarray(list(map(t, ps.points))), ps)) + data = asarray(list(map(t, ps.points))) + return gem.Literal(data.reshape(point_shape + data.shape[1:])) return gem.partial_indexed(params.entity_selector(callback, mt.restriction), ps.indices) From b66656c20d41189ba7a8e04af2c4909f322a0ab3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 20 Oct 2016 14:25:34 +0100 Subject: [PATCH 225/809] wire up FInAT interface to TensorProductElement --- tests/test_create_finat_element.py | 29 +++++++++++++++++++++++++++++ tsfc/finatinterface.py | 10 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index 53b5c97cb3..25167ccfe6 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -40,6 +40,35 @@ def test_triangle_vector(ufl_element, ufl_vector_element): assert scalar == vector.base_element +@pytest.fixture(params=["CG", "DG"]) +def tensor_name(request): + return request.param + + +@pytest.fixture(params=[ufl.interval, ufl.triangle, + ufl.quadrilateral], + ids=lambda x: x.cellname()) +def ufl_A(request, tensor_name): + return ufl.FiniteElement(tensor_name, request.param, 1) + + +@pytest.fixture +def ufl_B(tensor_name): + return ufl.FiniteElement(tensor_name, ufl.interval, 1) + + +def test_tensor_prod_simple(ufl_A, ufl_B): + tensor_ufl = ufl.TensorProductElement(ufl_A, ufl_B) + + tensor = f.create_element(tensor_ufl) + A = f.create_element(ufl_A) + B = f.create_element(ufl_B) + + assert isinstance(tensor, finat.TensorProductElement) + + assert tensor.factors == (A, B) + + def test_cache_hit(ufl_element): A = f.create_element(ufl_element) B = f.create_element(ufl_element) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index fe7fc9df7b..1cb7e8cd1e 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -117,6 +117,16 @@ def convert_tensorelement(element): return finat.TensorFiniteElement(scalar_element, element.reference_value_shape()) +# TensorProductElement case +@convert.register(ufl.TensorProductElement) +def convert_tensorproductelement(element): + cell = element.cell() + if type(cell) is not ufl.TensorProductCell: + raise ValueError("TensorProductElement not on TensorProductCell?") + return finat.TensorProductElement([create_element(elem) + for elem in element.sub_elements()]) + + _cache = weakref.WeakKeyDictionary() From 8179b94e7d4fae0d51ec8c0bf2a7f41017b8552e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 21 Oct 2016 10:34:41 +0100 Subject: [PATCH 226/809] cause not to trigger coneoproject/COFFEE#97 --- tsfc/coffee.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 3cae8ed919..399dd0137b 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -182,11 +182,6 @@ def statement_evaluate(leaf, parameters): return coffee.Block(ops, open_scope=False) elif isinstance(expr, gem.Constant): assert parameters.declare[leaf] - # Take all axes except the last one - axes = tuple(range(len(expr.array.shape) - 1)) - nz_indices, = expr.array.any(axis=axes).nonzero() - nz_bounds = tuple([(i, 0)] for i in expr.array.shape[:-1]) - nz_bounds += ([(max(nz_indices) - min(nz_indices) + 1, min(nz_indices))],) table = numpy.array(expr.array) # FFC uses one less digits for rounding than for printing epsilon = eval("1e-%d" % (parameters.precision - 1)) @@ -195,7 +190,7 @@ def statement_evaluate(leaf, parameters): table[abs(table + 1.0) < epsilon] = -1.0 table[abs(table - 0.5) < epsilon] = 0.5 table[abs(table + 0.5) < epsilon] = -0.5 - init = coffee.SparseArrayInit(table, parameters.precision, nz_bounds) + init = coffee.ArrayInit(table, parameters.precision) return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), init, From 81d9e6d01bd16de213ee5320cc2a6f29b7eb45a2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 21 Oct 2016 11:47:36 +0100 Subject: [PATCH 227/809] enable structured Q/DQ element on quadrilaterals --- tsfc/finatinterface.py | 77 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 1cb7e8cd1e..e26007bf66 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -27,9 +27,13 @@ import weakref import FIAT +from FIAT.reference_element import FiredrakeQuadrilateral + import finat +from finat.finiteelementbase import FiniteElementBase import ufl +from ufl.algorithms.elementtransformations import reconstruct_element from tsfc.ufl_utils import spanning_degree @@ -48,6 +52,9 @@ "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, "Raviart-Thomas": finat.RaviartThomas, "Regge": finat.Regge, + # These require special treatment below + "DQ": None, + "Q": None, } """A :class:`.dict` mapping UFL element family names to their FIAT-equivalent constructors. If the value is ``None``, the UFL @@ -55,6 +62,58 @@ have a direct FIAT equivalent.""" +class QuadrilateralElement(FiniteElementBase): + """Class for elements on quadrilaterals. Wraps a tensor product + element on an interval x interval cell, but appears on a + quadrilateral cell to the outside world.""" + + def __init__(self, element): + super(QuadrilateralElement, self).__init__() + self._cell = FiredrakeQuadrilateral() + self._degree = None # Who cares? Not used. + + self.product = element + + def basis_evaluation(self, ps, entity=None, derivative=0): + """Return code for evaluating the element at known points on the + reference element. + + :param ps: the point set object. + :param entity: the cell entity on which to tabulate. + :param derivative: the derivative to take of the basis functions. + """ + if entity is None: + entity = (2, 0) + + # Entity is provided in flattened form (d, i) + # We factor the entity and construct an appropriate + # entity id for a TensorProductCell: ((d1, d2), i) + entity_dim, entity_id = entity + if entity_dim == 2: + assert entity_id == 0 + product_entity = ((1, 1), 0) + elif entity_dim == 1: + facets = [((0, 1), 0), + ((0, 1), 1), + ((1, 0), 0), + ((1, 0), 1)] + product_entity = facets[entity_id] + elif entity_dim == 0: + raise NotImplementedError("Not implemented for 0 dimension entities") + else: + raise ValueError("Illegal entity dimension %s" % entity_dim) + + return self.product.basis_evaluation(ps, product_entity, derivative) + + @property + def index_shape(self): + return self.product.index_shape + + @property + def value_shape(self): + return self.product.value_shape + + def as_fiat_cell(cell): """Convert a ufl cell to a FIAT cell. @@ -90,11 +149,20 @@ def convert(element): @convert.register(ufl.FiniteElement) def convert_finiteelement(element): cell = as_fiat_cell(element.cell()) - lmbda = supported_elements.get(element.family()) - if lmbda: - return lmbda(cell, element.degree()) - else: + if element.family() not in supported_elements: return fiat_compat(element) + lmbda = supported_elements.get(element.family()) + if lmbda is None: + if element.cell().cellname() != "quadrilateral": + raise ValueError("%s is supported, but handled incorrectly" % + element.family()) + # Handle quadrilateral short names like RTCF and RTCE. + element = reconstruct_element(element, + element.family(), + quad_opc, + element.degree()) + return QuadrilateralElement(create_element(element)) + return lmbda(cell, element.degree()) # MixedElement case @@ -127,6 +195,7 @@ def convert_tensorproductelement(element): for elem in element.sub_elements()]) +quad_opc = ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")) _cache = weakref.WeakKeyDictionary() From a7b5ce95e7912dcb833298a1733e9982bad18732 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 21 Oct 2016 15:42:01 +0100 Subject: [PATCH 228/809] push QuadrilateralElement over to FInAT --- tsfc/finatinterface.py | 57 +----------------------------------------- 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index e26007bf66..d5847c99e6 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -27,10 +27,7 @@ import weakref import FIAT -from FIAT.reference_element import FiredrakeQuadrilateral - import finat -from finat.finiteelementbase import FiniteElementBase import ufl from ufl.algorithms.elementtransformations import reconstruct_element @@ -62,58 +59,6 @@ have a direct FIAT equivalent.""" -class QuadrilateralElement(FiniteElementBase): - """Class for elements on quadrilaterals. Wraps a tensor product - element on an interval x interval cell, but appears on a - quadrilateral cell to the outside world.""" - - def __init__(self, element): - super(QuadrilateralElement, self).__init__() - self._cell = FiredrakeQuadrilateral() - self._degree = None # Who cares? Not used. - - self.product = element - - def basis_evaluation(self, ps, entity=None, derivative=0): - """Return code for evaluating the element at known points on the - reference element. - - :param ps: the point set object. - :param entity: the cell entity on which to tabulate. - :param derivative: the derivative to take of the basis functions. - """ - if entity is None: - entity = (2, 0) - - # Entity is provided in flattened form (d, i) - # We factor the entity and construct an appropriate - # entity id for a TensorProductCell: ((d1, d2), i) - entity_dim, entity_id = entity - if entity_dim == 2: - assert entity_id == 0 - product_entity = ((1, 1), 0) - elif entity_dim == 1: - facets = [((0, 1), 0), - ((0, 1), 1), - ((1, 0), 0), - ((1, 0), 1)] - product_entity = facets[entity_id] - elif entity_dim == 0: - raise NotImplementedError("Not implemented for 0 dimension entities") - else: - raise ValueError("Illegal entity dimension %s" % entity_dim) - - return self.product.basis_evaluation(ps, product_entity, derivative) - - @property - def index_shape(self): - return self.product.index_shape - - @property - def value_shape(self): - return self.product.value_shape - - def as_fiat_cell(cell): """Convert a ufl cell to a FIAT cell. @@ -161,7 +106,7 @@ def convert_finiteelement(element): element.family(), quad_opc, element.degree()) - return QuadrilateralElement(create_element(element)) + return finat.QuadrilateralElement(create_element(element)) return lmbda(cell, element.degree()) From e386060dbb66be5fe24ec9db17cecaf319c1e41c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 24 Oct 2016 11:01:06 +0100 Subject: [PATCH 229/809] adopt the more FIAT-like API of FInAT --- tsfc/fem.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index fc8dc958ee..5fc67656d0 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -149,14 +149,37 @@ def translate_facetarea(terminal, mt, ctx): return ctx.facetarea() +def basis_evaluation(element, ps, derivative=0, entity=None): + # TODO: clean up, potentially remove this function. + import numpy + + finat_result = element.basis_evaluation(derivative, ps, entity) + i = element.get_indices() + vi = element.get_value_indices() + + dimension = element.cell.get_spatial_dimension() + eye = numpy.eye(dimension, dtype=int) + tensor = numpy.empty((dimension,) * derivative, dtype=object) + for multiindex in numpy.ndindex(tensor.shape): + alpha = tuple(eye[multiindex, :].sum(axis=0)) + tensor[multiindex] = gem.Indexed(finat_result[alpha], i + vi) + di = tuple(gem.Index(extent=dimension) for _ in range(derivative)) + if derivative: + tensor = gem.Indexed(gem.ListTensor(tensor), di) + else: + tensor = tensor[()] + return gem.ComponentTensor(tensor, i + vi + di) + + @translate.register(Argument) def translate_argument(terminal, mt, ctx): element = create_element(terminal.ufl_element()) def callback(entity_id): - return element.basis_evaluation(ctx.point_set, - derivative=mt.local_derivatives, - entity=(ctx.integration_dim, entity_id)) + return basis_evaluation(element, + ctx.point_set, + derivative=mt.local_derivatives, + entity=(ctx.integration_dim, entity_id)) M = ctx.entity_selector(callback, mt.restriction) vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) argument_index = ctx.argument_indices[terminal.number()] @@ -178,9 +201,10 @@ def translate_coefficient(terminal, mt, ctx): element = create_element(terminal.ufl_element()) def callback(entity_id): - return element.basis_evaluation(ctx.point_set, - derivative=mt.local_derivatives, - entity=(ctx.integration_dim, entity_id)) + return basis_evaluation(element, + ctx.point_set, + derivative=mt.local_derivatives, + entity=(ctx.integration_dim, entity_id)) M = ctx.entity_selector(callback, mt.restriction) alpha = element.get_indices() From e8072dfb7f2110cfa36f438e9e9653413cfbfaf6 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 24 Oct 2016 11:17:21 +0100 Subject: [PATCH 230/809] quad_opc -> quad_tpc --- tsfc/finatinterface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index d5847c99e6..a8ade7f34a 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -104,7 +104,7 @@ def convert_finiteelement(element): # Handle quadrilateral short names like RTCF and RTCE. element = reconstruct_element(element, element.family(), - quad_opc, + quad_tpc, element.degree()) return finat.QuadrilateralElement(create_element(element)) return lmbda(cell, element.degree()) @@ -140,7 +140,7 @@ def convert_tensorproductelement(element): for elem in element.sub_elements()]) -quad_opc = ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")) +quad_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() From 4353370982539f409898c909ab73d681f531d34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Fri, 28 Oct 2016 16:13:01 +0200 Subject: [PATCH 231/809] revise scalar formatting and rounding rules --- tsfc/coffee.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 9dd7f2caae..cdeb4ef4f2 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -41,6 +41,7 @@ def generate(impero_c, index_names, precision, roots=(), argument_indices=()): params.declare = impero_c.declare params.indices = impero_c.indices params.precision = precision + params.epsilon = 10.0 * eval("1e-%d" % precision) params.roots = roots params.argument_indices = argument_indices @@ -298,7 +299,11 @@ def _expression_scalar(expr, parameters): if isnan(expr.value): return coffee.Symbol("NAN") else: - return coffee.Symbol(("%%.%dg" % (parameters.precision - 1)) % expr.value) + v = expr.value + r = round(v, 1) + if r and abs(v - r) < parameters.epsilon: + v = r # round to nonzero + return coffee.Symbol(("%%.%dg" % parameters.precision) % v) @_expression.register(gem.Variable) From 3e0b546c6c1045079b51ca8c742386616190e1b9 Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Mon, 6 Jun 2016 16:40:25 +0100 Subject: [PATCH 232/809] Trace element and new FIAT compatibility --- tsfc/coffee.py | 5 ++++ tsfc/fem.py | 69 +++++++++++++++++++++++++------------------ tsfc/fiatinterface.py | 2 +- tsfc/mixedelement.py | 8 +++-- 4 files changed, 53 insertions(+), 31 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index cdeb4ef4f2..ab8b3090f6 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -221,6 +221,11 @@ def _expression(expr, parameters): raise AssertionError("cannot generate COFFEE from %s" % type(expr)) +@_expression.register(gem.Failure) +def _expression_failure(expr, parameters): + raise expr.exc_info[0], expr.exc_info[1], expr.exc_info[2] + + @_expression.register(gem.Product) def _expression_product(expr, parameters): return coffee.Prod(*[expression(c, parameters) diff --git a/tsfc/fem.py b/tsfc/fem.py index 96568f6a2d..eff4cbaa8f 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -11,10 +11,9 @@ from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, Coefficient, CellVolume, - FacetArea, FormArgument, - GeometricQuantity, QuadratureWeight) - +from ufl.classes import (Argument, Coefficient, CellVolume, ConstantValue, + FacetArea, FormArgument, GeometricQuantity, + QuadratureWeight) import gem from gem.utils import cached_property @@ -26,9 +25,10 @@ from tsfc.ufl_utils import (CollectModifiedTerminals, ModifiedTerminalMixin, PickRestriction, spanning_degree, simplify_abs) +from FIAT.hdiv_trace import TraceError -def _tabulate(ufl_element, order, points): +def _tabulate(ufl_element, order, points, entity): """Ask FIAT to tabulate ``points`` up to order ``order``, then rearranges the result into a series of ``(c, D, table)`` tuples, where: @@ -41,23 +41,35 @@ def _tabulate(ufl_element, order, points): :arg ufl_element: element to tabulate :arg order: FIAT gives all derivatives up to this order :arg points: points to tabulate the element on + :arg entity: particular entity to tabulate on """ element = create_element(ufl_element) phi = element.space_dimension() C = ufl_element.reference_value_size() q = len(points) - for D, fiat_table in iteritems(element.tabulate(order, points)): - reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) - for c, table in enumerate(reordered_table): - yield c, D, table - - -def tabulate(ufl_element, order, points, epsilon): - """Same as the above, but also applies FFC rounding with - threshold epsilon and recognises cellwise constantness. - Cellwise constantness is determined symbolically, but we - also check the numerics to be safe.""" - for c, D, table in _tabulate(ufl_element, order, points): + try: + for D, fiat_table in iteritems(element.tabulate(order, points, entity)): + reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) + for c, table in enumerate(reordered_table): + yield c, D, table + except TraceError as TE: + # import sys + # shape = (q, phi) + # TODO: Incorporate a proper Failure Table into the tabulation manager. + # Currently we are returned appropriately sized zeros arrays for gradient + # tabulations on trace element. + # gemTable = gem.Failure(shape, sys.exc_info()) + for D, fiat_table in TE.zeros.iteritems(): + reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) + for c, table in enumerate(reordered_table): + yield c, D, table + + +def tabulate(ufl_element, order, points, entity, epsilon): + """Same as the above, but also applies FFC rounding and recognises + cellwise constantness. Cellwise constantness is determined + symbolically, but we also check the numerics to be safe.""" + for c, D, table in _tabulate(ufl_element, order, points, entity): # Copied from FFC (ffc/quadrature/quadratureutils.py) table[abs(table) < epsilon] = 0 table[abs(table - 1.0) < epsilon] = 1.0 @@ -65,33 +77,34 @@ def tabulate(ufl_element, order, points, epsilon): table[abs(table - 0.5) < epsilon] = 0.5 table[abs(table + 0.5) < epsilon] = -0.5 - if spanning_degree(ufl_element) <= sum(D): + if spanning_degree(ufl_element) <= sum(D) and ufl_element.family() != "HDiv Trace": assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) table = table[0] yield c, D, table -def make_tabulator(points, epsilon): - """Creates a tabulator for an array of points with rounding - parameter epsilon.""" - return lambda elem, order: tabulate(elem, order, points, epsilon) +def make_tabulator(points, entity): + """Creates a tabulator for an array of points for a given entity.""" + return lambda elem, order: tabulate(elem, order, points, entity, epsilon) class TabulationManager(object): """Manages the generation of tabulation matrices for the different integral types.""" - def __init__(self, entity_points, epsilon): + def __init__(self, points, entity_dim, entity_ids, epsilon): """Constructs a TabulationManager. - :arg entity_points: An array of points in cell coordinates for - each integration entity, i.e. an iterable - of arrays of points. + :arg points: points on the integration entity (e.g. points on an + interval or facet integrals on a simplex) + :arg entity_dim: the integration dimension of the quadrature rule + :arg entity_id: list of ids for each entity with dimension `entity_dim` :arg epsilon: precision for rounding FE tables to 0, +-1/2, +-1 """ epsilons = itertools.repeat(epsilon, len(entity_points)) - self.tabulators = list(map(make_tabulator, entity_points, epsilons)) + self.tabulators = [make_tabulator(points, (entity_dim, entity_id), epsilons) + for entity_id in entity_ids] self.tables = {} def tabulate(self, ufl_element, max_deriv): @@ -377,7 +390,7 @@ def compile_ufl(expression, interior_facet=False, **kwargs): max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(context.entity_points, context.epsilon) + tabulation_manager = TabulationManager(context.entity_points, context.integration_dim, context.entity_ids, context.epsilon) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 34b1a4feb7..1d06f09d7a 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -58,7 +58,7 @@ "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, "TensorProductElement": FIAT.TensorProductElement, "Raviart-Thomas": FIAT.RaviartThomas, - "TraceElement": FIAT.HDivTrace, + "HDiv Trace": FIAT.HDivTrace, "Regge": FIAT.Regge, "Hellan-Herrmann-Johnson": FIAT.HellanHerrmannJohnson, # These require special treatment below diff --git a/tsfc/mixedelement.py b/tsfc/mixedelement.py index f7fb5ecf77..66a837d133 100644 --- a/tsfc/mixedelement.py +++ b/tsfc/mixedelement.py @@ -29,6 +29,7 @@ from collections import defaultdict from operator import add from functools import partial +from FIAT.hdiv_trace import TraceError class MixedElement(object): @@ -76,7 +77,7 @@ def entity_dofs(self): def num_components(self): return self.value_shape()[0] - def tabulate(self, order, points): + def tabulate(self, order, points, entity): """Tabulate a mixed element by appropriately splatting together the tabulation of the individual elements. """ @@ -95,7 +96,10 @@ def tabulate(self, order, points): crange = numpy.cumsum(sub_cmps) for i, e in enumerate(self.elements()): - table = e.tabulate(order, points) + try: + table = e.tabulate(order, points, entity) + except TraceError as TE: + table = TE.zeros for d, tab in table.items(): try: From 0116e34598582f3e9026e8085e460ea723772c54 Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Tue, 19 Jul 2016 13:42:22 +0100 Subject: [PATCH 233/809] Fix wrapper class for quad elements --- tsfc/fiatinterface.py | 14 ++++++++++++++ tsfc/mixedelement.py | 6 +----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 1d06f09d7a..57cd61b45e 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -100,6 +100,20 @@ def __init__(self, element): flat_entity_ids[2] = entity_ids[(1, 1)] self.dual = DualSet(nodes, self.ref_el, flat_entity_ids) + def dual_basis(self): + """Return the list of functionals for the finite element.""" + return self.dual.get_nodes() + + def entity_dofs(self): + """Return the map of topological entities to degrees of + freedom for the finite element.""" + return self.dual.get_entity_ids() + + def entity_closure_dofs(self): + """Return the map of topological entities to degrees of + freedom on the closure of those entities for the finite element.""" + return self.dual.get_entity_closure_ids() + def space_dimension(self): """Return the dimension of the finite element space.""" return self.element.space_dimension() diff --git a/tsfc/mixedelement.py b/tsfc/mixedelement.py index 66a837d133..daf28ca1f2 100644 --- a/tsfc/mixedelement.py +++ b/tsfc/mixedelement.py @@ -29,7 +29,6 @@ from collections import defaultdict from operator import add from functools import partial -from FIAT.hdiv_trace import TraceError class MixedElement(object): @@ -96,10 +95,7 @@ def tabulate(self, order, points, entity): crange = numpy.cumsum(sub_cmps) for i, e in enumerate(self.elements()): - try: - table = e.tabulate(order, points, entity) - except TraceError as TE: - table = TE.zeros + table = e.tabulate(order, points, entity) for d, tab in table.items(): try: From 62bd0acc87d9bf93b25ea6575835f79b70fa04c6 Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Mon, 6 Jun 2016 16:40:25 +0100 Subject: [PATCH 234/809] Trace element and new FIAT compatibility --- tsfc/fiatinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 57cd61b45e..1b6fa31caa 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -58,7 +58,7 @@ "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, "TensorProductElement": FIAT.TensorProductElement, "Raviart-Thomas": FIAT.RaviartThomas, - "HDiv Trace": FIAT.HDivTrace, + "HDiv Trace": FIAT.TraceHDiv, "Regge": FIAT.Regge, "Hellan-Herrmann-Johnson": FIAT.HellanHerrmannJohnson, # These require special treatment below From 3f097ffe873d599e532d223f900fee17b2f5dd6e Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Tue, 19 Jul 2016 13:42:22 +0100 Subject: [PATCH 235/809] Adding wrapper class for quad elements --- tsfc/fem.py | 6 +++--- tsfc/fiatinterface.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index eff4cbaa8f..b910336f5c 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -11,9 +11,9 @@ from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, Coefficient, CellVolume, ConstantValue, - FacetArea, FormArgument, GeometricQuantity, - QuadratureWeight) +from ufl.classes import (Argument, Coefficient, CellVolume, + FacetArea, FormArgument, + GeometricQuantity, QuadratureWeight) import gem from gem.utils import cached_property diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 1b6fa31caa..57cd61b45e 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -58,7 +58,7 @@ "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, "TensorProductElement": FIAT.TensorProductElement, "Raviart-Thomas": FIAT.RaviartThomas, - "HDiv Trace": FIAT.TraceHDiv, + "HDiv Trace": FIAT.HDivTrace, "Regge": FIAT.Regge, "Hellan-Herrmann-Johnson": FIAT.HellanHerrmannJohnson, # These require special treatment below From 3c5a5d148172f1f5851ba7ab47e5b1c2b646f670 Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Wed, 28 Sep 2016 19:49:07 +0100 Subject: [PATCH 236/809] Py3 revision for gem failure and fixing borked rebase --- tsfc/coffee.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index ab8b3090f6..f4af57754a 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -223,7 +223,12 @@ def _expression(expr, parameters): @_expression.register(gem.Failure) def _expression_failure(expr, parameters): - raise expr.exc_info[0], expr.exc_info[1], expr.exc_info[2] + E = expr.exc_info[0] + V = expr.exc_info[1] + T = expr.exc_info[2] + e = E(V) + e.__traceback__ = T + raise e @_expression.register(gem.Product) From 3c2ee387d72d5cbd0d4e6da109a454e26a9fbcd3 Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Wed, 12 Oct 2016 16:12:12 +0100 Subject: [PATCH 237/809] GEM Failure works - improved error handling --- tsfc/coffee.py | 7 +---- tsfc/fem.py | 70 +++++++++++++++++++++----------------------------- 2 files changed, 30 insertions(+), 47 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index f4af57754a..053ed21d72 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -223,12 +223,7 @@ def _expression(expr, parameters): @_expression.register(gem.Failure) def _expression_failure(expr, parameters): - E = expr.exc_info[0] - V = expr.exc_info[1] - T = expr.exc_info[2] - e = E(V) - e.__traceback__ = T - raise e + raise expr.exception @_expression.register(gem.Product) diff --git a/tsfc/fem.py b/tsfc/fem.py index b910336f5c..36b6d17437 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -25,10 +25,9 @@ from tsfc.ufl_utils import (CollectModifiedTerminals, ModifiedTerminalMixin, PickRestriction, spanning_degree, simplify_abs) -from FIAT.hdiv_trace import TraceError -def _tabulate(ufl_element, order, points, entity): +def tabulate(ufl_element, order, points, entity, epsilon): """Ask FIAT to tabulate ``points`` up to order ``order``, then rearranges the result into a series of ``(c, D, table)`` tuples, where: @@ -47,41 +46,28 @@ def _tabulate(ufl_element, order, points, entity): phi = element.space_dimension() C = ufl_element.reference_value_size() q = len(points) - try: - for D, fiat_table in iteritems(element.tabulate(order, points, entity)): - reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) - for c, table in enumerate(reordered_table): - yield c, D, table - except TraceError as TE: - # import sys - # shape = (q, phi) - # TODO: Incorporate a proper Failure Table into the tabulation manager. - # Currently we are returned appropriately sized zeros arrays for gradient - # tabulations on trace element. - # gemTable = gem.Failure(shape, sys.exc_info()) - for D, fiat_table in TE.zeros.iteritems(): + for D, fiat_table in iteritems(element.tabulate(order, points, entity)): + if isinstance(fiat_table, Exception): + # In the case an exception is found in the fiat table, do not + # perform any rounding + gem_fail = gem.Failure((q, phi), fiat_table) + for c in range(C): + yield c, D, gem_fail + else: reordered_table = fiat_table.reshape(phi, C, q).transpose(1, 2, 0) # (C, q, phi) for c, table in enumerate(reordered_table): - yield c, D, table - - -def tabulate(ufl_element, order, points, entity, epsilon): - """Same as the above, but also applies FFC rounding and recognises - cellwise constantness. Cellwise constantness is determined - symbolically, but we also check the numerics to be safe.""" - for c, D, table in _tabulate(ufl_element, order, points, entity): - # Copied from FFC (ffc/quadrature/quadratureutils.py) - table[abs(table) < epsilon] = 0 - table[abs(table - 1.0) < epsilon] = 1.0 - table[abs(table + 1.0) < epsilon] = -1.0 - table[abs(table - 0.5) < epsilon] = 0.5 - table[abs(table + 0.5) < epsilon] = -0.5 + # Copied from FFC (ffc/quadrature/quadratureutils.py) + table[abs(table) < epsilon] = 0 + table[abs(table - 1.0) < epsilon] = 1.0 + table[abs(table + 1.0) < epsilon] = -1.0 + table[abs(table - 0.5) < epsilon] = 0.5 + table[abs(table + 0.5) < epsilon] = -0.5 - if spanning_degree(ufl_element) <= sum(D) and ufl_element.family() != "HDiv Trace": - assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) - table = table[0] + if spanning_degree(ufl_element) <= sum(D) and ufl_element.family() != "HDiv Trace": + assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) + table = table[0] - yield c, D, table + yield c, D, gem.Literal(table) def make_tabulator(points, entity): @@ -120,11 +106,11 @@ def tabulate(self, ufl_element, max_deriv): store[(ufl_element, c, D)].append(table) for key, tables in iteritems(store): - table = numpy.array(tables) + table = gem.ListTensor(tables) if len(table.shape) == 2: # Cellwise constant; must not depend on the facet - assert compat.allclose(table, table.mean(axis=0, keepdims=True), equal_nan=True) - table = table[0] + assert compat.allclose(table.array, table.array.mean(axis=0, keepdims=True), equal_nan=True) + table = gem.Literal(table.array[0]) self.tables[key] = table def __getitem__(self, key): @@ -332,9 +318,10 @@ def callback(key): table = ctx.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant - row = gem.Literal(table) + row = table else: - table = ctx.index_selector(lambda i: gem.Literal(table[i]), mt.restriction) + table = ctx.index_selector(lambda i: gem.partial_indexed(table, (i,)), + mt.restriction) row = gem.partial_indexed(table, (ctx.point_index,)) return gem.Indexed(row, (argument_index,)) @@ -353,15 +340,16 @@ def callback(key): table = ctx.tabulation_manager[key] if len(table.shape) == 1: # Cellwise constant - row = gem.Literal(table) - if numpy.count_nonzero(table) <= 2: + row = table + if numpy.count_nonzero(table.array) <= 2: assert row.shape == vec.shape return reduce(gem.Sum, [gem.Product(gem.Indexed(row, (i,)), gem.Indexed(vec, (i,))) for i in range(row.shape[0])], gem.Zero()) else: - table = ctx.index_selector(lambda i: gem.Literal(table[i]), mt.restriction) + table = ctx.index_selector(lambda i: gem.partial_indexed(table, (i,)), + mt.restriction) row = gem.partial_indexed(table, (ctx.point_index,)) r = ctx.index_cache[terminal.ufl_element()] From 4e67c4569f98fbddfcf281637c1f403c109ff6be Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Thu, 13 Oct 2016 15:09:55 +0100 Subject: [PATCH 238/809] cleaner FlattenToQuad element --- tsfc/fem.py | 5 ++--- tsfc/fiatinterface.py | 44 ++++++++----------------------------------- 2 files changed, 10 insertions(+), 39 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 36b6d17437..69d65a01be 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -70,7 +70,7 @@ def tabulate(ufl_element, order, points, entity, epsilon): yield c, D, gem.Literal(table) -def make_tabulator(points, entity): +def make_tabulator(points, entity, epsilon): """Creates a tabulator for an array of points for a given entity.""" return lambda elem, order: tabulate(elem, order, points, entity, epsilon) @@ -88,8 +88,7 @@ def __init__(self, points, entity_dim, entity_ids, epsilon): :arg entity_id: list of ids for each entity with dimension `entity_dim` :arg epsilon: precision for rounding FE tables to 0, +-1/2, +-1 """ - epsilons = itertools.repeat(epsilon, len(entity_points)) - self.tabulators = [make_tabulator(points, (entity_dim, entity_id), epsilons) + self.tabulators = [make_tabulator(points, (entity_dim, entity_id), epsilon) for entity_id in entity_ids] self.tables = {} diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 57cd61b45e..1b94016dde 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -86,9 +86,8 @@ def __init__(self, element): :arg element: a fiat element """ - self.element = element nodes = element.dual.nodes - self.ref_el = FiredrakeQuadrilateral() + ref_el = FiredrakeQuadrilateral() entity_ids = element.dual.entity_ids flat_entity_ids = {} @@ -98,44 +97,17 @@ def __init__(self, element): [v for k, v in sorted(entity_ids[(1, 0)].items())] )) flat_entity_ids[2] = entity_ids[(1, 1)] - self.dual = DualSet(nodes, self.ref_el, flat_entity_ids) - - def dual_basis(self): - """Return the list of functionals for the finite element.""" - return self.dual.get_nodes() - - def entity_dofs(self): - """Return the map of topological entities to degrees of - freedom for the finite element.""" - return self.dual.get_entity_ids() - - def entity_closure_dofs(self): - """Return the map of topological entities to degrees of - freedom on the closure of those entities for the finite element.""" - return self.dual.get_entity_closure_ids() - - def space_dimension(self): - """Return the dimension of the finite element space.""" - return self.element.space_dimension() + dual = DualSet(nodes, ref_el, flat_entity_ids) + super(FlattenToQuad, self).__init__(ref_el, dual, + element.get_order(), + element.get_formdegree(), + element._mapping) + self.element = element def degree(self): - """Return the degree of the finite element.""" + """Return the degree of the (embedding) polynomial space.""" return self.element.degree() - def get_order(self): - """Return the order of the finite element.""" - return self.element.get_order() - - def get_formdegree(self): - """Return the degree of the associated form (FEEC).""" - return self.element.get_formdegree() - - def mapping(self): - """Return the list of appropriate mappings from the reference - element to a physical element for each basis function of the - finite element.""" - return self.element.mapping() - def tabulate(self, order, points, entity=None): """Return tabulated values of derivatives up to a given order of basis functions at given points. From d3c08e6f16d192eaaccd83f0a056b34975b78044 Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Mon, 17 Oct 2016 16:34:27 +0100 Subject: [PATCH 239/809] removing DiscontinuousLagrangeTrace from fiatinterface and changing entity_points to points in fem.py --- tsfc/fem.py | 2 +- tsfc/fiatinterface.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 69d65a01be..fd5a952888 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -377,7 +377,7 @@ def compile_ufl(expression, interior_facet=False, **kwargs): max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives - tabulation_manager = TabulationManager(context.entity_points, context.integration_dim, context.entity_ids, context.epsilon) + tabulation_manager = TabulationManager(context.points, context.integration_dim, context.entity_ids, context.epsilon) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 1b94016dde..c95fcd3be5 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -51,7 +51,6 @@ "Discontinuous Lagrange": FIAT.DiscontinuousLagrange, "Discontinuous Taylor": FIAT.DiscontinuousTaylor, "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, - "Discontinuous Lagrange Trace": FIAT.DiscontinuousLagrangeTrace, "EnrichedElement": FIAT.EnrichedElement, "Lagrange": FIAT.Lagrange, "Nedelec 1st kind H(curl)": FIAT.Nedelec, From b87bcf8a46efb87ea45290c9c2359f5d8fb88f3a Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Thu, 20 Oct 2016 10:33:53 +0100 Subject: [PATCH 240/809] removing TraceElement --- tsfc/fiatinterface.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index c95fcd3be5..08bd545941 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -238,8 +238,7 @@ def _(element, vector_is_mixed): create_element(B, vector_is_mixed)) -@convert.register(ufl.TraceElement) # noqa -@convert.register(ufl.BrokenElement) +@convert.register(ufl.BrokenElement) # noqa def _(element, vector_is_mixed): return supported_elements[element.family()](create_element(element._element, vector_is_mixed)) From 7beff346c5937139dfd34989a0c8966aa39e0b92 Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Wed, 26 Oct 2016 13:30:34 +0100 Subject: [PATCH 241/809] new test for gem failures --- tests/test_gem_failure.py | 41 +++++++++++++++++++++++++++++++++++++++ tsfc/fem.py | 1 + 2 files changed, 42 insertions(+) create mode 100644 tests/test_gem_failure.py diff --git a/tests/test_gem_failure.py b/tests/test_gem_failure.py new file mode 100644 index 0000000000..03fb35216e --- /dev/null +++ b/tests/test_gem_failure.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import, print_function, division +from ufl import (triangle, tetrahedron, FiniteElement, + TrialFunction, TestFunction, inner, grad, dx, dS) +from tsfc import compile_form +from FIAT.hdiv_trace import TraceError +import pytest + + +@pytest.mark.parametrize('cell', [triangle, tetrahedron]) +@pytest.mark.parametrize('degree', range(3)) +def test_cell_error(cell, degree): + """Test that tabulating the trace element deliberatly on the + cell triggers `gem.Failure` to raise the TraceError exception. + """ + trace_element = FiniteElement("HDiv Trace", cell, degree) + lambdar = TrialFunction(trace_element) + gammar = TestFunction(trace_element) + + with pytest.raises(TraceError): + compile_form(lambdar * gammar * dx) + + +@pytest.mark.parametrize('cell', [triangle, tetrahedron]) +@pytest.mark.parametrize('degree', range(3)) +def test_gradient_error(cell, degree): + """Test that tabulating gradient evaluations of the trace + element triggers `gem.Failure` to raise the TraceError + exception. + """ + trace_element = FiniteElement("HDiv Trace", cell, degree) + lambdar = TrialFunction(trace_element) + gammar = TestFunction(trace_element) + + with pytest.raises(TraceError): + compile_form(inner(grad(lambdar('+')), grad(gammar('+'))) * dS) + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) diff --git a/tsfc/fem.py b/tsfc/fem.py index fd5a952888..59828672aa 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -14,6 +14,7 @@ from ufl.classes import (Argument, Coefficient, CellVolume, FacetArea, FormArgument, GeometricQuantity, QuadratureWeight) + import gem from gem.utils import cached_property From f1ca89b543db43bdb390063ed4c6ac190193fa31 Mon Sep 17 00:00:00 2001 From: David Ham Date: Tue, 1 Nov 2016 22:56:57 +0000 Subject: [PATCH 242/809] Put the spectral elements in the list --- tsfc/fiatinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 34b1a4feb7..0e5aeb4874 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -53,6 +53,8 @@ "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, "Discontinuous Lagrange Trace": FIAT.DiscontinuousLagrangeTrace, "EnrichedElement": FIAT.EnrichedElement, + "Gauss-Lobatto-Legendre": FIAT.GaussLobattoLegendre, + "Gauss-Legendre": FIAT.GaussLegendre, "Lagrange": FIAT.Lagrange, "Nedelec 1st kind H(curl)": FIAT.Nedelec, "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, From fcc14e2ec555d8f5c93d6f2fc20190cdfe21c1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Fri, 4 Nov 2016 12:23:03 +0100 Subject: [PATCH 243/809] introduce as_gem --- tsfc/fem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 59828672aa..f97793d0a4 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -16,6 +16,7 @@ GeometricQuantity, QuadratureWeight) import gem +from gem import as_gem from gem.utils import cached_property from tsfc import compat, ufl2gem, geometric @@ -320,7 +321,7 @@ def callback(key): # Cellwise constant row = table else: - table = ctx.index_selector(lambda i: gem.partial_indexed(table, (i,)), + table = ctx.index_selector(lambda i: as_gem(table.array[i]), mt.restriction) row = gem.partial_indexed(table, (ctx.point_index,)) return gem.Indexed(row, (argument_index,)) @@ -348,7 +349,7 @@ def callback(key): for i in range(row.shape[0])], gem.Zero()) else: - table = ctx.index_selector(lambda i: gem.partial_indexed(table, (i,)), + table = ctx.index_selector(lambda i: as_gem(table.array[i]), mt.restriction) row = gem.partial_indexed(table, (ctx.point_index,)) From baa31eafb31bbc36102389f45f89a0b4c989dc9e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 4 Nov 2016 19:52:03 +0000 Subject: [PATCH 244/809] remove temporary FFC rounding from COFFEE generation --- tsfc/coffee.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 399dd0137b..578f3c807f 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -182,18 +182,9 @@ def statement_evaluate(leaf, parameters): return coffee.Block(ops, open_scope=False) elif isinstance(expr, gem.Constant): assert parameters.declare[leaf] - table = numpy.array(expr.array) - # FFC uses one less digits for rounding than for printing - epsilon = eval("1e-%d" % (parameters.precision - 1)) - table[abs(table) < epsilon] = 0 - table[abs(table - 1.0) < epsilon] = 1.0 - table[abs(table + 1.0) < epsilon] = -1.0 - table[abs(table - 0.5) < epsilon] = 0.5 - table[abs(table + 0.5) < epsilon] = -0.5 - init = coffee.ArrayInit(table, parameters.precision) return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), - init, + coffee.ArrayInit(expr.array, parameters.precision), qualifiers=["static", "const"]) else: code = expression(expr, parameters, top=True) From dcf487d204571232307428d14edc25fd4c04e9f7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 4 Nov 2016 19:57:53 +0000 Subject: [PATCH 245/809] wire in GEM rounding for FInAT expressions --- tsfc/fem.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 5fc67656d0..81da775085 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -12,6 +12,7 @@ GeometricQuantity, QuadratureWeight) import gem +from gem.optimise import ffc_rounding from gem.utils import cached_property from finat.quadrature import make_quadrature @@ -149,7 +150,7 @@ def translate_facetarea(terminal, mt, ctx): return ctx.facetarea() -def basis_evaluation(element, ps, derivative=0, entity=None): +def basis_evaluation(element, ps, derivative=0, entity=None, epsilon=0.0): # TODO: clean up, potentially remove this function. import numpy @@ -168,6 +169,7 @@ def basis_evaluation(element, ps, derivative=0, entity=None): tensor = gem.Indexed(gem.ListTensor(tensor), di) else: tensor = tensor[()] + tensor = ffc_rounding(tensor, epsilon) return gem.ComponentTensor(tensor, i + vi + di) @@ -179,7 +181,8 @@ def callback(entity_id): return basis_evaluation(element, ctx.point_set, derivative=mt.local_derivatives, - entity=(ctx.integration_dim, entity_id)) + entity=(ctx.integration_dim, entity_id), + epsilon=ctx.epsilon) M = ctx.entity_selector(callback, mt.restriction) vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) argument_index = ctx.argument_indices[terminal.number()] @@ -204,7 +207,8 @@ def callback(entity_id): return basis_evaluation(element, ctx.point_set, derivative=mt.local_derivatives, - entity=(ctx.integration_dim, entity_id)) + entity=(ctx.integration_dim, entity_id), + epsilon=ctx.epsilon) M = ctx.entity_selector(callback, mt.restriction) alpha = element.get_indices() From 2f01a2b3edc5d964eba2f60175a2f3f50c292dd7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 4 Nov 2016 20:03:13 +0000 Subject: [PATCH 246/809] drop scalar rounding in COFFEE code generation --- tsfc/coffee.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 578f3c807f..9ee9a7cff9 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -292,20 +292,7 @@ def _expression_scalar(expr, parameters): if isnan(expr.value): return coffee.Symbol("NAN") else: - value = float(expr.value) - # FFC uses one less digits for rounding than for printing - epsilon = eval("1e-%d" % (parameters.precision - 1)) - if abs(value) < epsilon: - value = 0 - if abs(value - 1.0) < epsilon: - value = 1.0 - if abs(value + 1.0) < epsilon: - value = -1.0 - if abs(value - 0.5) < epsilon: - value = 0.5 - if abs(value + 0.5) < epsilon: - value = -0.5 - return coffee.Symbol(("%%.%dg" % (parameters.precision - 1)) % value) + return coffee.Symbol(("%%.%dg" % (parameters.precision - 1)) % expr.value) @_expression.register(gem.Variable) From 5f51b6d700b1eff21549c57a047cc8d18af87abd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 4 Nov 2016 20:24:55 +0000 Subject: [PATCH 247/809] add comments --- tsfc/fem.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/fem.py b/tsfc/fem.py index 81da775085..f4f2be7145 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -169,6 +169,8 @@ def basis_evaluation(element, ps, derivative=0, entity=None, epsilon=0.0): tensor = gem.Indexed(gem.ListTensor(tensor), di) else: tensor = tensor[()] + # A numerical hack that FFC used to apply on FIAT tables still + # lives on after ditching FFC and switching to FInAT. tensor = ffc_rounding(tensor, epsilon) return gem.ComponentTensor(tensor, i + vi + di) From 95fd5ce3763d24193d1cab10e104a336791a0a2b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 16 Nov 2016 14:25:15 +0000 Subject: [PATCH 248/809] fix latest flake8 --- tsfc/kernel_interface/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index 6eb5f59554..51b0663349 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -23,4 +23,5 @@ def cell_orientation(self, restriction): def facet_number(self, restriction): """Facet number as a GEM index.""" + ProxyKernelInterface = make_proxy_class('ProxyKernelInterface', KernelInterface) From c9f0ab3cb9a6630758ee862a0a88afc4f8eb328a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 25 Oct 2016 13:48:33 +0100 Subject: [PATCH 249/809] fix #pragma coffee linear loop --- tsfc/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index e2d8b8c05f..2b05d312ed 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -188,7 +188,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body = generate_coffee(impero_c, index_names, parameters["precision"], ir, argument_indices) + body = generate_coffee(impero_c, index_names, parameters["precision"], ir, tuple(chain(*argument_indices))) kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) return builder.construct_kernel(kernel_name, body) From 22d6332801ac0dac408ffd577509f88ac53e228d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 24 Oct 2016 16:51:19 +0100 Subject: [PATCH 250/809] delta elimination and sum factorisation --- tsfc/fem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/fem.py b/tsfc/fem.py index f4f2be7145..b3b7010484 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -219,6 +219,7 @@ def callback(entity_id): gem.Indexed(vec, alpha)) for i in alpha: result = gem.IndexSum(result, i) + result = gem.optimise.contraction(result) if vi: return gem.ComponentTensor(result, vi) else: From a53f61d16d50ec7a5b0aee8ba75c913d25bd297c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 24 Oct 2016 19:27:51 +0100 Subject: [PATCH 251/809] IndexSum with multiindex --- tests/test_codegen.py | 2 +- tsfc/driver.py | 8 ++++---- tsfc/fem.py | 7 ++----- tsfc/ufl2gem.py | 4 ++-- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index af8c2f6366..ee9a8d31a5 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -13,7 +13,7 @@ def test_loop_fusion(): def make_expression(i, j): A = Variable('A', (6,)) - s = IndexSum(Indexed(A, (j,)), j) + s = IndexSum(Indexed(A, (j,)), (j,)) return Product(Indexed(A, (i,)), s) e1 = make_expression(i, j) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2b05d312ed..eeaf1c219d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -93,6 +93,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, argument_indices = tuple(tuple(gem.Index(extent=e) for e in create_element(arg.ufl_element()).index_shape) for arg in arguments) + flat_argument_indices = tuple(chain(*argument_indices)) quadrature_indices = [] # Dict mapping domains to index in original_form.ufl_domains() @@ -159,8 +160,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, ir = fem.compile_ufl(integrand, interior_facet=interior_facet, **config) if parameters["unroll_indexsum"]: ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) - for quadrature_index in quadrature_multiindex: - ir = [gem.index_sum(expr, quadrature_index) for expr in ir] + ir = [gem.index_sum(expr, quadrature_multiindex) for expr in ir] irs.append(ir) # Sum the expressions that are part of the same restriction @@ -175,7 +175,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, builder.require_cell_orientations() impero_c = impero_utils.compile_gem(return_variables, ir, - tuple(quadrature_indices) + tuple(chain(*argument_indices)), + tuple(quadrature_indices) + flat_argument_indices, remove_zeros=True) # Generate COFFEE @@ -188,7 +188,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body = generate_coffee(impero_c, index_names, parameters["precision"], ir, tuple(chain(*argument_indices))) + body = generate_coffee(impero_c, index_names, parameters["precision"], ir, flat_argument_indices) kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) return builder.construct_kernel(kernel_name, body) diff --git a/tsfc/fem.py b/tsfc/fem.py index b3b7010484..7aff1cb016 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -217,9 +217,7 @@ def callback(entity_id): vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) result = gem.Product(gem.Indexed(M, alpha + vi), gem.Indexed(vec, alpha)) - for i in alpha: - result = gem.IndexSum(result, i) - result = gem.optimise.contraction(result) + result = gem.optimise.contraction(gem.IndexSum(result, alpha)) if vi: return gem.ComponentTensor(result, vi) else: @@ -242,6 +240,5 @@ def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): translator = Translator(context) result = map_expr_dags(translator, expressions) if point_sum: - for index in context.point_set.indices: - result = [gem.index_sum(expr, index) for expr in result] + result = [gem.index_sum(expr, context.point_set.indices) for expr in result] return result diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 673d39c6d9..79ff09dfcf 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -112,9 +112,9 @@ def index_sum(self, o, summand, indices): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(IndexSum(Indexed(summand, indices), index), indices) + return ComponentTensor(IndexSum(Indexed(summand, indices), (index,)), indices) else: - return IndexSum(summand, index) + return IndexSum(summand, (index,)) def variable(self, o, expression, label): # Only used by UFL AD, at this point, the bare expression is From 774c16fd0ed44a4015a9b0b5c6366457862fb7ca Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 16 Nov 2016 16:08:44 +0000 Subject: [PATCH 252/809] little clean up --- tsfc/fem.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 7aff1cb016..365f82c3ee 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -189,10 +189,7 @@ def callback(entity_id): vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) argument_index = ctx.argument_indices[terminal.number()] result = gem.Indexed(M, argument_index + vi) - if vi: - return gem.ComponentTensor(result, vi) - else: - return result + return gem.ComponentTensor(result, vi) @translate.register(Coefficient) @@ -218,10 +215,7 @@ def callback(entity_id): result = gem.Product(gem.Indexed(M, alpha + vi), gem.Indexed(vec, alpha)) result = gem.optimise.contraction(gem.IndexSum(result, alpha)) - if vi: - return gem.ComponentTensor(result, vi) - else: - return result + return gem.ComponentTensor(result, vi) def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): From abca07af8bc5560184e73307369fbeee6b2760bf Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 21 Nov 2016 13:00:39 +0000 Subject: [PATCH 253/809] log warning for unexpected case --- tsfc/fem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 365f82c3ee..cc10cc055e 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -20,6 +20,7 @@ from tsfc import ufl2gem, geometric from tsfc.finatinterface import create_element, as_fiat_cell from tsfc.kernel_interface import ProxyKernelInterface +from tsfc.logging import logger from tsfc.modified_terminals import analyse_modified_terminal from tsfc.parameters import PARAMETERS from tsfc.ufl_utils import ModifiedTerminalMixin, PickRestriction, simplify_abs @@ -214,7 +215,7 @@ def callback(entity_id): vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) result = gem.Product(gem.Indexed(M, alpha + vi), gem.Indexed(vec, alpha)) - result = gem.optimise.contraction(gem.IndexSum(result, alpha)) + result = gem.optimise.contraction(gem.IndexSum(result, alpha), logger=logger) return gem.ComponentTensor(result, vi) From 0ee3cc78e6c4b1b3c576c8aae91653a7072b295b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 22 Nov 2016 10:16:20 +0000 Subject: [PATCH 254/809] avoid accidental float result (1.0) --- tsfc/kernel_interface/firedrake.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 4b06daa153..5eeb28b372 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -283,8 +283,8 @@ def prepare_coefficient(coefficient, name, interior_facet=False): pointers=[("const", "restrict"), ("restrict",)], qualifiers=["const"]) - scalar_size = numpy.prod(scalar_shape) - tensor_size = numpy.prod(tensor_shape) + scalar_size = numpy.prod(scalar_shape, dtype=int) + tensor_size = numpy.prod(tensor_shape, dtype=int) expression = gem.reshape( gem.Variable(name, (scalar_size, tensor_size)), scalar_shape, tensor_shape @@ -318,7 +318,7 @@ def prepare_arguments(arguments, indices, interior_facet=False): elements = tuple(create_element(arg.ufl_element()) for arg in arguments) shapes = tuple(element.index_shape for element in elements) - c_shape = numpy.array([numpy.prod(shape) for shape in shapes]) + c_shape = numpy.array([numpy.prod(shape, dtype=int) for shape in shapes]) if interior_facet: c_shape *= 2 c_shape = tuple(c_shape) From 02f7937c744cf70e5acdb1a8c21ab3d5d74bbf2c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 24 Nov 2016 16:36:33 +0000 Subject: [PATCH 255/809] revise coefficient evaluation Seems to fix sum factorisation for coefficient derivatives. --- tsfc/fem.py | 115 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 40 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index cc10cc055e..60cf3a9cb3 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, print_function, division +from six import iterkeys, iteritems, itervalues from six.moves import map import collections import itertools +import numpy from singledispatch import singledispatch from ufl.corealg.map_dag import map_expr_dag, map_expr_dags @@ -151,46 +153,51 @@ def translate_facetarea(terminal, mt, ctx): return ctx.facetarea() -def basis_evaluation(element, ps, derivative=0, entity=None, epsilon=0.0): - # TODO: clean up, potentially remove this function. - import numpy +def fiat_to_ufl(fiat_dict, order): + # All derivative multiindices must be of the same dimension. + dimension, = list(set(len(alpha) for alpha in iterkeys(fiat_dict))) - finat_result = element.basis_evaluation(derivative, ps, entity) - i = element.get_indices() - vi = element.get_value_indices() + # All derivative tables must have the same shape. + shape, = list(set(table.shape for table in itervalues(fiat_dict))) + sigma = tuple(gem.Index(extent=extent) for extent in shape) - dimension = element.cell.get_spatial_dimension() + # Convert from FIAT to UFL format eye = numpy.eye(dimension, dtype=int) - tensor = numpy.empty((dimension,) * derivative, dtype=object) + tensor = numpy.empty((dimension,) * order, dtype=object) for multiindex in numpy.ndindex(tensor.shape): alpha = tuple(eye[multiindex, :].sum(axis=0)) - tensor[multiindex] = gem.Indexed(finat_result[alpha], i + vi) - di = tuple(gem.Index(extent=dimension) for _ in range(derivative)) - if derivative: - tensor = gem.Indexed(gem.ListTensor(tensor), di) + tensor[multiindex] = gem.Indexed(fiat_dict[alpha], sigma) + delta = tuple(gem.Index(extent=dimension) for _ in range(order)) + if order > 0: + tensor = gem.Indexed(gem.ListTensor(tensor), delta) else: tensor = tensor[()] - # A numerical hack that FFC used to apply on FIAT tables still - # lives on after ditching FFC and switching to FInAT. - tensor = ffc_rounding(tensor, epsilon) - return gem.ComponentTensor(tensor, i + vi + di) + return gem.ComponentTensor(tensor, sigma + delta) @translate.register(Argument) def translate_argument(terminal, mt, ctx): + argument_index = ctx.argument_indices[terminal.number()] + sigma = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) element = create_element(terminal.ufl_element()) def callback(entity_id): - return basis_evaluation(element, - ctx.point_set, - derivative=mt.local_derivatives, - entity=(ctx.integration_dim, entity_id), - epsilon=ctx.epsilon) - M = ctx.entity_selector(callback, mt.restriction) - vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) - argument_index = ctx.argument_indices[terminal.number()] - result = gem.Indexed(M, argument_index + vi) - return gem.ComponentTensor(result, vi) + finat_dict = element.basis_evaluation(mt.local_derivatives, + ctx.point_set, + (ctx.integration_dim, entity_id)) + # Filter out irrelevant derivatives + filtered_dict = {alpha: table + for alpha, table in iteritems(finat_dict) + if sum(alpha) == mt.local_derivatives} + + # Change from FIAT to UFL arrangement + square = fiat_to_ufl(filtered_dict, mt.local_derivatives) + + # A numerical hack that FFC used to apply on FIAT tables still + # lives on after ditching FFC and switching to FInAT. + return ffc_rounding(square, ctx.epsilon) + table = ctx.entity_selector(callback, mt.restriction) + return gem.ComponentTensor(gem.Indexed(table, argument_index + sigma), sigma) @translate.register(Coefficient) @@ -203,20 +210,48 @@ def translate_coefficient(terminal, mt, ctx): element = create_element(terminal.ufl_element()) - def callback(entity_id): - return basis_evaluation(element, - ctx.point_set, - derivative=mt.local_derivatives, - entity=(ctx.integration_dim, entity_id), - epsilon=ctx.epsilon) - M = ctx.entity_selector(callback, mt.restriction) - - alpha = element.get_indices() - vi = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) - result = gem.Product(gem.Indexed(M, alpha + vi), - gem.Indexed(vec, alpha)) - result = gem.optimise.contraction(gem.IndexSum(result, alpha), logger=logger) - return gem.ComponentTensor(result, vi) + # Collect FInAT tabulation for all entities + per_derivative = collections.defaultdict(list) + for entity_id in ctx.entity_ids: + finat_dict = element.basis_evaluation(mt.local_derivatives, + ctx.point_set, + (ctx.integration_dim, entity_id)) + for alpha, table in iteritems(finat_dict): + # Filter out irrelevant derivatives + if sum(alpha) == mt.local_derivatives: + # A numerical hack that FFC used to apply on FIAT + # tables still lives on after ditching FFC and + # switching to FInAT. + table = ffc_rounding(table, ctx.epsilon) + per_derivative[alpha].append(table) + + # Merge entity tabulations for each derivative + if len(ctx.entity_ids) == 1: + def take_singleton(xs): + x, = xs # asserts singleton + return x + per_derivative = {alpha: take_singleton(tables) + for alpha, tables in iteritems(per_derivative)} + else: + f = ctx.facet_number(mt.restriction) + per_derivative = {alpha: gem.select_expression(tables, f) + for alpha, tables in iteritems(per_derivative)} + + # Coefficient evaluation + beta = element.get_indices() + zeta = element.get_value_indices() + value_dict = {} + for alpha, table in iteritems(per_derivative): + value = gem.IndexSum(gem.Product(gem.Indexed(table, beta + zeta), + gem.Indexed(vec, beta)), + beta) + optimised_value = gem.optimise.contraction(value, logger=logger) + value_dict[alpha] = gem.ComponentTensor(optimised_value, zeta) + + # Change from FIAT to UFL arrangement + result = fiat_to_ufl(value_dict, mt.local_derivatives) + assert result.shape == mt.expr.ufl_shape + return result def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): From 2b58c28519534ea92873c8031093cae9bd91c55d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 24 Nov 2016 11:45:53 +0000 Subject: [PATCH 256/809] generate fast Jacobian code snippets --- tsfc/fem.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tsfc/fem.py b/tsfc/fem.py index 60cf3a9cb3..545da67c6a 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -14,6 +14,7 @@ GeometricQuantity, QuadratureWeight) import gem +from gem.node import traversal from gem.optimise import ffc_rounding from gem.utils import cached_property @@ -251,6 +252,13 @@ def take_singleton(xs): # Change from FIAT to UFL arrangement result = fiat_to_ufl(value_dict, mt.local_derivatives) assert result.shape == mt.expr.ufl_shape + assert set(result.free_indices) <= set(ctx.point_set.indices) + + # Detect Jacobian of affine cells + if not result.free_indices and all(numpy.count_nonzero(node.array) <= 2 + for node in traversal((result,)) + if isinstance(node, gem.Literal)): + result = gem.optimise.aggressive_unroll(result) return result From df309c97df99ae66afb4c4f37d8ae6ea9a458a7e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 25 Nov 2016 15:34:14 +0000 Subject: [PATCH 257/809] clean up sum factorisation --- tsfc/fem.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 545da67c6a..e7ab97f77b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -23,7 +23,6 @@ from tsfc import ufl2gem, geometric from tsfc.finatinterface import create_element, as_fiat_cell from tsfc.kernel_interface import ProxyKernelInterface -from tsfc.logging import logger from tsfc.modified_terminals import analyse_modified_terminal from tsfc.parameters import PARAMETERS from tsfc.ufl_utils import ModifiedTerminalMixin, PickRestriction, simplify_abs @@ -246,7 +245,7 @@ def take_singleton(xs): value = gem.IndexSum(gem.Product(gem.Indexed(table, beta + zeta), gem.Indexed(vec, beta)), beta) - optimised_value = gem.optimise.contraction(value, logger=logger) + optimised_value = gem.optimise.contraction(value) value_dict[alpha] = gem.ComponentTensor(optimised_value, zeta) # Change from FIAT to UFL arrangement From 77a1b7692d045b064bec861974105425e673823b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 30 Nov 2016 11:28:06 +0000 Subject: [PATCH 258/809] not warn for COFFEE reshape indexing Generate nicer indexing code. --- tsfc/coffee.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 053ed21d72..f5a6fa1be7 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -17,7 +17,6 @@ from gem import gem, impero as imp from tsfc.parameters import SCALAR_TYPE -from tsfc.logging import logger class Bunch(object): @@ -376,16 +375,18 @@ def _expression_flexiblyindexed(expr, parameters): rank.append(parameters.index_names[i]) offset.append((s, off)) else: - # Warn that this might break COFFEE - logger.warning("Multiple free indices in FlexiblyIndexed: might break COFFEE.") - index_expr = reduce( - coffee.Sum, - [coffee.Prod(coffee.Symbol(parameters.index_names[i]), - coffee.Symbol(str(s))) - for i, s in iss], - coffee.Symbol(str(off)) - ) - rank.append(index_expr) + parts = [] + if off: + parts += [coffee.Symbol(str(off))] + for i, s in iss: + index_sym = coffee.Symbol(parameters.index_names[i]) + assert s + if s == 1: + parts += [index_sym] + else: + parts += [coffee.Prod(index_sym, coffee.Symbol(str(s)))] + assert parts + rank.append(reduce(coffee.Sum, parts)) offset.append((1, 0)) return coffee.Symbol(var.symbol, rank=tuple(rank), offset=tuple(offset)) From 8e0cee153d58573884d8e138f6fd5dfc1682c90e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 7 Dec 2016 17:14:05 +0000 Subject: [PATCH 259/809] adopt FInAT finite element construction changes --- tsfc/finatinterface.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index a8ade7f34a..29121ac95b 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -28,6 +28,7 @@ import FIAT import finat +from finat.fiat_elements import FiatElementBase import ufl from ufl.algorithms.elementtransformations import reconstruct_element @@ -59,6 +60,16 @@ have a direct FIAT equivalent.""" +class FiatElementWrapper(FiatElementBase): + def __init__(self, element, degree=None): + super(FiatElementWrapper, self).__init__(element) + self._degree = degree + + @property + def degree(self): + return self._degree or super(FiatElementWrapper, self).degree + + def as_fiat_cell(cell): """Convert a ufl cell to a FIAT cell. @@ -70,11 +81,8 @@ def as_fiat_cell(cell): def fiat_compat(element): from tsfc.fiatinterface import create_element - from finat.fiat_elements import FiatElementBase - cell = as_fiat_cell(element.cell()) - finat_element = FiatElementBase(cell, spanning_degree(element)) - finat_element._fiat_element = create_element(element) - return finat_element + return FiatElementWrapper(create_element(element), + degree=spanning_degree(element)) @singledispatch From 5ced3df217eb572a20e26d69ec69fc92776ffac3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 7 Dec 2016 17:37:41 +0000 Subject: [PATCH 260/809] remove some no longer used code --- tsfc/fiatinterface.py | 26 +------------------------- tsfc/finatinterface.py | 11 +---------- tsfc/ufl_utils.py | 9 --------- 3 files changed, 2 insertions(+), 44 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 0c7bfa8ee1..2edcd1f15e 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -35,7 +35,7 @@ from ufl.algorithms.elementtransformations import reconstruct_element -__all__ = ("create_element", "create_quadrature", "supported_elements", "as_fiat_cell") +__all__ = ("create_element", "supported_elements", "as_fiat_cell") supported_elements = { @@ -154,30 +154,6 @@ def as_fiat_cell(cell): return FIAT.ufc_cell(cell) -def create_quadrature(cell, degree, scheme="default"): - """Create a quadrature rule. - - :arg cell: The FIAT cell. - :arg degree: The degree of polynomial that should be integrated - exactly by the rule. - :kwarg scheme: optional scheme to use (either ``"default"``, or - ``"canonical"``). - - .. note :: - - If the cell is a tensor product cell, the degree should be a - tuple, indicating the degree in each direction of the tensor - product. - """ - if scheme not in ("default", "canonical"): - raise ValueError("Unknown quadrature scheme '%s'" % scheme) - - rule = FIAT.create_quadrature(cell, degree, scheme) - if len(rule.get_points()) > 900: - raise RuntimeError("Requested a quadrature rule with %d points" % len(rule.get_points())) - return rule - - @singledispatch def convert(element, vector_is_mixed): """Handler for converting UFL elements to FIAT elements. diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 29121ac95b..334ebd4774 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -26,13 +26,13 @@ from singledispatch import singledispatch import weakref -import FIAT import finat from finat.fiat_elements import FiatElementBase import ufl from ufl.algorithms.elementtransformations import reconstruct_element +from tsfc.fiatinterface import as_fiat_cell from tsfc.ufl_utils import spanning_degree @@ -70,15 +70,6 @@ def degree(self): return self._degree or super(FiatElementWrapper, self).degree -def as_fiat_cell(cell): - """Convert a ufl cell to a FIAT cell. - - :arg cell: the :class:`ufl.Cell` to convert.""" - if not isinstance(cell, ufl.AbstractCell): - raise ValueError("Expecting a UFL Cell") - return FIAT.ufc_cell(cell) - - def fiat_compat(element): from tsfc.fiatinterface import create_element return FiatElementWrapper(create_element(element), diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 45499ccc94..7dda1ed8f8 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -95,12 +95,6 @@ def preprocess_expression(expression): return expression -def is_element_affine(ufl_element): - """Tells if a UFL element is affine.""" - affine_cells = ["interval", "triangle", "tetrahedron"] - return ufl_element.cell().cellname() in affine_cells and ufl_element.degree() == 1 - - class SpatialCoordinateReplacer(MultiFunction): """Replace SpatialCoordinate nodes with the ReferenceValue of a Coefficient. Assumes that the coordinate element only needs @@ -208,9 +202,6 @@ def modified_terminal(self, o): def split_coefficients(expression, split): """Split mixed coefficients, so mixed elements need not be implemented.""" - if split is None: - # Skip this step for DOLFIN - return expression splitter = CoefficientSplitter(split) return map_expr_dag(splitter, expression) From ddba190cb8f4362f509b9ebe59963748b5c36864 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 7 Dec 2016 17:54:43 +0000 Subject: [PATCH 261/809] do not duplicate work --- tsfc/driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index eeaf1c219d..5c695ba6be 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -167,8 +167,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, ir = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*irs)) # Need optimised roots for COFFEE - ir = opt.replace_delta(ir) - ir = opt.remove_componenttensors(ir) + ir = impero_utils.preprocess_gem(ir) # Look for cell orientations in the IR if builder.needs_cell_orientations(ir): @@ -303,6 +302,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non return_var = gem.Variable('A', return_shape) return_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('A', rank=return_shape)) return_expr = gem.Indexed(return_var, return_indices) + ir, = impero_utils.preprocess_gem([ir]) impero_c = impero_utils.compile_gem([return_expr], [ir], return_indices) point_index, = point_set.indices body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"]) From 18295873b4042a933584f4bb19aba60dcab9a7e0 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 22 Nov 2016 11:32:03 +0000 Subject: [PATCH 262/809] restore FFC MixedElement We still need it to support DOLFIN memory layout. --- tsfc/fiatinterface.py | 17 ++++++- tsfc/finatinterface.py | 6 --- tsfc/mixedelement.py | 112 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 tsfc/mixedelement.py diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 2edcd1f15e..74fefaabc7 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -24,6 +24,7 @@ from __future__ import absolute_import, print_function, division from singledispatch import singledispatch +from functools import partial import weakref import FIAT @@ -34,6 +35,8 @@ import ufl from ufl.algorithms.elementtransformations import reconstruct_element +from .mixedelement import MixedElement + __all__ = ("create_element", "supported_elements", "as_fiat_cell") @@ -249,7 +252,19 @@ def _(element, vector_is_mixed): ufl.TensorElement)) return create_element(element.sub_elements()[0], vector_is_mixed) - raise NotImplementedError("ffc.MixedElement removed.") + elements = [] + + def rec(eles): + for ele in eles: + if isinstance(ele, ufl.MixedElement): + rec(ele.sub_elements()) + else: + elements.append(ele) + + rec(element.sub_elements()) + fiat_elements = map(partial(create_element, vector_is_mixed=vector_is_mixed), + elements) + return MixedElement(fiat_elements) quad_opc = ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 334ebd4774..35338ee4b5 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -109,12 +109,6 @@ def convert_finiteelement(element): return lmbda(cell, element.degree()) -# MixedElement case -@convert.register(ufl.MixedElement) -def convert_mixedelement(element): - raise ValueError("FInAT does not implement generic mixed element.") - - # VectorElement case @convert.register(ufl.VectorElement) def convert_vectorelement(element): diff --git a/tsfc/mixedelement.py b/tsfc/mixedelement.py new file mode 100644 index 0000000000..daf28ca1f2 --- /dev/null +++ b/tsfc/mixedelement.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# +# This file was modified from FFC +# (http://bitbucket.org/fenics-project/ffc), copyright notice +# reproduced below. +# +# Copyright (C) 2005-2010 Anders Logg +# +# This file is part of FFC. +# +# FFC is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# FFC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with FFC. If not, see . + +from __future__ import absolute_import, print_function, division +from six.moves import map + +import numpy + +from collections import defaultdict +from operator import add +from functools import partial + + +class MixedElement(object): + """A FIAT-like representation of a mixed element. + + :arg elements: An iterable of FIAT elements. + + This object offers tabulation of the concatenated basis function + tables along with an entity_dofs dict.""" + def __init__(self, elements): + self._elements = tuple(elements) + self._entity_dofs = None + + def get_reference_element(self): + return self.elements()[0].get_reference_element() + + def elements(self): + return self._elements + + def space_dimension(self): + return sum(e.space_dimension() for e in self.elements()) + + def value_shape(self): + return (sum(numpy.prod(e.value_shape(), dtype=int) for e in self.elements()), ) + + def entity_dofs(self): + if self._entity_dofs is not None: + return self._entity_dofs + + ret = defaultdict(partial(defaultdict, list)) + + dicts = (e.entity_dofs() for e in self.elements()) + + offsets = numpy.cumsum([0] + list(e.space_dimension() + for e in self.elements()), + dtype=int) + for i, d in enumerate(dicts): + for dim, dofs in d.items(): + for ent, off in dofs.items(): + ret[dim][ent] += list(map(partial(add, offsets[i]), + off)) + self._entity_dofs = ret + return self._entity_dofs + + def num_components(self): + return self.value_shape()[0] + + def tabulate(self, order, points, entity): + """Tabulate a mixed element by appropriately splatting + together the tabulation of the individual elements. + """ + # FIXME: Could we reorder the basis functions so that indexing + # in the form compiler for mixed interior facets becomes + # easier? + # Would probably need to redo entity_dofs as well. + shape = (self.space_dimension(), self.num_components(), len(points)) + + output = {} + + sub_dims = [0] + list(e.space_dimension() for e in self.elements()) + sub_cmps = [0] + list(numpy.prod(e.value_shape(), dtype=int) + for e in self.elements()) + irange = numpy.cumsum(sub_dims) + crange = numpy.cumsum(sub_cmps) + + for i, e in enumerate(self.elements()): + table = e.tabulate(order, points, entity) + + for d, tab in table.items(): + try: + arr = output[d] + except KeyError: + arr = numpy.zeros(shape) + output[d] = arr + + ir = irange[i:i+2] + cr = crange[i:i+2] + tab = tab.reshape(ir[1] - ir[0], cr[1] - cr[0], -1) + arr[slice(*ir), slice(*cr)] = tab + + return output From 5cbc8bf7ffee692a5549e1d6e7d3043f3a1201cb Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 23 Nov 2016 11:44:41 +0000 Subject: [PATCH 263/809] ugly and suboptimal but works --- tsfc/kernel_interface/ufc.py | 107 ++++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 34 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 6dcb9e9af0..6de13d288f 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -1,5 +1,5 @@ from __future__ import absolute_import, print_function, division -from six.moves import map +from six.moves import range, zip import numpy from itertools import product @@ -9,7 +9,7 @@ import gem from tsfc.kernel_interface.common import KernelBuilderBase -from tsfc.fiatinterface import create_element +from tsfc.finatinterface import create_element from tsfc.coffee import SCALAR_TYPE, cumulative_strides @@ -40,6 +40,21 @@ def __init__(self, integral_type, subdomain_id, domain_number): '-': gem.VariableIndex(gem.Variable("facet_1", ())) } + def coefficient(self, ufl_coefficient, restriction): + """A function that maps :class:`ufl.Coefficient`s to GEM + expressions.""" + kernel_arg = self.coefficient_map[ufl_coefficient] + if ufl_coefficient.ufl_element().family() == 'Real': + return kernel_arg + elif not isinstance(kernel_arg, tuple): + return gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[restriction]) + elif restriction == '+': + return kernel_arg[0] + elif restriction == '-': + return kernel_arg[1] + else: + assert False + def set_arguments(self, arguments, indices): """Process arguments. @@ -154,21 +169,33 @@ def prepare_coefficients(coefficients, coefficient_numbers, name, interior_facet varexp = gem.Variable(name, (None, None)) expressions = [] for coefficient_number, coefficient in zip(coefficient_numbers, coefficients): + cells_shape = () + tensor_shape = () if coefficient.ufl_element().family() == 'Real': - shape = coefficient.ufl_shape + scalar_shape = coefficient.ufl_shape else: - fiat_element = create_element(coefficient.ufl_element()) - shape = (fiat_element.space_dimension(),) - if interior_facet: - shape = (2,) + shape + finat_element = create_element(coefficient.ufl_element()) + if hasattr(finat_element, '_base_element'): + scalar_shape = finat_element._base_element.index_shape + tensor_shape = finat_element.index_shape[len(scalar_shape):] + else: + scalar_shape = finat_element.index_shape - alpha = tuple(gem.Index() for s in shape) + if interior_facet: + cells_shape = (2,) + + cells_indices = tuple(gem.Index() for s in cells_shape) + tensor_indices = tuple(gem.Index() for s in tensor_shape) + scalar_indices = tuple(gem.Index() for s in scalar_shape) + shape = cells_shape + tensor_shape + scalar_shape + alpha = cells_indices + tensor_indices + scalar_indices + beta = cells_indices + scalar_indices + tensor_indices expressions.append(gem.ComponentTensor( gem.FlexiblyIndexed( varexp, ((coefficient_number, ()), (0, tuple(zip(alpha, shape)))) ), - alpha + beta )) return funarg, expressions @@ -198,34 +225,29 @@ def prepare_coordinates(coefficient, name, interior_facet=False): pointers=[("",)], qualifiers=["const"])] - fiat_element = create_element(coefficient.ufl_element()) - shape = (fiat_element.space_dimension(),) - gdim = coefficient.ufl_element().cell().geometric_dimension() - assert len(shape) == 1 and shape[0] % gdim == 0 - num_nodes = shape[0] // gdim + finat_element = create_element(coefficient.ufl_element()) + shape = finat_element.index_shape + size = numpy.prod(shape, dtype=int) - # Translate coords from XYZXYZXYZXYZ into XXXXYYYYZZZZ - # NOTE: See dolfin/mesh/Cell.h:get_coordinate_dofs for ordering scheme - indices = list(map(int, numpy.arange(num_nodes * gdim).reshape(num_nodes, gdim).transpose().flat)) if not interior_facet: - variable = gem.Variable(name, shape) - expression = gem.ListTensor([gem.Indexed(variable, (i,)) for i in indices]) + variable = gem.Variable(name, (size,)) + expression = gem.reshape(variable, shape) else: - variable0 = gem.Variable(name+"_0", shape) - variable1 = gem.Variable(name+"_1", shape) - expression = gem.ListTensor([[gem.Indexed(variable0, (i,)) for i in indices], - [gem.Indexed(variable1, (i,)) for i in indices]]) + variable0 = gem.Variable(name+"_0", (size,)) + variable1 = gem.Variable(name+"_1", (size,)) + expression = (gem.reshape(variable0, shape), + gem.reshape(variable1, shape)) return funargs, expression -def prepare_arguments(arguments, indices, interior_facet=False): +def prepare_arguments(arguments, multiindices, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. :arg arguments: UFL Arguments - :arg indices: Argument indices + :arg multiindices: Argument multiindices :arg interior_facet: interior facet integral? :returns: (funarg, prepare, expressions) funarg - :class:`coffee.Decl` function argument @@ -238,29 +260,46 @@ def prepare_arguments(arguments, indices, interior_facet=False): varexp = gem.Variable("A", (None,)) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - space_dimensions = tuple(element.space_dimension() for element in elements) - for i, sd in zip(indices, space_dimensions): - i.set_extent(sd) + ushape = tuple(numpy.prod(element.index_shape, dtype=int) for element in elements) + + # Flat indices and flat shape + indices = [] + shape = [] + for element, multiindex in zip(elements, multiindices): + if hasattr(element, '_base_element'): + scalar_shape = element._base_element.index_shape + tensor_shape = element.index_shape[len(scalar_shape):] + else: + scalar_shape = element.index_shape + tensor_shape = () + + haha = tensor_shape + scalar_shape + if interior_facet and haha: + haha = (haha[0] * 2,) + haha[1:] + shape.extend(haha) + indices.extend(multiindex[len(scalar_shape):] + multiindex[:len(scalar_shape)]) + indices = tuple(indices) + shape = tuple(shape) if arguments and interior_facet: - shape = tuple(2 * element.space_dimension() for element in elements) - strides = cumulative_strides(shape) + result_shape = tuple(2 * sd for sd in ushape) + strides = cumulative_strides(result_shape) expressions = [] for restrictions in product((0, 1), repeat=len(arguments)): offset = sum(r * sd * stride - for r, sd, stride in zip(restrictions, space_dimensions, strides)) + for r, sd, stride in zip(restrictions, ushape, strides)) expressions.append(gem.FlexiblyIndexed( varexp, ((offset, tuple((i, s) for i, s in zip(indices, shape))),) )) else: - shape = space_dimensions - expressions = [gem.FlexiblyIndexed(varexp, ((0, tuple(zip(indices, space_dimensions))),))] + result_shape = ushape + expressions = [gem.FlexiblyIndexed(varexp, ((0, tuple(zip(indices, shape))),))] zero = coffee.FlatBlock( str.format("memset({name}, 0, {size} * sizeof(*{name}));\n", - name=funarg.sym.gencode(), size=numpy.product(shape, dtype=int)) + name=funarg.sym.gencode(), size=numpy.product(result_shape, dtype=int)) ) return funarg, [zero], expressions From ccaae1115caf47a5a709f49f20cebce02d1eea4e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 5 Dec 2016 21:54:56 +0000 Subject: [PATCH 264/809] refactor ufc.prepare_coefficient --- tsfc/kernel_interface/ufc.py | 93 ++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 53 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 6de13d288f..562f503867 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -97,13 +97,13 @@ def set_coefficients(self, integral_data, form_data): # coefficients, but each integral only requires one. coefficient_numbers.append(i) - funarg, expressions = prepare_coefficients( - coefficients, coefficient_numbers, "w", - interior_facet=self.interior_facet) - - self.coefficient_args = [funarg] - for i, coefficient in enumerate(coefficients): - self.coefficient_map[coefficient] = expressions[i] + self.coefficient_args = [ + coffee.Decl(SCALAR_TYPE, coffee.Symbol("w"), + pointers=[("const",), ()], + qualifiers=["const"]) + ] + for c, n in zip(coefficients, coefficient_numbers): + self.coefficient_map[c] = prepare_coefficient(c, n, "w", self.interior_facet) def construct_kernel(self, name, body): """Construct a fully built :class:`Kernel`. @@ -146,59 +146,46 @@ def needs_cell_orientations(ir): return True -def prepare_coefficients(coefficients, coefficient_numbers, name, interior_facet=False): +def prepare_coefficient(coefficient, coefficient_number, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for - Coefficients. Mixed element Coefficients are rearranged here for - interior facet integrals. + Coefficients. - :arg coefficient: iterable of UFL Coefficients - :arg coefficient_numbers: iterable of coefficient indices in the original form + :arg coefficient: UFL Coefficient + :arg coefficient_numbers: coefficient index in the original form :arg name: unique name to refer to the Coefficient in the kernel :arg interior_facet: interior facet integral? - :returns: (funarg, expressions) - funarg - :class:`coffee.Decl` function argument - expressions- GEM expressions referring to the Coefficient - values + :returns: GEM expression referring to the Coefficient value """ - assert len(coefficients) == len(coefficient_numbers) - - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("const",), ()], - qualifiers=["const"]) - varexp = gem.Variable(name, (None, None)) - expressions = [] - for coefficient_number, coefficient in zip(coefficient_numbers, coefficients): - cells_shape = () - tensor_shape = () - if coefficient.ufl_element().family() == 'Real': - scalar_shape = coefficient.ufl_shape + + cells_shape = () + tensor_shape = () + if coefficient.ufl_element().family() == 'Real': + scalar_shape = coefficient.ufl_shape + else: + finat_element = create_element(coefficient.ufl_element()) + if hasattr(finat_element, '_base_element'): + scalar_shape = finat_element._base_element.index_shape + tensor_shape = finat_element.index_shape[len(scalar_shape):] else: - finat_element = create_element(coefficient.ufl_element()) - if hasattr(finat_element, '_base_element'): - scalar_shape = finat_element._base_element.index_shape - tensor_shape = finat_element.index_shape[len(scalar_shape):] - else: - scalar_shape = finat_element.index_shape - - if interior_facet: - cells_shape = (2,) - - cells_indices = tuple(gem.Index() for s in cells_shape) - tensor_indices = tuple(gem.Index() for s in tensor_shape) - scalar_indices = tuple(gem.Index() for s in scalar_shape) - shape = cells_shape + tensor_shape + scalar_shape - alpha = cells_indices + tensor_indices + scalar_indices - beta = cells_indices + scalar_indices + tensor_indices - expressions.append(gem.ComponentTensor( - gem.FlexiblyIndexed( - varexp, ((coefficient_number, ()), - (0, tuple(zip(alpha, shape)))) - ), - beta - )) - - return funarg, expressions + scalar_shape = finat_element.index_shape + + if interior_facet: + cells_shape = (2,) + + cells_indices = tuple(gem.Index() for s in cells_shape) + tensor_indices = tuple(gem.Index() for s in tensor_shape) + scalar_indices = tuple(gem.Index() for s in scalar_shape) + shape = cells_shape + tensor_shape + scalar_shape + alpha = cells_indices + tensor_indices + scalar_indices + beta = cells_indices + scalar_indices + tensor_indices + return gem.ComponentTensor( + gem.FlexiblyIndexed( + varexp, ((coefficient_number, ()), + (0, tuple(zip(alpha, shape)))) + ), + beta + ) def prepare_coordinates(coefficient, name, interior_facet=False): From dea2ea906468eda567090a4fddaeb9917c4a2e22 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 6 Dec 2016 10:26:02 +0000 Subject: [PATCH 265/809] coefficient splitting for UFC --- tsfc/kernel_interface/ufc.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 562f503867..afb58448e7 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -4,6 +4,8 @@ import numpy from itertools import product +from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace + import coffee.base as coffee import gem @@ -24,7 +26,7 @@ def __init__(self, integral_type, subdomain_id, domain_number): self.local_tensor = None self.coordinates_args = None self.coefficient_args = None - self.coefficient_split = None + self.coefficient_split = {} if self.interior_facet: self._cell_orientations = (gem.Variable("cell_orientation_0", ()), @@ -84,26 +86,29 @@ def set_coefficients(self, integral_data, form_data): :arg form_data: UFL form data """ coefficients = [] - coefficient_numbers = [] # enabled_coefficients is a boolean array that indicates which # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: coefficient = form_data.reduced_coefficients[i] - coefficients.append(coefficient) - # This is which coefficient in the original form the - # current coefficient is. - # Consider f*v*dx + g*v*ds, the full form contains two - # coefficients, but each integral only requires one. - coefficient_numbers.append(i) + if type(coefficient.ufl_element()) == ufl_MixedElement: + split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) + for element in coefficient.ufl_element().sub_elements()] + space_dims = [numpy.prod(create_element(element).index_shape, dtype=int) + for element in coefficient.ufl_element().sub_elements()] + offsets = numpy.cumsum([0] + space_dims[:-1]) + coefficients.extend((c, i, o) for c, o in zip(split, offsets)) + self.coefficient_split[coefficient] = split + else: + coefficients.append((coefficient, i, 0)) self.coefficient_args = [ coffee.Decl(SCALAR_TYPE, coffee.Symbol("w"), pointers=[("const",), ()], qualifiers=["const"]) ] - for c, n in zip(coefficients, coefficient_numbers): - self.coefficient_map[c] = prepare_coefficient(c, n, "w", self.interior_facet) + for c, n, o in coefficients: + self.coefficient_map[c] = prepare_coefficient(c, n, o, "w", self.interior_facet) def construct_kernel(self, name, body): """Construct a fully built :class:`Kernel`. @@ -146,12 +151,13 @@ def needs_cell_orientations(ir): return True -def prepare_coefficient(coefficient, coefficient_number, name, interior_facet=False): +def prepare_coefficient(coefficient, number, offset, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. :arg coefficient: UFL Coefficient - :arg coefficient_numbers: coefficient index in the original form + :arg number: coefficient index in the original form + :arg offset: subcoefficient DoFs start at this offset :arg name: unique name to refer to the Coefficient in the kernel :arg interior_facet: interior facet integral? :returns: GEM expression referring to the Coefficient value @@ -181,8 +187,8 @@ def prepare_coefficient(coefficient, coefficient_number, name, interior_facet=Fa beta = cells_indices + scalar_indices + tensor_indices return gem.ComponentTensor( gem.FlexiblyIndexed( - varexp, ((coefficient_number, ()), - (0, tuple(zip(alpha, shape)))) + varexp, ((number, ()), + (offset, tuple(zip(alpha, shape)))) ), beta ) From b2375bca716ae7a7f91f127bbf3c679bf40fddd9 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 8 Dec 2016 11:44:21 +0000 Subject: [PATCH 266/809] test gem.reshape (with FlexiblyIndexed) --- tests/test_flexibly_indexed.py | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/test_flexibly_indexed.py diff --git a/tests/test_flexibly_indexed.py b/tests/test_flexibly_indexed.py new file mode 100644 index 0000000000..cbdacc687a --- /dev/null +++ b/tests/test_flexibly_indexed.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import, print_function, division +from six.moves import range + +import gem +import numpy +import pytest +import tsfc + + +parameters = tsfc.coffee.Bunch() +parameters.names = {} + + +def convert(expression, multiindex): + assert not expression.free_indices + element = gem.Indexed(expression, multiindex) + element, = gem.optimise.remove_componenttensors((element,)) + return tsfc.coffee.expression(element, parameters).rank + + +@pytest.fixture(scope='module') +def vector(): + return gem.Variable('u', (12,)) + + +@pytest.fixture(scope='module') +def matrix(): + return gem.Variable('A', (10, 12)) + + +def test_reshape(vector): + expression = gem.reshape(vector, (3, 4)) + assert expression.shape == (3, 4) + + actual = [convert(expression, multiindex) + for multiindex in numpy.ndindex(expression.shape)] + + assert [(i,) for i in range(12)] == actual + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 9397e8b4c38f5b60a70ed60f48f7a644fbedec2f Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 8 Dec 2016 14:11:51 +0000 Subject: [PATCH 267/809] change the internal representation of FlexiblyIndexed --- tests/test_flexibly_indexed.py | 15 ++++++++++++++- tsfc/coffee.py | 12 ------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/test_flexibly_indexed.py b/tests/test_flexibly_indexed.py index cbdacc687a..4c31ac2860 100644 --- a/tests/test_flexibly_indexed.py +++ b/tests/test_flexibly_indexed.py @@ -1,9 +1,12 @@ from __future__ import absolute_import, print_function, division from six.moves import range -import gem +from itertools import product + import numpy import pytest + +import gem import tsfc @@ -38,6 +41,16 @@ def test_reshape(vector): assert [(i,) for i in range(12)] == actual +def test_view(matrix): + expression = gem.view(matrix, slice(3, 8), slice(5, 12)) + assert expression.shape == (5, 7) + + actual = [convert(expression, multiindex) + for multiindex in numpy.ndindex(expression.shape)] + + assert list(product(range(3, 8), range(5, 12))) == actual + + if __name__ == "__main__": import os import sys diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 55615a4900..8377be094a 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -323,18 +323,6 @@ def _expression_indexed(expr, parameters): rank=tuple(rank)) -def cumulative_strides(strides): - """Calculate cumulative strides from per-dimension capacities. - - For example: - - [2, 3, 4] ==> [12, 4, 1] - - """ - temp = numpy.flipud(numpy.cumprod(numpy.flipud(list(strides)[1:]))) - return list(temp) + [1] - - @_expression.register(gem.FlexiblyIndexed) def _expression_flexiblyindexed(expr, parameters): var = expression(expr.children[0], parameters) From 67813d06b89c04449d6520479ddb37d1519fa78d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 8 Dec 2016 14:42:23 +0000 Subject: [PATCH 268/809] generalised gem.view --- tests/test_flexibly_indexed.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_flexibly_indexed.py b/tests/test_flexibly_indexed.py index 4c31ac2860..03afa18a8f 100644 --- a/tests/test_flexibly_indexed.py +++ b/tests/test_flexibly_indexed.py @@ -51,6 +51,27 @@ def test_view(matrix): assert list(product(range(3, 8), range(5, 12))) == actual +def test_view_view(matrix): + expression = gem.view(gem.view(matrix, slice(3, 8), slice(5, 12)), + slice(4), slice(3, 6)) + assert expression.shape == (4, 3) + + actual = [convert(expression, multiindex) + for multiindex in numpy.ndindex(expression.shape)] + + assert list(product(range(3, 7), range(8, 11))) == actual + + +def test_view_reshape(vector): + expression = gem.view(gem.reshape(vector, (3, 4)), slice(2), slice(1, 3)) + assert expression.shape == (2, 2) + + actual = [convert(expression, multiindex) + for multiindex in numpy.ndindex(expression.shape)] + + assert [(1,), (2,), (5,), (6,)] == actual + + if __name__ == "__main__": import os import sys From 5fd86b8e6e73d33430b3b553a7560c58bb9847d7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 8 Dec 2016 14:58:12 +0000 Subject: [PATCH 269/809] generalise gem.reshape --- tests/test_flexibly_indexed.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_flexibly_indexed.py b/tests/test_flexibly_indexed.py index 03afa18a8f..cd40b7013a 100644 --- a/tests/test_flexibly_indexed.py +++ b/tests/test_flexibly_indexed.py @@ -72,6 +72,26 @@ def test_view_reshape(vector): assert [(1,), (2,), (5,), (6,)] == actual +def test_reshape_shape(vector): + expression = gem.reshape(gem.view(vector, slice(5, 11)), (3, 2)) + assert expression.shape == (3, 2) + + actual = [convert(expression, multiindex) + for multiindex in numpy.ndindex(expression.shape)] + + assert [(i,) for i in range(5, 11)] == actual + + +def test_reshape_reshape(vector): + expression = gem.reshape(gem.reshape(vector, (4, 3)), (2, 2), (3,)) + assert expression.shape == (2, 2, 3) + + actual = [convert(expression, multiindex) + for multiindex in numpy.ndindex(expression.shape)] + + assert [(i,) for i in range(12)] == actual + + if __name__ == "__main__": import os import sys From 38e83051b0aaf9798392d647343647f8c0967901 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 8 Dec 2016 15:37:07 +0000 Subject: [PATCH 270/809] update COFFEE code generation for FlexiblyIndexed --- tsfc/coffee.py | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 8377be094a..ee4b49555e 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -333,40 +333,27 @@ def _expression_flexiblyindexed(expr, parameters): rank = [] offset = [] for off, idxs in expr.dim2idxs: - if idxs: - indices, strides = zip(*idxs) - strides = cumulative_strides(strides) - else: - indices = () - strides = () - - iss = [] - for i, s in zip(indices, strides): - if isinstance(i, int): - off += i * s - elif isinstance(i, gem.Index): - iss.append((i, s)) - else: - raise AssertionError("Unexpected index type!") - - if len(iss) == 0: + for index, stride in idxs: + assert isinstance(index, gem.Index) + + if len(idxs) == 0: rank.append(off) offset.append((1, 0)) - elif len(iss) == 1: - (i, s), = iss - rank.append(parameters.index_names[i]) - offset.append((s, off)) + elif len(idxs) == 1: + (index, stride), = idxs + rank.append(parameters.index_names[index]) + offset.append((stride, off)) else: parts = [] if off: parts += [coffee.Symbol(str(off))] - for i, s in iss: - index_sym = coffee.Symbol(parameters.index_names[i]) - assert s - if s == 1: + for index, stride in idxs: + index_sym = coffee.Symbol(parameters.index_names[index]) + assert stride + if stride == 1: parts += [index_sym] else: - parts += [coffee.Prod(index_sym, coffee.Symbol(str(s)))] + parts += [coffee.Prod(index_sym, coffee.Symbol(str(stride)))] assert parts rank.append(reduce(coffee.Sum, parts)) offset.append((1, 0)) From 158bd0d7a76a54ea8a1b6abb35abc87002976684 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 8 Dec 2016 15:56:41 +0000 Subject: [PATCH 271/809] WIP: update Firedrake kernel interface --- tsfc/kernel_interface/firedrake.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 5eeb28b372..d6b5a65114 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -2,7 +2,7 @@ import numpy from collections import namedtuple -from itertools import product +from itertools import chain, product from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace @@ -293,13 +293,13 @@ def prepare_coefficient(coefficient, name, interior_facet=False): return funarg, expression -def prepare_arguments(arguments, indices, interior_facet=False): +def prepare_arguments(arguments, multiindices, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. :arg arguments: UFL Arguments - :arg indices: Argument indices + :arg multiindices: Argument multiindices :arg interior_facet: interior facet integral? :returns: (funarg, expression) funarg - :class:`coffee.Decl` function argument @@ -327,21 +327,17 @@ def prepare_arguments(arguments, indices, interior_facet=False): varexp = gem.Variable("A", c_shape) if not interior_facet: - expression = gem.FlexiblyIndexed( - varexp, - tuple((0, tuple(zip(alpha, shape))) - for shape, alpha in zip(shapes, indices)) - ) - return funarg, [expression] + flat_multiindex = tuple(chain(*multiindices)) + expression = gem.Indexed(gem.reshape(varexp, *shapes), flat_multiindex) + return funarg, gem.optimise.remove_componenttensors([expression]) else: expressions = [] for restrictions in product((0, 1), repeat=len(arguments)): - expressions.append(gem.FlexiblyIndexed( - varexp, - tuple((0, ((r, 2),) + tuple(zip(alpha, shape))) - for r, shape, alpha in zip(restrictions, shapes, indices)) - )) - return funarg, expressions + shapes_ = tuple((2,) + shape for shape in shapes) + flat_multiindex = tuple(chain(*[(restriction,) + multiindex + for restriction, multiindex in zip(restrictions, multiindices)])) + expressions.append(gem.Indexed(gem.reshape(varexp, *shapes_), flat_multiindex)) + return funarg, gem.optimise.remove_componenttensors(expressions) cell_orientations_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), From 3ed02add38398e910331419cd3750b8607017b96 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 8 Dec 2016 15:56:57 +0000 Subject: [PATCH 272/809] WIP: update UFC kernel interface --- tsfc/kernel_interface/ufc.py | 39 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index afb58448e7..5e9731f006 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -2,7 +2,7 @@ from six.moves import range, zip import numpy -from itertools import product +from itertools import chain, product from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace @@ -12,7 +12,7 @@ from tsfc.kernel_interface.common import KernelBuilderBase from tsfc.finatinterface import create_element -from tsfc.coffee import SCALAR_TYPE, cumulative_strides +from tsfc.coffee import SCALAR_TYPE class KernelBuilder(KernelBuilderBase): @@ -185,13 +185,9 @@ def prepare_coefficient(coefficient, number, offset, name, interior_facet=False) shape = cells_shape + tensor_shape + scalar_shape alpha = cells_indices + tensor_indices + scalar_indices beta = cells_indices + scalar_indices + tensor_indices - return gem.ComponentTensor( - gem.FlexiblyIndexed( - varexp, ((number, ()), - (offset, tuple(zip(alpha, shape)))) - ), - beta - ) + return gem.ComponentTensor(gem.Indexed(gem.reshape(gem.view(varexp, slice(number, number + 1), slice(numpy.prod(shape, dtype=int))), (), shape), + alpha), + beta) def prepare_coordinates(coefficient, name, interior_facet=False): @@ -256,6 +252,7 @@ def prepare_arguments(arguments, multiindices, interior_facet=False): ushape = tuple(numpy.prod(element.index_shape, dtype=int) for element in elements) # Flat indices and flat shape + reordered_multiindices = [] indices = [] shape = [] for element, multiindex in zip(elements, multiindices): @@ -267,32 +264,28 @@ def prepare_arguments(arguments, multiindices, interior_facet=False): tensor_shape = () haha = tensor_shape + scalar_shape - if interior_facet and haha: - haha = (haha[0] * 2,) + haha[1:] + if interior_facet: + haha = (2,) + haha shape.extend(haha) - indices.extend(multiindex[len(scalar_shape):] + multiindex[:len(scalar_shape)]) + reordered_multiindex = multiindex[len(scalar_shape):] + multiindex[:len(scalar_shape)] + reordered_multiindices.append(reordered_multiindex) + indices.extend(reordered_multiindex) indices = tuple(indices) shape = tuple(shape) if arguments and interior_facet: result_shape = tuple(2 * sd for sd in ushape) - strides = cumulative_strides(result_shape) - expressions = [] for restrictions in product((0, 1), repeat=len(arguments)): - offset = sum(r * sd * stride - for r, sd, stride in zip(restrictions, ushape, strides)) - expressions.append(gem.FlexiblyIndexed( - varexp, - ((offset, - tuple((i, s) for i, s in zip(indices, shape))),) - )) + flat_multiindex = tuple(chain(*[(restriction,) + multiindex + for restriction, multiindex in zip(restrictions, reordered_multiindices)])) + expressions.append(gem.Indexed(gem.reshape(varexp, shape), flat_multiindex)) else: result_shape = ushape - expressions = [gem.FlexiblyIndexed(varexp, ((0, tuple(zip(indices, shape))),))] + expressions = [gem.Indexed(gem.reshape(varexp, shape), indices)] zero = coffee.FlatBlock( str.format("memset({name}, 0, {size} * sizeof(*{name}));\n", name=funarg.sym.gencode(), size=numpy.product(result_shape, dtype=int)) ) - return funarg, [zero], expressions + return funarg, [zero], gem.optimise.remove_componenttensors(expressions) From fd29847a5e0ecd24a3ab9768c359dc2a5dd7a30b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 8 Dec 2016 17:17:17 +0000 Subject: [PATCH 273/809] do away with UFC override of coefficient --- tsfc/kernel_interface/common.py | 4 +++- tsfc/kernel_interface/firedrake.py | 3 +++ tsfc/kernel_interface/ufc.py | 25 +++++++------------------ 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 548138f6a2..8d2c95a06b 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -32,8 +32,10 @@ def coefficient(self, ufl_coefficient, restriction): kernel_arg = self.coefficient_map[ufl_coefficient] if ufl_coefficient.ufl_element().family() == 'Real': return kernel_arg + elif not self.interior_facet: + return kernel_arg else: - return gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[restriction]) + return kernel_arg[{'+': 0, '-': 1}[restriction]] def cell_orientation(self, restriction): """Cell orientation as a GEM expression.""" diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index d6b5a65114..448d6f5ab0 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -290,6 +290,9 @@ def prepare_coefficient(coefficient, name, interior_facet=False): scalar_shape, tensor_shape ) + if interior_facet: + expression = (gem.partial_indexed(expression, (0,)), + gem.partial_indexed(expression, (1,))) return funarg, expression diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 5e9731f006..348fd18a6f 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -42,21 +42,6 @@ def __init__(self, integral_type, subdomain_id, domain_number): '-': gem.VariableIndex(gem.Variable("facet_1", ())) } - def coefficient(self, ufl_coefficient, restriction): - """A function that maps :class:`ufl.Coefficient`s to GEM - expressions.""" - kernel_arg = self.coefficient_map[ufl_coefficient] - if ufl_coefficient.ufl_element().family() == 'Real': - return kernel_arg - elif not isinstance(kernel_arg, tuple): - return gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[restriction]) - elif restriction == '+': - return kernel_arg[0] - elif restriction == '-': - return kernel_arg[1] - else: - assert False - def set_arguments(self, arguments, indices): """Process arguments. @@ -185,9 +170,13 @@ def prepare_coefficient(coefficient, number, offset, name, interior_facet=False) shape = cells_shape + tensor_shape + scalar_shape alpha = cells_indices + tensor_indices + scalar_indices beta = cells_indices + scalar_indices + tensor_indices - return gem.ComponentTensor(gem.Indexed(gem.reshape(gem.view(varexp, slice(number, number + 1), slice(numpy.prod(shape, dtype=int))), (), shape), - alpha), - beta) + expression = gem.ComponentTensor(gem.Indexed(gem.reshape(gem.view(varexp, slice(number, number + 1), slice(numpy.prod(shape, dtype=int))), (), shape), + alpha), + beta) + if interior_facet: + expression = (gem.partial_indexed(expression, (0,)), + gem.partial_indexed(expression, (1,))) + return expression def prepare_coordinates(coefficient, name, interior_facet=False): From e197161dbf977fc85fe65d98450d67fa9cf172fb Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 9 Dec 2016 10:09:44 +0000 Subject: [PATCH 274/809] revise Firedrake kernel interface --- tsfc/kernel_interface/firedrake.py | 64 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 448d6f5ab0..85ec55de79 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -10,6 +10,9 @@ import gem from gem.node import traversal +from gem.optimise import remove_componenttensors as prune + +from finat import TensorFiniteElement from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase @@ -268,31 +271,30 @@ def prepare_coefficient(coefficient, name, interior_facet=False): finat_element = create_element(coefficient.ufl_element()) - # TODO: move behind the FInAT interface! - if hasattr(finat_element, '_base_element'): - scalar_shape = finat_element._base_element.index_shape + if isinstance(finat_element, TensorFiniteElement): + scalar_shape = finat_element.base_element.index_shape tensor_shape = finat_element.index_shape[len(scalar_shape):] else: scalar_shape = finat_element.index_shape tensor_shape = () - - if interior_facet: - scalar_shape = (2,) + scalar_shape + scalar_size = numpy.prod(scalar_shape, dtype=int) + tensor_size = numpy.prod(tensor_shape, dtype=int) funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), pointers=[("const", "restrict"), ("restrict",)], qualifiers=["const"]) - scalar_size = numpy.prod(scalar_shape, dtype=int) - tensor_size = numpy.prod(tensor_shape, dtype=int) - expression = gem.reshape( - gem.Variable(name, (scalar_size, tensor_size)), - scalar_shape, tensor_shape - ) - - if interior_facet: - expression = (gem.partial_indexed(expression, (0,)), - gem.partial_indexed(expression, (1,))) + if not interior_facet: + expression = gem.reshape( + gem.Variable(name, (scalar_size, tensor_size)), + scalar_shape, tensor_shape + ) + else: + varexp = gem.Variable(name, (2 * scalar_size, tensor_size)) + plus = gem.view(varexp, slice(scalar_size), slice(tensor_size)) + minus = gem.view(varexp, slice(scalar_size, 2 * scalar_size), slice(tensor_size)) + expression = (gem.reshape(plus, scalar_shape, tensor_shape), + gem.reshape(minus, scalar_shape, tensor_shape)) return funarg, expression @@ -321,26 +323,24 @@ def prepare_arguments(arguments, multiindices, interior_facet=False): elements = tuple(create_element(arg.ufl_element()) for arg in arguments) shapes = tuple(element.index_shape for element in elements) - c_shape = numpy.array([numpy.prod(shape, dtype=int) for shape in shapes]) + def expression(restricted): + return gem.Indexed(gem.reshape(restricted, *shapes), + tuple(chain(*multiindices))) + + u_shape = numpy.array([numpy.prod(shape, dtype=int) for shape in shapes]) if interior_facet: - c_shape *= 2 - c_shape = tuple(c_shape) + c_shape = tuple(2 * u_shape) + slicez = [[slice(r * s, (r + 1) * s) + for r, s in zip(restrictions, u_shape)] + for restrictions in product((0, 1), repeat=len(arguments))] + else: + c_shape = tuple(u_shape) + slicez = [[slice(s) for s in u_shape]] funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=c_shape)) varexp = gem.Variable("A", c_shape) - - if not interior_facet: - flat_multiindex = tuple(chain(*multiindices)) - expression = gem.Indexed(gem.reshape(varexp, *shapes), flat_multiindex) - return funarg, gem.optimise.remove_componenttensors([expression]) - else: - expressions = [] - for restrictions in product((0, 1), repeat=len(arguments)): - shapes_ = tuple((2,) + shape for shape in shapes) - flat_multiindex = tuple(chain(*[(restriction,) + multiindex - for restriction, multiindex in zip(restrictions, multiindices)])) - expressions.append(gem.Indexed(gem.reshape(varexp, *shapes_), flat_multiindex)) - return funarg, gem.optimise.remove_componenttensors(expressions) + expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] + return funarg, prune(expressions) cell_orientations_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), From 7cac47a581e75f407f19a5016b88a2404025be24 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 9 Dec 2016 14:00:32 +0000 Subject: [PATCH 275/809] revise UFC arguments interface --- tsfc/kernel_interface/ufc.py | 83 +++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 348fd18a6f..5562d4c663 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -2,13 +2,16 @@ from six.moves import range, zip import numpy -from itertools import chain, product +from itertools import product from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace import coffee.base as coffee import gem +from gem.optimise import remove_componenttensors as prune + +from finat import TensorFiniteElement from tsfc.kernel_interface.common import KernelBuilderBase from tsfc.finatinterface import create_element @@ -191,10 +194,16 @@ def prepare_coordinates(coefficient, name, interior_facet=False): expression - GEM expression referring to the Coefficient values """ + finat_element = create_element(coefficient.ufl_element()) + shape = finat_element.index_shape + size = numpy.prod(shape, dtype=int) + if not interior_facet: funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), pointers=[("",)], qualifiers=["const"])] + variable = gem.Variable(name, (size,)) + expression = gem.reshape(variable, shape) else: funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_0"), pointers=[("",)], @@ -202,15 +211,6 @@ def prepare_coordinates(coefficient, name, interior_facet=False): coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_1"), pointers=[("",)], qualifiers=["const"])] - - finat_element = create_element(coefficient.ufl_element()) - shape = finat_element.index_shape - size = numpy.prod(shape, dtype=int) - - if not interior_facet: - variable = gem.Variable(name, (size,)) - expression = gem.reshape(variable, shape) - else: variable0 = gem.Variable(name+"_0", (size,)) variable1 = gem.Variable(name+"_1", (size,)) expression = (gem.reshape(variable0, shape), @@ -237,44 +237,49 @@ def prepare_arguments(arguments, multiindices, interior_facet=False): funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A"), pointers=[()]) varexp = gem.Variable("A", (None,)) - elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - ushape = tuple(numpy.prod(element.index_shape, dtype=int) for element in elements) + if len(arguments) == 0: + # No arguments + zero = coffee.FlatBlock( + "memset({name}, 0, sizeof(*{name}));\n".format(name=funarg.sym.gencode()) + ) + return funarg, [zero], [gem.reshape(varexp, ())] - # Flat indices and flat shape - reordered_multiindices = [] - indices = [] - shape = [] + elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + transposed_shapes = [] + transposed_indices = [] for element, multiindex in zip(elements, multiindices): - if hasattr(element, '_base_element'): - scalar_shape = element._base_element.index_shape + if isinstance(element, TensorFiniteElement): + scalar_shape = element.base_element.index_shape tensor_shape = element.index_shape[len(scalar_shape):] else: scalar_shape = element.index_shape tensor_shape = () - haha = tensor_shape + scalar_shape - if interior_facet: - haha = (2,) + haha - shape.extend(haha) - reordered_multiindex = multiindex[len(scalar_shape):] + multiindex[:len(scalar_shape)] - reordered_multiindices.append(reordered_multiindex) - indices.extend(reordered_multiindex) - indices = tuple(indices) - shape = tuple(shape) - - if arguments and interior_facet: - result_shape = tuple(2 * sd for sd in ushape) - expressions = [] - for restrictions in product((0, 1), repeat=len(arguments)): - flat_multiindex = tuple(chain(*[(restriction,) + multiindex - for restriction, multiindex in zip(restrictions, reordered_multiindices)])) - expressions.append(gem.Indexed(gem.reshape(varexp, shape), flat_multiindex)) + transposed_shapes.append(tensor_shape + scalar_shape) + scalar_rank = len(scalar_shape) + transposed_indices.extend(multiindex[scalar_rank:] + multiindex[:scalar_rank]) + transposed_indices = tuple(transposed_indices) + + def expression(restricted): + return gem.Indexed(gem.reshape(restricted, *transposed_shapes), + transposed_indices) + + u_shape = numpy.array([numpy.prod(element.index_shape, dtype=int) + for element in elements]) + if interior_facet: + c_shape = tuple(2 * u_shape) + slicez = [[slice(r * s, (r + 1) * s) + for r, s in zip(restrictions, u_shape)] + for restrictions in product((0, 1), repeat=len(arguments))] else: - result_shape = ushape - expressions = [gem.Indexed(gem.reshape(varexp, shape), indices)] + c_shape = tuple(u_shape) + slicez = [[slice(s) for s in u_shape]] + + expressions = [expression(gem.view(gem.reshape(varexp, c_shape), *slices)) + for slices in slicez] zero = coffee.FlatBlock( str.format("memset({name}, 0, {size} * sizeof(*{name}));\n", - name=funarg.sym.gencode(), size=numpy.product(result_shape, dtype=int)) + name=funarg.sym.gencode(), size=numpy.product(c_shape, dtype=int)) ) - return funarg, [zero], gem.optimise.remove_componenttensors(expressions) + return funarg, [zero], prune(expressions) From 1a915968ab89fe346d67751b0cf3840bb287bb9e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 9 Dec 2016 14:47:35 +0000 Subject: [PATCH 276/809] revise UFC coefficients interface --- tsfc/kernel_interface/ufc.py | 122 ++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 5562d4c663..271196588f 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -73,30 +73,28 @@ def set_coefficients(self, integral_data, form_data): :arg integral_data: UFL integral data :arg form_data: UFL form data """ - coefficients = [] - # enabled_coefficients is a boolean array that indicates which - # of reduced_coefficients the integral requires. - for i in range(len(integral_data.enabled_coefficients)): - if integral_data.enabled_coefficients[i]: - coefficient = form_data.reduced_coefficients[i] - if type(coefficient.ufl_element()) == ufl_MixedElement: - split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) - for element in coefficient.ufl_element().sub_elements()] - space_dims = [numpy.prod(create_element(element).index_shape, dtype=int) - for element in coefficient.ufl_element().sub_elements()] - offsets = numpy.cumsum([0] + space_dims[:-1]) - coefficients.extend((c, i, o) for c, o in zip(split, offsets)) - self.coefficient_split[coefficient] = split - else: - coefficients.append((coefficient, i, 0)) - + name = "w" self.coefficient_args = [ - coffee.Decl(SCALAR_TYPE, coffee.Symbol("w"), + coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), pointers=[("const",), ()], qualifiers=["const"]) ] - for c, n, o in coefficients: - self.coefficient_map[c] = prepare_coefficient(c, n, o, "w", self.interior_facet) + + # enabled_coefficients is a boolean array that indicates which + # of reduced_coefficients the integral requires. + for n in range(len(integral_data.enabled_coefficients)): + if not integral_data.enabled_coefficients[n]: + continue + + coeff = form_data.reduced_coefficients[n] + if type(coeff.ufl_element()) == ufl_MixedElement: + coeffs = [Coefficient(FunctionSpace(coeff.ufl_domain(), element)) + for element in coeff.ufl_element().sub_elements()] + self.coefficient_split[coeff] = coeffs + else: + coeffs = [coeff] + expressions = prepare_coefficients(coeffs, n, name, self.interior_facet) + self.coefficient_map.update(zip(coeffs, expressions)) def construct_kernel(self, name, body): """Construct a fully built :class:`Kernel`. @@ -139,47 +137,69 @@ def needs_cell_orientations(ir): return True -def prepare_coefficient(coefficient, number, offset, name, interior_facet=False): +def prepare_coefficients(coefficients, num, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. - :arg coefficient: UFL Coefficient - :arg number: coefficient index in the original form - :arg offset: subcoefficient DoFs start at this offset + :arg coefficients: split UFL Coefficients + :arg num: coefficient index in the original form :arg name: unique name to refer to the Coefficient in the kernel :arg interior_facet: interior facet integral? :returns: GEM expression referring to the Coefficient value """ varexp = gem.Variable(name, (None, None)) - cells_shape = () - tensor_shape = () - if coefficient.ufl_element().family() == 'Real': - scalar_shape = coefficient.ufl_shape - else: - finat_element = create_element(coefficient.ufl_element()) - if hasattr(finat_element, '_base_element'): - scalar_shape = finat_element._base_element.index_shape - tensor_shape = finat_element.index_shape[len(scalar_shape):] + if len(coefficients) == 1 and coefficients[0].ufl_element().family() == 'Real': + coefficient, = coefficients + size = numpy.prod(coefficient.ufl_shape, dtype=int) + data = gem.view(varexp, slice(num, num + 1), slice(size)) + expression = gem.reshape(data, (), coefficient.ufl_shape) + return [expression] + + elements = [create_element(coeff.ufl_element()) for coeff in coefficients] + space_dimensions = [numpy.prod(element.index_shape, dtype=int) + for element in elements] + ends = list(numpy.cumsum(space_dimensions)) + starts = [0] + ends[:-1] + slices = [slice(start, end) for start, end in zip(starts, ends)] + + transposed_shapes = [] + tensor_ranks = [] + for element in elements: + if isinstance(element, TensorFiniteElement): + scalar_shape = element.base_element.index_shape + tensor_shape = element.index_shape[len(scalar_shape):] else: - scalar_shape = finat_element.index_shape - - if interior_facet: - cells_shape = (2,) - - cells_indices = tuple(gem.Index() for s in cells_shape) - tensor_indices = tuple(gem.Index() for s in tensor_shape) - scalar_indices = tuple(gem.Index() for s in scalar_shape) - shape = cells_shape + tensor_shape + scalar_shape - alpha = cells_indices + tensor_indices + scalar_indices - beta = cells_indices + scalar_indices + tensor_indices - expression = gem.ComponentTensor(gem.Indexed(gem.reshape(gem.view(varexp, slice(number, number + 1), slice(numpy.prod(shape, dtype=int))), (), shape), - alpha), - beta) - if interior_facet: - expression = (gem.partial_indexed(expression, (0,)), - gem.partial_indexed(expression, (1,))) - return expression + scalar_shape = element.index_shape + tensor_shape = () + + transposed_shapes.append(tensor_shape + scalar_shape) + tensor_ranks.append(len(tensor_shape)) + + def transpose(expr, rank): + assert not expr.free_indices + assert 0 <= rank < len(expr.shape) + if rank == 0: + return expr + else: + indices = tuple(gem.Index(extent=extent) for extent in expr.shape) + transposed_indices = indices[rank:] + indices[:rank] + return gem.ComponentTensor(gem.Indexed(expr, indices), + transposed_indices) + + def expressions(data): + return prune([transpose(gem.reshape(gem.view(data, slice_), shape), rank) + for slice_, shape, rank in zip(slices, transposed_shapes, tensor_ranks)]) + + size = sum(space_dimensions) + if not interior_facet: + data = gem.view(varexp, slice(num, num + 1), slice(size)) + return expressions(gem.reshape(data, (), (size,))) + else: + data_p = gem.view(varexp, slice(num, num + 1), slice(size)) + data_m = gem.view(varexp, slice(num, num + 1), slice(size, 2 * size)) + return list(zip(expressions(gem.reshape(data_p, (), (size,))), + expressions(gem.reshape(data_m, (), (size,))))) def prepare_coordinates(coefficient, name, interior_facet=False): From 98b779f0d3bd7b4fe791bab7148dfe1444ca5cc7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 9 Dec 2016 15:35:12 +0000 Subject: [PATCH 277/809] comment no longer applies --- tsfc/mixedelement.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tsfc/mixedelement.py b/tsfc/mixedelement.py index daf28ca1f2..77501533aa 100644 --- a/tsfc/mixedelement.py +++ b/tsfc/mixedelement.py @@ -80,10 +80,6 @@ def tabulate(self, order, points, entity): """Tabulate a mixed element by appropriately splatting together the tabulation of the individual elements. """ - # FIXME: Could we reorder the basis functions so that indexing - # in the form compiler for mixed interior facets becomes - # easier? - # Would probably need to redo entity_dofs as well. shape = (self.space_dimension(), self.num_components(), len(points)) output = {} From 54876a5d8329531366f0c9608fc8d68cddd04cfe Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 12 Dec 2016 12:56:32 +0000 Subject: [PATCH 278/809] update docstrings: FIAT -> FInAT --- tsfc/finatinterface.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 35338ee4b5..239db6bd59 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -55,9 +55,9 @@ "Q": None, } """A :class:`.dict` mapping UFL element family names to their -FIAT-equivalent constructors. If the value is ``None``, the UFL +FInAT-equivalent constructors. If the value is ``None``, the UFL element is supported, but must be handled specially because it doesn't -have a direct FIAT equivalent.""" +have a direct FInAT equivalent.""" class FiatElementWrapper(FiatElementBase): @@ -78,7 +78,7 @@ def fiat_compat(element): @singledispatch def convert(element): - """Handler for converting UFL elements to FIAT elements. + """Handler for converting UFL elements to FInAT elements. :arg element: The UFL element to convert. @@ -138,9 +138,9 @@ def convert_tensorproductelement(element): def create_element(element): - """Create a FIAT element (suitable for tabulating with) given a UFL element. + """Create a FInAT element (suitable for tabulating with) given a UFL element. - :arg element: The UFL element to create a FIAT element from. + :arg element: The UFL element to create a FInAT element from. """ try: return _cache[element] From 4359bc05e878c3100236837aa8a76b4a8855b361 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 13 Dec 2016 09:52:16 +0000 Subject: [PATCH 279/809] merge geometric.py into fem.py They were split because the old fem.py was big, but since then both fem.py and geometric.py got shorter, so separation is no longer helpful. --- tests/test_geometry.py | 2 +- tsfc/fem.py | 114 ++++++++++++++++++++++++++++++++++++-- tsfc/geometric.py | 122 ----------------------------------------- 3 files changed, 109 insertions(+), 129 deletions(-) delete mode 100644 tsfc/geometric.py diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 95ffd08e31..05840afebe 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -6,7 +6,7 @@ from FIAT.reference_element import UFCInterval, UFCTriangle, UFCTetrahedron from FIAT.reference_element import FiredrakeQuadrilateral, TensorProductCell -from tsfc.geometric import make_cell_facet_jacobian +from tsfc.fem import make_cell_facet_jacobian interval = UFCInterval() triangle = UFCTriangle() diff --git a/tsfc/fem.py b/tsfc/fem.py index e7ab97f77b..b65b152994 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -1,6 +1,9 @@ +"""Functions to translate UFL finite element objects and reference +geometric quantities into GEM expressions.""" + from __future__ import absolute_import, print_function, division from six import iterkeys, iteritems, itervalues -from six.moves import map +from six.moves import map, range, zip import collections import itertools @@ -10,8 +13,14 @@ from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, Coefficient, CellVolume, FacetArea, - GeometricQuantity, QuadratureWeight) +from ufl.classes import (Argument, CellCoordinate, CellEdgeVectors, + CellFacetJacobian, CellOrientation, + CellVolume, Coefficient, FacetArea, + FacetCoordinate, GeometricQuantity, + QuadratureWeight, ReferenceCellVolume, + ReferenceFacetVolume, ReferenceNormal) + +from FIAT.reference_element import make_affine_mapping import gem from gem.node import traversal @@ -20,11 +29,11 @@ from finat.quadrature import make_quadrature -from tsfc import ufl2gem, geometric +from tsfc import ufl2gem from tsfc.finatinterface import create_element, as_fiat_cell from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import analyse_modified_terminal -from tsfc.parameters import PARAMETERS +from tsfc.parameters import NUMPY_TYPE, PARAMETERS from tsfc.ufl_utils import ModifiedTerminalMixin, PickRestriction, simplify_abs @@ -140,7 +149,100 @@ def translate_quadratureweight(terminal, mt, ctx): @translate.register(GeometricQuantity) def translate_geometricquantity(terminal, mt, ctx): - return geometric.translate(terminal, mt, ctx) + raise NotImplementedError("Cannot handle geometric quantity type: %s" % type(terminal)) + + +@translate.register(CellOrientation) +def translate_cell_orientation(terminal, mt, ctx): + return ctx.cell_orientation(mt.restriction) + + +@translate.register(ReferenceCellVolume) +def translate_reference_cell_volume(terminal, mt, ctx): + return gem.Literal(ctx.fiat_cell.volume()) + + +@translate.register(ReferenceFacetVolume) +def translate_reference_facet_volume(terminal, mt, ctx): + # FIXME: simplex only code path + dim = ctx.fiat_cell.get_spatial_dimension() + facet_cell = ctx.fiat_cell.construct_subelement(dim - 1) + return gem.Literal(facet_cell.volume()) + + +@translate.register(CellFacetJacobian) +def translate_cell_facet_jacobian(terminal, mt, ctx): + cell = ctx.fiat_cell + facet_dim = ctx.integration_dim + assert facet_dim != cell.get_dimension() + + def callback(entity_id): + return gem.Literal(make_cell_facet_jacobian(cell, facet_dim, entity_id)) + return ctx.entity_selector(callback, mt.restriction) + + +def make_cell_facet_jacobian(cell, facet_dim, facet_i): + facet_cell = cell.construct_subelement(facet_dim) + xs = facet_cell.get_vertices() + ys = cell.get_vertices_of_subcomplex(cell.get_topology()[facet_dim][facet_i]) + + # Use first 'dim' points to make an affine mapping + dim = cell.get_spatial_dimension() + A, b = make_affine_mapping(xs[:dim], ys[:dim]) + + for x, y in zip(xs[dim:], ys[dim:]): + # The rest of the points are checked to make sure the + # mapping really *is* affine. + assert numpy.allclose(y, A.dot(x) + b) + + return A + + +@translate.register(ReferenceNormal) +def translate_reference_normal(terminal, mt, ctx): + def callback(facet_i): + n = ctx.fiat_cell.compute_reference_normal(ctx.integration_dim, facet_i) + return gem.Literal(n) + return ctx.entity_selector(callback, mt.restriction) + + +@translate.register(CellEdgeVectors) +def translate_cell_edge_vectors(terminal, mt, ctx): + from FIAT.reference_element import TensorProductCell as fiat_TensorProductCell + fiat_cell = ctx.fiat_cell + if isinstance(fiat_cell, fiat_TensorProductCell): + raise NotImplementedError("CellEdgeVectors not implemented on TensorProductElements yet") + + nedges = len(fiat_cell.get_topology()[1]) + vecs = numpy.vstack(map(fiat_cell.compute_edge_tangent, range(nedges))).astype(NUMPY_TYPE) + assert vecs.shape == terminal.ufl_shape + return gem.Literal(vecs) + + +@translate.register(CellCoordinate) +def translate_cell_coordinate(terminal, mt, ctx): + ps = ctx.point_set + if ctx.integration_dim == ctx.fiat_cell.get_dimension(): + return ps.expression + + # This destroys the structure of the quadrature points, but since + # this code path is only used to implement CellCoordinate in facet + # integrals, hopefully it does not matter much. + point_shape = tuple(index.extent for index in ps.indices) + + def callback(entity_id): + t = ctx.fiat_cell.get_entity_transform(ctx.integration_dim, entity_id) + data = numpy.asarray(list(map(t, ps.points))) + return gem.Literal(data.reshape(point_shape + data.shape[1:])) + + return gem.partial_indexed(ctx.entity_selector(callback, mt.restriction), + ps.indices) + + +@translate.register(FacetCoordinate) +def translate_facet_coordinate(terminal, mt, ctx): + assert ctx.integration_dim != ctx.fiat_cell.get_dimension() + return ctx.point_set.expression @translate.register(CellVolume) diff --git a/tsfc/geometric.py b/tsfc/geometric.py deleted file mode 100644 index 004218295e..0000000000 --- a/tsfc/geometric.py +++ /dev/null @@ -1,122 +0,0 @@ -"""Functions to translate UFL reference geometric quantities into GEM -expressions.""" - -from __future__ import absolute_import, print_function, division - -from numpy import allclose, asarray, vstack -from singledispatch import singledispatch - -from ufl.classes import (CellCoordinate, CellEdgeVectors, - CellFacetJacobian, CellOrientation, - FacetCoordinate, ReferenceCellVolume, - ReferenceFacetVolume, ReferenceNormal) - -from FIAT.reference_element import make_affine_mapping - -import gem - -from tsfc.parameters import NUMPY_TYPE - - -@singledispatch -def translate(terminal, mt, params): - """Translate geometric UFL quantity into GEM expression. - - :arg terminal: UFL geometric quantity terminal - :arg mt: modified terminal data (e.g. for restriction) - :arg params: miscellaneous - """ - raise AssertionError("Cannot handle geometric quantity type: %s" % type(terminal)) - - -@translate.register(CellOrientation) -def translate_cell_orientation(terminal, mt, params): - return params.cell_orientation(mt.restriction) - - -@translate.register(ReferenceCellVolume) -def translate_reference_cell_volume(terminal, mt, params): - return gem.Literal(params.fiat_cell.volume()) - - -@translate.register(ReferenceFacetVolume) -def translate_reference_facet_volume(terminal, mt, params): - # FIXME: simplex only code path - dim = params.fiat_cell.get_spatial_dimension() - facet_cell = params.fiat_cell.construct_subelement(dim - 1) - return gem.Literal(facet_cell.volume()) - - -@translate.register(CellFacetJacobian) -def translate_cell_facet_jacobian(terminal, mt, params): - cell = params.fiat_cell - facet_dim = params.integration_dim - assert facet_dim != cell.get_dimension() - - def callback(entity_id): - return gem.Literal(make_cell_facet_jacobian(cell, facet_dim, entity_id)) - return params.entity_selector(callback, mt.restriction) - - -def make_cell_facet_jacobian(cell, facet_dim, facet_i): - facet_cell = cell.construct_subelement(facet_dim) - xs = facet_cell.get_vertices() - ys = cell.get_vertices_of_subcomplex(cell.get_topology()[facet_dim][facet_i]) - - # Use first 'dim' points to make an affine mapping - dim = cell.get_spatial_dimension() - A, b = make_affine_mapping(xs[:dim], ys[:dim]) - - for x, y in zip(xs[dim:], ys[dim:]): - # The rest of the points are checked to make sure the - # mapping really *is* affine. - assert allclose(y, A.dot(x) + b) - - return A - - -@translate.register(ReferenceNormal) -def translate_reference_normal(terminal, mt, params): - def callback(facet_i): - n = params.fiat_cell.compute_reference_normal(params.integration_dim, facet_i) - return gem.Literal(n) - return params.entity_selector(callback, mt.restriction) - - -@translate.register(CellEdgeVectors) -def translate_cell_edge_vectors(terminal, mt, params): - from FIAT.reference_element import TensorProductCell as fiat_TensorProductCell - fiat_cell = params.fiat_cell - if isinstance(fiat_cell, fiat_TensorProductCell): - raise NotImplementedError("CellEdgeVectors not implemented on TensorProductElements yet") - - nedges = len(fiat_cell.get_topology()[1]) - vecs = vstack(map(fiat_cell.compute_edge_tangent, range(nedges))).astype(NUMPY_TYPE) - assert vecs.shape == terminal.ufl_shape - return gem.Literal(vecs) - - -@translate.register(CellCoordinate) -def translate_cell_coordinate(terminal, mt, params): - ps = params.point_set - if params.integration_dim == params.fiat_cell.get_dimension(): - return ps.expression - - # This destroys the structure of the quadrature points, but since - # this code path is only used to implement CellCoordinate in facet - # integrals, hopefully it does not matter much. - point_shape = tuple(index.extent for index in ps.indices) - - def callback(entity_id): - t = params.fiat_cell.get_entity_transform(params.integration_dim, entity_id) - data = asarray(list(map(t, ps.points))) - return gem.Literal(data.reshape(point_shape + data.shape[1:])) - - return gem.partial_indexed(params.entity_selector(callback, mt.restriction), - ps.indices) - - -@translate.register(FacetCoordinate) -def translate_facet_coordinate(terminal, mt, params): - assert params.integration_dim != params.fiat_cell.get_dimension() - return params.point_set.expression From 6c29fc86c3994da182f6fc52d808ced35707c9ad Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 12 Jan 2017 15:57:17 +0000 Subject: [PATCH 280/809] adopt UFL changes --- tsfc/fiatinterface.py | 8 ++------ tsfc/finatinterface.py | 6 +----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 74fefaabc7..6c16f6e917 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -33,7 +33,6 @@ from FIAT.quadrature import QuadratureRule # noqa import ufl -from ufl.algorithms.elementtransformations import reconstruct_element from .mixedelement import MixedElement @@ -186,10 +185,7 @@ def _(element, vector_is_mixed): raise ValueError("%s is supported, but handled incorrectly" % element.family()) # Handle quadrilateral short names like RTCF and RTCE. - element = reconstruct_element(element, - element.family(), - quad_opc, - element.degree()) + element = element.reconstruct(cell=quad_tpc) return FlattenToQuad(create_element(element, vector_is_mixed)) return lmbda(cell, element.degree()) @@ -267,7 +263,7 @@ def rec(eles): return MixedElement(fiat_elements) -quad_opc = ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")) +quad_tpc = ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")) _cache = weakref.WeakKeyDictionary() diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 239db6bd59..347ecb650b 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -30,7 +30,6 @@ from finat.fiat_elements import FiatElementBase import ufl -from ufl.algorithms.elementtransformations import reconstruct_element from tsfc.fiatinterface import as_fiat_cell from tsfc.ufl_utils import spanning_degree @@ -101,10 +100,7 @@ def convert_finiteelement(element): raise ValueError("%s is supported, but handled incorrectly" % element.family()) # Handle quadrilateral short names like RTCF and RTCE. - element = reconstruct_element(element, - element.family(), - quad_tpc, - element.degree()) + element = element.reconstruct(cell=quad_tpc) return finat.QuadrilateralElement(create_element(element)) return lmbda(cell, element.degree()) From ca2e6d9a43f016d8fe0a35e8c899bd200a8fcbac Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 20 Jan 2017 13:57:09 +0000 Subject: [PATCH 281/809] translate spectral elements to GLL/GL --- tsfc/fiatinterface.py | 19 +++++++++++++++++++ tsfc/finatinterface.py | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 6c16f6e917..af8c706a8a 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -187,6 +187,25 @@ def _(element, vector_is_mixed): # Handle quadrilateral short names like RTCF and RTCE. element = element.reconstruct(cell=quad_tpc) return FlattenToQuad(create_element(element, vector_is_mixed)) + + kind = element.variant() + if kind is None: + kind = 'equispaced' # default variant + + if element.family() == "Lagrange": + if kind == 'equispaced': + lmbda = FIAT.Lagrange + elif kind == 'spectral' and element.cell().cellname() == 'interval': + lmbda = FIAT.GaussLobattoLegendre + else: + raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) + elif element.family() == "Discontinuous Lagrange": + if kind == 'equispaced': + lmbda = FIAT.DiscontinuousLagrange + elif kind == 'spectral' and element.cell().cellname() == 'interval': + lmbda = FIAT.GaussLegendre + else: + raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) return lmbda(cell, element.degree()) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 347ecb650b..1e48cac2a7 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -102,6 +102,26 @@ def convert_finiteelement(element): # Handle quadrilateral short names like RTCF and RTCE. element = element.reconstruct(cell=quad_tpc) return finat.QuadrilateralElement(create_element(element)) + + kind = element.variant() + if kind is None: + kind = 'equispaced' # default variant + + if element.family() == "Lagrange": + if kind == 'equispaced': + lmbda = finat.Lagrange + elif kind == 'spectral' and element.cell().cellname() == 'interval': + lmbda = finat.GaussLobattoLegendre + else: + raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) + elif element.family() == "Discontinuous Lagrange": + kind = element.variant() or 'equispaced' + if kind == 'equispaced': + lmbda = finat.DiscontinuousLagrange + elif kind == 'spectral' and element.cell().cellname() == 'interval': + lmbda = finat.GaussLegendre + else: + raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) return lmbda(cell, element.degree()) From 40474f95eff0eb281866122f5abd4ff304d006c2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 20 Jan 2017 13:57:33 +0000 Subject: [PATCH 282/809] test translation of element variants --- tests/test_create_fiat_element.py | 76 ++++++++++++++++++++++++++---- tests/test_create_finat_element.py | 71 +++++++++++++++++++++++----- 2 files changed, 126 insertions(+), 21 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index a1322685c4..4e74878ae2 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -1,7 +1,11 @@ from __future__ import absolute_import, print_function, division -from tsfc import fiatinterface as f import pytest + +import FIAT +from FIAT.discontinuous_lagrange import HigherOrderDiscontinuousLagrange as FIAT_DiscontinuousLagrange + import ufl +from tsfc.fiatinterface import create_element, supported_elements @pytest.fixture(params=["BDM", @@ -22,8 +26,8 @@ def ufl_element(triangle_names): def test_triangle_basic(ufl_element): - element = f.create_element(ufl_element) - assert isinstance(element, f.supported_elements[ufl_element.family()]) + element = create_element(ufl_element) + assert isinstance(element, supported_elements[ufl_element.family()]) @pytest.fixture(params=["CG", "DG"]) @@ -46,19 +50,73 @@ def ufl_B(tensor_name): def test_tensor_prod_simple(ufl_A, ufl_B): tensor_ufl = ufl.TensorProductElement(ufl_A, ufl_B) - tensor = f.create_element(tensor_ufl) - A = f.create_element(ufl_A) - B = f.create_element(ufl_B) + tensor = create_element(tensor_ufl) + A = create_element(ufl_A) + B = create_element(ufl_B) - assert isinstance(tensor, f.supported_elements[tensor_ufl.family()]) + assert isinstance(tensor, supported_elements[tensor_ufl.family()]) assert tensor.A is A assert tensor.B is B +@pytest.mark.parametrize(('family', 'expected_cls'), + [('P', FIAT.Lagrange), + ('DP', FIAT_DiscontinuousLagrange)]) +def test_interval_variant_default(family, expected_cls): + ufl_element = ufl.FiniteElement(family, ufl.interval, 3) + assert isinstance(create_element(ufl_element), expected_cls) + + +@pytest.mark.parametrize(('family', 'variant', 'expected_cls'), + [('P', 'equispaced', FIAT.Lagrange), + ('P', 'spectral', FIAT.GaussLobattoLegendre), + ('DP', 'equispaced', FIAT_DiscontinuousLagrange), + ('DP', 'spectral', FIAT.GaussLegendre)]) +def test_interval_variant(family, variant, expected_cls): + ufl_element = ufl.FiniteElement(family, ufl.interval, 3, variant=variant) + assert isinstance(create_element(ufl_element), expected_cls) + + +def test_triangle_variant_spectral_fail(): + ufl_element = ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') + with pytest.raises(ValueError): + create_element(ufl_element) + + +def test_triangle_x_interval_variant(): + ufl_cell = ufl.TensorProductCell(ufl.triangle, ufl.interval) + ufl_element = ufl.FiniteElement('DG', ufl_cell, 2, variant=('equispaced', 'spectral')) + actual = create_element(ufl_element) + + assert isinstance(actual, FIAT.TensorProductElement) + assert isinstance(actual.A, FIAT_DiscontinuousLagrange) + assert isinstance(actual.B, FIAT.GaussLegendre) + + +def test_quadrilateral_variant_spectral_q(): + element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) + assert isinstance(element.element.A, FIAT.GaussLobattoLegendre) + assert isinstance(element.element.B, FIAT.GaussLobattoLegendre) + + +def test_quadrilateral_variant_spectral_dq(): + element = create_element(ufl.FiniteElement('DQ', ufl.quadrilateral, 1, variant='spectral')) + assert isinstance(element.element.A, FIAT.GaussLegendre) + assert isinstance(element.element.B, FIAT.GaussLegendre) + + +def test_quadrilateral_variant_spectral_rtcf(): + element = create_element(ufl.FiniteElement('RTCF', ufl.quadrilateral, 2, variant='spectral')) + assert isinstance(element.element.A.A, FIAT.GaussLobattoLegendre) + assert isinstance(element.element.A.B, FIAT.GaussLegendre) + assert isinstance(element.element.B.A, FIAT.GaussLegendre) + assert isinstance(element.element.B.B, FIAT.GaussLobattoLegendre) + + def test_cache_hit(ufl_element): - A = f.create_element(ufl_element) - B = f.create_element(ufl_element) + A = create_element(ufl_element) + B = create_element(ufl_element) assert A is B diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index 25167ccfe6..743c9c158e 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -1,8 +1,9 @@ from __future__ import absolute_import, print_function, division -from tsfc import finatinterface as f import pytest + import ufl import finat +from tsfc.finatinterface import create_element, supported_elements @pytest.fixture(params=["BDM", @@ -23,8 +24,8 @@ def ufl_element(triangle_names): def test_triangle_basic(ufl_element): - element = f.create_element(ufl_element) - assert isinstance(element, f.supported_elements[ufl_element.family()]) + element = create_element(ufl_element) + assert isinstance(element, supported_elements[ufl_element.family()]) @pytest.fixture @@ -33,8 +34,8 @@ def ufl_vector_element(triangle_names): def test_triangle_vector(ufl_element, ufl_vector_element): - scalar = f.create_element(ufl_element) - vector = f.create_element(ufl_vector_element) + scalar = create_element(ufl_element) + vector = create_element(ufl_vector_element) assert isinstance(vector, finat.TensorFiniteElement) assert scalar == vector.base_element @@ -60,25 +61,71 @@ def ufl_B(tensor_name): def test_tensor_prod_simple(ufl_A, ufl_B): tensor_ufl = ufl.TensorProductElement(ufl_A, ufl_B) - tensor = f.create_element(tensor_ufl) - A = f.create_element(ufl_A) - B = f.create_element(ufl_B) + tensor = create_element(tensor_ufl) + A = create_element(ufl_A) + B = create_element(ufl_B) assert isinstance(tensor, finat.TensorProductElement) assert tensor.factors == (A, B) +@pytest.mark.parametrize(('family', 'expected_cls'), + [('P', finat.Lagrange), + ('DP', finat.DiscontinuousLagrange)]) +def test_interval_variant_default(family, expected_cls): + ufl_element = ufl.FiniteElement(family, ufl.interval, 3) + assert isinstance(create_element(ufl_element), expected_cls) + + +@pytest.mark.parametrize(('family', 'variant', 'expected_cls'), + [('P', 'equispaced', finat.Lagrange), + ('P', 'spectral', finat.GaussLobattoLegendre), + ('DP', 'equispaced', finat.DiscontinuousLagrange), + ('DP', 'spectral', finat.GaussLegendre)]) +def test_interval_variant(family, variant, expected_cls): + ufl_element = ufl.FiniteElement(family, ufl.interval, 3, variant=variant) + assert isinstance(create_element(ufl_element), expected_cls) + + +def test_triangle_variant_spectral_fail(): + ufl_element = ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') + with pytest.raises(ValueError): + create_element(ufl_element) + + +def test_triangle_x_interval_variant(): + ufl_cell = ufl.TensorProductCell(ufl.triangle, ufl.interval) + ufl_element = ufl.FiniteElement('DG', ufl_cell, 2, variant=('equispaced', 'spectral')) + actual = create_element(ufl_element) + + assert isinstance(actual, finat.TensorProductElement) + assert isinstance(actual.factors[0], finat.DiscontinuousLagrange) + assert isinstance(actual.factors[1], finat.GaussLegendre) + + +def test_quadrilateral_variant_spectral_q(): + element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) + assert isinstance(element.product.factors[0], finat.GaussLobattoLegendre) + assert isinstance(element.product.factors[1], finat.GaussLobattoLegendre) + + +def test_quadrilateral_variant_spectral_dq(): + element = create_element(ufl.FiniteElement('DQ', ufl.quadrilateral, 1, variant='spectral')) + assert isinstance(element.product.factors[0], finat.GaussLegendre) + assert isinstance(element.product.factors[1], finat.GaussLegendre) + + def test_cache_hit(ufl_element): - A = f.create_element(ufl_element) - B = f.create_element(ufl_element) + A = create_element(ufl_element) + B = create_element(ufl_element) assert A is B def test_cache_hit_vector(ufl_vector_element): - A = f.create_element(ufl_vector_element) - B = f.create_element(ufl_vector_element) + A = create_element(ufl_vector_element) + B = create_element(ufl_vector_element) assert A is B From 7babfd12dc82120275b3cbe2873c658dd59b794e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 27 Jan 2017 11:20:26 +0000 Subject: [PATCH 283/809] remove variant=('equispaced', 'spectral') tests --- tests/test_create_fiat_element.py | 10 ---------- tests/test_create_finat_element.py | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 4e74878ae2..ea8e0dc0c0 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -84,16 +84,6 @@ def test_triangle_variant_spectral_fail(): create_element(ufl_element) -def test_triangle_x_interval_variant(): - ufl_cell = ufl.TensorProductCell(ufl.triangle, ufl.interval) - ufl_element = ufl.FiniteElement('DG', ufl_cell, 2, variant=('equispaced', 'spectral')) - actual = create_element(ufl_element) - - assert isinstance(actual, FIAT.TensorProductElement) - assert isinstance(actual.A, FIAT_DiscontinuousLagrange) - assert isinstance(actual.B, FIAT.GaussLegendre) - - def test_quadrilateral_variant_spectral_q(): element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) assert isinstance(element.element.A, FIAT.GaussLobattoLegendre) diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index 743c9c158e..ebac51c3ef 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -94,16 +94,6 @@ def test_triangle_variant_spectral_fail(): create_element(ufl_element) -def test_triangle_x_interval_variant(): - ufl_cell = ufl.TensorProductCell(ufl.triangle, ufl.interval) - ufl_element = ufl.FiniteElement('DG', ufl_cell, 2, variant=('equispaced', 'spectral')) - actual = create_element(ufl_element) - - assert isinstance(actual, finat.TensorProductElement) - assert isinstance(actual.factors[0], finat.DiscontinuousLagrange) - assert isinstance(actual.factors[1], finat.GaussLegendre) - - def test_quadrilateral_variant_spectral_q(): element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) assert isinstance(element.product.factors[0], finat.GaussLobattoLegendre) From 4283b6be43449d299f22dd69cf20d5bc9d8de0f5 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 1 Feb 2017 12:25:49 +0000 Subject: [PATCH 284/809] Add test of simplification in Conditional --- tests/test_simplification.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_simplification.py diff --git a/tests/test_simplification.py b/tests/test_simplification.py new file mode 100644 index 0000000000..6df0be4365 --- /dev/null +++ b/tests/test_simplification.py @@ -0,0 +1,33 @@ +from __future__ import absolute_import, print_function, division + +import pytest + +from gem.gem import Variable, Zero, Conditional, \ + LogicalAnd, Index, Indexed, Product + + +def test_conditional_simplification(): + a = Variable("A", ()) + b = Variable("B", ()) + + expr = Conditional(LogicalAnd(b, a), a, a) + + assert expr == a + + +def test_conditional_zero_folding(): + b = Variable("B", ()) + a = Variable("A", (3, )) + i = Index() + expr = Conditional(LogicalAnd(b, b), + Product(Indexed(a, (i, )), + Zero()), + Zero()) + + assert expr == Zero() + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 82aaeff3056511e1e63a713bb3b7e4118923e43e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 15 Dec 2016 10:21:57 +0000 Subject: [PATCH 285/809] add pickling test for "all" protocol versions --- tests/test_pickle_gem.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/test_pickle_gem.py diff --git a/tests/test_pickle_gem.py b/tests/test_pickle_gem.py new file mode 100644 index 0000000000..f246a0cbfa --- /dev/null +++ b/tests/test_pickle_gem.py @@ -0,0 +1,24 @@ +from __future__ import absolute_import, print_function, division +from six.moves import cPickle as pickle, range +import gem +import numpy +import pytest + + +@pytest.mark.parametrize('protocol', range(3)) +def test_pickle_gem(protocol): + f = gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (1,))) + q = gem.Index() + r = gem.Index() + _1 = gem.Indexed(gem.Literal(numpy.random.rand(3, 6, 8)), (f, q, r)) + _2 = gem.Indexed(gem.view(gem.Variable('w', (None, None)), slice(8), slice(1)), (r, 0)) + expr = gem.ComponentTensor(gem.IndexSum(gem.Product(_1, _2), (r,)), (q,)) + + unpickled = pickle.loads(pickle.dumps(expr, protocol)) + assert repr(expr) == repr(unpickled) + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From ca7480fc67dadd060f3cb42ea43b0065eb79259a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 9 Mar 2017 15:40:21 +0000 Subject: [PATCH 286/809] Generalise IndexSum unrolling --- tsfc/driver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 5c695ba6be..8898ebadc7 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -159,7 +159,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, config.update(quadrature_rule=quad_rule) ir = fem.compile_ufl(integrand, interior_facet=interior_facet, **config) if parameters["unroll_indexsum"]: - ir = opt.unroll_indexsum(ir, max_extent=parameters["unroll_indexsum"]) + def predicate(index): + return index.extent <= parameters["unroll_indexsum"] + ir = opt.unroll_indexsum(ir, predicate=predicate) ir = [gem.index_sum(expr, quadrature_multiindex) for expr in ir] irs.append(ir) From 1a0ae4c0d5ab25f1bdbdfb630e0ab45605ff002b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 15 Mar 2017 14:33:53 +0000 Subject: [PATCH 287/809] add simple test case --- tests/test_refactorise.py | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/test_refactorise.py diff --git a/tests/test_refactorise.py b/tests/test_refactorise.py new file mode 100644 index 0000000000..c12747eaf6 --- /dev/null +++ b/tests/test_refactorise.py @@ -0,0 +1,68 @@ +from __future__ import absolute_import, print_function, division + +from functools import partial + +import pytest + +import gem +from gem.node import traversal +from gem.refactorise import ATOMIC, COMPOUND, OTHER, Monomial, collect_monomials + + +def test_refactorise(): + f = gem.Variable('f', (3,)) + u = gem.Variable('u', (3,)) + v = gem.Variable('v', ()) + + i = gem.Index() + f_i = gem.Indexed(f, (i,)) + u_i = gem.Indexed(u, (i,)) + + def classify(atomics_set, expression): + if expression in atomics_set: + return ATOMIC + + for node in traversal([expression]): + if node in atomics_set: + return COMPOUND + + return OTHER + classifier = partial(classify, {u_i, v}) + + # \sum_i 5*(2*u_i + -1*v)*(u_i + v*f) + expr = gem.IndexSum( + gem.Product( + gem.Literal(5), + gem.Product( + gem.Sum(gem.Product(gem.Literal(2), u_i), + gem.Product(gem.Literal(-1), v)), + gem.Sum(u_i, gem.Product(v, f_i)) + ) + ), + (i,) + ) + + expected = [ + Monomial((i,), + (u_i, u_i), + gem.Literal(10)), + Monomial((i,), + (u_i, v), + gem.Product(gem.Literal(5), + gem.Sum(gem.Product(f_i, gem.Literal(2)), + gem.Literal(-1)))), + Monomial((), + (v, v), + gem.Product(gem.Literal(5), + gem.IndexSum(gem.Product(f_i, gem.Literal(-1)), + (i,)))), + ] + + actual, = collect_monomials([expr], classifier) + assert expected == list(actual) + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 7d24fdaab108f514a508ed8aa7d233460117fa35 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 20 Mar 2017 11:59:01 +0000 Subject: [PATCH 288/809] long overdue renames --- tsfc/driver.py | 19 +++++++++---------- tsfc/fem.py | 10 +++++----- tsfc/kernel_interface/firedrake.py | 6 +++--- tsfc/kernel_interface/ufc.py | 6 +++--- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 8898ebadc7..fbd3bcc6c4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -90,17 +90,16 @@ def compile_integral(integral_data, form_data, prefix, parameters, fiat_cell = as_fiat_cell(cell) integration_dim, entity_ids = lower_integral_type(fiat_cell, integral_type) - argument_indices = tuple(tuple(gem.Index(extent=e) - for e in create_element(arg.ufl_element()).index_shape) - for arg in arguments) - flat_argument_indices = tuple(chain(*argument_indices)) + argument_multiindices = tuple(create_element(arg.ufl_element()).get_indices() + for arg in arguments) + argument_indices = tuple(chain(*argument_multiindices)) quadrature_indices = [] # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() builder = interface.KernelBuilder(integral_type, integral_data.subdomain_id, domain_numbering[integral_data.domain]) - return_variables = builder.set_arguments(arguments, argument_indices) + return_variables = builder.set_arguments(arguments, argument_multiindices) coordinates = ufl_utils.coordinate_coefficient(mesh) builder.set_coordinates(coordinates) @@ -119,7 +118,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, precision=parameters["precision"], integration_dim=integration_dim, entity_ids=entity_ids, - argument_indices=argument_indices, + argument_multiindices=argument_multiindices, index_cache=index_cache) kernel_cfg["facetarea"] = facetarea_generator(mesh, coordinates, kernel_cfg, integral_type) @@ -153,7 +152,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, type(quad_rule)) quadrature_multiindex = quad_rule.point_set.indices - quadrature_indices += quadrature_multiindex + quadrature_indices.extend(quadrature_multiindex) config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule) @@ -176,12 +175,12 @@ def predicate(index): builder.require_cell_orientations() impero_c = impero_utils.compile_gem(return_variables, ir, - tuple(quadrature_indices) + flat_argument_indices, + tuple(quadrature_indices) + argument_indices, remove_zeros=True) # Generate COFFEE index_names = [(si, name + str(n)) - for index, name in zip(argument_indices, ['j', 'k']) + for index, name in zip(argument_multiindices, ['j', 'k']) for n, si in enumerate(index)] if len(quadrature_indices) == 1: index_names.append((quadrature_indices[0], 'ip')) @@ -189,7 +188,7 @@ def predicate(index): for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body = generate_coffee(impero_c, index_names, parameters["precision"], ir, flat_argument_indices) + body = generate_coffee(impero_c, index_names, parameters["precision"], ir, argument_indices) kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) return builder.construct_kernel(kernel_name, body) diff --git a/tsfc/fem.py b/tsfc/fem.py index b65b152994..7d8b5eb23f 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -47,7 +47,7 @@ class Context(ProxyKernelInterface): 'point_set', 'weight_expr', 'precision', - 'argument_indices', + 'argument_multiindices', 'cellvolume', 'facetarea', 'index_cache') @@ -107,7 +107,7 @@ def entity_selector(self, callback, restriction): f = self.facet_number(restriction) return gem.select_expression(list(map(callback, self.entity_ids)), f) - argument_indices = () + argument_multiindices = () @cached_property def index_cache(self): @@ -279,7 +279,7 @@ def fiat_to_ufl(fiat_dict, order): @translate.register(Argument) def translate_argument(terminal, mt, ctx): - argument_index = ctx.argument_indices[terminal.number()] + argument_multiindex = ctx.argument_multiindices[terminal.number()] sigma = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) element = create_element(terminal.ufl_element()) @@ -299,7 +299,7 @@ def callback(entity_id): # lives on after ditching FFC and switching to FInAT. return ffc_rounding(square, ctx.epsilon) table = ctx.entity_selector(callback, mt.restriction) - return gem.ComponentTensor(gem.Indexed(table, argument_index + sigma), sigma) + return gem.ComponentTensor(gem.Indexed(table, argument_multiindex + sigma), sigma) @translate.register(Coefficient) @@ -370,7 +370,7 @@ def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): expression = simplify_abs(expression) if interior_facet: expressions = [] - for rs in itertools.product(("+", "-"), repeat=len(context.argument_indices)): + for rs in itertools.product(("+", "-"), repeat=len(context.argument_multiindices)): expressions.append(map_expr_dag(PickRestriction(*rs), expression)) else: expressions = [expression] diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 85ec55de79..c41cafc02b 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -165,15 +165,15 @@ def __init__(self, integral_type, subdomain_id, domain_number): elif integral_type == 'interior_facet_horiz': self._facet_number = {'+': 1, '-': 0} - def set_arguments(self, arguments, indices): + def set_arguments(self, arguments, multiindices): """Process arguments. :arg arguments: :class:`ufl.Argument`s - :arg indices: GEM argument indices + :arg multiindices: GEM argument multiindices :returns: GEM expression representing the return variable """ self.local_tensor, expressions = prepare_arguments( - arguments, indices, interior_facet=self.interior_facet) + arguments, multiindices, interior_facet=self.interior_facet) return expressions def set_coordinates(self, coefficient): diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 271196588f..a0492cc35b 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -45,15 +45,15 @@ def __init__(self, integral_type, subdomain_id, domain_number): '-': gem.VariableIndex(gem.Variable("facet_1", ())) } - def set_arguments(self, arguments, indices): + def set_arguments(self, arguments, multiindices): """Process arguments. :arg arguments: :class:`ufl.Argument`s - :arg indices: GEM argument indices + :arg multiindices: GEM argument multiindices :returns: GEM expression representing the return variable """ self.local_tensor, prepare, expressions = prepare_arguments( - arguments, indices, interior_facet=self.interior_facet) + arguments, multiindices, interior_facet=self.interior_facet) self.apply_glue(prepare) return expressions From 37f44b3194d13b90c0ebe72090dd7fcd9f6b6fbf Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 20 Mar 2017 12:08:50 +0000 Subject: [PATCH 289/809] fix unuse of tsfc.fem.Context.index_cache --- tsfc/driver.py | 9 +++------ tsfc/fem.py | 5 +++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index fbd3bcc6c4..558c83c4c6 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,7 +1,6 @@ from __future__ import absolute_import, print_function, division from six.moves import range -import collections import time from functools import reduce from itertools import chain @@ -106,12 +105,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, builder.set_coefficients(integral_data, form_data) - # Map from UFL FiniteElement objects to Index instances. This is + # Map from UFL FiniteElement objects to multiindices. This is # so we reuse Index instances when evaluating the same coefficient - # multiple times with the same table. Occurs, for example, if we - # have multiple integrals here (and the affine coordinate - # evaluation can be hoisted). - index_cache = collections.defaultdict(gem.Index) + # multiple times with the same table. + index_cache = {} kernel_cfg = dict(interface=builder, ufl_cell=cell, diff --git a/tsfc/fem.py b/tsfc/fem.py index 7d8b5eb23f..01a67054ac 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -111,7 +111,7 @@ def entity_selector(self, callback, restriction): @cached_property def index_cache(self): - return collections.defaultdict(gem.Index) + return {} class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): @@ -340,7 +340,8 @@ def take_singleton(xs): for alpha, tables in iteritems(per_derivative)} # Coefficient evaluation - beta = element.get_indices() + ctx.index_cache.setdefault(terminal.ufl_element(), element.get_indices()) + beta = ctx.index_cache[terminal.ufl_element()] zeta = element.get_value_indices() value_dict = {} for alpha, table in iteritems(per_derivative): From 3c044f29667d82d44c5a891bf2eeaa52c51ac5d4 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 23 Mar 2017 13:36:13 +0000 Subject: [PATCH 290/809] change impero_utils.compile_gem API --- tests/test_codegen.py | 2 +- tsfc/driver.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index ee9a8d31a5..b06942814a 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -20,7 +20,7 @@ def make_expression(i, j): e2 = make_expression(i, i) def gencode(expr): - impero_c = impero_utils.compile_gem([Ri], [expr], (i, j)) + impero_c = impero_utils.compile_gem([(Ri, expr)], (i, j)) return impero_c.tree assert len(gencode(e1).children) == len(gencode(e2).children) diff --git a/tsfc/driver.py b/tsfc/driver.py index 558c83c4c6..37efb55165 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,5 +1,5 @@ from __future__ import absolute_import, print_function, division -from six.moves import range +from six.moves import range, zip import time from functools import reduce @@ -171,7 +171,8 @@ def predicate(index): if builder.needs_cell_orientations(ir): builder.require_cell_orientations() - impero_c = impero_utils.compile_gem(return_variables, ir, + assignments = list(zip(return_variables, ir)) + impero_c = impero_utils.compile_gem(assignments, tuple(quadrature_indices) + argument_indices, remove_zeros=True) @@ -301,7 +302,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non return_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('A', rank=return_shape)) return_expr = gem.Indexed(return_var, return_indices) ir, = impero_utils.preprocess_gem([ir]) - impero_c = impero_utils.compile_gem([return_expr], [ir], return_indices) + impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) point_index, = point_set.indices body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"]) From 2dd631bdfab151b7352ba34a9316988519eecd2b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 23 Mar 2017 13:56:18 +0000 Subject: [PATCH 291/809] add nascent modes --- tsfc/driver.py | 51 ++++++++++++++++++++++++++++++---------------- tsfc/parameters.py | 2 ++ tsfc/vanilla.py | 21 +++++++++++++++++++ 3 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 tsfc/vanilla.py diff --git a/tsfc/driver.py b/tsfc/driver.py index 37efb55165..b1ed13f252 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,8 +1,9 @@ from __future__ import absolute_import, print_function, division +from six import iteritems from six.moves import range, zip +import collections import time -from functools import reduce from itertools import chain from ufl.algorithms import extract_arguments, extract_coefficients @@ -11,7 +12,6 @@ from ufl.log import GREEN import gem -import gem.optimise as opt import gem.impero_utils as impero_utils from finat.point_set import PointSet @@ -121,7 +121,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, kernel_cfg["facetarea"] = facetarea_generator(mesh, coordinates, kernel_cfg, integral_type) kernel_cfg["cellvolume"] = cellvolume_generator(mesh, coordinates, kernel_cfg) - irs = [] + mode_irs = collections.OrderedDict() for integral in integral_data.integrals: params = {} # Record per-integral parameters @@ -131,6 +131,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, # parameters override per-integral metadata params.update(parameters) + mode = pick_mode(params["mode"]) + mode_irs.setdefault(mode, collections.OrderedDict()) + integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) @@ -153,25 +156,30 @@ def compile_integral(integral_data, form_data, prefix, parameters, config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule) - ir = fem.compile_ufl(integrand, interior_facet=interior_facet, **config) - if parameters["unroll_indexsum"]: - def predicate(index): - return index.extent <= parameters["unroll_indexsum"] - ir = opt.unroll_indexsum(ir, predicate=predicate) - ir = [gem.index_sum(expr, quadrature_multiindex) for expr in ir] - irs.append(ir) - - # Sum the expressions that are part of the same restriction - ir = list(reduce(gem.Sum, e, gem.Zero()) for e in zip(*irs)) + exps = fem.compile_ufl(integrand, interior_facet=interior_facet, **config) + reps = mode.integrate(exps, quadrature_multiindex, parameters) + for var, rep in zip(return_variables, reps): + mode_irs[mode].setdefault(var, []).append(rep) + + # Finalise mode representations into a set of assignments + assignments = [] + for mode, rep_dict in iteritems(mode_irs): + assignments.extend(mode.aggregate(rep_dict)) + + if assignments: + return_variables, expressions = zip(*assignments) + else: + return_variables = [] + expressions = [] # Need optimised roots for COFFEE - ir = impero_utils.preprocess_gem(ir) + expressions = impero_utils.preprocess_gem(expressions) # Look for cell orientations in the IR - if builder.needs_cell_orientations(ir): + if builder.needs_cell_orientations(expressions): builder.require_cell_orientations() - assignments = list(zip(return_variables, ir)) + assignments = list(zip(return_variables, expressions)) impero_c = impero_utils.compile_gem(assignments, tuple(quadrature_indices) + argument_indices, remove_zeros=True) @@ -186,7 +194,7 @@ def predicate(index): for i, quadrature_index in enumerate(quadrature_indices): index_names.append((quadrature_index, 'ip_%d' % i)) - body = generate_coffee(impero_c, index_names, parameters["precision"], ir, argument_indices) + body = generate_coffee(impero_c, index_names, parameters["precision"], expressions, argument_indices) kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) return builder.construct_kernel(kernel_name, body) @@ -346,3 +354,12 @@ def lower_integral_type(fiat_cell, integral_type): entity_ids = list(range(len(fiat_cell.get_topology()[integration_dim]))) return integration_dim, entity_ids + + +def pick_mode(mode): + "Return one of the specialized optimisation modules from a mode string." + if mode == "vanilla": + import tsfc.vanilla as m + else: + raise ValueError("Unknown mode: {}".format(mode)) + return m diff --git a/tsfc/parameters.py b/tsfc/parameters.py index df977435ff..7f83f3178f 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -13,6 +13,8 @@ "quadrature_rule": "auto", "quadrature_degree": "auto", + "mode": "vanilla", + # Maximum extent to unroll index sums. Default is 3, so that loops # over geometric dimensions are unrolled; this improves assembly # performance. Can be disabled by setting it to None, False or 0; diff --git a/tsfc/vanilla.py b/tsfc/vanilla.py new file mode 100644 index 0000000000..f7b77ac138 --- /dev/null +++ b/tsfc/vanilla.py @@ -0,0 +1,21 @@ +from __future__ import absolute_import, print_function, division +from six import iteritems + +from gem import index_sum +from gem.optimise import unroll_indexsum + + +def integrate(expressions, quadrature_multiindex, parameters): + max_extent = parameters["unroll_indexsum"] + if max_extent: + def predicate(index): + return index.extent <= max_extent + expressions = unroll_indexsum(expressions, predicate=predicate) + return [index_sum(e, quadrature_multiindex) for e in expressions] + + +def aggregate(rep_dict): + for variable, reps in iteritems(rep_dict): + expressions = reps + for expression in expressions: + yield (variable, expression) From 21f4e8eaf5110db753f874bb19c5853ae96f92d1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 24 Mar 2017 11:44:27 +0000 Subject: [PATCH 292/809] refactor mode API to pass argument indices explicitly --- tsfc/driver.py | 13 ++++++++----- tsfc/vanilla.py | 7 +++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b1ed13f252..ecec5a792c 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,5 +1,5 @@ from __future__ import absolute_import, print_function, division -from six import iteritems +from six import iteritems, viewitems from six.moves import range, zip import collections @@ -156,15 +156,18 @@ def compile_integral(integral_data, form_data, prefix, parameters, config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule) - exps = fem.compile_ufl(integrand, interior_facet=interior_facet, **config) - reps = mode.integrate(exps, quadrature_multiindex, parameters) + expressions = fem.compile_ufl(integrand, + interior_facet=interior_facet, + **config) + reps = mode.Integrals(expressions, quadrature_multiindex, + argument_multiindices, parameters) for var, rep in zip(return_variables, reps): mode_irs[mode].setdefault(var, []).append(rep) # Finalise mode representations into a set of assignments assignments = [] - for mode, rep_dict in iteritems(mode_irs): - assignments.extend(mode.aggregate(rep_dict)) + for mode, var_reps in iteritems(mode_irs): + assignments.extend(mode.flatten(viewitems(var_reps))) if assignments: return_variables, expressions = zip(*assignments) diff --git a/tsfc/vanilla.py b/tsfc/vanilla.py index f7b77ac138..78abda62f7 100644 --- a/tsfc/vanilla.py +++ b/tsfc/vanilla.py @@ -1,11 +1,10 @@ from __future__ import absolute_import, print_function, division -from six import iteritems from gem import index_sum from gem.optimise import unroll_indexsum -def integrate(expressions, quadrature_multiindex, parameters): +def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): @@ -14,8 +13,8 @@ def predicate(index): return [index_sum(e, quadrature_multiindex) for e in expressions] -def aggregate(rep_dict): - for variable, reps in iteritems(rep_dict): +def flatten(var_reps): + for variable, reps in var_reps: expressions = reps for expression in expressions: yield (variable, expression) From e5be42a2b6ee59f8f650e675e474c5bd0c1679cb Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 24 Mar 2017 13:51:36 +0000 Subject: [PATCH 293/809] increase precedence of per-integral metadata --- tsfc/driver.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index ecec5a792c..3de7687564 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -123,13 +123,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, mode_irs = collections.OrderedDict() for integral in integral_data.integrals: - params = {} - # Record per-integral parameters - params.update(integral.metadata()) + params = parameters.copy() + params.update(integral.metadata()) # integral metadata overrides if params.get("quadrature_rule") == "default": del params["quadrature_rule"] - # parameters override per-integral metadata - params.update(parameters) mode = pick_mode(params["mode"]) mode_irs.setdefault(mode, collections.OrderedDict()) @@ -160,7 +157,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interior_facet=interior_facet, **config) reps = mode.Integrals(expressions, quadrature_multiindex, - argument_multiindices, parameters) + argument_multiindices, params) for var, rep in zip(return_variables, reps): mode_irs[mode].setdefault(var, []).append(rep) From fc0e5e2a265f02a4108a49e6aa517078195260ea Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 24 Mar 2017 14:33:44 +0000 Subject: [PATCH 294/809] add docstrings --- tsfc/parameters.py | 1 + tsfc/vanilla.py | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 7f83f3178f..11c6101e1a 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -13,6 +13,7 @@ "quadrature_rule": "auto", "quadrature_degree": "auto", + # Default mode "mode": "vanilla", # Maximum extent to unroll index sums. Default is 3, so that loops diff --git a/tsfc/vanilla.py b/tsfc/vanilla.py index 78abda62f7..a92fd6f8e0 100644 --- a/tsfc/vanilla.py +++ b/tsfc/vanilla.py @@ -5,16 +5,37 @@ def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): + """Constructs an integral representation for each GEM integrand + expression. + + :arg expressions: integrand multiplied with quadrature weight; + multi-root GEM expression DAG + :arg quadrature_multiindex: quadrature multiindex (tuple) + :arg argument_multiindices: tuple of argument multiindices, + one multiindex for each argument + :arg parameters: parameters dictionary + + :returns: list of integral representations + """ + # Unroll max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent expressions = unroll_indexsum(expressions, predicate=predicate) + # Integral representation: just a GEM expression return [index_sum(e, quadrature_multiindex) for e in expressions] def flatten(var_reps): + """Flatten mode-specific intermediate representation to a series of + assignments. + + :arg var_reps: series of (return variable, [integral representation]) pairs + + :returns: series of (return variable, GEM expression root) pairs + """ for variable, reps in var_reps: - expressions = reps + expressions = reps # representations are expressions for expression in expressions: yield (variable, expression) From f1b8b70db6a4471738665f95a30a974ffba18c06 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 29 Mar 2017 16:33:30 +0100 Subject: [PATCH 295/809] avoid duplicate preprocessing --- tsfc/driver.py | 9 +++++++-- tsfc/vanilla.py | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 3de7687564..e685707ae4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, print_function, division -from six import iteritems, viewitems +from six import iterkeys, iteritems, viewitems from six.moves import range, zip import collections +import operator import time +from functools import reduce from itertools import chain from ufl.algorithms import extract_arguments, extract_coefficients @@ -173,7 +175,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, expressions = [] # Need optimised roots for COFFEE - expressions = impero_utils.preprocess_gem(expressions) + options = dict(reduce(operator.and_, + [viewitems(mode.finalise_options) + for mode in iterkeys(mode_irs)])) + expressions = impero_utils.preprocess_gem(expressions, **options) # Look for cell orientations in the IR if builder.needs_cell_orientations(expressions): diff --git a/tsfc/vanilla.py b/tsfc/vanilla.py index a92fd6f8e0..dbaa862c18 100644 --- a/tsfc/vanilla.py +++ b/tsfc/vanilla.py @@ -39,3 +39,8 @@ def flatten(var_reps): expressions = reps # representations are expressions for expression in expressions: yield (variable, expression) + + +finalise_options = {} +"""To avoid duplicate work, these options that are safe to pass to +:py:func:`gem.impero_utils.preprocess_gem`.""" From db8947277533de57265e240b7cde34a543cfcd67 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 30 Mar 2017 18:15:37 +0100 Subject: [PATCH 296/809] Fix for #103 Provide a Sum back from the list of expressions in vanilla.flatten. --- tsfc/vanilla.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tsfc/vanilla.py b/tsfc/vanilla.py index dbaa862c18..84b7be41e7 100644 --- a/tsfc/vanilla.py +++ b/tsfc/vanilla.py @@ -1,6 +1,8 @@ from __future__ import absolute_import, print_function, division -from gem import index_sum +from functools import reduce + +from gem import index_sum, Sum from gem.optimise import unroll_indexsum @@ -37,8 +39,7 @@ def flatten(var_reps): """ for variable, reps in var_reps: expressions = reps # representations are expressions - for expression in expressions: - yield (variable, expression) + yield (variable, reduce(Sum, expressions)) finalise_options = {} From 5f845db8dd89b0e7b2fcb443fe4eb7679733720b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 26 Oct 2016 16:59:22 +0100 Subject: [PATCH 297/809] spectral mode: sum factorisation of arguments --- tsfc/driver.py | 2 ++ tsfc/spectral.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tsfc/spectral.py diff --git a/tsfc/driver.py b/tsfc/driver.py index e685707ae4..109170f00f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -365,6 +365,8 @@ def pick_mode(mode): "Return one of the specialized optimisation modules from a mode string." if mode == "vanilla": import tsfc.vanilla as m + elif mode == "spectral": + import tsfc.spectral as m else: raise ValueError("Unknown mode: {}".format(mode)) return m diff --git a/tsfc/spectral.py b/tsfc/spectral.py new file mode 100644 index 0000000000..a9aa4945dc --- /dev/null +++ b/tsfc/spectral.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import, print_function, division +from six.moves import map, zip + +from functools import partial +from itertools import chain + +from gem import index_sum +from gem.optimise import delta_elimination, sum_factorise as _sum_factorise, unroll_indexsum +from gem.refactorise import ATOMIC, COMPOUND, OTHER, collect_monomials + + +def sum_factorise(sum_indices, factors): + sum_indices = list(sum_indices) + sum_indices.reverse() + return _sum_factorise(*delta_elimination(sum_indices, factors)) + + +def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): + max_extent = parameters["unroll_indexsum"] + if max_extent: + def predicate(index): + return index.extent <= max_extent + expressions = unroll_indexsum(expressions, predicate=predicate) + argument_indices = set(chain(*argument_multiindices)) + return [(argument_indices, + index_sum(e, quadrature_multiindex)) + for e in expressions] + + +def flatten(var_reps): + def classify(argument_indices, expression): + n = len(argument_indices.intersection(expression.free_indices)) + if n == 0: + return OTHER + elif n == 1: + return ATOMIC + else: + return COMPOUND + + for variable, reps in var_reps: + argument_indicez, expressions = zip(*reps) + argument_indices, = set(map(frozenset, argument_indicez)) + classifier = partial(classify, argument_indices) + for monomial_sum in collect_monomials(expressions, classifier): + for sum_indices, args, rest in monomial_sum: + yield (variable, sum_factorise(sum_indices, args + (rest,))) + + +finalise_options = {} From 102829cbc1adb4f64750b7b783e483d140fdf11e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 5 Apr 2017 17:44:01 +0100 Subject: [PATCH 298/809] fix simplification with delta elimination --- tsfc/spectral.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index a9aa4945dc..30fe5a7383 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -5,14 +5,25 @@ from itertools import chain from gem import index_sum -from gem.optimise import delta_elimination, sum_factorise as _sum_factorise, unroll_indexsum -from gem.refactorise import ATOMIC, COMPOUND, OTHER, collect_monomials +from gem.optimise import delta_elimination as _delta_elimination +from gem.optimise import sum_factorise as _sum_factorise +from gem.optimise import unroll_indexsum +from gem.refactorise import ATOMIC, COMPOUND, OTHER, MonomialSum, collect_monomials -def sum_factorise(sum_indices, factors): +def delta_elimination(sum_indices, args, rest): + factors = [rest] + list(args) + sum_indices, factors = _delta_elimination(sum_indices, factors) + rest = factors.pop(0) + args = factors + return sum_indices, args, rest + + +def sum_factorise(sum_indices, args, rest): sum_indices = list(sum_indices) sum_indices.reverse() - return _sum_factorise(*delta_elimination(sum_indices, factors)) + factors = args + (rest,) + return _sum_factorise(sum_indices, factors) def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): @@ -42,8 +53,12 @@ def classify(argument_indices, expression): argument_indices, = set(map(frozenset, argument_indicez)) classifier = partial(classify, argument_indices) for monomial_sum in collect_monomials(expressions, classifier): - for sum_indices, args, rest in monomial_sum: - yield (variable, sum_factorise(sum_indices, args + (rest,))) + delta_simplified = MonomialSum() + for monomial in monomial_sum: + delta_simplified.add(*delta_elimination(*monomial)) + + for monomial in delta_simplified: + yield (variable, sum_factorise(*monomial)) finalise_options = {} From 6f9b42bac364f7b70ba5308e5fe576fc3376b622 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 6 Apr 2017 11:25:10 +0100 Subject: [PATCH 299/809] test case for Alastair's UFL abuse Resolves firedrakeproject/firedrake#972. --- tests/test_firedrake_972.py | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/test_firedrake_972.py diff --git a/tests/test_firedrake_972.py b/tests/test_firedrake_972.py new file mode 100644 index 0000000000..b318233d32 --- /dev/null +++ b/tests/test_firedrake_972.py @@ -0,0 +1,46 @@ +from __future__ import absolute_import, print_function, division +import numpy +import pytest + +from coffee.visitors import EstimateFlops + +from ufl import (Mesh, FunctionSpace, VectorElement, TensorElement, + Coefficient, TestFunction, interval, indices, dx) +from ufl.classes import IndexSum, Product, MultiIndex + +from tsfc import compile_form + + +def count_flops(n): + mesh = Mesh(VectorElement('CG', interval, 1)) + tfs = FunctionSpace(mesh, TensorElement('DG', interval, 1, shape=(n, n))) + vfs = FunctionSpace(mesh, VectorElement('DG', interval, 1, dim=n)) + + ensemble_f = Coefficient(vfs) + ensemble2_f = Coefficient(vfs) + phi = TestFunction(tfs) + + i, j = indices(2) + nc = 42 # magic number + L = ((IndexSum(IndexSum(Product(nc * phi[i, j], Product(ensemble_f[i], ensemble_f[i])), + MultiIndex((i,))), MultiIndex((j,))) * dx) + + (IndexSum(IndexSum(Product(nc * phi[i, j], Product(ensemble2_f[j], ensemble2_f[j])), + MultiIndex((i,))), MultiIndex((j,))) * dx) - + (IndexSum(IndexSum(2 * nc * Product(phi[i, j], Product(ensemble_f[i], ensemble2_f[j])), + MultiIndex((i,))), MultiIndex((j,))) * dx)) + + kernel, = compile_form(L, parameters=dict(mode='spectral')) + return EstimateFlops().visit(kernel.ast) + + +def test_convergence(): + ns = [10, 20, 40, 80, 100] + flops = [count_flops(n) for n in ns] + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(ns)) + assert (rates < 2).all() # only quadratic operation count, not more + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 3d9937b607ece1fa750c72f5336c15bdb5cbd9f6 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 6 Apr 2017 12:50:09 +0100 Subject: [PATCH 300/809] test case for sum factorisation --- tests/test_sum_factorisation.py | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/test_sum_factorisation.py diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py new file mode 100644 index 0000000000..0c22e9354b --- /dev/null +++ b/tests/test_sum_factorisation.py @@ -0,0 +1,62 @@ +from __future__ import absolute_import, print_function, division +from six.moves import range + +import numpy +import pytest + +from coffee.visitors import EstimateFlops + +from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, + TestFunction, TrialFunction, TensorProductCell, dx, + action, interval, triangle, quadrilateral, dot, grad) + +from tsfc import compile_form + + +def helmholtz(cell, degree): + m = Mesh(VectorElement('CG', cell, 1)) + V = FunctionSpace(m, FiniteElement('CG', cell, degree)) + u = TrialFunction(V) + v = TestFunction(V) + return (u*v + dot(grad(u), grad(v)))*dx + + +def count_flops(form): + kernel, = compile_form(form, parameters=dict(mode='spectral')) + return EstimateFlops().visit(kernel.ast) + + +@pytest.mark.parametrize(('cell', 'order'), + [(quadrilateral, 5), + (TensorProductCell(interval, interval), 5), + (TensorProductCell(triangle, interval), 7), + (TensorProductCell(quadrilateral, interval), 7)]) +def test_lhs(cell, order): + degrees = list(range(3, 8)) + if cell == TensorProductCell(triangle, interval): + degrees = list(range(3, 6)) + flops = [count_flops(helmholtz(cell, degree)) + for degree in degrees] + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees)) + assert (rates < order).all() + + +@pytest.mark.parametrize(('cell', 'order'), + [(quadrilateral, 3), + (TensorProductCell(interval, interval), 3), + (TensorProductCell(triangle, interval), 5), + (TensorProductCell(quadrilateral, interval), 4)]) +def test_rhs(cell, order): + degrees = list(range(3, 8)) + if cell == TensorProductCell(triangle, interval): + degrees = list(range(3, 6)) + flops = [count_flops(action(helmholtz(cell, degree))) + for degree in degrees] + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees)) + assert (rates < order).all() + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From ef25c10065aa841a53825b88eeafe35010aecafc Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 6 Apr 2017 14:59:15 +0100 Subject: [PATCH 301/809] disable latest test cases in Python3 --- tests/test_sum_factorisation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index 0c22e9354b..bb8909ed59 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -3,6 +3,7 @@ import numpy import pytest +import sys from coffee.visitors import EstimateFlops @@ -26,6 +27,8 @@ def count_flops(form): return EstimateFlops().visit(kernel.ast) +@pytest.mark.skipif(sys.version_info >= (3,), + reason="Tuple degrees break UFL in Python3") @pytest.mark.parametrize(('cell', 'order'), [(quadrilateral, 5), (TensorProductCell(interval, interval), 5), @@ -41,6 +44,8 @@ def test_lhs(cell, order): assert (rates < order).all() +@pytest.mark.skipif(sys.version_info >= (3,), + reason="Tuple degrees break UFL in Python3") @pytest.mark.parametrize(('cell', 'order'), [(quadrilateral, 3), (TensorProductCell(interval, interval), 3), From 26720ff27b2836725c24fd7352e57e4a3eb597f1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 6 Apr 2017 16:08:16 +0100 Subject: [PATCH 302/809] spectral mode: add comments and docstrings --- tsfc/spectral.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 30fe5a7383..0f60c4a187 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -12,14 +12,18 @@ def delta_elimination(sum_indices, args, rest): - factors = [rest] + list(args) + """IndexSum-Delta cancellation for monomials.""" + factors = [rest] + list(args) # construct factors sum_indices, factors = _delta_elimination(sum_indices, factors) + # Destructure factors after cancellation rest = factors.pop(0) args = factors return sum_indices, args, rest def sum_factorise(sum_indices, args, rest): + """Optimised monomial product construction through sum factorisation + with reversed sum indices.""" sum_indices = list(sum_indices) sum_indices.reverse() factors = args + (rest,) @@ -27,11 +31,14 @@ def sum_factorise(sum_indices, args, rest): def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): + # Unroll max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent expressions = unroll_indexsum(expressions, predicate=predicate) + # Integral representation: pair with the set of argument indices + # and a GEM expression argument_indices = set(chain(*argument_multiindices)) return [(argument_indices, index_sum(e, quadrature_multiindex)) @@ -39,6 +46,7 @@ def predicate(index): def flatten(var_reps): + # Classifier for argument factorisation def classify(argument_indices, expression): n = len(argument_indices.intersection(expression.free_indices)) if n == 0: @@ -49,14 +57,19 @@ def classify(argument_indices, expression): return COMPOUND for variable, reps in var_reps: + # Destructure representation argument_indicez, expressions = zip(*reps) + # Assert identical argument indices for all integrals argument_indices, = set(map(frozenset, argument_indicez)) + # Argument factorise classifier = partial(classify, argument_indices) for monomial_sum in collect_monomials(expressions, classifier): + # Compact MonomialSum after IndexSum-Delta cancellation delta_simplified = MonomialSum() for monomial in monomial_sum: delta_simplified.add(*delta_elimination(*monomial)) + # Yield assignments for monomial in delta_simplified: yield (variable, sum_factorise(*monomial)) From fa228ee7cf4ca0c671d3a9d3d1156d82f324b5ec Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 7 Apr 2017 15:50:16 +0100 Subject: [PATCH 303/809] add support for vertex integral type --- tsfc/driver.py | 2 ++ tsfc/kernel_interface/ufc.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index e685707ae4..3c88d734b4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -339,6 +339,8 @@ def lower_integral_type(fiat_cell, integral_type): integration_dim = dim elif integral_type in ['exterior_facet', 'interior_facet']: integration_dim = dim - 1 + elif integral_type == 'vertex': + integration_dim = 0 else: # Extrusion case basedim, extrdim = dim diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index a0492cc35b..9ab3a7a114 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -44,6 +44,8 @@ def __init__(self, integral_type, subdomain_id, domain_number): '+': gem.VariableIndex(gem.Variable("facet_0", ())), '-': gem.VariableIndex(gem.Variable("facet_1", ())) } + elif integral_type == "vertex": + self._facet_number = {None: gem.VariableIndex(gem.Variable("vertex", ()))} def set_arguments(self, arguments, multiindices): """Process arguments. @@ -116,6 +118,8 @@ def construct_kernel(self, name, body): elif self.integral_type == "interior_facet": args.append(coffee.Decl("std::size_t", coffee.Symbol("facet_0"))) args.append(coffee.Decl("std::size_t", coffee.Symbol("facet_1"))) + elif self.integral_type == "vertex": + args.append(coffee.Decl("std::size_t", coffee.Symbol("vertex"))) # Cell orientation(s) if self.interior_facet: From 157bbf38c515f7d6506af0bc06e01144dcbcf4ac Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 7 Apr 2017 15:56:00 +0100 Subject: [PATCH 304/809] rename: facet_number -> entity_number Facets are no longer the only supported subentity. --- tsfc/fem.py | 4 ++-- tsfc/kernel_interface/__init__.py | 4 ++-- tsfc/kernel_interface/common.py | 8 ++++---- tsfc/kernel_interface/firedrake.py | 6 +++--- tsfc/kernel_interface/ufc.py | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 01a67054ac..24ed817a06 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -104,7 +104,7 @@ def entity_selector(self, callback, restriction): if len(self.entity_ids) == 1: return callback(self.entity_ids[0]) else: - f = self.facet_number(restriction) + f = self.entity_number(restriction) return gem.select_expression(list(map(callback, self.entity_ids)), f) argument_multiindices = () @@ -335,7 +335,7 @@ def take_singleton(xs): per_derivative = {alpha: take_singleton(tables) for alpha, tables in iteritems(per_derivative)} else: - f = ctx.facet_number(mt.restriction) + f = ctx.entity_number(mt.restriction) per_derivative = {alpha: gem.select_expression(tables, f) for alpha, tables in iteritems(per_derivative)} diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index 51b0663349..2b04803d3b 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -20,8 +20,8 @@ def cell_orientation(self, restriction): """Cell orientation as a GEM expression.""" @abstractmethod - def facet_number(self, restriction): - """Facet number as a GEM index.""" + def entity_number(self, restriction): + """Facet or vertex number as a GEM index.""" ProxyKernelInterface = make_proxy_class('ProxyKernelInterface', KernelInterface) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 8d2c95a06b..ac01068b2e 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -48,10 +48,10 @@ def cell_orientation(self, restriction): gem.Literal(1), gem.Literal(numpy.nan))) - def facet_number(self, restriction): - """Facet number as a GEM index.""" - # Assume self._facet_number dict is set up at this point. - return self._facet_number[restriction] + def entity_number(self, restriction): + """Facet or vertex number as a GEM index.""" + # Assume self._entity_number dict is set up at this point. + return self._entity_number[restriction] def apply_glue(self, prepare=None, finalise=None): """Append glue code for operations that are not handled in the diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index c41cafc02b..9062cdb278 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -155,15 +155,15 @@ def __init__(self, integral_type, subdomain_id, domain_number): # Facet number if integral_type in ['exterior_facet', 'exterior_facet_vert']: facet = gem.Variable('facet', (1,)) - self._facet_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} + self._entity_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} elif integral_type in ['interior_facet', 'interior_facet_vert']: facet = gem.Variable('facet', (2,)) - self._facet_number = { + self._entity_number = { '+': gem.VariableIndex(gem.Indexed(facet, (0,))), '-': gem.VariableIndex(gem.Indexed(facet, (1,))) } elif integral_type == 'interior_facet_horiz': - self._facet_number = {'+': 1, '-': 0} + self._entity_number = {'+': 1, '-': 0} def set_arguments(self, arguments, multiindices): """Process arguments. diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 9ab3a7a114..d959f7fda2 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -38,14 +38,14 @@ def __init__(self, integral_type, subdomain_id, domain_number): self._cell_orientations = (gem.Variable("cell_orientation", ()),) if integral_type == "exterior_facet": - self._facet_number = {None: gem.VariableIndex(gem.Variable("facet", ()))} + self._entity_number = {None: gem.VariableIndex(gem.Variable("facet", ()))} elif integral_type == "interior_facet": - self._facet_number = { + self._entity_number = { '+': gem.VariableIndex(gem.Variable("facet_0", ())), '-': gem.VariableIndex(gem.Variable("facet_1", ())) } elif integral_type == "vertex": - self._facet_number = {None: gem.VariableIndex(gem.Variable("vertex", ()))} + self._entity_number = {None: gem.VariableIndex(gem.Variable("vertex", ()))} def set_arguments(self, arguments, multiindices): """Process arguments. @@ -112,7 +112,7 @@ def construct_kernel(self, name, body): args.extend(self.coefficient_args) args.extend(self.coordinates_args) - # Facet number(s) + # Facet and vertex number(s) if self.integral_type == "exterior_facet": args.append(coffee.Decl("std::size_t", coffee.Symbol("facet"))) elif self.integral_type == "interior_facet": From 7dd85c566b47bbfe9c73fbf8160ea4e005e7fa2e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 7 Apr 2017 17:29:25 +0100 Subject: [PATCH 305/809] clean up unused element references --- tests/test_create_fiat_element.py | 2 +- tsfc/fiatinterface.py | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index ea8e0dc0c0..7d9ebda5d9 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -54,7 +54,7 @@ def test_tensor_prod_simple(ufl_A, ufl_B): A = create_element(ufl_A) B = create_element(ufl_B) - assert isinstance(tensor, supported_elements[tensor_ufl.family()]) + assert isinstance(tensor, FIAT.TensorProductElement) assert tensor.A is A assert tensor.B is B diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index af8c706a8a..2e02d90d3a 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -44,29 +44,23 @@ # These all map directly to FIAT elements "Brezzi-Douglas-Marini": FIAT.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": FIAT.BrezziDouglasFortinMarini, - "BrokenElement": FIAT.DiscontinuousElement, "Bubble": FIAT.Bubble, "Crouzeix-Raviart": FIAT.CrouzeixRaviart, "Discontinuous Lagrange": FIAT.DiscontinuousLagrange, "Discontinuous Taylor": FIAT.DiscontinuousTaylor, "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, - "EnrichedElement": FIAT.EnrichedElement, "Gauss-Lobatto-Legendre": FIAT.GaussLobattoLegendre, "Gauss-Legendre": FIAT.GaussLegendre, "Lagrange": FIAT.Lagrange, "Nedelec 1st kind H(curl)": FIAT.Nedelec, "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, - "TensorProductElement": FIAT.TensorProductElement, "Raviart-Thomas": FIAT.RaviartThomas, "HDiv Trace": FIAT.HDivTrace, "Regge": FIAT.Regge, "Hellan-Herrmann-Johnson": FIAT.HellanHerrmannJohnson, # These require special treatment below "DQ": None, - "FacetElement": None, - "InteriorElement": None, "Q": None, - "Real": None, "RTCE": None, "RTCF": None, } @@ -233,8 +227,7 @@ def _(element, vector_is_mixed): @convert.register(ufl.BrokenElement) # noqa def _(element, vector_is_mixed): - return supported_elements[element.family()](create_element(element._element, - vector_is_mixed)) + return FIAT.DiscontinuousElement(create_element(element._element, vector_is_mixed)) # Now for the TPE-specific stuff From 8702322873650a4cefc1c0c8377f32afe7942983 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 7 Apr 2017 17:30:07 +0100 Subject: [PATCH 306/809] wire up RestrictedElement in the FIAT interface --- tsfc/fiatinterface.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 2e02d90d3a..585bbffd02 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -216,6 +216,12 @@ def _(element, vector_is_mixed): restriction_domain="interior") +@convert.register(ufl.RestrictedElement) # noqa +def _(element, vector_is_mixed): + return FIAT.RestrictedElement(create_element(element.sub_element(), vector_is_mixed), + restriction_domain=element.restriction_domain()) + + @convert.register(ufl.EnrichedElement) # noqa def _(element, vector_is_mixed): if len(element._elements) != 2: From 28a2e53fb9d5f9b9c2707aba23c848f98ecb775c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 7 Apr 2017 19:44:39 +0100 Subject: [PATCH 307/809] wire up FFC QuadratureElement --- tsfc/fiatinterface.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 585bbffd02..a19744402d 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -172,6 +172,10 @@ def _(element, vector_is_mixed): # Real element is just DG0 cell = element.cell() return create_element(ufl.FiniteElement("DG", cell, 0), vector_is_mixed) + if element.family() == "Quadrature": + # Sneaky import from FFC + from ffc.quadratureelement import QuadratureElement + return QuadratureElement(element) cell = as_fiat_cell(element.cell()) lmbda = supported_elements[element.family()] if lmbda is None: From a81ddc870678f3cc144ee31a27309ba17d948720 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 7 Apr 2017 20:11:15 +0100 Subject: [PATCH 308/809] monkey-patch FFC QuadratureElement to accept an entity argument --- tsfc/fiatinterface.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index a19744402d..d8dc78f725 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -25,6 +25,7 @@ from singledispatch import singledispatch from functools import partial +import types import weakref import FIAT @@ -175,7 +176,12 @@ def _(element, vector_is_mixed): if element.family() == "Quadrature": # Sneaky import from FFC from ffc.quadratureelement import QuadratureElement - return QuadratureElement(element) + ffc_element = QuadratureElement(element) + + def tabulate(self, order, points, entity): + return QuadratureElement.tabulate(self, order, points) + ffc_element.tabulate = types.MethodType(tabulate, ffc_element) + return ffc_element cell = as_fiat_cell(element.cell()) lmbda = supported_elements[element.family()] if lmbda is None: From 2b6f03e0505f8124a9b007f71336f70887bec681 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 7 Apr 2017 19:42:41 +0100 Subject: [PATCH 309/809] wire up FInAT QuadratureElement --- tsfc/finatinterface.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 1e48cac2a7..f2d19f9a22 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -92,6 +92,16 @@ def convert(element): @convert.register(ufl.FiniteElement) def convert_finiteelement(element): cell = as_fiat_cell(element.cell()) + if element.family() == "Quadrature": + degree = element.degree() + if degree is None: + # FEniCS default (ffc/quadratureelement.py:34) + degree = 1 + scheme = element.quadrature_scheme() + if scheme is None: + # FEniCS default (ffc/quadratureelement.py:35) + scheme = "canonical" + return finat.QuadratureElement(cell, degree, scheme) if element.family() not in supported_elements: return fiat_compat(element) lmbda = supported_elements.get(element.family()) From 76889818c9cfd3c27f9dc51df98d8c7ba23ecdd4 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 11 Apr 2017 11:27:26 +0100 Subject: [PATCH 310/809] handle empty kernels for FFC --- tsfc/driver.py | 47 ++++++++++++++++-------------- tsfc/kernel_interface/firedrake.py | 8 +++++ tsfc/kernel_interface/ufc.py | 15 ++++++++-- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2c4abeae75..dfdb8104fd 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -48,10 +48,9 @@ def compile_form(form, prefix="form", parameters=None): kernels = [] for integral_data in fd.integral_data: start = time.time() - try: - kernels.append(compile_integral(integral_data, fd, prefix, parameters)) - except impero_utils.NoopError: - pass + kernel = compile_integral(integral_data, fd, prefix, parameters) + if kernel is not None: + kernels.append(kernel) logger.info(GREEN % "compile_integral finished in %g seconds.", time.time() - start) logger.info(GREEN % "TSFC finished in %g seconds.", time.time() - cpu_time) @@ -179,30 +178,34 @@ def compile_integral(integral_data, form_data, prefix, parameters, [viewitems(mode.finalise_options) for mode in iterkeys(mode_irs)])) expressions = impero_utils.preprocess_gem(expressions, **options) + assignments = list(zip(return_variables, expressions)) # Look for cell orientations in the IR if builder.needs_cell_orientations(expressions): builder.require_cell_orientations() - assignments = list(zip(return_variables, expressions)) - impero_c = impero_utils.compile_gem(assignments, - tuple(quadrature_indices) + argument_indices, - remove_zeros=True) - - # Generate COFFEE - index_names = [(si, name + str(n)) - for index, name in zip(argument_multiindices, ['j', 'k']) - for n, si in enumerate(index)] - if len(quadrature_indices) == 1: - index_names.append((quadrature_indices[0], 'ip')) - else: - for i, quadrature_index in enumerate(quadrature_indices): - index_names.append((quadrature_index, 'ip_%d' % i)) - - body = generate_coffee(impero_c, index_names, parameters["precision"], expressions, argument_indices) - kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) - return builder.construct_kernel(kernel_name, body) + try: + # Construct ImperoC + index_ordering = tuple(quadrature_indices) + argument_indices + impero_c = impero_utils.compile_gem(assignments, index_ordering, remove_zeros=True) + + # Generate COFFEE + index_names = [(si, name + str(n)) + for index, name in zip(argument_multiindices, ['j', 'k']) + for n, si in enumerate(index)] + if len(quadrature_indices) == 1: + index_names.append((quadrature_indices[0], 'ip')) + else: + for i, quadrature_index in enumerate(quadrature_indices): + index_names.append((quadrature_index, 'ip_%d' % i)) + + # Construct kernel + body = generate_coffee(impero_c, index_names, parameters["precision"], expressions, argument_indices) + return builder.construct_kernel(kernel_name, body) + except impero_utils.NoopError: + # No operations, construct empty kernel + return builder.construct_empty_kernel(kernel_name) class CellVolumeKernelInterface(ProxyKernelInterface): diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 9062cdb278..85cddf2665 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -243,6 +243,14 @@ def construct_kernel(self, name, body): self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel + def construct_empty_kernel(self, name): + """Return None, since Firedrake needs no empty kernels. + + :arg name: function name + :returns: None + """ + return None + def prepare_coefficient(coefficient, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index d959f7fda2..965dd5ef85 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -99,14 +99,14 @@ def set_coefficients(self, integral_data, form_data): self.coefficient_map.update(zip(coeffs, expressions)) def construct_kernel(self, name, body): - """Construct a fully built :class:`Kernel`. + """Construct a fully built kernel function. This function contains the logic for building the argument list for assembly kernels. :arg name: function name :arg body: function body (:class:`coffee.Block` node) - :returns: :class:`Kernel` object + :returns: a COFFEE function definition object """ args = [self.local_tensor] args.extend(self.coefficient_args) @@ -130,6 +130,17 @@ def construct_kernel(self, name, body): return KernelBuilderBase.construct_kernel(self, name, args, body) + def construct_empty_kernel(self, name): + """Construct an empty kernel function. + + Kernel will just zero the return buffer and do nothing else. + + :arg name: function name + :returns: a COFFEE function definition object + """ + body = coffee.Block([]) # empty block + return self.construct_kernel(name, body) + @staticmethod def require_cell_orientations(): # Nothing to do From 62cb151b6e4f64bd637a307f823cbdb974888497 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 3 Mar 2016 18:13:06 +0000 Subject: [PATCH 311/809] support Bessel functions Modified Bessel functions are FEniCS only due to dependency on Boost. --- tsfc/coffee.py | 30 ++++++++++++++++++++++++++++-- tsfc/ufl2gem.py | 12 ++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index ee4b49555e..0bae0591d9 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -245,9 +245,35 @@ def _expression_power(expr, parameters): @_expression.register(gem.MathFunction) def _expression_mathfunction(expr, parameters): - name_map = {'abs': 'fabs', 'ln': 'log'} + name_map = { + 'abs': 'fabs', + 'ln': 'log', + + # Bessel functions + 'cyl_bessel_j': 'jn', + 'cyl_bessel_y': 'yn', + + # Modified Bessel functions (C++ only) + # + # These mappings work for FEniCS only, and fail with Firedrake + # since no Boost available. + 'cyl_bessel_i': 'boost::math::cyl_bessel_i', + 'cyl_bessel_k': 'boost::math::cyl_bessel_k', + } name = name_map.get(expr.name, expr.name) - return coffee.FunCall(name, expression(expr.children[0], parameters)) + if name == 'jn': + nu, arg = expr.children + if nu == gem.Zero(): + return coffee.FunCall('j0', expression(arg, parameters)) + elif nu == gem.one: + return coffee.FunCall('j1', expression(arg, parameters)) + if name == 'yn': + nu, arg = expr.children + if nu == gem.Zero(): + return coffee.FunCall('y0', expression(arg, parameters)) + elif nu == gem.one: + return coffee.FunCall('y1', expression(arg, parameters)) + return coffee.FunCall(name, *[expression(c, parameters) for c in expr.children]) @_expression.register(gem.MinValue) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 79ff09dfcf..6f12d58b23 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -59,6 +59,18 @@ def power(self, o, base, exponent): def math_function(self, o, expr): return MathFunction(o._name, expr) + def bessel_i(self, o, nu, arg): + return MathFunction(o._name, nu, arg) + + def bessel_j(self, o, nu, arg): + return MathFunction(o._name, nu, arg) + + def bessel_k(self, o, nu, arg): + return MathFunction(o._name, nu, arg) + + def bessel_y(self, o, nu, arg): + return MathFunction(o._name, nu, arg) + def min_value(self, o, *ops): return MinValue(*ops) From f4889dcff3e9ad8dbc5cc98329d5ae242890bbb8 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 11 Apr 2017 17:59:15 +0100 Subject: [PATCH 312/809] make try block tighter --- tsfc/driver.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index dfdb8104fd..448789f3de 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -86,6 +86,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, mesh = integral_data.domain cell = integral_data.domain.ufl_cell() arguments = form_data.preprocessed_form.arguments() + kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) fiat_cell = as_fiat_cell(cell) integration_dim, entity_ids = lower_integral_type(fiat_cell, integral_type) @@ -184,29 +185,29 @@ def compile_integral(integral_data, form_data, prefix, parameters, if builder.needs_cell_orientations(expressions): builder.require_cell_orientations() - kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) + # Construct ImperoC + index_ordering = tuple(quadrature_indices) + argument_indices try: - # Construct ImperoC - index_ordering = tuple(quadrature_indices) + argument_indices impero_c = impero_utils.compile_gem(assignments, index_ordering, remove_zeros=True) - - # Generate COFFEE - index_names = [(si, name + str(n)) - for index, name in zip(argument_multiindices, ['j', 'k']) - for n, si in enumerate(index)] - if len(quadrature_indices) == 1: - index_names.append((quadrature_indices[0], 'ip')) - else: - for i, quadrature_index in enumerate(quadrature_indices): - index_names.append((quadrature_index, 'ip_%d' % i)) - - # Construct kernel - body = generate_coffee(impero_c, index_names, parameters["precision"], expressions, argument_indices) - return builder.construct_kernel(kernel_name, body) except impero_utils.NoopError: # No operations, construct empty kernel return builder.construct_empty_kernel(kernel_name) + # Generate COFFEE + index_names = [(si, name + str(n)) + for index, name in zip(argument_multiindices, ['j', 'k']) + for n, si in enumerate(index)] + if len(quadrature_indices) == 1: + index_names.append((quadrature_indices[0], 'ip')) + else: + for i, quadrature_index in enumerate(quadrature_indices): + index_names.append((quadrature_index, 'ip_%d' % i)) + + # Construct kernel + body = generate_coffee(impero_c, index_names, parameters["precision"], expressions, argument_indices) + + return builder.construct_kernel(kernel_name, body) + class CellVolumeKernelInterface(ProxyKernelInterface): # Since CellVolume is evaluated as a cell integral, we must ensure From e32e3b8f7f87610ae76cbc04823b29588db367d7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 18 Apr 2017 11:29:43 +0100 Subject: [PATCH 313/809] remove InteriorElement and FacetElement from FIAT interface --- tsfc/fiatinterface.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index d8dc78f725..0d82100757 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -214,18 +214,6 @@ def tabulate(self, order, points, entity): # Element modifiers -@convert.register(ufl.FacetElement) # noqa -def _(element, vector_is_mixed): - return FIAT.RestrictedElement(create_element(element._element, vector_is_mixed), - restriction_domain="facet") - - -@convert.register(ufl.InteriorElement) # noqa -def _(element, vector_is_mixed): - return FIAT.RestrictedElement(create_element(element._element, vector_is_mixed), - restriction_domain="interior") - - @convert.register(ufl.RestrictedElement) # noqa def _(element, vector_is_mixed): return FIAT.RestrictedElement(create_element(element.sub_element(), vector_is_mixed), From f374dbdbc6e26e967d5298899dc0eb260f937191 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 21 Apr 2017 13:33:11 +0100 Subject: [PATCH 314/809] first stab at fixing FFC issue #144 --- tsfc/driver.py | 4 +++- tsfc/kernel_interface/ufc.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 448789f3de..33b60925d8 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -8,6 +8,7 @@ from functools import reduce from itertools import chain +import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type from ufl.classes import Form, CellVolume @@ -133,7 +134,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, mode = pick_mode(params["mode"]) mode_irs.setdefault(mode, collections.OrderedDict()) - integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) + integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) + integrand = ufl_utils.replace_coordinates(integrand, coordinates) integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) # Check if the integral has a quad degree attached, otherwise use diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 965dd5ef85..ec80bb7cba 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -89,6 +89,7 @@ def set_coefficients(self, integral_data, form_data): continue coeff = form_data.reduced_coefficients[n] + coeff = form_data.function_replace_map[coeff] if type(coeff.ufl_element()) == ufl_MixedElement: coeffs = [Coefficient(FunctionSpace(coeff.ufl_domain(), element)) for element in coeff.ufl_element().sub_elements()] From 063ef3d5177368baacfe6b6ba5ffbd2c94c19e42 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 21 Apr 2017 13:43:53 +0100 Subject: [PATCH 315/809] do not break Firedrake form compilation --- tsfc/kernel_interface/firedrake.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 85cddf2665..162e871e4b 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -196,6 +196,7 @@ def set_coefficients(self, integral_data, form_data): for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: coefficient = form_data.reduced_coefficients[i] + coefficient = form_data.function_replace_map[coefficient] if type(coefficient.ufl_element()) == ufl_MixedElement: split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) for element in coefficient.ufl_element().sub_elements()] From d47089a9c932826b8fd7b50ec22cfad476559b7e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 21 Apr 2017 16:23:31 +0100 Subject: [PATCH 316/809] better failure message for custom integrals --- tsfc/driver.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 33b60925d8..2014e6b336 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -340,6 +340,9 @@ def lower_integral_type(fiat_cell, integral_type): :arg fiat_cell: FIAT reference cell :arg integral_type: integral type (string) """ + vert_facet_types = ['exterior_facet_vert', 'interior_facet_vert'] + horiz_facet_types = ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz'] + dim = fiat_cell.get_dimension() if integral_type == 'cell': integration_dim = dim @@ -347,17 +350,17 @@ def lower_integral_type(fiat_cell, integral_type): integration_dim = dim - 1 elif integral_type == 'vertex': integration_dim = 0 - else: + elif integral_type in vert_facet_types + horiz_facet_types: # Extrusion case basedim, extrdim = dim assert extrdim == 1 - if integral_type in ['exterior_facet_vert', 'interior_facet_vert']: + if integral_type in vert_facet_types: integration_dim = (basedim - 1, 1) - elif integral_type in ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz']: + elif integral_type in horiz_facet_types: integration_dim = (basedim, 0) - else: - raise NotImplementedError("integral type %s not supported" % integral_type) + else: + raise NotImplementedError("integral type %s not supported" % integral_type) if integral_type == 'exterior_facet_bottom': entity_ids = [0] From 6dc5c20628e0001286edac60a4725da8ce35d7f1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 21 Apr 2017 17:20:59 +0100 Subject: [PATCH 317/809] distinguish between degree=None and degree=0 --- tsfc/finatinterface.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index f2d19f9a22..f36df95756 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -66,7 +66,10 @@ def __init__(self, element, degree=None): @property def degree(self): - return self._degree or super(FiatElementWrapper, self).degree + if self._degree is not None: + return self._degree + else: + return super(FiatElementWrapper, self).degree def fiat_compat(element): From 9bf3547fe13c678ee80f2a5182b8c9738b8724aa Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 21 Apr 2017 17:32:56 +0100 Subject: [PATCH 318/809] minor clean up --- tsfc/driver.py | 4 ++-- tsfc/kernel_interface/firedrake.py | 3 +-- tsfc/kernel_interface/ufc.py | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2014e6b336..14a93325ef 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -134,8 +134,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, mode = pick_mode(params["mode"]) mode_irs.setdefault(mode, collections.OrderedDict()) - integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) - integrand = ufl_utils.replace_coordinates(integrand, coordinates) + integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) + integrand = ufl.replace(integrand, form_data.function_replace_map) integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) # Check if the integral has a quad degree attached, otherwise use diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 162e871e4b..9b28bd4ad1 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -195,8 +195,7 @@ def set_coefficients(self, integral_data, form_data): # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: - coefficient = form_data.reduced_coefficients[i] - coefficient = form_data.function_replace_map[coefficient] + coefficient = form_data.function_replace_map[form_data.reduced_coefficients[i]] if type(coefficient.ufl_element()) == ufl_MixedElement: split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) for element in coefficient.ufl_element().sub_elements()] diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index ec80bb7cba..bd717d0609 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -88,8 +88,7 @@ def set_coefficients(self, integral_data, form_data): if not integral_data.enabled_coefficients[n]: continue - coeff = form_data.reduced_coefficients[n] - coeff = form_data.function_replace_map[coeff] + coeff = form_data.function_replace_map[form_data.reduced_coefficients[n]] if type(coeff.ufl_element()) == ufl_MixedElement: coeffs = [Coefficient(FunctionSpace(coeff.ufl_domain(), element)) for element in coeff.ufl_element().sub_elements()] From d1ad71dc06ee652a0ec2b84166a0992f4cc80dbf Mon Sep 17 00:00:00 2001 From: tj-sun Date: Wed, 3 May 2017 13:47:54 +0100 Subject: [PATCH 319/809] Coffee migration (#98) Migration of factorisation for loop optimisation from COFFEE. This change unconditionally performs argument factorisation on the input expression. --- tests/test_coffee_optimise.py | 80 +++++++++++ tsfc/coffee_mode.py | 246 ++++++++++++++++++++++++++++++++++ tsfc/driver.py | 2 + tsfc/parameters.py | 2 +- 4 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 tests/test_coffee_optimise.py create mode 100644 tsfc/coffee_mode.py diff --git a/tests/test_coffee_optimise.py b/tests/test_coffee_optimise.py new file mode 100644 index 0000000000..2237a1cf1b --- /dev/null +++ b/tests/test_coffee_optimise.py @@ -0,0 +1,80 @@ +from __future__ import absolute_import, print_function, division + +import pytest + +from gem.gem import Index, Indexed, Product, Variable, Division, Literal, Sum +from gem.optimise import replace_division +from tsfc.coffee_mode import optimise_expressions + + +def test_replace_div(): + i = Index() + A = Variable('A', ()) + B = Variable('B', (6,)) + Bi = Indexed(B, (i,)) + d = Division(Bi, A) + result, = replace_division([d]) + expected = Product(Bi, Division(Literal(1.0), A)) + + assert result == expected + + +def test_loop_optimise(): + I = 20 + J = K = 10 + i = Index('i', I) + j = Index('j', J) + k = Index('k', K) + + A1 = Variable('a1', (I,)) + A2 = Variable('a2', (I,)) + A3 = Variable('a3', (I,)) + A1i = Indexed(A1, (i,)) + A2i = Indexed(A2, (i,)) + A3i = Indexed(A3, (i,)) + + B = Variable('b', (J,)) + C = Variable('c', (J,)) + Bj = Indexed(B, (j,)) + Cj = Indexed(C, (j,)) + + E = Variable('e', (K,)) + F = Variable('f', (K,)) + G = Variable('g', (K,)) + Ek = Indexed(E, (k,)) + Fk = Indexed(F, (k,)) + Gk = Indexed(G, (k,)) + + Z = Variable('z', ()) + + # Bj*Ek + Bj*Fk => (Ek + Fk)*Bj + expr = Sum(Product(Bj, Ek), Product(Bj, Fk)) + result, = optimise_expressions([expr], ((j,), (k,))) + expected = Product(Sum(Ek, Fk), Bj) + assert result == expected + + # Bj*Ek + Bj*Fk + Bj*Gk + Cj*Ek + Cj*Fk => + # (Ek + Fk + Gk)*Bj + (Ek+Fk)*Cj + expr = Sum(Sum(Sum(Sum(Product(Bj, Ek), Product(Bj, Fk)), Product(Bj, Gk)), + Product(Cj, Ek)), Product(Cj, Fk)) + result, = optimise_expressions([expr], ((j,), (k,))) + expected = Sum(Product(Sum(Sum(Ek, Fk), Gk), Bj), Product(Sum(Ek, Fk), Cj)) + assert result == expected + + # Z*A1i*Bj*Ek + Z*A2i*Bj*Ek + A3i*Bj*Ek + Z*A1i*Bj*Fk => + # Bj*(Ek*(Z*A1i + Z*A2i) + A3i) + Z*A1i*Fk) + + expr = Sum(Sum(Sum(Product(Z, Product(A1i, Product(Bj, Ek))), + Product(Z, Product(A2i, Product(Bj, Ek)))), + Product(A3i, Product(Bj, Ek))), + Product(Z, Product(A1i, Product(Bj, Fk)))) + result, = optimise_expressions([expr], ((j,), (k,))) + expected = Product(Sum(Product(Ek, Sum(Sum(Product(Z, A1i), Product(Z, A2i)), A3i)), + Product(Fk, Product(Z, A1i))), Bj) + assert result == expected + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py new file mode 100644 index 0000000000..ae9a79c0ef --- /dev/null +++ b/tsfc/coffee_mode.py @@ -0,0 +1,246 @@ +from __future__ import absolute_import, print_function, division + +import numpy +import itertools +from functools import partial +from six import iteritems, iterkeys +from six.moves import filter +from collections import defaultdict +from gem.optimise import (replace_division, make_sum, make_product, + unroll_indexsum, replace_delta, remove_componenttensors) +from gem.refactorise import Monomial, ATOMIC, COMPOUND, OTHER, collect_monomials +from gem.node import traversal +from gem.gem import Conditional, Indexed, IndexSum, Failure, one, index_sum +from gem.utils import groupby + + +import tsfc.vanilla as vanilla + +flatten = vanilla.flatten + +finalise_options = dict(replace_delta=False, remove_componenttensors=False) + + +def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): + """Constructs an integral representation for each GEM integrand + expression. + + :arg expressions: integrand multiplied with quadrature weight; + multi-root GEM expression DAG + :arg quadrature_multiindex: quadrature multiindex (tuple) + :arg argument_multiindices: tuple of argument multiindices, + one multiindex for each argument + :arg parameters: parameters dictionary + + :returns: list of integral representations + """ + # Unroll + max_extent = parameters["unroll_indexsum"] + if max_extent: + def predicate(index): + return index.extent <= max_extent + expressions = unroll_indexsum(expressions, predicate=predicate) + # Choose GEM expression as the integral representation + expressions = [index_sum(e, quadrature_multiindex) for e in expressions] + expressions = replace_delta(expressions) + expressions = remove_componenttensors(expressions) + expressions = replace_division(expressions) + return optimise_expressions(expressions, argument_multiindices) + + +def optimise_expressions(expressions, argument_multiindices): + """Perform loop optimisations on GEM DAGs + + :arg expressions: list of GEM DAGs + :arg argument_multiindices: tuple of argument multiindices, + one multiindex for each argument + + :returns: list of optimised GEM DAGs + """ + # Skip optimisation for if Failure node is present + for n in traversal(expressions): + if isinstance(n, Failure): + return expressions + + def classify(argument_indices, expression): + n = len(argument_indices.intersection(expression.free_indices)) + if n == 0: + return OTHER + elif n == 1: + if isinstance(expression, (Indexed, Conditional)): + return ATOMIC + else: + return COMPOUND + else: + return COMPOUND + + argument_indices = tuple(itertools.chain(*argument_multiindices)) + # Apply argument factorisation unconditionally + classifier = partial(classify, set(argument_indices)) + monomial_sums = collect_monomials(expressions, classifier) + return [optimise_monomial_sum(ms, argument_indices) for ms in monomial_sums] + + +def index_extent(factor, argument_indices): + """Compute the product of the extents of argument indices of a GEM expression + + :arg factor: GEM expression + :arg argument_indices: set of argument indices + + :returns: product of extents of argument indices + """ + return numpy.product([i.extent for i in set(factor.free_indices).intersection(argument_indices)]) + + +def monomial_sum_to_expression(monomial_sum): + """Convert a monomial sum to a GEM expression. + + :arg monomial_sum: an iterable of :class:`Monomial`s + + :returns: GEM expression + """ + indexsums = [] # The result is summation of indexsums + # Group monomials according to their sum indices + groups = groupby(monomial_sum, key=lambda m: frozenset(m.sum_indices)) + # Create IndexSum's from each monomial group + for _, monomials in groups: + sum_indices = monomials[0].sum_indices + products = [make_product(monomial.atomics + (monomial.rest,)) for monomial in monomials] + indexsums.append(IndexSum(make_sum(products), sum_indices)) + return make_sum(indexsums) + + +def find_optimal_atomics(monomials, argument_indices): + """Find optimal atomic common subexpressions, which produce least number of + terms in the resultant IndexSum when factorised. + + :arg monomials: An iterable of :class:`Monomial`s, all of which should have + the same sum indices + :arg argument_indices: tuple of argument indices + + :returns: list of atomic GEM expressions + """ + atomic_index = defaultdict(partial(next, itertools.count())) # atomic -> int + connections = [] + # add connections (list of tuples, items in each tuple form a product) + for monomial in monomials: + connections.append(tuple(map(lambda a: atomic_index[a], monomial.atomics))) + + if len(atomic_index) <= 1: + return tuple(iterkeys(atomic_index)) + + # set up the ILP + import pulp as ilp + ilp_prob = ilp.LpProblem('gem factorise', ilp.LpMinimize) + ilp_var = ilp.LpVariable.dicts('node', range(len(atomic_index)), 0, 1, ilp.LpBinary) + + # Objective function + # Minimise number of factors to pull. If same number, favour factor with larger extent + penalty = 2 * max(index_extent(atomic, argument_indices) for atomic in atomic_index) * len(atomic_index) + ilp_prob += ilp.lpSum(ilp_var[index] * (penalty - index_extent(atomic, argument_indices)) + for atomic, index in iteritems(atomic_index)) + + # constraints + for connection in connections: + ilp_prob += ilp.lpSum(ilp_var[index] for index in connection) >= 1 + + ilp_prob.solve() + if ilp_prob.status != 1: + raise RuntimeError("Something bad happened during ILP") + + def optimal(atomic): + return ilp_var[atomic_index[atomic]].value() == 1 + + return tuple(sorted(filter(optimal, atomic_index), key=atomic_index.get)) + + +def factorise_atomics(monomials, optimal_atomics, argument_indices): + """Group and factorise monomials using a list of atomics as common + subexpressions. Create new monomials for each group and optimise them recursively. + + :arg monomials: an iterable of :class:`Monomial`s, all of which should have + the same sum indices + :arg optimal_atomics: list of tuples of atomics to be used as common subexpression + :arg argument_indices: tuple of argument indices + + :returns: an iterable of :class:`Monomials`s after factorisation + """ + if not optimal_atomics or len(monomials) <= 1: + return monomials + + # Group monomials with respect to each optimal atomic + def group_key(monomial): + for oa in optimal_atomics: + if oa in monomial.atomics: + return oa + assert False, "Expect at least one optimal atomic per monomial." + factor_group = groupby(monomials, key=group_key) + + # We should not drop monomials + assert sum(len(ms) for _, ms in factor_group) == len(monomials) + + sum_indices = next(iter(monomials)).sum_indices + new_monomials = [] + for oa, monomials in factor_group: + # Create new MonomialSum for the factorised out terms + sub_monomials = [] + for monomial in monomials: + atomics = list(monomial.atomics) + atomics.remove(oa) # remove common factor + sub_monomials.append(Monomial((), tuple(atomics), monomial.rest)) + # Continue to factorise the remaining expression + sub_monomials = optimise_monomials(sub_monomials, argument_indices) + if len(sub_monomials) == 1: + # Factorised part is a product, we add back the common atomics then + # add to new MonomialSum directly rather than forming a product node + # Retaining the monomial structure enables applying associativity + # when forming GEM nodes later. + sub_monomial, = sub_monomials + new_monomials.append( + Monomial(sum_indices, (oa,) + sub_monomial.atomics, sub_monomial.rest)) + else: + # Factorised part is a summation, we need to create a new GEM node + # and multiply with the common factor + node = monomial_sum_to_expression(sub_monomials) + # If the free indices of the new node intersect with argument indices, + # add to the new monomial as `atomic`, otherwise add as `rest`. + # Note: we might want to continue to factorise with the new atomics + # by running optimise_monoials twice. + if set(argument_indices) & set(node.free_indices): + new_monomials.append(Monomial(sum_indices, (oa, node), one)) + else: + new_monomials.append(Monomial(sum_indices, (oa, ), node)) + return new_monomials + + +def optimise_monomial_sum(monomial_sum, argument_indices): + """Choose optimal common atomic subexpressions and factorise a + :class:`MonomialSum` object to create a GEM expression. + + :arg monomial_sum: a :class:`MonomialSum` object + :arg argument_indices: tuple of argument indices + + :returns: factorised GEM expression + """ + groups = groupby(monomial_sum, key=lambda m: frozenset(m.sum_indices)) + new_monomials = [] + for _, monomials in groups: + new_monomials.extend(optimise_monomials(monomials, argument_indices)) + return monomial_sum_to_expression(new_monomials) + + +def optimise_monomials(monomials, argument_indices): + """Choose optimal common atomic subexpressions and factorise an iterable + of monomials. + + :arg monomials: an iterable of :class:`Monomial`s, all of which should have + the same sum indices + :arg argument_indices: tuple of argument indices + + :returns: an iterable of factorised :class:`Monomials`s + """ + assert len(set(frozenset(m.sum_indices) for m in monomials)) <= 1,\ + "All monomials required to have same sum indices for factorisation" + + optimal_atomics = find_optimal_atomics(monomials, argument_indices) + return factorise_atomics(monomials, optimal_atomics, argument_indices) diff --git a/tsfc/driver.py b/tsfc/driver.py index 14a93325ef..91be0c87f0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -376,6 +376,8 @@ def pick_mode(mode): "Return one of the specialized optimisation modules from a mode string." if mode == "vanilla": import tsfc.vanilla as m + elif mode == "coffee": + import tsfc.coffee_mode as m elif mode == "spectral": import tsfc.spectral as m else: diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 11c6101e1a..3d112a4182 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -14,7 +14,7 @@ "quadrature_degree": "auto", # Default mode - "mode": "vanilla", + "mode": "coffee", # Maximum extent to unroll index sums. Default is 3, so that loops # over geometric dimensions are unrolled; this improves assembly From 92cdc574992994d612d724dff9fc7ccde5126c76 Mon Sep 17 00:00:00 2001 From: tj-sun Date: Tue, 9 May 2017 18:37:29 +0100 Subject: [PATCH 320/809] Rewrite Conditional nodes before optimisation (#120) Fixes #119. --- tests/test_coffee_optimise.py | 6 +++--- tsfc/coffee_mode.py | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/test_coffee_optimise.py b/tests/test_coffee_optimise.py index 2237a1cf1b..bef6d825fa 100644 --- a/tests/test_coffee_optimise.py +++ b/tests/test_coffee_optimise.py @@ -49,7 +49,7 @@ def test_loop_optimise(): # Bj*Ek + Bj*Fk => (Ek + Fk)*Bj expr = Sum(Product(Bj, Ek), Product(Bj, Fk)) - result, = optimise_expressions([expr], ((j,), (k,))) + result, = optimise_expressions([expr], (j, k)) expected = Product(Sum(Ek, Fk), Bj) assert result == expected @@ -57,7 +57,7 @@ def test_loop_optimise(): # (Ek + Fk + Gk)*Bj + (Ek+Fk)*Cj expr = Sum(Sum(Sum(Sum(Product(Bj, Ek), Product(Bj, Fk)), Product(Bj, Gk)), Product(Cj, Ek)), Product(Cj, Fk)) - result, = optimise_expressions([expr], ((j,), (k,))) + result, = optimise_expressions([expr], (j, k)) expected = Sum(Product(Sum(Sum(Ek, Fk), Gk), Bj), Product(Sum(Ek, Fk), Cj)) assert result == expected @@ -68,7 +68,7 @@ def test_loop_optimise(): Product(Z, Product(A2i, Product(Bj, Ek)))), Product(A3i, Product(Bj, Ek))), Product(Z, Product(A1i, Product(Bj, Fk)))) - result, = optimise_expressions([expr], ((j,), (k,))) + result, = optimise_expressions([expr], (j, k)) expected = Product(Sum(Product(Ek, Sum(Sum(Product(Z, A1i), Product(Z, A2i)), A3i)), Product(Fk, Product(Z, A1i))), Bj) assert result == expected diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index ae9a79c0ef..0854930ee7 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -10,7 +10,7 @@ unroll_indexsum, replace_delta, remove_componenttensors) from gem.refactorise import Monomial, ATOMIC, COMPOUND, OTHER, collect_monomials from gem.node import traversal -from gem.gem import Conditional, Indexed, IndexSum, Failure, one, index_sum +from gem.gem import Indexed, IndexSum, Failure, one, index_sum from gem.utils import groupby @@ -45,15 +45,15 @@ def predicate(index): expressions = replace_delta(expressions) expressions = remove_componenttensors(expressions) expressions = replace_division(expressions) - return optimise_expressions(expressions, argument_multiindices) + argument_indices = tuple(itertools.chain(*argument_multiindices)) + return optimise_expressions(expressions, argument_indices) -def optimise_expressions(expressions, argument_multiindices): +def optimise_expressions(expressions, argument_indices): """Perform loop optimisations on GEM DAGs :arg expressions: list of GEM DAGs - :arg argument_multiindices: tuple of argument multiindices, - one multiindex for each argument + :arg argument_indices: tuple of argument indices :returns: list of optimised GEM DAGs """ @@ -67,14 +67,13 @@ def classify(argument_indices, expression): if n == 0: return OTHER elif n == 1: - if isinstance(expression, (Indexed, Conditional)): + if isinstance(expression, Indexed): return ATOMIC else: return COMPOUND else: return COMPOUND - argument_indices = tuple(itertools.chain(*argument_multiindices)) # Apply argument factorisation unconditionally classifier = partial(classify, set(argument_indices)) monomial_sums = collect_monomials(expressions, classifier) From c0d9596c1ab43e831d9e7ceea0601075adc53633 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 26 Apr 2017 16:05:27 +0100 Subject: [PATCH 321/809] refactor fem.py for point evaluations --- tsfc/fem.py | 95 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 24ed817a06..d08c39a37e 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -37,15 +37,13 @@ from tsfc.ufl_utils import ModifiedTerminalMixin, PickRestriction, simplify_abs -class Context(ProxyKernelInterface): +class ContextBase(ProxyKernelInterface): + """Common UFL -> GEM translation context.""" + keywords = ('ufl_cell', 'fiat_cell', 'integration_dim', 'entity_ids', - 'quadrature_degree', - 'quadrature_rule', - 'point_set', - 'weight_expr', 'precision', 'argument_multiindices', 'cellvolume', @@ -55,7 +53,7 @@ class Context(ProxyKernelInterface): def __init__(self, interface, **kwargs): ProxyKernelInterface.__init__(self, interface) - invalid_keywords = set(kwargs.keys()) - set(Context.keywords) + invalid_keywords = set(kwargs.keys()) - set(self.keywords) if invalid_keywords: raise ValueError("unexpected keyword argument '{0}'".format(invalid_keywords.pop())) self.__dict__.update(kwargs) @@ -70,19 +68,6 @@ def integration_dim(self): entity_ids = [0] - @cached_property - def quadrature_rule(self): - integration_cell = self.fiat_cell.construct_subelement(self.integration_dim) - return make_quadrature(integration_cell, self.quadrature_degree) - - @cached_property - def point_set(self): - return self.quadrature_rule.point_set - - @cached_property - def weight_expr(self): - return self.quadrature_rule.weight_expression - precision = PARAMETERS["precision"] @cached_property @@ -114,6 +99,58 @@ def index_cache(self): return {} +class PointSetContext(ContextBase): + """Context for compile-time known evaluation points.""" + + keywords = ContextBase.keywords + ( + 'quadrature_degree', + 'quadrature_rule', + 'point_set', + 'weight_expr', + ) + + @cached_property + def quadrature_rule(self): + integration_cell = self.fiat_cell.construct_subelement(self.integration_dim) + return make_quadrature(integration_cell, self.quadrature_degree) + + @cached_property + def point_set(self): + return self.quadrature_rule.point_set + + @cached_property + def point_indices(self): + return self.point_set.indices + + @cached_property + def point_expr(self): + return self.point_set.expression + + @cached_property + def weight_expr(self): + return self.quadrature_rule.weight_expression + + def basis_evaluation(self, finat_element, local_derivatives, entity_id): + return finat_element.basis_evaluation(local_derivatives, + self.point_set, + (self.integration_dim, entity_id)) + + +class GemPointContext(ContextBase): + """Context for evaluation at arbitrary reference points.""" + + keywords = ContextBase.keywords + ( + 'point_indices', + 'point_expr', + 'weight_expr', + ) + + def basis_evaluation(self, finat_element, local_derivatives, entity_id): + return finat_element.point_evaluation(local_derivatives, + self.point_expr, + (self.integration_dim, entity_id)) + + class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): """Contains all the context necessary to translate UFL into GEM.""" @@ -221,13 +258,13 @@ def translate_cell_edge_vectors(terminal, mt, ctx): @translate.register(CellCoordinate) def translate_cell_coordinate(terminal, mt, ctx): - ps = ctx.point_set if ctx.integration_dim == ctx.fiat_cell.get_dimension(): - return ps.expression + return ctx.point_expr # This destroys the structure of the quadrature points, but since # this code path is only used to implement CellCoordinate in facet # integrals, hopefully it does not matter much. + ps = ctx.point_set point_shape = tuple(index.extent for index in ps.indices) def callback(entity_id): @@ -242,7 +279,7 @@ def callback(entity_id): @translate.register(FacetCoordinate) def translate_facet_coordinate(terminal, mt, ctx): assert ctx.integration_dim != ctx.fiat_cell.get_dimension() - return ctx.point_set.expression + return ctx.point_expr @translate.register(CellVolume) @@ -284,9 +321,7 @@ def translate_argument(terminal, mt, ctx): element = create_element(terminal.ufl_element()) def callback(entity_id): - finat_dict = element.basis_evaluation(mt.local_derivatives, - ctx.point_set, - (ctx.integration_dim, entity_id)) + finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) # Filter out irrelevant derivatives filtered_dict = {alpha: table for alpha, table in iteritems(finat_dict) @@ -315,9 +350,7 @@ def translate_coefficient(terminal, mt, ctx): # Collect FInAT tabulation for all entities per_derivative = collections.defaultdict(list) for entity_id in ctx.entity_ids: - finat_dict = element.basis_evaluation(mt.local_derivatives, - ctx.point_set, - (ctx.integration_dim, entity_id)) + finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) for alpha, table in iteritems(finat_dict): # Filter out irrelevant derivatives if sum(alpha) == mt.local_derivatives: @@ -354,7 +387,7 @@ def take_singleton(xs): # Change from FIAT to UFL arrangement result = fiat_to_ufl(value_dict, mt.local_derivatives) assert result.shape == mt.expr.ufl_shape - assert set(result.free_indices) <= set(ctx.point_set.indices) + assert set(result.free_indices) <= set(ctx.point_indices) # Detect Jacobian of affine cells if not result.free_indices and all(numpy.count_nonzero(node.array) <= 2 @@ -365,7 +398,7 @@ def take_singleton(xs): def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): - context = Context(**kwargs) + context = PointSetContext(**kwargs) # Abs-simplification expression = simplify_abs(expression) @@ -380,5 +413,5 @@ def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): translator = Translator(context) result = map_expr_dags(translator, expressions) if point_sum: - result = [gem.index_sum(expr, context.point_set.indices) for expr in result] + result = [gem.index_sum(expr, context.point_indices) for expr in result] return result From 99ed114787ae65de67f71aaa2fb616e5bf5b68e0 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 15 May 2017 13:47:54 +0100 Subject: [PATCH 322/809] Add UFL handler for Atan2 Apparently Atan2 is not a MathFunction. --- tsfc/ufl2gem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 6f12d58b23..f739e0dc41 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -59,6 +59,9 @@ def power(self, o, base, exponent): def math_function(self, o, expr): return MathFunction(o._name, expr) + def atan_2(self, o, y, x): + return MathFunction("atan2", y, x) + def bessel_i(self, o, nu, arg): return MathFunction(o._name, nu, arg) From 52322cd923e9f2b5522fb97fdab7adddd5fc3ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Mon, 15 May 2017 16:52:05 +0100 Subject: [PATCH 323/809] Better failure message --- tsfc/driver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 91be0c87f0..b7b252bd38 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -17,6 +17,8 @@ import gem import gem.impero_utils as impero_utils +from FIAT.reference_element import TensorProductCell + from finat.point_set import PointSet from finat.quadrature import AbstractQuadratureRule, make_quadrature @@ -352,6 +354,8 @@ def lower_integral_type(fiat_cell, integral_type): integration_dim = 0 elif integral_type in vert_facet_types + horiz_facet_types: # Extrusion case + if not isinstance(fiat_cell, TensorProductCell): + raise ValueError("{} requires a TensorProductCell.".format(integral_type)) basedim, extrdim = dim assert extrdim == 1 From 66ed034b55969d4ba495e39bfd5d4c266e0912d7 Mon Sep 17 00:00:00 2001 From: Andrew McRae Date: Mon, 15 May 2017 17:00:19 +0100 Subject: [PATCH 324/809] catch the 'unstructured integral on extruded mesh' case --- tsfc/driver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b7b252bd38..a6863d4a52 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -349,13 +349,15 @@ def lower_integral_type(fiat_cell, integral_type): if integral_type == 'cell': integration_dim = dim elif integral_type in ['exterior_facet', 'interior_facet']: + if isinstance(fiat_cell, TensorProductCell): + raise ValueError("{} integral cannot be used with a TensorProductCell; need to distinguish between vertical and horizontal contributions.".format(integral_type)) integration_dim = dim - 1 elif integral_type == 'vertex': integration_dim = 0 elif integral_type in vert_facet_types + horiz_facet_types: # Extrusion case if not isinstance(fiat_cell, TensorProductCell): - raise ValueError("{} requires a TensorProductCell.".format(integral_type)) + raise ValueError("{} integral requires a TensorProductCell.".format(integral_type)) basedim, extrdim = dim assert extrdim == 1 From a7ad23a49f129f102c76b799b0cb1bfa771e3603 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 19 Apr 2017 15:32:53 +0100 Subject: [PATCH 325/809] use MixedElement from FIAT --- tsfc/fiatinterface.py | 3 +- tsfc/mixedelement.py | 108 ------------------------------------------ 2 files changed, 1 insertion(+), 110 deletions(-) delete mode 100644 tsfc/mixedelement.py diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 0d82100757..574dba15e7 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -31,12 +31,11 @@ import FIAT from FIAT.reference_element import FiredrakeQuadrilateral from FIAT.dual_set import DualSet +from FIAT.mixed import MixedElement from FIAT.quadrature import QuadratureRule # noqa import ufl -from .mixedelement import MixedElement - __all__ = ("create_element", "supported_elements", "as_fiat_cell") diff --git a/tsfc/mixedelement.py b/tsfc/mixedelement.py deleted file mode 100644 index 77501533aa..0000000000 --- a/tsfc/mixedelement.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file was modified from FFC -# (http://bitbucket.org/fenics-project/ffc), copyright notice -# reproduced below. -# -# Copyright (C) 2005-2010 Anders Logg -# -# This file is part of FFC. -# -# FFC is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# FFC is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with FFC. If not, see . - -from __future__ import absolute_import, print_function, division -from six.moves import map - -import numpy - -from collections import defaultdict -from operator import add -from functools import partial - - -class MixedElement(object): - """A FIAT-like representation of a mixed element. - - :arg elements: An iterable of FIAT elements. - - This object offers tabulation of the concatenated basis function - tables along with an entity_dofs dict.""" - def __init__(self, elements): - self._elements = tuple(elements) - self._entity_dofs = None - - def get_reference_element(self): - return self.elements()[0].get_reference_element() - - def elements(self): - return self._elements - - def space_dimension(self): - return sum(e.space_dimension() for e in self.elements()) - - def value_shape(self): - return (sum(numpy.prod(e.value_shape(), dtype=int) for e in self.elements()), ) - - def entity_dofs(self): - if self._entity_dofs is not None: - return self._entity_dofs - - ret = defaultdict(partial(defaultdict, list)) - - dicts = (e.entity_dofs() for e in self.elements()) - - offsets = numpy.cumsum([0] + list(e.space_dimension() - for e in self.elements()), - dtype=int) - for i, d in enumerate(dicts): - for dim, dofs in d.items(): - for ent, off in dofs.items(): - ret[dim][ent] += list(map(partial(add, offsets[i]), - off)) - self._entity_dofs = ret - return self._entity_dofs - - def num_components(self): - return self.value_shape()[0] - - def tabulate(self, order, points, entity): - """Tabulate a mixed element by appropriately splatting - together the tabulation of the individual elements. - """ - shape = (self.space_dimension(), self.num_components(), len(points)) - - output = {} - - sub_dims = [0] + list(e.space_dimension() for e in self.elements()) - sub_cmps = [0] + list(numpy.prod(e.value_shape(), dtype=int) - for e in self.elements()) - irange = numpy.cumsum(sub_dims) - crange = numpy.cumsum(sub_cmps) - - for i, e in enumerate(self.elements()): - table = e.tabulate(order, points, entity) - - for d, tab in table.items(): - try: - arr = output[d] - except KeyError: - arr = numpy.zeros(shape) - output[d] = arr - - ir = irange[i:i+2] - cr = crange[i:i+2] - tab = tab.reshape(ir[1] - ir[0], cr[1] - cr[0], -1) - arr[slice(*ir), slice(*cr)] = tab - - return output From 173a65d2d8df7725fb42b7940a3edd3fe8061c18 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 20 Apr 2017 16:03:58 +0100 Subject: [PATCH 326/809] adopt n-ary EnrichedElement --- tests/test_create_fiat_element.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 7d9ebda5d9..259a0c8b57 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -98,10 +98,10 @@ def test_quadrilateral_variant_spectral_dq(): def test_quadrilateral_variant_spectral_rtcf(): element = create_element(ufl.FiniteElement('RTCF', ufl.quadrilateral, 2, variant='spectral')) - assert isinstance(element.element.A.A, FIAT.GaussLobattoLegendre) - assert isinstance(element.element.A.B, FIAT.GaussLegendre) - assert isinstance(element.element.B.A, FIAT.GaussLegendre) - assert isinstance(element.element.B.B, FIAT.GaussLobattoLegendre) + assert isinstance(element.element._elements[0].A, FIAT.GaussLobattoLegendre) + assert isinstance(element.element._elements[0].B, FIAT.GaussLegendre) + assert isinstance(element.element._elements[1].A, FIAT.GaussLegendre) + assert isinstance(element.element._elements[1].B, FIAT.GaussLobattoLegendre) def test_cache_hit(ufl_element): From 4f9ef99f912cc1832fd9707decbddffe4890b830 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 20 Apr 2017 16:44:51 +0100 Subject: [PATCH 327/809] update FIAT interface --- tsfc/fiatinterface.py | 25 +++++++++++++------------ tsfc/finatinterface.py | 4 ++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 574dba15e7..a5f8b169d9 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -25,13 +25,11 @@ from singledispatch import singledispatch from functools import partial -import types import weakref import FIAT from FIAT.reference_element import FiredrakeQuadrilateral from FIAT.dual_set import DualSet -from FIAT.mixed import MixedElement from FIAT.quadrature import QuadratureRule # noqa import ufl @@ -172,16 +170,19 @@ def _(element, vector_is_mixed): # Real element is just DG0 cell = element.cell() return create_element(ufl.FiniteElement("DG", cell, 0), vector_is_mixed) - if element.family() == "Quadrature": - # Sneaky import from FFC - from ffc.quadratureelement import QuadratureElement - ffc_element = QuadratureElement(element) - - def tabulate(self, order, points, entity): - return QuadratureElement.tabulate(self, order, points) - ffc_element.tabulate = types.MethodType(tabulate, ffc_element) - return ffc_element cell = as_fiat_cell(element.cell()) + if element.family() == "Quadrature": + degree = element.degree() + if degree is None: + # FEniCS default (ffc/fiatinterface.py:65) + degree = 1 + scheme = element.quadrature_scheme() + if scheme is None: + # FEniCS default (ffc/fiatinterface.py:66) + scheme = "canonical" + + quad_rule = FIAT.create_quadrature(cell, degree, scheme) + return FIAT.QuadratureElement(cell, quad_rule.get_points()) lmbda = supported_elements[element.family()] if lmbda is None: if element.cell().cellname() != "quadrilateral": @@ -275,7 +276,7 @@ def rec(eles): rec(element.sub_elements()) fiat_elements = map(partial(create_element, vector_is_mixed=vector_is_mixed), elements) - return MixedElement(fiat_elements) + return FIAT.MixedElement(fiat_elements) quad_tpc = ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index f36df95756..0edbd268d4 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -98,11 +98,11 @@ def convert_finiteelement(element): if element.family() == "Quadrature": degree = element.degree() if degree is None: - # FEniCS default (ffc/quadratureelement.py:34) + # FEniCS default (ffc/fiatinterface.py:65) degree = 1 scheme = element.quadrature_scheme() if scheme is None: - # FEniCS default (ffc/quadratureelement.py:35) + # FEniCS default (ffc/fiatinterface.py:66) scheme = "canonical" return finat.QuadratureElement(cell, degree, scheme) if element.family() not in supported_elements: From b763aeffb279a3cb042b4e239db50c914194bf3e Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 8 May 2017 14:28:51 +0100 Subject: [PATCH 328/809] reject default quadrature scheme and degree --- tsfc/fiatinterface.py | 8 ++------ tsfc/finatinterface.py | 9 +++------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index a5f8b169d9..2c9e16cbc4 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -173,13 +173,9 @@ def _(element, vector_is_mixed): cell = as_fiat_cell(element.cell()) if element.family() == "Quadrature": degree = element.degree() - if degree is None: - # FEniCS default (ffc/fiatinterface.py:65) - degree = 1 scheme = element.quadrature_scheme() - if scheme is None: - # FEniCS default (ffc/fiatinterface.py:66) - scheme = "canonical" + if degree is None or scheme is None: + raise ValueError("Quadrature scheme and degree must be specified!") quad_rule = FIAT.create_quadrature(cell, degree, scheme) return FIAT.QuadratureElement(cell, quad_rule.get_points()) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0edbd268d4..b15647b46e 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -97,13 +97,10 @@ def convert_finiteelement(element): cell = as_fiat_cell(element.cell()) if element.family() == "Quadrature": degree = element.degree() - if degree is None: - # FEniCS default (ffc/fiatinterface.py:65) - degree = 1 scheme = element.quadrature_scheme() - if scheme is None: - # FEniCS default (ffc/fiatinterface.py:66) - scheme = "canonical" + if degree is None or scheme is None: + raise ValueError("Quadrature scheme and degree must be specified!") + return finat.QuadratureElement(cell, degree, scheme) if element.family() not in supported_elements: return fiat_compat(element) From fc1c1a484da514b1133887b69bb169c4857ae374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Thu, 5 Jan 2017 16:50:05 +0100 Subject: [PATCH 329/809] add "tensor" mode --- tsfc/driver.py | 2 ++ tsfc/tensor.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tsfc/tensor.py diff --git a/tsfc/driver.py b/tsfc/driver.py index a6863d4a52..1f63b8f7c5 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -386,6 +386,8 @@ def pick_mode(mode): import tsfc.coffee_mode as m elif mode == "spectral": import tsfc.spectral as m + elif mode == "tensor": + import tsfc.tensor as m else: raise ValueError("Unknown mode: {}".format(mode)) return m diff --git a/tsfc/tensor.py b/tsfc/tensor.py new file mode 100644 index 0000000000..f216577888 --- /dev/null +++ b/tsfc/tensor.py @@ -0,0 +1,93 @@ +from __future__ import absolute_import, print_function, division +from six import iteritems +from six.moves import zip + +from collections import defaultdict +from functools import partial, reduce +from itertools import count + +import numpy + +import gem +from gem.optimise import remove_componenttensors, unroll_indexsum +from gem.refactorise import ATOMIC, COMPOUND, OTHER, collect_monomials + + +def einsum(factors, sum_indices): + """Evaluates a tensor product at compile time. + + :arg factors: iterable of indexed GEM literals + :arg sum_indices: indices to sum over + :returns: a single indexed GEM literal + """ + # Maps the problem onto numpy.einsum + index2letter = defaultdict(partial(lambda c: chr(ord('i') + next(c)), count())) + operands = [] + subscript_parts = [] + for factor in factors: + literal, = factor.children + selectors = [] + letters = [] + for index in factor.multiindex: + if isinstance(index, int): + selectors.append(index) + else: + selectors.append(slice(None)) + letters.append(index2letter[index]) + operands.append(literal.array.__getitem__(selectors)) + subscript_parts.append(''.join(letters)) + + result_pairs = sorted((letter, index) + for index, letter in iteritems(index2letter) + if index not in sum_indices) + + subscripts = ','.join(subscript_parts) + '->' + ''.join(l for l, i in result_pairs) + tensor = numpy.einsum(subscripts, *operands) + return gem.Indexed(gem.Literal(tensor), tuple(i for l, i in result_pairs)) + + +def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): + # Unroll + max_extent = parameters["unroll_indexsum"] + if max_extent: + def predicate(index): + return index.extent <= max_extent + expressions = unroll_indexsum(expressions, predicate=predicate) + + # Refactorise + def classify(quadrature_indices, expression): + if not quadrature_indices.intersection(expression.free_indices): + return OTHER + elif isinstance(expression, gem.Indexed) and isinstance(expression.children[0], gem.Literal): + return ATOMIC + else: + return COMPOUND + classifier = partial(classify, set(quadrature_multiindex)) + + result = [] + for expr, monomial_sum in zip(expressions, collect_monomials(expressions, classifier)): + # Select quadrature indices that are present + quadrature_indices = set(index for index in quadrature_multiindex + if index in expr.free_indices) + + products = [] + for sum_indices, factors, rest in monomial_sum: + # Collapse quadrature literals for each monomial + if factors or quadrature_indices: + replacement = einsum(remove_componenttensors(factors), quadrature_indices) + else: + replacement = gem.Literal(1) + # Rebuild expression + products.append(gem.IndexSum(gem.Product(replacement, rest), sum_indices)) + result.append(reduce(gem.Sum, products, gem.Zero())) + return result + + +def flatten(var_reps): + for variable, reps in var_reps: + expressions = reps + for expression in expressions: + yield (variable, expression) + + +finalise_options = {} From 47f9c4a9d6e15f5023ef8a62e1eab0db08aa2b1f Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 22 May 2017 11:52:53 +0100 Subject: [PATCH 330/809] add test case tensor mode operation count growth --- tests/test_tensor.py | 108 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/test_tensor.py diff --git a/tests/test_tensor.py b/tests/test_tensor.py new file mode 100644 index 0000000000..e74942dce8 --- /dev/null +++ b/tests/test_tensor.py @@ -0,0 +1,108 @@ +from __future__ import absolute_import, print_function, division + +import numpy +import pytest + +from coffee.visitors import EstimateFlops + +from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, + Coefficient, TestFunction, TrialFunction, dx, div, + inner, interval, triangle, tetrahedron, dot, grad) + +from tsfc import compile_form + + +def mass(cell, degree): + m = Mesh(VectorElement('CG', cell, 1)) + V = FunctionSpace(m, FiniteElement('CG', cell, degree)) + u = TrialFunction(V) + v = TestFunction(V) + return u*v*dx + + +def poisson(cell, degree): + m = Mesh(VectorElement('CG', cell, 1)) + V = FunctionSpace(m, FiniteElement('CG', cell, degree)) + u = TrialFunction(V) + v = TestFunction(V) + return dot(grad(u), grad(v))*dx + + +def helmholtz(cell, degree): + m = Mesh(VectorElement('CG', cell, 1)) + V = FunctionSpace(m, FiniteElement('CG', cell, degree)) + u = TrialFunction(V) + v = TestFunction(V) + return (u*v + dot(grad(u), grad(v)))*dx + + +def elasticity(cell, degree): + m = Mesh(VectorElement('CG', cell, 1)) + V = FunctionSpace(m, VectorElement('CG', cell, degree)) + u = TrialFunction(V) + v = TestFunction(V) + + def eps(u): + return 0.5*(grad(u) + grad(u).T) + return inner(eps(u), eps(v))*dx + + +def count_flops(form): + kernel, = compile_form(form, parameters=dict(mode='tensor')) + return EstimateFlops().visit(kernel.ast) + + +@pytest.mark.parametrize('form', [mass, poisson, helmholtz, elasticity]) +@pytest.mark.parametrize(('cell', 'order'), + [(interval, 2), + (triangle, 4), + (tetrahedron, 6)]) +def test_bilinear(form, cell, order): + degrees = numpy.arange(1, 9 - 2 * cell.topological_dimension()) + flops = [count_flops(form(cell, int(degree))) + for degree in degrees] + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees + 1)) + assert (rates < order).all() + + +@pytest.mark.parametrize(('cell', 'order'), + [(interval, 1), + (triangle, 2), + (tetrahedron, 3)]) +def test_linear(cell, order): + def form(cell, degree): + m = Mesh(VectorElement('CG', cell, 1)) + V = FunctionSpace(m, FiniteElement('CG', cell, degree)) + v = TestFunction(V) + return v*dx + + degrees = numpy.arange(2, 9 - 1.5 * cell.topological_dimension()) + flops = [count_flops(form(cell, int(degree))) + for degree in degrees] + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees + 1)) + assert (rates < order).all() + + +@pytest.mark.parametrize(('cell', 'order'), + [(interval, 1), + (triangle, 2), + (tetrahedron, 3)]) +def test_functional(cell, order): + def form(cell, degree): + m = Mesh(VectorElement('CG', cell, 1)) + V = FunctionSpace(m, VectorElement('CG', cell, degree)) + f = Coefficient(V) + return div(f)*dx + + dim = cell.topological_dimension() + degrees = numpy.arange(1, 7 - dim) + (3 - dim) + flops = [count_flops(form(cell, int(degree))) + for degree in degrees] + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees + 1)) + assert (rates < order).all() + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 373a3df2e0413303112f0f52f52853806a7047c1 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 24 May 2017 17:23:17 +0100 Subject: [PATCH 331/809] fix ListTensor pickling and add test case --- tests/test_pickle_gem.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_pickle_gem.py b/tests/test_pickle_gem.py index f246a0cbfa..4d484a988b 100644 --- a/tests/test_pickle_gem.py +++ b/tests/test_pickle_gem.py @@ -18,6 +18,14 @@ def test_pickle_gem(protocol): assert repr(expr) == repr(unpickled) +@pytest.mark.parametrize('protocol', range(3)) +def test_listtensor(protocol): + expr = gem.ListTensor([gem.Variable('x', ()), gem.Zero()]) + + unpickled = pickle.loads(pickle.dumps(expr, protocol)) + assert expr == unpickled + + if __name__ == "__main__": import os import sys From 967e331a987f599410a9bb3107a8eca621cdc247 Mon Sep 17 00:00:00 2001 From: Tianjiao Sun Date: Thu, 29 Jun 2017 10:42:51 +0100 Subject: [PATCH 332/809] Implement integer programming without PuLP --- tsfc/coffee_mode.py | 58 +++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 0854930ee7..50950e6396 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -121,34 +121,50 @@ def find_optimal_atomics(monomials, argument_indices): """ atomic_index = defaultdict(partial(next, itertools.count())) # atomic -> int connections = [] - # add connections (list of tuples, items in each tuple form a product) + # add connections (list of sets, items in each tuple form a product) for monomial in monomials: - connections.append(tuple(map(lambda a: atomic_index[a], monomial.atomics))) + connections.append(set(map(lambda a: atomic_index[a], monomial.atomics))) - if len(atomic_index) <= 1: + num_atomics = len(atomic_index) + if num_atomics <= 1: return tuple(iterkeys(atomic_index)) - # set up the ILP - import pulp as ilp - ilp_prob = ilp.LpProblem('gem factorise', ilp.LpMinimize) - ilp_var = ilp.LpVariable.dicts('node', range(len(atomic_index)), 0, 1, ilp.LpBinary) - - # Objective function - # Minimise number of factors to pull. If same number, favour factor with larger extent - penalty = 2 * max(index_extent(atomic, argument_indices) for atomic in atomic_index) * len(atomic_index) - ilp_prob += ilp.lpSum(ilp_var[index] * (penalty - index_extent(atomic, argument_indices)) - for atomic, index in iteritems(atomic_index)) - - # constraints - for connection in connections: - ilp_prob += ilp.lpSum(ilp_var[index] for index in connection) >= 1 + # 0-1 integer programming + def calculate_extent(solution): + extent = 0 + for atomic, index in iteritems(atomic_index): + if index in solution: + extent += index_extent(atomic, argument_indices) + return extent + + def solve_ip(idx, solution, optimal_solution): + if idx >= num_atomics: + return + if len(solution) >= len(optimal_solution): + return + solution.add(idx) + feasible = True + for conn in connections: + if not solution.intersection(conn): + feasible = False + break + if feasible: + if len(solution) < len(optimal_solution) or \ + (len(solution) == len(optimal_solution) and calculate_extent(solution) > calculate_extent(optimal_solution)): + optimal_solution.clear() + optimal_solution.update(solution) + # No need to search further + else: + solve_ip(idx + 1, solution, optimal_solution) + solution.remove(idx) + solve_ip(idx + 1, solution, optimal_solution) - ilp_prob.solve() - if ilp_prob.status != 1: - raise RuntimeError("Something bad happened during ILP") + optimal_solution = set(range(num_atomics)) # start by choosing all atomics + solution = set() + solve_ip(0, solution, optimal_solution) def optimal(atomic): - return ilp_var[atomic_index[atomic]].value() == 1 + return atomic_index[atomic] in optimal_solution return tuple(sorted(filter(optimal, atomic_index), key=atomic_index.get)) From 79e8f303c99407f4ba0e5ccf5f20280e9938d719 Mon Sep 17 00:00:00 2001 From: Tianjiao Sun Date: Thu, 29 Jun 2017 17:32:03 +0100 Subject: [PATCH 333/809] Define solve_ip() as an stand alone function --- tsfc/coffee_mode.py | 80 ++++++++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 50950e6396..6afd7a4c23 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -109,6 +109,48 @@ def monomial_sum_to_expression(monomial_sum): return make_sum(indexsums) +def solve_ip(size, is_feasible, compare): + """Solve a 0-1 integer programming problem. The algorithm tries to set each + variable to 1 recursively, and stop the recursion early by comparing with + the optimal solution at entry. At worst case this is 2^N (as this is a + NP-hard problem), but recursions can be trimmed as soon as a reasonable + solutions have been found. One potential improvement is to keep track of + violated constraints at each step, and only try to set the variables which + could help these constraints in the next step. This will help find decent + solutions faster with additional overhead of maintaining a list of + violated constraints and finding helpful variables. + + :param size: number of 0-1 variables + :param is_feasible: function to test if a combination is feasible + :param compare: function to compare two solutions, compare(s1, s2) returns + True if s1 is a better solution than s2, and False otherwise + :returns: optimal solution represented as a set of variables with value 1 + """ + + def solve(idx, solution, optimal_solution): + if idx >= size: + return + if not compare(solution, optimal_solution): + return + solution.add(idx) + if is_feasible(solution): + if compare(solution, optimal_solution): + optimal_solution.clear() + optimal_solution.update(solution) + # No need to search further + # as adding more variable will only make the solution worse + else: + solve(idx + 1, solution, optimal_solution) + solution.remove(idx) + solve(idx + 1, solution, optimal_solution) + + optimal_solution = set(range(size)) # start by choosing all atomics + solution = set() + solve(0, solution, optimal_solution) + + return optimal_solution + + def find_optimal_atomics(monomials, argument_indices): """Find optimal atomic common subexpressions, which produce least number of terms in the resultant IndexSum when factorised. @@ -129,7 +171,6 @@ def find_optimal_atomics(monomials, argument_indices): if num_atomics <= 1: return tuple(iterkeys(atomic_index)) - # 0-1 integer programming def calculate_extent(solution): extent = 0 for atomic, index in iteritems(atomic_index): @@ -137,36 +178,23 @@ def calculate_extent(solution): extent += index_extent(atomic, argument_indices) return extent - def solve_ip(idx, solution, optimal_solution): - if idx >= num_atomics: - return - if len(solution) >= len(optimal_solution): - return - solution.add(idx) - feasible = True + def is_feasible(solution): for conn in connections: if not solution.intersection(conn): - feasible = False - break - if feasible: - if len(solution) < len(optimal_solution) or \ - (len(solution) == len(optimal_solution) and calculate_extent(solution) > calculate_extent(optimal_solution)): - optimal_solution.clear() - optimal_solution.update(solution) - # No need to search further + return False + return True + + def compare(sol1, sol2): + if len(sol1) < len(sol2): + return True + elif len(sol1) > len(sol2): + return False else: - solve_ip(idx + 1, solution, optimal_solution) - solution.remove(idx) - solve_ip(idx + 1, solution, optimal_solution) - - optimal_solution = set(range(num_atomics)) # start by choosing all atomics - solution = set() - solve_ip(0, solution, optimal_solution) + return calculate_extent(sol1) > calculate_extent(sol2) - def optimal(atomic): - return atomic_index[atomic] in optimal_solution + optimal_solution = solve_ip(num_atomics, is_feasible, compare) - return tuple(sorted(filter(optimal, atomic_index), key=atomic_index.get)) + return tuple(sorted(filter(lambda a: atomic_index[a] in optimal_solution, atomic_index), key=atomic_index.get)) def factorise_atomics(monomials, optimal_atomics, argument_indices): From f32f5473229db11299438c02e20b1476c0365917 Mon Sep 17 00:00:00 2001 From: Tianjiao Sun Date: Fri, 30 Jun 2017 08:59:53 +0100 Subject: [PATCH 334/809] Amendments according to feedback on PR - remove solution and optimal_solution arguments from solve() - replace filter with list comprehension --- tsfc/coffee_mode.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 6afd7a4c23..9158440749 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -4,7 +4,6 @@ import itertools from functools import partial from six import iteritems, iterkeys -from six.moves import filter from collections import defaultdict from gem.optimise import (replace_division, make_sum, make_product, unroll_indexsum, replace_delta, remove_componenttensors) @@ -127,7 +126,10 @@ def solve_ip(size, is_feasible, compare): :returns: optimal solution represented as a set of variables with value 1 """ - def solve(idx, solution, optimal_solution): + optimal_solution = set(range(size)) # start by choosing all atomics + solution = set() + + def solve(idx): if idx >= size: return if not compare(solution, optimal_solution): @@ -140,13 +142,11 @@ def solve(idx, solution, optimal_solution): # No need to search further # as adding more variable will only make the solution worse else: - solve(idx + 1, solution, optimal_solution) + solve(idx + 1) solution.remove(idx) - solve(idx + 1, solution, optimal_solution) + solve(idx + 1) - optimal_solution = set(range(size)) # start by choosing all atomics - solution = set() - solve(0, solution, optimal_solution) + solve(0) return optimal_solution @@ -193,8 +193,7 @@ def compare(sol1, sol2): return calculate_extent(sol1) > calculate_extent(sol2) optimal_solution = solve_ip(num_atomics, is_feasible, compare) - - return tuple(sorted(filter(lambda a: atomic_index[a] in optimal_solution, atomic_index), key=atomic_index.get)) + return tuple(sorted([a for a, i in iteritems(atomic_index) if i in optimal_solution], key=atomic_index.get)) def factorise_atomics(monomials, optimal_atomics, argument_indices): From fb66f57d5c3b3c81bb4458af99b35e02115e3400 Mon Sep 17 00:00:00 2001 From: Tianjiao Sun Date: Fri, 30 Jun 2017 11:07:02 +0100 Subject: [PATCH 335/809] Correct typo --- tsfc/coffee_mode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 9158440749..83001388ab 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -113,10 +113,10 @@ def solve_ip(size, is_feasible, compare): variable to 1 recursively, and stop the recursion early by comparing with the optimal solution at entry. At worst case this is 2^N (as this is a NP-hard problem), but recursions can be trimmed as soon as a reasonable - solutions have been found. One potential improvement is to keep track of + solution have been found. One potential improvement is to keep track of violated constraints at each step, and only try to set the variables which could help these constraints in the next step. This will help find decent - solutions faster with additional overhead of maintaining a list of + solutions faster with the additional overhead of maintaining a list of violated constraints and finding helpful variables. :param size: number of 0-1 variables From b68d4c8393aa281357ad276c5f99687efbbd8599 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 11 Jul 2017 15:44:04 +0100 Subject: [PATCH 336/809] FlattenToQuad moves to FIAT as FlattenedDimensions Additionally, FiredrakeQuadrilateral is renamed to UFCQuadrilateral. --- tests/test_geometry.py | 4 +-- tsfc/fiatinterface.py | 77 ++---------------------------------------- 2 files changed, 4 insertions(+), 77 deletions(-) diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 05840afebe..6c36ce1347 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -4,13 +4,13 @@ import numpy as np from FIAT.reference_element import UFCInterval, UFCTriangle, UFCTetrahedron -from FIAT.reference_element import FiredrakeQuadrilateral, TensorProductCell +from FIAT.reference_element import UFCQuadrilateral, TensorProductCell from tsfc.fem import make_cell_facet_jacobian interval = UFCInterval() triangle = UFCTriangle() -quadrilateral = FiredrakeQuadrilateral() +quadrilateral = UFCQuadrilateral() tetrahedron = UFCTetrahedron() interval_x_interval = TensorProductCell(interval, interval) triangle_x_interval = TensorProductCell(triangle, interval) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 2c9e16cbc4..6cf579c1c8 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -28,9 +28,7 @@ import weakref import FIAT -from FIAT.reference_element import FiredrakeQuadrilateral -from FIAT.dual_set import DualSet -from FIAT.quadrature import QuadratureRule # noqa +from FIAT.tensor_product import FlattenedDimensions import ufl @@ -68,77 +66,6 @@ have a direct FIAT equivalent.""" -class FlattenToQuad(FIAT.FiniteElement): - """A wrapper class that flattens a FIAT quadrilateral element defined - on a TensorProductCell to one with FiredrakeQuadrilateral entities - and tabulation properties.""" - - def __init__(self, element): - """ Constructs a FlattenToQuad element. - - :arg element: a fiat element - """ - nodes = element.dual.nodes - ref_el = FiredrakeQuadrilateral() - entity_ids = element.dual.entity_ids - - flat_entity_ids = {} - flat_entity_ids[0] = entity_ids[(0, 0)] - flat_entity_ids[1] = dict(enumerate( - [v for k, v in sorted(entity_ids[(0, 1)].items())] + - [v for k, v in sorted(entity_ids[(1, 0)].items())] - )) - flat_entity_ids[2] = entity_ids[(1, 1)] - dual = DualSet(nodes, ref_el, flat_entity_ids) - super(FlattenToQuad, self).__init__(ref_el, dual, - element.get_order(), - element.get_formdegree(), - element._mapping) - self.element = element - - def degree(self): - """Return the degree of the (embedding) polynomial space.""" - return self.element.degree() - - def tabulate(self, order, points, entity=None): - """Return tabulated values of derivatives up to a given order of - basis functions at given points. - - :arg order: The maximum order of derivative. - :arg points: An iterable of points. - :arg entity: Optional (dimension, entity number) pair - indicating which topological entity of the - reference element to tabulate on. If ``None``, - default cell-wise tabulation is performed. - """ - if entity is None: - entity = (2, 0) - - # Entity is provided in flattened form (d, i) - # We factor the entity and construct an appropriate - # entity id for a TensorProductCell: ((d1, d2), i) - entity_dim, entity_id = entity - if entity_dim == 2: - assert entity_id == 0 - product_entity = ((1, 1), 0) - elif entity_dim == 1: - facets = [((0, 1), 0), - ((0, 1), 1), - ((1, 0), 0), - ((1, 0), 1)] - product_entity = facets[entity_id] - elif entity_dim == 0: - raise NotImplementedError("Not implemented for 0 dimension entities") - else: - raise ValueError("Illegal entity dimension %s" % entity_dim) - - return self.element.tabulate(order, points, product_entity) - - def value_shape(self): - """Return the value shape of the finite element functions.""" - return self.element.value_shape() - - def as_fiat_cell(cell): """Convert a ufl cell to a FIAT cell. @@ -186,7 +113,7 @@ def _(element, vector_is_mixed): element.family()) # Handle quadrilateral short names like RTCF and RTCE. element = element.reconstruct(cell=quad_tpc) - return FlattenToQuad(create_element(element, vector_is_mixed)) + return FlattenedDimensions(create_element(element, vector_is_mixed)) kind = element.variant() if kind is None: From 4a8af5c1d7c728970921aac41012deafeeff094b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 17 Jul 2017 10:31:20 +0100 Subject: [PATCH 337/809] check estimated quadrature degree against function degrees --- tsfc/driver.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 1f63b8f7c5..b673c2600e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -8,6 +8,8 @@ from functools import reduce from itertools import chain +from numpy import asarray + import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type @@ -144,6 +146,19 @@ def compile_integral(integral_data, form_data, prefix, parameters, # the estimated polynomial degree attached by compute_form_data quadrature_degree = params.get("quadrature_degree", params["estimated_polynomial_degree"]) + try: + quadrature_degree = params["quadrature_degree"] + except KeyError: + quadrature_degree = params["estimated_polynomial_degree"] + functions = list(arguments) + [coordinates] + list(integral_data.integral_coefficients) + function_degrees = [f.ufl_function_space().ufl_element().degree() for f in functions] + if all((asarray(quadrature_degree) > 10 * asarray(degree)).all() + for degree in function_degrees): + logger.warning("Estimated quadrature degree %d more " + "than tenfold greater than any " + "argument/coefficient degree (max %d)", + quadrature_degree, max(function_degrees)) + try: quad_rule = params["quadrature_rule"] except KeyError: From 46d70f4f76eb67b412d7e505bd0e6be0af522f0b Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 18 Jul 2017 12:23:23 +0100 Subject: [PATCH 338/809] increase similarity between coffee and spectral modes Spectral mode: - Adopts tighter argument factorisation from coffee mode. Not necessary for sum factorisation, but useful for delta elimination on arguments. - Not needed to call remove_componenttensors afterwards, as no new ComponentTensor nodes are introduced. Coffee mode: - No longer lowers Delta nodes early. - Eliminate unnecessary remove_componenttensors call at the beginning. collect_monomials takes care of this itself. --- tsfc/coffee_mode.py | 26 ++++++-------------------- tsfc/spectral.py | 22 +++++++++++++--------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 0854930ee7..24d4dd13ba 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -6,19 +6,19 @@ from six import iteritems, iterkeys from six.moves import filter from collections import defaultdict -from gem.optimise import (replace_division, make_sum, make_product, - unroll_indexsum, replace_delta, remove_componenttensors) -from gem.refactorise import Monomial, ATOMIC, COMPOUND, OTHER, collect_monomials +from gem.optimise import make_sum, make_product, replace_division, unroll_indexsum +from gem.refactorise import Monomial, collect_monomials from gem.node import traversal -from gem.gem import Indexed, IndexSum, Failure, one, index_sum +from gem.gem import IndexSum, Failure, one, index_sum from gem.utils import groupby - import tsfc.vanilla as vanilla +from tsfc.spectral import classify + flatten = vanilla.flatten -finalise_options = dict(replace_delta=False, remove_componenttensors=False) +finalise_options = dict(remove_componenttensors=False) def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): @@ -42,8 +42,6 @@ def predicate(index): expressions = unroll_indexsum(expressions, predicate=predicate) # Choose GEM expression as the integral representation expressions = [index_sum(e, quadrature_multiindex) for e in expressions] - expressions = replace_delta(expressions) - expressions = remove_componenttensors(expressions) expressions = replace_division(expressions) argument_indices = tuple(itertools.chain(*argument_multiindices)) return optimise_expressions(expressions, argument_indices) @@ -62,18 +60,6 @@ def optimise_expressions(expressions, argument_indices): if isinstance(n, Failure): return expressions - def classify(argument_indices, expression): - n = len(argument_indices.intersection(expression.free_indices)) - if n == 0: - return OTHER - elif n == 1: - if isinstance(expression, Indexed): - return ATOMIC - else: - return COMPOUND - else: - return COMPOUND - # Apply argument factorisation unconditionally classifier = partial(classify, set(argument_indices)) monomial_sums = collect_monomials(expressions, classifier) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 0f60c4a187..f711bde7e6 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -4,7 +4,7 @@ from functools import partial from itertools import chain -from gem import index_sum +from gem import Delta, Indexed, index_sum from gem.optimise import delta_elimination as _delta_elimination from gem.optimise import sum_factorise as _sum_factorise from gem.optimise import unroll_indexsum @@ -45,17 +45,21 @@ def predicate(index): for e in expressions] -def flatten(var_reps): - # Classifier for argument factorisation - def classify(argument_indices, expression): - n = len(argument_indices.intersection(expression.free_indices)) - if n == 0: - return OTHER - elif n == 1: +def classify(argument_indices, expression): + """Classifier for argument factorisation""" + n = len(argument_indices.intersection(expression.free_indices)) + if n == 0: + return OTHER + elif n == 1: + if isinstance(expression, (Delta, Indexed)): return ATOMIC else: return COMPOUND + else: + return COMPOUND + +def flatten(var_reps): for variable, reps in var_reps: # Destructure representation argument_indicez, expressions = zip(*reps) @@ -74,4 +78,4 @@ def classify(argument_indices, expression): yield (variable, sum_factorise(*monomial)) -finalise_options = {} +finalise_options = dict(remove_componenttensors=False) From d32e7a6d5052218c051f845e97ba92d1690a42bc Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 17 Jul 2017 13:21:46 +0100 Subject: [PATCH 339/809] add test case --- tests/test_estimated_degree.py | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/test_estimated_degree.py diff --git a/tests/test_estimated_degree.py b/tests/test_estimated_degree.py new file mode 100644 index 0000000000..d07cfeca68 --- /dev/null +++ b/tests/test_estimated_degree.py @@ -0,0 +1,35 @@ +from __future__ import absolute_import, print_function, division +import logging + +import pytest + +import ufl +from tsfc import compile_form +from tsfc.logging import logger + + +class MockHandler(logging.Handler): + def emit(self, record): + raise RuntimeError() + + +def test_estimated_degree(): + cell = ufl.tetrahedron + mesh = ufl.Mesh(ufl.VectorElement('P', cell, 1)) + V = ufl.FunctionSpace(mesh, ufl.FiniteElement('P', cell, 1)) + f = ufl.Coefficient(V) + u = ufl.TrialFunction(V) + v = ufl.TestFunction(V) + a = u * v * ufl.tanh(ufl.sqrt(ufl.sinh(f) / ufl.sin(f**f))) * ufl.dx + + handler = MockHandler() + logger.addHandler(handler) + with pytest.raises(RuntimeError): + compile_form(a) + logger.removeHandler(handler) + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From b4bac22330d8351357a813bf112fb486ff333903 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 19 Jul 2017 11:12:21 +0100 Subject: [PATCH 340/809] enable RTCF/RTCE expansion on unstructured quads --- tsfc/finatinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index b15647b46e..567e659247 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -52,6 +52,8 @@ # These require special treatment below "DQ": None, "Q": None, + "RTCE": None, + "RTCF": None, } """A :class:`.dict` mapping UFL element family names to their FInAT-equivalent constructors. If the value is ``None``, the UFL From 18e6bc9e48c055fc8a0e31bf94c8a836b1cfbfc3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 19 Jul 2017 11:14:16 +0100 Subject: [PATCH 341/809] add FInAT interface binding for EnrichedElement --- tsfc/finatinterface.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 567e659247..6621821806 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -137,6 +137,11 @@ def convert_finiteelement(element): return lmbda(cell, element.degree()) +@convert.register(ufl.EnrichedElement) +def convert_enrichedelement(element): + return finat.EnrichedElement([create_element(elem) for elem in element._elements]) + + # VectorElement case @convert.register(ufl.VectorElement) def convert_vectorelement(element): From 2365831c2cdb442d628ab98c70150ab19cc401de Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 21 Jul 2017 16:32:40 +0100 Subject: [PATCH 342/809] update everything else --- tsfc/coffee_mode.py | 61 ++++++++++++++++++++++----------------------- tsfc/driver.py | 37 +++++++++++++++++++++------ tsfc/fem.py | 12 ++++++--- tsfc/spectral.py | 54 ++++++++++++++++++++++++--------------- tsfc/tensor.py | 2 +- tsfc/vanilla.py | 12 +++++---- 6 files changed, 110 insertions(+), 68 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 24d4dd13ba..13462b0845 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -1,50 +1,49 @@ from __future__ import absolute_import, print_function, division +from six.moves import filter, map, range, zip import numpy import itertools -from functools import partial +from functools import partial, reduce from six import iteritems, iterkeys -from six.moves import filter from collections import defaultdict -from gem.optimise import make_sum, make_product, replace_division, unroll_indexsum +from gem.optimise import make_sum, make_product from gem.refactorise import Monomial, collect_monomials +from gem.unconcatenate import unconcatenate from gem.node import traversal -from gem.gem import IndexSum, Failure, one, index_sum +from gem.gem import IndexSum, Failure, Sum, one from gem.utils import groupby -import tsfc.vanilla as vanilla -from tsfc.spectral import classify +import tsfc.spectral as spectral -flatten = vanilla.flatten - -finalise_options = dict(remove_componenttensors=False) +Integrals = spectral.Integrals -def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): - """Constructs an integral representation for each GEM integrand - expression. +def flatten(var_reps, index_cache): + """Flatten mode-specific intermediate representation to a series of + assignments. - :arg expressions: integrand multiplied with quadrature weight; - multi-root GEM expression DAG - :arg quadrature_multiindex: quadrature multiindex (tuple) - :arg argument_multiindices: tuple of argument multiindices, - one multiindex for each argument - :arg parameters: parameters dictionary + :arg var_reps: series of (return variable, [integral representation]) pairs + :arg index_cache: cache :py:class:`dict` for :py:func:`unconcatenate` - :returns: list of integral representations + :returns: series of (return variable, GEM expression root) pairs """ - # Unroll - max_extent = parameters["unroll_indexsum"] - if max_extent: - def predicate(index): - return index.extent <= max_extent - expressions = unroll_indexsum(expressions, predicate=predicate) - # Choose GEM expression as the integral representation - expressions = [index_sum(e, quadrature_multiindex) for e in expressions] - expressions = replace_division(expressions) - argument_indices = tuple(itertools.chain(*argument_multiindices)) - return optimise_expressions(expressions, argument_indices) + assignments = unconcatenate([(variable, reduce(Sum, reps)) + for variable, reps in var_reps], + cache=index_cache) + + def group_key(assignment): + variable, expression = assignment + return variable.free_indices + + for argument_indices, assignment_group in groupby(assignments, group_key): + variables, expressions = zip(*assignment_group) + expressions = optimise_expressions(expressions, argument_indices) + for var, expr in zip(variables, expressions): + yield (var, expr) + + +finalise_options = dict(remove_componenttensors=False) def optimise_expressions(expressions, argument_indices): @@ -61,7 +60,7 @@ def optimise_expressions(expressions, argument_indices): return expressions # Apply argument factorisation unconditionally - classifier = partial(classify, set(argument_indices)) + classifier = partial(spectral.classify, set(argument_indices)) monomial_sums = collect_monomials(expressions, classifier) return [optimise_monomial_sum(ms, argument_indices) for ms in monomial_sums] diff --git a/tsfc/driver.py b/tsfc/driver.py index b673c2600e..0d4a1f7a93 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -4,6 +4,7 @@ import collections import operator +import string import time from functools import reduce from itertools import chain @@ -98,7 +99,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, argument_multiindices = tuple(create_element(arg.ufl_element()).get_indices() for arg in arguments) - argument_indices = tuple(chain(*argument_multiindices)) quadrature_indices = [] # Dict mapping domains to index in original_form.ufl_domains() @@ -115,6 +115,11 @@ def compile_integral(integral_data, form_data, prefix, parameters, # Map from UFL FiniteElement objects to multiindices. This is # so we reuse Index instances when evaluating the same coefficient # multiple times with the same table. + # + # We also use the same dict for the unconcatenate index cache, + # which maps index objects to tuples of multiindices. These two + # caches shall never conflict as their keys have different types + # (UFL finite elements vs. GEM index objects). index_cache = {} kernel_cfg = dict(interface=builder, @@ -185,7 +190,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, # Finalise mode representations into a set of assignments assignments = [] for mode, var_reps in iteritems(mode_irs): - assignments.extend(mode.flatten(viewitems(var_reps))) + assignments.extend(mode.flatten(viewitems(var_reps), index_cache)) if assignments: return_variables, expressions = zip(*assignments) @@ -205,7 +210,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, builder.require_cell_orientations() # Construct ImperoC - index_ordering = tuple(quadrature_indices) + argument_indices + split_argument_indices = tuple(chain(*[var.index_ordering() + for var in return_variables])) + index_ordering = tuple(quadrature_indices) + split_argument_indices try: impero_c = impero_utils.compile_gem(assignments, index_ordering, remove_zeros=True) except impero_utils.NoopError: @@ -213,9 +220,25 @@ def compile_integral(integral_data, form_data, prefix, parameters, return builder.construct_empty_kernel(kernel_name) # Generate COFFEE - index_names = [(si, name + str(n)) - for index, name in zip(argument_multiindices, ['j', 'k']) - for n, si in enumerate(index)] + index_names = [] + + def name_index(index, name): + index_names.append((index, name)) + if index in index_cache: + for multiindex, suffix in zip(index_cache[index], + string.ascii_lowercase): + name_multiindex(multiindex, name + suffix) + + def name_multiindex(multiindex, name): + if len(multiindex) == 1: + name_index(multiindex[0], name) + else: + for i, index in enumerate(multiindex): + name_index(index, name + str(i)) + + for multiindex, name in zip(argument_multiindices, ['j', 'k']): + name_multiindex(multiindex, name) + if len(quadrature_indices) == 1: index_names.append((quadrature_indices[0], 'ip')) else: @@ -223,7 +246,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, index_names.append((quadrature_index, 'ip_%d' % i)) # Construct kernel - body = generate_coffee(impero_c, index_names, parameters["precision"], expressions, argument_indices) + body = generate_coffee(impero_c, index_names, parameters["precision"], expressions, split_argument_indices) return builder.construct_kernel(kernel_name, body) diff --git a/tsfc/fem.py b/tsfc/fem.py index d08c39a37e..7b0bb2df72 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -25,6 +25,7 @@ import gem from gem.node import traversal from gem.optimise import ffc_rounding +from gem.unconcatenate import unconcatenate from gem.utils import cached_property from finat.quadrature import make_quadrature @@ -376,12 +377,15 @@ def take_singleton(xs): ctx.index_cache.setdefault(terminal.ufl_element(), element.get_indices()) beta = ctx.index_cache[terminal.ufl_element()] zeta = element.get_value_indices() + vec_beta, = gem.optimise.remove_componenttensors([gem.Indexed(vec, beta)]) value_dict = {} for alpha, table in iteritems(per_derivative): - value = gem.IndexSum(gem.Product(gem.Indexed(table, beta + zeta), - gem.Indexed(vec, beta)), - beta) - optimised_value = gem.optimise.contraction(value) + table_qi = gem.Indexed(table, beta + zeta) + summands = [] + for var, expr in unconcatenate([(vec_beta, table_qi)], ctx.index_cache): + value = gem.IndexSum(gem.Product(expr, var), var.index_ordering()) + summands.append(gem.optimise.contraction(value)) + optimised_value = gem.optimise.make_sum(summands) value_dict[alpha] = gem.ComponentTensor(optimised_value, zeta) # Change from FIAT to UFL arrangement diff --git a/tsfc/spectral.py b/tsfc/spectral.py index f711bde7e6..722889ca0a 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -1,14 +1,15 @@ from __future__ import absolute_import, print_function, division -from six.moves import map, zip +from six.moves import zip -from functools import partial -from itertools import chain +from functools import partial, reduce -from gem import Delta, Indexed, index_sum +from gem import Delta, Indexed, Sum, index_sum from gem.optimise import delta_elimination as _delta_elimination from gem.optimise import sum_factorise as _sum_factorise -from gem.optimise import unroll_indexsum +from gem.optimise import replace_division, unroll_indexsum from gem.refactorise import ATOMIC, COMPOUND, OTHER, MonomialSum, collect_monomials +from gem.unconcatenate import unconcatenate +from gem.utils import groupby def delta_elimination(sum_indices, args, rest): @@ -31,18 +32,26 @@ def sum_factorise(sum_indices, args, rest): def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): + """Constructs an integral representation for each GEM integrand + expression. + + :arg expressions: integrand multiplied with quadrature weight; + multi-root GEM expression DAG + :arg quadrature_multiindex: quadrature multiindex (tuple) + :arg argument_multiindices: tuple of argument multiindices, + one multiindex for each argument + :arg parameters: parameters dictionary + + :returns: list of integral representations + """ # Unroll max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent expressions = unroll_indexsum(expressions, predicate=predicate) - # Integral representation: pair with the set of argument indices - # and a GEM expression - argument_indices = set(chain(*argument_multiindices)) - return [(argument_indices, - index_sum(e, quadrature_multiindex)) - for e in expressions] + # Integral representation: just a GEM expression + return replace_division([index_sum(e, quadrature_multiindex) for e in expressions]) def classify(argument_indices, expression): @@ -59,15 +68,20 @@ def classify(argument_indices, expression): return COMPOUND -def flatten(var_reps): - for variable, reps in var_reps: - # Destructure representation - argument_indicez, expressions = zip(*reps) - # Assert identical argument indices for all integrals - argument_indices, = set(map(frozenset, argument_indicez)) - # Argument factorise - classifier = partial(classify, argument_indices) - for monomial_sum in collect_monomials(expressions, classifier): +def flatten(var_reps, index_cache): + assignments = unconcatenate([(variable, reduce(Sum, reps)) + for variable, reps in var_reps], + cache=index_cache) + + def group_key(assignment): + variable, expression = assignment + return variable.free_indices + + for free_indices, assignment_group in groupby(assignments, group_key): + variables, expressions = zip(*assignment_group) + classifier = partial(classify, set(free_indices)) + monomial_sums = collect_monomials(expressions, classifier) + for variable, monomial_sum in zip(variables, monomial_sums): # Compact MonomialSum after IndexSum-Delta cancellation delta_simplified = MonomialSum() for monomial in monomial_sum: diff --git a/tsfc/tensor.py b/tsfc/tensor.py index f216577888..3d68875bcb 100644 --- a/tsfc/tensor.py +++ b/tsfc/tensor.py @@ -83,7 +83,7 @@ def classify(quadrature_indices, expression): return result -def flatten(var_reps): +def flatten(var_reps, index_cache): for variable, reps in var_reps: expressions = reps for expression in expressions: diff --git a/tsfc/vanilla.py b/tsfc/vanilla.py index 84b7be41e7..c995e16210 100644 --- a/tsfc/vanilla.py +++ b/tsfc/vanilla.py @@ -4,6 +4,7 @@ from gem import index_sum, Sum from gem.optimise import unroll_indexsum +from gem.unconcatenate import unconcatenate def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): @@ -29,19 +30,20 @@ def predicate(index): return [index_sum(e, quadrature_multiindex) for e in expressions] -def flatten(var_reps): +def flatten(var_reps, index_cache): """Flatten mode-specific intermediate representation to a series of assignments. :arg var_reps: series of (return variable, [integral representation]) pairs + :arg index_cache: cache :py:class:`dict` for :py:func:`unconcatenate` :returns: series of (return variable, GEM expression root) pairs """ - for variable, reps in var_reps: - expressions = reps # representations are expressions - yield (variable, reduce(Sum, expressions)) + return unconcatenate([(variable, reduce(Sum, reps)) + for variable, reps in var_reps], + cache=index_cache) -finalise_options = {} +finalise_options = dict(remove_componenttensors=False) """To avoid duplicate work, these options that are safe to pass to :py:func:`gem.impero_utils.preprocess_gem`.""" From 6e375967b15f5ea841b5375f869be697251f2fd9 Mon Sep 17 00:00:00 2001 From: tj Date: Sun, 23 Jul 2017 22:08:48 +0100 Subject: [PATCH 343/809] Updates based on feedbacks on PR - represent optimal atomics directly as a list, without converting them to integers --- tsfc/coffee_mode.py | 61 +++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 63780afd6d..507c13b289 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -3,8 +3,7 @@ import numpy import itertools from functools import partial -from six import iteritems, iterkeys -from collections import defaultdict +from collections import OrderedDict from gem.optimise import make_sum, make_product, replace_division, unroll_indexsum from gem.refactorise import Monomial, collect_monomials from gem.node import traversal @@ -94,42 +93,37 @@ def monomial_sum_to_expression(monomial_sum): return make_sum(indexsums) -def solve_ip(size, is_feasible, compare): +def solve_ip(variables, is_feasible, compare): """Solve a 0-1 integer programming problem. The algorithm tries to set each variable to 1 recursively, and stop the recursion early by comparing with the optimal solution at entry. At worst case this is 2^N (as this is a NP-hard problem), but recursions can be trimmed as soon as a reasonable - solution have been found. One potential improvement is to keep track of - violated constraints at each step, and only try to set the variables which - could help these constraints in the next step. This will help find decent - solutions faster with the additional overhead of maintaining a list of - violated constraints and finding helpful variables. + solution have been found. - :param size: number of 0-1 variables + :param varialbes: list of unique 0-1 variables :param is_feasible: function to test if a combination is feasible :param compare: function to compare two solutions, compare(s1, s2) returns True if s1 is a better solution than s2, and False otherwise :returns: optimal solution represented as a set of variables with value 1 """ - optimal_solution = set(range(size)) # start by choosing all atomics + optimal_solution = set(variables) # start by choosing all atomics solution = set() def solve(idx): - if idx >= size: + if idx >= len(variables): return if not compare(solution, optimal_solution): return - solution.add(idx) + solution.add(variables[idx]) if is_feasible(solution): if compare(solution, optimal_solution): optimal_solution.clear() optimal_solution.update(solution) - # No need to search further - # as adding more variable will only make the solution worse + # No need to search further as adding more variable will only make the solution worse else: solve(idx + 1) - solution.remove(idx) + solution.remove(variables[idx]) solve(idx + 1) solve(0) @@ -147,39 +141,34 @@ def find_optimal_atomics(monomials, argument_indices): :returns: list of atomic GEM expressions """ - atomic_index = defaultdict(partial(next, itertools.count())) # atomic -> int - connections = [] - # add connections (list of sets, items in each tuple form a product) + atomics = OrderedDict() for monomial in monomials: - connections.append(set(map(lambda a: atomic_index[a], monomial.atomics))) + for atomic in monomial.atomics: + atomics[atomic] = 0 + atomics = list(atomics.keys()) - num_atomics = len(atomic_index) - if num_atomics <= 1: - return tuple(iterkeys(atomic_index)) + if len(atomics) <= 1: + return tuple(atomics) def calculate_extent(solution): - extent = 0 - for atomic, index in iteritems(atomic_index): - if index in solution: - extent += index_extent(atomic, argument_indices) - return extent + return sum(map(lambda atomic: index_extent(atomic, argument_indices), solution)) def is_feasible(solution): - for conn in connections: - if not solution.intersection(conn): + # Solution is only feasible if it intersects with all monomials + # Potentially can improve this by keeping track of violated constraints + # and suggest the next atomic to try (instead of just returning True or False) + for monomial in monomials: + if not solution.intersection(monomial.atomics): return False return True def compare(sol1, sol2): - if len(sol1) < len(sol2): - return True - elif len(sol1) > len(sol2): - return False - else: + if len(sol1) == len(sol2): return calculate_extent(sol1) > calculate_extent(sol2) + else: + return len(sol1) < len(sol2) - optimal_solution = solve_ip(num_atomics, is_feasible, compare) - return tuple(sorted([a for a, i in iteritems(atomic_index) if i in optimal_solution], key=atomic_index.get)) + return tuple(sorted(solve_ip(atomics, is_feasible, compare))) def factorise_atomics(monomials, optimal_atomics, argument_indices): From 977a2ae7b8fdd1079f49d62db505e93ddccea325 Mon Sep 17 00:00:00 2001 From: tj Date: Sun, 23 Jul 2017 22:36:29 +0100 Subject: [PATCH 344/809] Rewrite is_feasilbe() --- tsfc/coffee_mode.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 507c13b289..aa7ff49da5 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -157,10 +157,7 @@ def is_feasible(solution): # Solution is only feasible if it intersects with all monomials # Potentially can improve this by keeping track of violated constraints # and suggest the next atomic to try (instead of just returning True or False) - for monomial in monomials: - if not solution.intersection(monomial.atomics): - return False - return True + return all(solution.intersection(monomial.atomics) for monomial in monomials) def compare(sol1, sol2): if len(sol1) == len(sol2): From d76af213309bed0bfb45efebfd02590cf0ae5059 Mon Sep 17 00:00:00 2001 From: Tianjiao Sun Date: Sun, 23 Jul 2017 23:23:19 +0100 Subject: [PATCH 345/809] Sorting of atomics in python3 --- tsfc/coffee_mode.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index aa7ff49da5..46837b1d69 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -142,10 +142,12 @@ def find_optimal_atomics(monomials, argument_indices): :returns: list of atomic GEM expressions """ atomics = OrderedDict() + idx = 0 for monomial in monomials: for atomic in monomial.atomics: - atomics[atomic] = 0 - atomics = list(atomics.keys()) + if atomic not in atomics: + atomics[atomic] = idx + idx += 1 if len(atomics) <= 1: return tuple(atomics) @@ -165,7 +167,7 @@ def compare(sol1, sol2): else: return len(sol1) < len(sol2) - return tuple(sorted(solve_ip(atomics, is_feasible, compare))) + return tuple(sorted(solve_ip(list(atomics.keys()), is_feasible, compare), key=atomics.get)) def factorise_atomics(monomials, optimal_atomics, argument_indices): From 5e32f0956b6b43932452cff789e1a632045056d1 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 24 Jul 2017 15:42:09 +0100 Subject: [PATCH 346/809] Clean up IP code a little more --- tsfc/coffee_mode.py | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 46837b1d69..7deca8a171 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -93,17 +93,16 @@ def monomial_sum_to_expression(monomial_sum): return make_sum(indexsums) -def solve_ip(variables, is_feasible, compare): +def solve_ip(variables, is_feasible, key): """Solve a 0-1 integer programming problem. The algorithm tries to set each variable to 1 recursively, and stop the recursion early by comparing with the optimal solution at entry. At worst case this is 2^N (as this is a NP-hard problem), but recursions can be trimmed as soon as a reasonable solution have been found. - :param varialbes: list of unique 0-1 variables + :param variables: list of unique 0-1 variables :param is_feasible: function to test if a combination is feasible - :param compare: function to compare two solutions, compare(s1, s2) returns - True if s1 is a better solution than s2, and False otherwise + :param key: function to use when comparing solutions. :returns: optimal solution represented as a set of variables with value 1 """ @@ -113,14 +112,15 @@ def solve_ip(variables, is_feasible, compare): def solve(idx): if idx >= len(variables): return - if not compare(solution, optimal_solution): + if key(solution) >= key(optimal_solution): return solution.add(variables[idx]) if is_feasible(solution): - if compare(solution, optimal_solution): + if key(solution) < key(optimal_solution): optimal_solution.clear() optimal_solution.update(solution) - # No need to search further as adding more variable will only make the solution worse + # No need to search further as adding more variables will + # only make the solution worse. else: solve(idx + 1) solution.remove(variables[idx]) @@ -141,19 +141,7 @@ def find_optimal_atomics(monomials, argument_indices): :returns: list of atomic GEM expressions """ - atomics = OrderedDict() - idx = 0 - for monomial in monomials: - for atomic in monomial.atomics: - if atomic not in atomics: - atomics[atomic] = idx - idx += 1 - - if len(atomics) <= 1: - return tuple(atomics) - - def calculate_extent(solution): - return sum(map(lambda atomic: index_extent(atomic, argument_indices), solution)) + atomics = tuple(OrderedDict.fromkeys(itertools.chain(*(monomial.atomics for monomial in monomials)))) def is_feasible(solution): # Solution is only feasible if it intersects with all monomials @@ -161,13 +149,13 @@ def is_feasible(solution): # and suggest the next atomic to try (instead of just returning True or False) return all(solution.intersection(monomial.atomics) for monomial in monomials) - def compare(sol1, sol2): - if len(sol1) == len(sol2): - return calculate_extent(sol1) > calculate_extent(sol2) - else: - return len(sol1) < len(sol2) + def cost(solution): + extent = sum(map(lambda atomic: index_extent(atomic, argument_indices), solution)) + # Prefer shorter solutions, but larger extents + return (len(solution), -extent) - return tuple(sorted(solve_ip(list(atomics.keys()), is_feasible, compare), key=atomics.get)) + optimal_atomics = solve_ip(atomics, is_feasible, key=cost) + return tuple(atomic for atomic in atomics if atomic in optimal_atomics) def factorise_atomics(monomials, optimal_atomics, argument_indices): From 794d921cfa5eeda1cf2d3945ec440812f19c8a00 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 25 Jul 2017 12:00:21 +0100 Subject: [PATCH 347/809] Allow negative subdomain ids The subdomain id is used in generating the kernel name, but a negative value results in an invalid function name. --- tsfc/driver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index b673c2600e..6a494b043d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -92,6 +92,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, cell = integral_data.domain.ufl_cell() arguments = form_data.preprocessed_form.arguments() kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) + # Handle negative subdomain_id + kernel_name = kernel_name.replace("-", "_") fiat_cell = as_fiat_cell(cell) integration_dim, entity_ids = lower_integral_type(fiat_cell, integral_type) From 1e43c478f4b29dd82c23e5c28ef325b7b6dcce62 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 25 Jul 2017 17:23:48 +0100 Subject: [PATCH 348/809] easy fixes for PR comments --- tsfc/driver.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 78b2bcfa89..9cc8967bf3 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -238,15 +238,10 @@ def name_multiindex(multiindex, name): for i, index in enumerate(multiindex): name_index(index, name + str(i)) + name_multiindex(quadrature_indices, 'ip') for multiindex, name in zip(argument_multiindices, ['j', 'k']): name_multiindex(multiindex, name) - if len(quadrature_indices) == 1: - index_names.append((quadrature_indices[0], 'ip')) - else: - for i, quadrature_index in enumerate(quadrature_indices): - index_names.append((quadrature_index, 'ip_%d' % i)) - # Construct kernel body = generate_coffee(impero_c, index_names, parameters["precision"], expressions, split_argument_indices) From 364569d2e76336e0f8c3ee8a725cb9d59741a7d5 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 25 Jul 2017 17:42:41 +0100 Subject: [PATCH 349/809] add failing test case for tensor representation --- tests/test_tensor.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_tensor.py b/tests/test_tensor.py index e74942dce8..c3e8950e03 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -102,6 +102,17 @@ def form(cell, degree): assert (rates < order).all() +def test_mini(): + m = Mesh(VectorElement('CG', triangle, 1)) + P1 = FiniteElement('Lagrange', triangle, 1) + B = FiniteElement("Bubble", triangle, 3) + V = FunctionSpace(m, VectorElement(P1 + B)) + u = TrialFunction(V) + v = TestFunction(V) + a = inner(grad(u), grad(v))*dx + count_flops(a) + + if __name__ == "__main__": import os import sys From 3eae3815f2ebb0f1647f8f1d78fa6bddf300de8d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 25 Jul 2017 18:05:25 +0100 Subject: [PATCH 350/809] fix tensor mode --- tsfc/tensor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tsfc/tensor.py b/tsfc/tensor.py index 3d68875bcb..a9f1ad17f3 100644 --- a/tsfc/tensor.py +++ b/tsfc/tensor.py @@ -11,6 +11,7 @@ import gem from gem.optimise import remove_componenttensors, unroll_indexsum from gem.refactorise import ATOMIC, COMPOUND, OTHER, collect_monomials +from gem.unconcatenate import flatten as concatenate def einsum(factors, sum_indices): @@ -47,6 +48,9 @@ def einsum(factors, sum_indices): def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): + # Concatenate + expressions = concatenate(expressions) + # Unroll max_extent = parameters["unroll_indexsum"] if max_extent: From a0b8ae304857fd59161053555d506adffd4330cc Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 26 Jul 2017 16:50:06 +0100 Subject: [PATCH 351/809] register FInAT HDivElement --- tsfc/finatinterface.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 6621821806..311ca6a2ee 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -166,6 +166,11 @@ def convert_tensorproductelement(element): for elem in element.sub_elements()]) +@convert.register(ufl.HDivElement) +def convert_hdivelement(element): + return finat.HDivElement(create_element(element._element)) + + quad_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() From 000e5ebf92ea33ac6de29e1c8cc50bd60bec8720 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 26 Jul 2017 17:25:25 +0100 Subject: [PATCH 352/809] register FInAT HCurlElement --- tsfc/finatinterface.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 311ca6a2ee..3308b9fe35 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -171,6 +171,11 @@ def convert_hdivelement(element): return finat.HDivElement(create_element(element._element)) +@convert.register(ufl.HCurlElement) +def convert_hcurlelement(element): + return finat.HCurlElement(create_element(element._element)) + + quad_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() From f4646c558a3ceae50ef63eec4292569d0e6cd7e9 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 27 Jul 2017 11:43:15 +0100 Subject: [PATCH 353/809] should be fixed by now --- tests/test_sum_factorisation.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index bb8909ed59..0c22e9354b 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -3,7 +3,6 @@ import numpy import pytest -import sys from coffee.visitors import EstimateFlops @@ -27,8 +26,6 @@ def count_flops(form): return EstimateFlops().visit(kernel.ast) -@pytest.mark.skipif(sys.version_info >= (3,), - reason="Tuple degrees break UFL in Python3") @pytest.mark.parametrize(('cell', 'order'), [(quadrilateral, 5), (TensorProductCell(interval, interval), 5), @@ -44,8 +41,6 @@ def test_lhs(cell, order): assert (rates < order).all() -@pytest.mark.skipif(sys.version_info >= (3,), - reason="Tuple degrees break UFL in Python3") @pytest.mark.parametrize(('cell', 'order'), [(quadrilateral, 3), (TensorProductCell(interval, interval), 3), From 9dc0d4e634b27d23339cbab5761059ca36e6badc Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 27 Jul 2017 12:31:48 +0100 Subject: [PATCH 354/809] test H(div) sum factorisation with split mixed Poisson --- tests/test_sum_factorisation.py | 62 +++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index 0c22e9354b..47c506bb5b 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -7,8 +7,10 @@ from coffee.visitors import EstimateFlops from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, - TestFunction, TrialFunction, TensorProductCell, dx, - action, interval, triangle, quadrilateral, dot, grad) + TestFunction, TrialFunction, TensorProductCell, + EnrichedElement, HDivElement, TensorProductElement, + dx, action, interval, triangle, quadrilateral, dot, + div, grad) from tsfc import compile_form @@ -21,6 +23,29 @@ def helmholtz(cell, degree): return (u*v + dot(grad(u), grad(v)))*dx +def split_mixed_poisson(cell, degree): + m = Mesh(VectorElement('CG', cell, 1)) + if cell.cellname() in ['interval * interval', 'quadrilateral']: + hdiv_element = FiniteElement('RTCF', cell, degree) + elif cell.cellname() == 'triangle * interval': + U0 = FiniteElement('RT', triangle, degree) + U1 = FiniteElement('DG', triangle, degree - 1) + V0 = FiniteElement('CG', interval, degree) + V1 = FiniteElement('DG', interval, degree - 1) + Wa = HDivElement(TensorProductElement(U0, V1)) + Wb = HDivElement(TensorProductElement(U1, V0)) + hdiv_element = EnrichedElement(Wa, Wb) + elif cell.cellname() == 'quadrilateral * interval': + hdiv_element = FiniteElement('NCF', cell, degree) + RT = FunctionSpace(m, hdiv_element) + DG = FunctionSpace(m, FiniteElement('DQ', cell, degree - 1)) + sigma = TrialFunction(RT) + u = TrialFunction(DG) + tau = TestFunction(RT) + v = TestFunction(DG) + return [dot(sigma, tau) * dx, div(tau) * u * dx, div(sigma) * v * dx] + + def count_flops(form): kernel, = compile_form(form, parameters=dict(mode='spectral')) return EstimateFlops().visit(kernel.ast) @@ -56,6 +81,39 @@ def test_rhs(cell, order): assert (rates < order).all() +@pytest.mark.parametrize(('cell', 'order'), + [(quadrilateral, 5), + (TensorProductCell(interval, interval), 5), + (TensorProductCell(triangle, interval), 7), + (TensorProductCell(quadrilateral, interval), 7) + ]) +def test_mixed_poisson(cell, order): + degrees = numpy.arange(3, 8) + if cell == TensorProductCell(triangle, interval): + degrees = numpy.arange(3, 6) + flops = [list(map(count_flops, split_mixed_poisson(cell, degree))) + for degree in degrees] + rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) + assert (rates < order).all() + + +@pytest.mark.parametrize(('cell', 'order'), + [(quadrilateral, 3), + (TensorProductCell(interval, interval), 3), + (TensorProductCell(triangle, interval), 5), + (TensorProductCell(quadrilateral, interval), 4) + ]) +def test_mixed_poisson_action(cell, order): + degrees = numpy.arange(3, 8) + if cell == TensorProductCell(triangle, interval): + degrees = numpy.arange(3, 6) + flops = [[count_flops(action(form)) + for form in split_mixed_poisson(cell, degree)] + for degree in degrees] + rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) + assert (rates < order).all() + + if __name__ == "__main__": import os import sys From f43eb1f9a9b42e0f785d0da3eedddcaaa6105b70 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 27 Jul 2017 15:24:50 +0100 Subject: [PATCH 355/809] test H(curl) sum factorisation with split vector Laplace --- tests/test_sum_factorisation.py | 67 +++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index 47c506bb5b..4db822c037 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -1,5 +1,5 @@ from __future__ import absolute_import, print_function, division -from six.moves import range +from six.moves import map, range import numpy import pytest @@ -8,9 +8,9 @@ from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, TestFunction, TrialFunction, TensorProductCell, - EnrichedElement, HDivElement, TensorProductElement, - dx, action, interval, triangle, quadrilateral, dot, - div, grad) + EnrichedElement, HCurlElement, HDivElement, + TensorProductElement, dx, action, interval, triangle, + quadrilateral, curl, dot, div, grad) from tsfc import compile_form @@ -46,6 +46,29 @@ def split_mixed_poisson(cell, degree): return [dot(sigma, tau) * dx, div(tau) * u * dx, div(sigma) * v * dx] +def split_vector_laplace(cell, degree): + m = Mesh(VectorElement('CG', cell, 1)) + if cell.cellname() in ['interval * interval', 'quadrilateral']: + hcurl_element = FiniteElement('RTCE', cell, degree) + elif cell.cellname() == 'triangle * interval': + U0 = FiniteElement('RT', triangle, degree) + U1 = FiniteElement('CG', triangle, degree) + V0 = FiniteElement('CG', interval, degree) + V1 = FiniteElement('DG', interval, degree - 1) + Wa = HCurlElement(TensorProductElement(U0, V0)) + Wb = HCurlElement(TensorProductElement(U1, V1)) + hcurl_element = EnrichedElement(Wa, Wb) + elif cell.cellname() == 'quadrilateral * interval': + hcurl_element = FiniteElement('NCE', cell, degree) + RT = FunctionSpace(m, hcurl_element) + CG = FunctionSpace(m, FiniteElement('Q', cell, degree)) + sigma = TrialFunction(CG) + u = TrialFunction(RT) + tau = TestFunction(CG) + v = TestFunction(RT) + return [dot(u, grad(tau))*dx, dot(grad(sigma), v)*dx, dot(curl(u), curl(v))*dx] + + def count_flops(form): kernel, = compile_form(form, parameters=dict(mode='spectral')) return EstimateFlops().visit(kernel.ast) @@ -114,6 +137,42 @@ def test_mixed_poisson_action(cell, order): assert (rates < order).all() +@pytest.mark.parametrize(('cell', 'order'), + [(quadrilateral, 5), + (TensorProductCell(interval, interval), 5), + (TensorProductCell(triangle, interval), 7), + (TensorProductCell(quadrilateral, interval), 7) + ]) +def test_vector_laplace(cell, order): + degrees = numpy.arange(3, 8) + if cell == TensorProductCell(triangle, interval): + degrees = numpy.arange(3, 6) + flops = [[count_flops(form) + for form in split_vector_laplace(cell, degree)] + for degree in degrees] + rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) + print(rates) + assert (rates < order).all() + + +@pytest.mark.parametrize(('cell', 'order'), + [(quadrilateral, 3), + (TensorProductCell(interval, interval), 3), + (TensorProductCell(triangle, interval), 5), + (TensorProductCell(quadrilateral, interval), 4) + ]) +def test_vector_laplace_action(cell, order): + degrees = numpy.arange(3, 8) + if cell == TensorProductCell(triangle, interval): + degrees = numpy.arange(3, 6) + flops = [[count_flops(action(form)) + for form in split_vector_laplace(cell, degree)] + for degree in degrees] + rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) + print(rates) + assert (rates < order).all() + + if __name__ == "__main__": import os import sys From ad68c041a34343d7c6d7521465e769ab44c0305d Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 27 Jul 2017 18:58:57 +0100 Subject: [PATCH 356/809] fix Python 3 type strictness --- tests/test_sum_factorisation.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index 4db822c037..2fcf411db5 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -1,5 +1,5 @@ from __future__ import absolute_import, print_function, division -from six.moves import map, range +from six.moves import range import numpy import pytest @@ -114,7 +114,8 @@ def test_mixed_poisson(cell, order): degrees = numpy.arange(3, 8) if cell == TensorProductCell(triangle, interval): degrees = numpy.arange(3, 6) - flops = [list(map(count_flops, split_mixed_poisson(cell, degree))) + flops = [[count_flops(form) + for form in split_mixed_poisson(cell, int(degree))] for degree in degrees] rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) assert (rates < order).all() @@ -131,7 +132,7 @@ def test_mixed_poisson_action(cell, order): if cell == TensorProductCell(triangle, interval): degrees = numpy.arange(3, 6) flops = [[count_flops(action(form)) - for form in split_mixed_poisson(cell, degree)] + for form in split_mixed_poisson(cell, int(degree))] for degree in degrees] rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) assert (rates < order).all() @@ -148,7 +149,7 @@ def test_vector_laplace(cell, order): if cell == TensorProductCell(triangle, interval): degrees = numpy.arange(3, 6) flops = [[count_flops(form) - for form in split_vector_laplace(cell, degree)] + for form in split_vector_laplace(cell, int(degree))] for degree in degrees] rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) print(rates) @@ -166,7 +167,7 @@ def test_vector_laplace_action(cell, order): if cell == TensorProductCell(triangle, interval): degrees = numpy.arange(3, 6) flops = [[count_flops(action(form)) - for form in split_vector_laplace(cell, degree)] + for form in split_vector_laplace(cell, int(degree))] for degree in degrees] rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) print(rates) From 921b38d5f22db568320a72de4800e35fbdb759ed Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 28 Jul 2017 10:14:11 +0100 Subject: [PATCH 357/809] remove spurious print calls --- tests/test_sum_factorisation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index 2fcf411db5..0b36d4dfa5 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -152,7 +152,6 @@ def test_vector_laplace(cell, order): for form in split_vector_laplace(cell, int(degree))] for degree in degrees] rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) - print(rates) assert (rates < order).all() @@ -170,7 +169,6 @@ def test_vector_laplace_action(cell, order): for form in split_vector_laplace(cell, int(degree))] for degree in degrees] rates = numpy.diff(numpy.log(flops).T) / numpy.diff(numpy.log(degrees)) - print(rates) assert (rates < order).all() From 35fbaf95dc6feda26936b91a3a10d8c6442ee090 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 28 Jul 2017 15:12:47 +0100 Subject: [PATCH 358/809] revise existing ILP algorithm for coffee mode --- tsfc/coffee_mode.py | 97 +++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index f98d3cdaf2..bb4d8020aa 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -1,5 +1,5 @@ from __future__ import absolute_import, print_function, division -from six.moves import map, zip +from six.moves import map, range, zip import numpy import itertools @@ -12,6 +12,8 @@ from gem.gem import IndexSum, Failure, Sum, one from gem.utils import groupby +from tsfc.logging import logger + import tsfc.spectral as spectral @@ -64,17 +66,6 @@ def optimise_expressions(expressions, argument_indices): return [optimise_monomial_sum(ms, argument_indices) for ms in monomial_sums] -def index_extent(factor, argument_indices): - """Compute the product of the extents of argument indices of a GEM expression - - :arg factor: GEM expression - :arg argument_indices: set of argument indices - - :returns: product of extents of argument indices - """ - return numpy.product([i.extent for i in set(factor.free_indices).intersection(argument_indices)]) - - def monomial_sum_to_expression(monomial_sum): """Convert a monomial sum to a GEM expression. @@ -93,49 +84,22 @@ def monomial_sum_to_expression(monomial_sum): return make_sum(indexsums) -def solve_ip(variables, is_feasible, key): - """Solve a 0-1 integer programming problem. The algorithm tries to set each - variable to 1 recursively, and stop the recursion early by comparing with - the optimal solution at entry. At worst case this is 2^N (as this is a - NP-hard problem), but recursions can be trimmed as soon as a reasonable - solution have been found. - - :param variables: list of unique 0-1 variables - :param is_feasible: function to test if a combination is feasible - :param key: function to use when comparing solutions. - :returns: optimal solution represented as a set of variables with value 1 - """ - - optimal_solution = set(variables) # start by choosing all atomics - solution = set() - - def solve(idx): - if idx >= len(variables): - return - if key(solution) >= key(optimal_solution): - return - solution.add(variables[idx]) - if is_feasible(solution): - if key(solution) < key(optimal_solution): - optimal_solution.clear() - optimal_solution.update(solution) - # No need to search further as adding more variables will - # only make the solution worse. - else: - solve(idx + 1) - solution.remove(variables[idx]) - solve(idx + 1) +def index_extent(factor, argument_indices): + """Compute the product of the extents of argument indices of a GEM expression - solve(0) + :arg factor: GEM expression + :arg argument_indices: set of argument indices - return optimal_solution + :returns: product of extents of argument indices + """ + return numpy.prod([i.extent for i in factor.free_indices if i in argument_indices]) def find_optimal_atomics(monomials, argument_indices): """Find optimal atomic common subexpressions, which produce least number of terms in the resultant IndexSum when factorised. - :arg monomials: An iterable of :class:`Monomial`s, all of which should have + :arg monomials: A list of :class:`Monomial`s, all of which should have the same sum indices :arg argument_indices: tuple of argument indices @@ -143,19 +107,40 @@ def find_optimal_atomics(monomials, argument_indices): """ atomics = tuple(OrderedDict.fromkeys(itertools.chain(*(monomial.atomics for monomial in monomials)))) - def is_feasible(solution): - # Solution is only feasible if it intersects with all monomials - # Potentially can improve this by keeping track of violated constraints - # and suggest the next atomic to try (instead of just returning True or False) - return all(solution.intersection(monomial.atomics) for monomial in monomials) - def cost(solution): extent = sum(map(lambda atomic: index_extent(atomic, argument_indices), solution)) # Prefer shorter solutions, but larger extents return (len(solution), -extent) - optimal_atomics = solve_ip(atomics, is_feasible, key=cost) - return tuple(atomic for atomic in atomics if atomic in optimal_atomics) + optimal_solution = set(atomics) # pessimal but feasible solution + solution = set() + + max_it = 1 << 12 + it = iter(range(max_it)) + + def solve(idx): + while idx < len(monomials) and solution.intersection(monomials[idx].atomics): + idx += 1 + + if idx < len(monomials): + if len(solution) < len(optimal_solution): + for atomic in monomials[idx].atomics: + solution.add(atomic) + solve(idx + 1) + solution.remove(atomic) + else: + if cost(solution) < cost(optimal_solution): + optimal_solution.clear() + optimal_solution.update(solution) + next(it) + + try: + solve(0) + except StopIteration: + logger.warning("Solution to ILP problem may not be optimal: search " + "interrupted after examining %d solutions.", max_it) + + return tuple(atomic for atomic in atomics if atomic in optimal_solution) def factorise_atomics(monomials, optimal_atomics, argument_indices): @@ -237,7 +222,7 @@ def optimise_monomials(monomials, argument_indices): """Choose optimal common atomic subexpressions and factorise an iterable of monomials. - :arg monomials: an iterable of :class:`Monomial`s, all of which should have + :arg monomials: a list of :class:`Monomial`s, all of which should have the same sum indices :arg argument_indices: tuple of argument indices From 4cc3418242b89056f1ed9caf77fcd53be383ddb4 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 23 May 2017 12:45:16 +0100 Subject: [PATCH 359/809] Register Luporini 2016 citation in coffee mode --- tsfc/coffee_mode.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index bb4d8020aa..c80298f313 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -29,6 +29,11 @@ def flatten(var_reps, index_cache): :returns: series of (return variable, GEM expression root) pairs """ + try: + from firedrake import Citations + Citations().register("Luporini2016") + except ImportError: + pass assignments = unconcatenate([(variable, reduce(Sum, reps)) for variable, reps in var_reps], cache=index_cache) From 93262859470e49079851c45c9d854238c5659383 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 28 Jul 2017 16:51:46 +0100 Subject: [PATCH 360/809] Register TSFC preprint with Firedrake Citations --- tsfc/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tsfc/__init__.py b/tsfc/__init__.py index 54e44b8a69..5012401cd3 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -2,3 +2,10 @@ from tsfc.driver import compile_form, compile_expression_at_points # noqa: F401 from tsfc.parameters import default_parameters # noqa: F401 + +try: + from firedrake import Citations + Citations().register("Homolya2017") + del Citations +except ImportError: + pass From 4ea91f17a7122fadc4aba22de69cecb55ed23e25 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 31 Jul 2017 15:55:47 +0100 Subject: [PATCH 361/809] optimise away Delta in UFL arguments --- tsfc/spectral.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 722889ca0a..95fc5442f4 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, print_function, division from six.moves import zip +from collections import OrderedDict, defaultdict from functools import partial, reduce from gem import Delta, Indexed, Sum, index_sum @@ -12,14 +13,19 @@ from gem.utils import groupby -def delta_elimination(sum_indices, args, rest): +def delta_elimination(variable, sum_indices, args, rest): """IndexSum-Delta cancellation for monomials.""" - factors = [rest] + list(args) # construct factors + factors = list(args) + [rest, variable] # construct factors sum_indices, factors = _delta_elimination(sum_indices, factors) + var_indices, factors = _delta_elimination(variable.free_indices, factors) + # Destructure factors after cancellation - rest = factors.pop(0) + variable = factors.pop() + rest = factors.pop() args = factors - return sum_indices, args, rest + + assert set(var_indices) == set(variable.free_indices) + return variable, sum_indices, args, rest def sum_factorise(sum_indices, args, rest): @@ -77,19 +83,25 @@ def group_key(assignment): variable, expression = assignment return variable.free_indices + simplified_variables = OrderedDict() + delta_simplified = defaultdict(MonomialSum) for free_indices, assignment_group in groupby(assignments, group_key): variables, expressions = zip(*assignment_group) classifier = partial(classify, set(free_indices)) monomial_sums = collect_monomials(expressions, classifier) for variable, monomial_sum in zip(variables, monomial_sums): # Compact MonomialSum after IndexSum-Delta cancellation - delta_simplified = MonomialSum() for monomial in monomial_sum: - delta_simplified.add(*delta_elimination(*monomial)) + var, s, a, r = delta_elimination(variable, *monomial) + simplified_variables.setdefault(var) + delta_simplified[var].add(s, a, r) + + for variable in simplified_variables: + monomial_sum = delta_simplified[variable] - # Yield assignments - for monomial in delta_simplified: - yield (variable, sum_factorise(*monomial)) + # Yield assignments + for monomial in monomial_sum: + yield (variable, sum_factorise(*monomial)) -finalise_options = dict(remove_componenttensors=False) +finalise_options = dict(remove_componenttensors=False, replace_delta=False) From 443179af7d9b97befeefa7c2722c2aacb3814a3a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 1 Aug 2017 11:40:10 +0100 Subject: [PATCH 362/809] disconnect coffee_mode.Integrals from spectral.Integrals --- tsfc/coffee_mode.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index c80298f313..e030e03346 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -5,11 +5,11 @@ import itertools from functools import partial, reduce from collections import OrderedDict -from gem.optimise import make_sum, make_product +from gem.optimise import make_sum, make_product, replace_division, unroll_indexsum from gem.refactorise import Monomial, collect_monomials from gem.unconcatenate import unconcatenate from gem.node import traversal -from gem.gem import IndexSum, Failure, Sum, one +from gem.gem import IndexSum, Failure, Sum, index_sum, one from gem.utils import groupby from tsfc.logging import logger @@ -17,7 +17,27 @@ import tsfc.spectral as spectral -Integrals = spectral.Integrals +def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): + """Constructs an integral representation for each GEM integrand + expression. + + :arg expressions: integrand multiplied with quadrature weight; + multi-root GEM expression DAG + :arg quadrature_multiindex: quadrature multiindex (tuple) + :arg argument_multiindices: tuple of argument multiindices, + one multiindex for each argument + :arg parameters: parameters dictionary + + :returns: list of integral representations + """ + # Unroll + max_extent = parameters["unroll_indexsum"] + if max_extent: + def predicate(index): + return index.extent <= max_extent + expressions = unroll_indexsum(expressions, predicate=predicate) + # Integral representation: just a GEM expression + return replace_division([index_sum(e, quadrature_multiindex) for e in expressions]) def flatten(var_reps, index_cache): From 28befa434f7cf12cc151669618cdbc6e016e4f60 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 1 Aug 2017 17:20:00 +0100 Subject: [PATCH 363/809] tolerate and skip monomials with no atomics --- tsfc/coffee_mode.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index e030e03346..63fa2f23c2 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -256,5 +256,8 @@ def optimise_monomials(monomials, argument_indices): assert len(set(frozenset(m.sum_indices) for m in monomials)) <= 1,\ "All monomials required to have same sum indices for factorisation" - optimal_atomics = find_optimal_atomics(monomials, argument_indices) - return factorise_atomics(monomials, optimal_atomics, argument_indices) + result = [m for m in monomials if not m.atomics] # skipped monomials + active_monomials = [m for m in monomials if m.atomics] + optimal_atomics = find_optimal_atomics(active_monomials, argument_indices) + result += factorise_atomics(active_monomials, optimal_atomics, argument_indices) + return result From 15fd7a3673164cc046bfba71c081312a76c65426 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 1 Aug 2017 16:17:55 +0100 Subject: [PATCH 364/809] rewrite spectral mode --- tsfc/spectral.py | 164 +++++++++++++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 54 deletions(-) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 95fc5442f4..fd74a7e10f 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -1,40 +1,21 @@ from __future__ import absolute_import, print_function, division -from six.moves import zip +from six.moves import zip, zip_longest -from collections import OrderedDict, defaultdict +from collections import OrderedDict, defaultdict, namedtuple from functools import partial, reduce +from itertools import chain -from gem import Delta, Indexed, Sum, index_sum +from gem.gem import Delta, Indexed, Sum, index_sum, one from gem.optimise import delta_elimination as _delta_elimination -from gem.optimise import sum_factorise as _sum_factorise -from gem.optimise import replace_division, unroll_indexsum +from gem.optimise import remove_componenttensors, replace_division, unroll_indexsum from gem.refactorise import ATOMIC, COMPOUND, OTHER, MonomialSum, collect_monomials from gem.unconcatenate import unconcatenate from gem.utils import groupby -def delta_elimination(variable, sum_indices, args, rest): - """IndexSum-Delta cancellation for monomials.""" - factors = list(args) + [rest, variable] # construct factors - sum_indices, factors = _delta_elimination(sum_indices, factors) - var_indices, factors = _delta_elimination(variable.free_indices, factors) - - # Destructure factors after cancellation - variable = factors.pop() - rest = factors.pop() - args = factors - - assert set(var_indices) == set(variable.free_indices) - return variable, sum_indices, args, rest - - -def sum_factorise(sum_indices, args, rest): - """Optimised monomial product construction through sum factorisation - with reversed sum indices.""" - sum_indices = list(sum_indices) - sum_indices.reverse() - factors = args + (rest,) - return _sum_factorise(sum_indices, factors) +Integral = namedtuple('Integral', ['expression', + 'quadrature_multiindex', + 'argument_indices']) def Integrals(expressions, quadrature_multiindex, argument_multiindices, parameters): @@ -50,14 +31,72 @@ def Integrals(expressions, quadrature_multiindex, argument_multiindices, paramet :returns: list of integral representations """ + # Rewrite: a / b => a * (1 / b) + expressions = replace_division(expressions) + # Unroll max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent expressions = unroll_indexsum(expressions, predicate=predicate) - # Integral representation: just a GEM expression - return replace_division([index_sum(e, quadrature_multiindex) for e in expressions]) + + expressions = [index_sum(e, quadrature_multiindex) for e in expressions] + argument_indices = tuple(chain(*argument_multiindices)) + return [Integral(e, quadrature_multiindex, argument_indices) for e in expressions] + + +def flatten(var_reps, index_cache): + quadrature_indices = OrderedDict() + + pairs = [] # assignment pairs + for variable, reps in var_reps: + # Extract argument indices + argument_indices, = set(r.argument_indices for r in reps) + assert set(variable.free_indices) == set(argument_indices) + + # Extract and verify expressions + expressions = [r.expression for r in reps] + assert all(set(e.free_indices) <= set(argument_indices) + for e in expressions) + + # Save assignment pair + pairs.append((variable, reduce(Sum, expressions))) + + # Collect quadrature_indices + for r in reps: + quadrature_indices.update(zip_longest(r.quadrature_multiindex, ())) + + # Split Concatenate nodes + pairs = unconcatenate(pairs, cache=index_cache) + + def group_key(pair): + variable, expression = pair + return variable.free_indices + + # Delta cancellation for arguments + narrow_variables = OrderedDict() + delta_simplified = defaultdict(MonomialSum) + for free_indices, pair_group in groupby(pairs, group_key): + variables, expressions = zip(*pair_group) + classifier = partial(classify, set(free_indices)) + monomial_sums = collect_monomials(expressions, classifier) + for variable, monomial_sum in zip(variables, monomial_sums): + for monomial in monomial_sum: + var, s, a, r = delta_elimination(variable, *monomial) + narrow_variables.setdefault(var) + delta_simplified[var].add(s, a, r) + + # Final factorisation + for variable in narrow_variables: + monomial_sum = delta_simplified[variable] + sum_indices = set().union(*[m.sum_indices for m in monomial_sum]) + sum_indices = tuple(i for i in quadrature_indices if i in sum_indices) + expression = sum_factorise(variable, sum_indices, monomial_sum) + yield (variable, expression) + + +finalise_options = dict(replace_delta=False) def classify(argument_indices, expression): @@ -74,34 +113,51 @@ def classify(argument_indices, expression): return COMPOUND -def flatten(var_reps, index_cache): - assignments = unconcatenate([(variable, reduce(Sum, reps)) - for variable, reps in var_reps], - cache=index_cache) +def delta_elimination(variable, sum_indices, args, rest): + """IndexSum-Delta cancellation for monomials.""" + factors = list(args) + [variable, rest] # construct factors - def group_key(assignment): - variable, expression = assignment - return variable.free_indices + def prune(factors): + result = remove_componenttensors(factors[:-1]) + result.append(factors[-1]) + return result - simplified_variables = OrderedDict() - delta_simplified = defaultdict(MonomialSum) - for free_indices, assignment_group in groupby(assignments, group_key): - variables, expressions = zip(*assignment_group) - classifier = partial(classify, set(free_indices)) - monomial_sums = collect_monomials(expressions, classifier) - for variable, monomial_sum in zip(variables, monomial_sums): - # Compact MonomialSum after IndexSum-Delta cancellation - for monomial in monomial_sum: - var, s, a, r = delta_elimination(variable, *monomial) - simplified_variables.setdefault(var) - delta_simplified[var].add(s, a, r) + # Cancel sum indices + sum_indices, factors = _delta_elimination(sum_indices, factors) + factors = prune(factors) - for variable in simplified_variables: - monomial_sum = delta_simplified[variable] + # Cancel variable indices + var_indices, factors = _delta_elimination(variable.free_indices, factors) + factors = prune(factors) - # Yield assignments - for monomial in monomial_sum: - yield (variable, sum_factorise(*monomial)) + # Destructure factors after cancellation + rest = factors.pop() + variable = factors.pop() + args = [f for f in factors if f != one] + + assert set(var_indices) == set(variable.free_indices) + return variable, sum_indices, args, rest -finalise_options = dict(remove_componenttensors=False, replace_delta=False) +def sum_factorise(variable, tail_ordering, monomial_sum): + if tail_ordering: + key_ordering = OrderedDict() + sub_monosums = defaultdict(MonomialSum) + for sum_indices, atomics, rest in monomial_sum: + tail_indices = tuple(i for i in sum_indices if i in tail_ordering) + tail_atomics = tuple(a for a in atomics + if set(tail_indices) & set(a.free_indices)) + head_indices = tuple(i for i in sum_indices if i not in tail_ordering) + head_atomics = tuple(a for a in atomics if a not in tail_atomics) + key = (head_indices, head_atomics) + key_ordering.setdefault(key) + sub_monosums[key].add(tail_indices, tail_atomics, rest) + sub_monosums = [(key, sub_monosums[key]) for key in key_ordering] + + monomial_sum = MonomialSum() + for (sum_indices, atomics), monosum in sub_monosums: + new_rest = sum_factorise(variable, tail_ordering[1:], monosum) + monomial_sum.add(sum_indices, atomics, new_rest) + + from tsfc.coffee_mode import optimise_monomial_sum + return optimise_monomial_sum(monomial_sum, variable.index_ordering()) From 14970cffc88954083beb1a1fe4271607bb0e5c30 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 1 Aug 2017 18:51:36 +0100 Subject: [PATCH 365/809] fix failing test case --- tsfc/spectral.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index fd74a7e10f..58538f5661 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -91,7 +91,8 @@ def group_key(pair): for variable in narrow_variables: monomial_sum = delta_simplified[variable] sum_indices = set().union(*[m.sum_indices for m in monomial_sum]) - sum_indices = tuple(i for i in quadrature_indices if i in sum_indices) + sum_indices = [i for i in quadrature_indices if i in sum_indices] + sum_indices = sorted(sum_indices, key=lambda index: index.extent) expression = sum_factorise(variable, sum_indices, monomial_sum) yield (variable, expression) From e69c08fbc0c5a731bfd10d6c591073c5ef66879c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 1 Aug 2017 19:04:18 +0100 Subject: [PATCH 366/809] fix Python 2 flake8 --- tsfc/spectral.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 58538f5661..6974d8b86a 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -153,7 +153,7 @@ def sum_factorise(variable, tail_ordering, monomial_sum): key = (head_indices, head_atomics) key_ordering.setdefault(key) sub_monosums[key].add(tail_indices, tail_atomics, rest) - sub_monosums = [(key, sub_monosums[key]) for key in key_ordering] + sub_monosums = [(k, sub_monosums[k]) for k in key_ordering] monomial_sum = MonomialSum() for (sum_indices, atomics), monosum in sub_monosums: From 105f7a630a1e96588462ec36242937a3b3dfd296 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 31 Jul 2017 12:41:55 +0100 Subject: [PATCH 367/809] register MixedElement in FInAT interface --- tsfc/finatinterface.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 3308b9fe35..6c976c1332 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -137,11 +137,18 @@ def convert_finiteelement(element): return lmbda(cell, element.degree()) +# EnrichedElement case @convert.register(ufl.EnrichedElement) def convert_enrichedelement(element): return finat.EnrichedElement([create_element(elem) for elem in element._elements]) +# Generic MixedElement case +@convert.register(ufl.MixedElement) +def convert_mixedelement(element): + return finat.MixedElement([create_element(elem) for elem in element.sub_elements()]) + + # VectorElement case @convert.register(ufl.VectorElement) def convert_vectorelement(element): From 732af18ea489ba6047d5f6817951f31bc8402cfa Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 31 Jul 2017 13:23:14 +0100 Subject: [PATCH 368/809] undo transpose in UFC kernel interface --- tsfc/kernel_interface/ufc.py | 50 +++++------------------------------- 1 file changed, 7 insertions(+), 43 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index bd717d0609..11e58f2ac8 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -2,7 +2,7 @@ from six.moves import range, zip import numpy -from itertools import product +from itertools import chain, product from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace @@ -177,34 +177,11 @@ def prepare_coefficients(coefficients, num, name, interior_facet=False): ends = list(numpy.cumsum(space_dimensions)) starts = [0] + ends[:-1] slices = [slice(start, end) for start, end in zip(starts, ends)] - - transposed_shapes = [] - tensor_ranks = [] - for element in elements: - if isinstance(element, TensorFiniteElement): - scalar_shape = element.base_element.index_shape - tensor_shape = element.index_shape[len(scalar_shape):] - else: - scalar_shape = element.index_shape - tensor_shape = () - - transposed_shapes.append(tensor_shape + scalar_shape) - tensor_ranks.append(len(tensor_shape)) - - def transpose(expr, rank): - assert not expr.free_indices - assert 0 <= rank < len(expr.shape) - if rank == 0: - return expr - else: - indices = tuple(gem.Index(extent=extent) for extent in expr.shape) - transposed_indices = indices[rank:] + indices[:rank] - return gem.ComponentTensor(gem.Indexed(expr, indices), - transposed_indices) + shapes = [element.index_shape for element in elements] def expressions(data): - return prune([transpose(gem.reshape(gem.view(data, slice_), shape), rank) - for slice_, shape, rank in zip(slices, transposed_shapes, tensor_ranks)]) + return prune([gem.reshape(gem.view(data, slice_), shape) + for slice_, shape in zip(slices, shapes)]) size = sum(space_dimensions) if not interior_facet: @@ -280,24 +257,11 @@ def prepare_arguments(arguments, multiindices, interior_facet=False): return funarg, [zero], [gem.reshape(varexp, ())] elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - transposed_shapes = [] - transposed_indices = [] - for element, multiindex in zip(elements, multiindices): - if isinstance(element, TensorFiniteElement): - scalar_shape = element.base_element.index_shape - tensor_shape = element.index_shape[len(scalar_shape):] - else: - scalar_shape = element.index_shape - tensor_shape = () - - transposed_shapes.append(tensor_shape + scalar_shape) - scalar_rank = len(scalar_shape) - transposed_indices.extend(multiindex[scalar_rank:] + multiindex[:scalar_rank]) - transposed_indices = tuple(transposed_indices) + shapes = [element.index_shape for element in elements] + indices = tuple(chain(*multiindices)) def expression(restricted): - return gem.Indexed(gem.reshape(restricted, *transposed_shapes), - transposed_indices) + return gem.Indexed(gem.reshape(restricted, *shapes), indices) u_shape = numpy.array([numpy.prod(element.index_shape, dtype=int) for element in elements]) From fd82294cd90c9e3456e3b06bdd4955182385aeca Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 31 Jul 2017 13:37:33 +0100 Subject: [PATCH 369/809] do not split coefficients in UFC kernel interface --- tsfc/kernel_interface/ufc.py | 50 ++++++++++++------------------------ tsfc/ufl_utils.py | 3 +++ 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 11e58f2ac8..a6157eccdb 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -4,8 +4,6 @@ import numpy from itertools import chain, product -from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace - import coffee.base as coffee import gem @@ -29,7 +27,7 @@ def __init__(self, integral_type, subdomain_id, domain_number): self.local_tensor = None self.coordinates_args = None self.coefficient_args = None - self.coefficient_split = {} + self.coefficient_split = None if self.interior_facet: self._cell_orientations = (gem.Variable("cell_orientation_0", ()), @@ -88,15 +86,9 @@ def set_coefficients(self, integral_data, form_data): if not integral_data.enabled_coefficients[n]: continue - coeff = form_data.function_replace_map[form_data.reduced_coefficients[n]] - if type(coeff.ufl_element()) == ufl_MixedElement: - coeffs = [Coefficient(FunctionSpace(coeff.ufl_domain(), element)) - for element in coeff.ufl_element().sub_elements()] - self.coefficient_split[coeff] = coeffs - else: - coeffs = [coeff] - expressions = prepare_coefficients(coeffs, n, name, self.interior_facet) - self.coefficient_map.update(zip(coeffs, expressions)) + coefficient = form_data.function_replace_map[form_data.reduced_coefficients[n]] + expression = prepare_coefficient(coefficient, n, name, self.interior_facet) + self.coefficient_map[coefficient] = expression def construct_kernel(self, name, body): """Construct a fully built kernel function. @@ -152,11 +144,11 @@ def needs_cell_orientations(ir): return True -def prepare_coefficients(coefficients, num, name, interior_facet=False): +def prepare_coefficient(coefficient, num, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. - :arg coefficients: split UFL Coefficients + :arg coefficient: UFL Coefficient :arg num: coefficient index in the original form :arg name: unique name to refer to the Coefficient in the kernel :arg interior_facet: interior facet integral? @@ -164,34 +156,26 @@ def prepare_coefficients(coefficients, num, name, interior_facet=False): """ varexp = gem.Variable(name, (None, None)) - if len(coefficients) == 1 and coefficients[0].ufl_element().family() == 'Real': - coefficient, = coefficients + if coefficient.ufl_element().family() == 'Real': size = numpy.prod(coefficient.ufl_shape, dtype=int) data = gem.view(varexp, slice(num, num + 1), slice(size)) - expression = gem.reshape(data, (), coefficient.ufl_shape) - return [expression] - - elements = [create_element(coeff.ufl_element()) for coeff in coefficients] - space_dimensions = [numpy.prod(element.index_shape, dtype=int) - for element in elements] - ends = list(numpy.cumsum(space_dimensions)) - starts = [0] + ends[:-1] - slices = [slice(start, end) for start, end in zip(starts, ends)] - shapes = [element.index_shape for element in elements] + return gem.reshape(data, (), coefficient.ufl_shape) + + element = create_element(coefficient.ufl_element()) + size = numpy.prod(element.index_shape, dtype=int) - def expressions(data): - return prune([gem.reshape(gem.view(data, slice_), shape) - for slice_, shape in zip(slices, shapes)]) + def expression(data): + result, = prune([gem.reshape(gem.view(data, slice(size)), element.index_shape)]) + return result - size = sum(space_dimensions) if not interior_facet: data = gem.view(varexp, slice(num, num + 1), slice(size)) - return expressions(gem.reshape(data, (), (size,))) + return expression(gem.reshape(data, (), (size,))) else: data_p = gem.view(varexp, slice(num, num + 1), slice(size)) data_m = gem.view(varexp, slice(num, num + 1), slice(size, 2 * size)) - return list(zip(expressions(gem.reshape(data_p, (), (size,))), - expressions(gem.reshape(data_m, (), (size,))))) + return (expression(gem.reshape(data_p, (), (size,))), + expression(gem.reshape(data_m, (), (size,)))) def prepare_coordinates(coefficient, name, interior_facet=False): diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 7dda1ed8f8..d756b5b871 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -202,6 +202,9 @@ def modified_terminal(self, o): def split_coefficients(expression, split): """Split mixed coefficients, so mixed elements need not be implemented.""" + if split is None: + return expression + splitter = CoefficientSplitter(split) return map_expr_dag(splitter, expression) From 96b8573f6950b7ef299730a2b2b2c7f28cf53997 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 31 Jul 2017 14:06:36 +0100 Subject: [PATCH 370/809] implement per-backend element conversion --- tsfc/driver.py | 5 +-- tsfc/fem.py | 6 +-- tsfc/finatinterface.py | 61 +++++++++++++++++++----------- tsfc/kernel_interface/__init__.py | 5 +++ tsfc/kernel_interface/firedrake.py | 5 +++ tsfc/kernel_interface/ufc.py | 12 +++++- 6 files changed, 64 insertions(+), 30 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9cc8967bf3..53ce2fc6be 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -28,7 +28,6 @@ from tsfc import fem, ufl_utils from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee from tsfc.fiatinterface import as_fiat_cell -from tsfc.finatinterface import create_element from tsfc.logging import logger from tsfc.parameters import default_parameters @@ -99,14 +98,14 @@ def compile_integral(integral_data, form_data, prefix, parameters, fiat_cell = as_fiat_cell(cell) integration_dim, entity_ids = lower_integral_type(fiat_cell, integral_type) - argument_multiindices = tuple(create_element(arg.ufl_element()).get_indices() - for arg in arguments) quadrature_indices = [] # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() builder = interface.KernelBuilder(integral_type, integral_data.subdomain_id, domain_numbering[integral_data.domain]) + argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() + for arg in arguments) return_variables = builder.set_arguments(arguments, argument_multiindices) coordinates = ufl_utils.coordinate_coefficient(mesh) diff --git a/tsfc/fem.py b/tsfc/fem.py index 7b0bb2df72..f341bc6453 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -31,7 +31,7 @@ from finat.quadrature import make_quadrature from tsfc import ufl2gem -from tsfc.finatinterface import create_element, as_fiat_cell +from tsfc.finatinterface import as_fiat_cell from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import analyse_modified_terminal from tsfc.parameters import NUMPY_TYPE, PARAMETERS @@ -319,7 +319,7 @@ def fiat_to_ufl(fiat_dict, order): def translate_argument(terminal, mt, ctx): argument_multiindex = ctx.argument_multiindices[terminal.number()] sigma = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) - element = create_element(terminal.ufl_element()) + element = ctx.create_element(terminal.ufl_element()) def callback(entity_id): finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) @@ -346,7 +346,7 @@ def translate_coefficient(terminal, mt, ctx): assert mt.local_derivatives == 0 return vec - element = create_element(terminal.ufl_element()) + element = ctx.create_element(terminal.ufl_element()) # Collect FInAT tabulation for all entities per_derivative = collections.defaultdict(list) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 6c976c1332..e71b408238 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -81,7 +81,7 @@ def fiat_compat(element): @singledispatch -def convert(element): +def convert(element, vector_transpose=False): """Handler for converting UFL elements to FInAT elements. :arg element: The UFL element to convert. @@ -95,7 +95,7 @@ def convert(element): # Base finite elements first @convert.register(ufl.FiniteElement) -def convert_finiteelement(element): +def convert_finiteelement(element, vector_transpose=False): cell = as_fiat_cell(element.cell()) if element.family() == "Quadrature": degree = element.degree() @@ -113,7 +113,7 @@ def convert_finiteelement(element): element.family()) # Handle quadrilateral short names like RTCF and RTCE. element = element.reconstruct(cell=quad_tpc) - return finat.QuadrilateralElement(create_element(element)) + return finat.QuadrilateralElement(create_element(element, vector_transpose)) kind = element.variant() if kind is None: @@ -139,67 +139,82 @@ def convert_finiteelement(element): # EnrichedElement case @convert.register(ufl.EnrichedElement) -def convert_enrichedelement(element): - return finat.EnrichedElement([create_element(elem) for elem in element._elements]) +def convert_enrichedelement(element, vector_transpose=False): + return finat.EnrichedElement([create_element(elem, vector_transpose) + for elem in element._elements]) # Generic MixedElement case @convert.register(ufl.MixedElement) -def convert_mixedelement(element): - return finat.MixedElement([create_element(elem) for elem in element.sub_elements()]) +def convert_mixedelement(element, vector_transpose=False): + return finat.MixedElement([create_element(elem, vector_transpose) + for elem in element.sub_elements()]) # VectorElement case @convert.register(ufl.VectorElement) -def convert_vectorelement(element): - scalar_element = create_element(element.sub_elements()[0]) - return finat.TensorFiniteElement(scalar_element, (element.num_sub_elements(),)) +def convert_vectorelement(element, vector_transpose=False): + scalar_element = create_element(element.sub_elements()[0], vector_transpose) + return finat.TensorFiniteElement(scalar_element, + (element.num_sub_elements(),), + transpose=vector_transpose) # TensorElement case @convert.register(ufl.TensorElement) -def convert_tensorelement(element): - scalar_element = create_element(element.sub_elements()[0]) - return finat.TensorFiniteElement(scalar_element, element.reference_value_shape()) +def convert_tensorelement(element, vector_transpose=False): + scalar_element = create_element(element.sub_elements()[0], vector_transpose) + return finat.TensorFiniteElement(scalar_element, + element.reference_value_shape(), + transpose=vector_transpose) # TensorProductElement case @convert.register(ufl.TensorProductElement) -def convert_tensorproductelement(element): +def convert_tensorproductelement(element, vector_transpose=False): cell = element.cell() if type(cell) is not ufl.TensorProductCell: raise ValueError("TensorProductElement not on TensorProductCell?") - return finat.TensorProductElement([create_element(elem) + return finat.TensorProductElement([create_element(elem, vector_transpose) for elem in element.sub_elements()]) +# HDivElement case @convert.register(ufl.HDivElement) -def convert_hdivelement(element): - return finat.HDivElement(create_element(element._element)) +def convert_hdivelement(element, vector_transpose=False): + return finat.HDivElement(create_element(element._element, vector_transpose)) +# HDivElement case @convert.register(ufl.HCurlElement) -def convert_hcurlelement(element): - return finat.HCurlElement(create_element(element._element)) +def convert_hcurlelement(element, vector_transpose=False): + return finat.HCurlElement(create_element(element._element, vector_transpose)) quad_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() -def create_element(element): +def create_element(element, vector_transpose=False): """Create a FInAT element (suitable for tabulating with) given a UFL element. :arg element: The UFL element to create a FInAT element from. + :arg vector_transpose: Vector/tensor indices come before basis function indices """ try: - return _cache[element] + cache = _cache[element] + except KeyError: + _cache[element] = {} + cache = _cache[element] + + try: + return cache[vector_transpose] except KeyError: pass if element.cell() is None: raise ValueError("Don't know how to build element when cell is not given") - finat_element = convert(element) - _cache[element] = finat_element + finat_element = convert(element, vector_transpose=vector_transpose) + cache[vector_transpose] = finat_element return finat_element diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index 2b04803d3b..a81eb3426d 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -23,5 +23,10 @@ def cell_orientation(self, restriction): def entity_number(self, restriction): """Facet or vertex number as a GEM index.""" + @abstractmethod + def create_element(self, element): + """Create a FInAT element (suitable for tabulating with) given + a UFL element.""" + ProxyKernelInterface = make_proxy_class('ProxyKernelInterface', KernelInterface) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 9b28bd4ad1..44e0ca5453 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -91,6 +91,11 @@ def needs_cell_orientations(ir): return True return False + def create_element(self, element): + """Create a FInAT element (suitable for tabulating with) given + a UFL element.""" + return create_element(element) + class ExpressionKernelBuilder(KernelBuilderBase): """Builds expression kernels for UFL interpolation in Firedrake.""" diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index a6157eccdb..07373e567a 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -12,10 +12,15 @@ from finat import TensorFiniteElement from tsfc.kernel_interface.common import KernelBuilderBase -from tsfc.finatinterface import create_element +from tsfc.finatinterface import create_element as _create_element from tsfc.coffee import SCALAR_TYPE +def create_element(element): + # UFC DoF ordering for vector/tensor elements is XXXX YYYY ZZZZ. + return _create_element(element, vector_transpose=True) + + class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" @@ -143,6 +148,11 @@ def needs_cell_orientations(ir): # UFC tabulate_tensor always have cell orientations return True + def create_element(self, element): + """Create a FInAT element (suitable for tabulating with) given + a UFL element.""" + return create_element(element) + def prepare_coefficient(coefficient, num, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for From 25a6dbad869a509dd9db035633c84b90efebe38a Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Mon, 31 Jul 2017 14:13:01 +0100 Subject: [PATCH 371/809] transpose UFC coordinates Because their DoF ordering, of course, still does not match the DoF ordering of normal vector elements. --- tsfc/kernel_interface/ufc.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 07373e567a..4c3e51875b 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -204,12 +204,24 @@ def prepare_coordinates(coefficient, name, interior_facet=False): shape = finat_element.index_shape size = numpy.prod(shape, dtype=int) + assert isinstance(finat_element, TensorFiniteElement) + scalar_shape = finat_element.base_element.index_shape + tensor_shape = finat_element._shape + transposed_shape = scalar_shape + tensor_shape + scalar_rank = len(scalar_shape) + + def transpose(expr): + indices = tuple(gem.Index(extent=extent) for extent in expr.shape) + transposed_indices = indices[scalar_rank:] + indices[:scalar_rank] + return gem.ComponentTensor(gem.Indexed(expr, indices), + transposed_indices) + if not interior_facet: funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), pointers=[("",)], qualifiers=["const"])] variable = gem.Variable(name, (size,)) - expression = gem.reshape(variable, shape) + expression = transpose(gem.reshape(variable, transposed_shape)) else: funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_0"), pointers=[("",)], @@ -219,8 +231,8 @@ def prepare_coordinates(coefficient, name, interior_facet=False): qualifiers=["const"])] variable0 = gem.Variable(name+"_0", (size,)) variable1 = gem.Variable(name+"_1", (size,)) - expression = (gem.reshape(variable0, shape), - gem.reshape(variable1, shape)) + expression = (transpose(gem.reshape(variable0, transposed_shape)), + transpose(gem.reshape(variable1, transposed_shape))) return funargs, expression From b75ef8ff2524a30be0a63f581271a6ae0814a930 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Aug 2017 11:54:13 +0100 Subject: [PATCH 372/809] max not safe on degrees Fixes #140. --- tsfc/driver.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9cc8967bf3..d3b66bb27f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -16,6 +16,7 @@ from ufl.algorithms.analysis import has_type from ufl.classes import Form, CellVolume from ufl.log import GREEN +from ufl.utils.sequences import max_degree import gem import gem.impero_utils as impero_utils @@ -161,10 +162,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, function_degrees = [f.ufl_function_space().ufl_element().degree() for f in functions] if all((asarray(quadrature_degree) > 10 * asarray(degree)).all() for degree in function_degrees): - logger.warning("Estimated quadrature degree %d more " + logger.warning("Estimated quadrature degree %s more " "than tenfold greater than any " - "argument/coefficient degree (max %d)", - quadrature_degree, max(function_degrees)) + "argument/coefficient degree (max %s)", + quadrature_degree, max_degree(function_degrees)) try: quad_rule = params["quadrature_rule"] From 93e51d9a7b2a155c7cd2dc13dcc368a833be3a89 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Aug 2017 14:19:35 +0100 Subject: [PATCH 373/809] test underintegration tricks Mass matrix and mass action in O(p^d) operations. Laplace operator in O(p^(d+2)) operations, c.f. O(p^(2d+1)) required by mere sum factorisation. --- tests/test_underintegration.py | 103 +++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 tests/test_underintegration.py diff --git a/tests/test_underintegration.py b/tests/test_underintegration.py new file mode 100644 index 0000000000..9bd2cc1c1f --- /dev/null +++ b/tests/test_underintegration.py @@ -0,0 +1,103 @@ +from __future__ import absolute_import, print_function, division +from six.moves import range + +from functools import reduce + +import numpy +import pytest + +from coffee.visitors import EstimateFlops + +from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, + TestFunction, TrialFunction, TensorProductCell, dx, + action, interval, quadrilateral, dot, grad) + +from FIAT import ufc_cell +from FIAT.quadrature import GaussLobattoLegendreQuadratureLineRule + +from finat.point_set import GaussLobattoLegendrePointSet +from finat.quadrature import QuadratureRule, TensorProductQuadratureRule + +from tsfc import compile_form + + +def gll_quadrature_rule(cell, elem_deg): + fiat_cell = ufc_cell("interval") + fiat_rule = GaussLobattoLegendreQuadratureLineRule(fiat_cell, elem_deg + 1) + line_rules = [QuadratureRule(GaussLobattoLegendrePointSet(fiat_rule.get_points()), + fiat_rule.get_weights()) + for _ in range(cell.topological_dimension())] + finat_rule = reduce(lambda a, b: TensorProductQuadratureRule([a, b]), line_rules) + return finat_rule + + +def mass_cg(cell, degree): + m = Mesh(VectorElement('Q', cell, 1)) + V = FunctionSpace(m, FiniteElement('Q', cell, degree, variant='spectral')) + u = TrialFunction(V) + v = TestFunction(V) + return u*v*dx(rule=gll_quadrature_rule(cell, degree)) + + +def mass_dg(cell, degree): + m = Mesh(VectorElement('Q', cell, 1)) + V = FunctionSpace(m, FiniteElement('DQ', cell, degree, variant='spectral')) + u = TrialFunction(V) + v = TestFunction(V) + # In this case, the estimated quadrature degree will give the + # correct number of quadrature points by luck. + return u*v*dx + + +def laplace(cell, degree): + m = Mesh(VectorElement('Q', cell, 1)) + V = FunctionSpace(m, FiniteElement('Q', cell, degree, variant='spectral')) + u = TrialFunction(V) + v = TestFunction(V) + return dot(grad(u), grad(v))*dx(rule=gll_quadrature_rule(cell, degree)) + + +def count_flops(form): + kernel, = compile_form(form, parameters=dict(mode='spectral')) + return EstimateFlops().visit(kernel.ast) + + +@pytest.mark.parametrize('form', [mass_cg, mass_dg]) +@pytest.mark.parametrize(('cell', 'order'), + [(quadrilateral, 2), + (TensorProductCell(interval, interval), 2), + (TensorProductCell(quadrilateral, interval), 3)]) +def test_mass(form, cell, order): + degrees = numpy.arange(4, 10) + flops = [count_flops(form(cell, int(degree))) for degree in degrees] + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees + 1)) + assert (rates < order).all() + + +@pytest.mark.parametrize('form', [mass_cg, mass_dg]) +@pytest.mark.parametrize(('cell', 'order'), + [(quadrilateral, 2), + (TensorProductCell(interval, interval), 2), + (TensorProductCell(quadrilateral, interval), 3)]) +def test_mass_action(form, cell, order): + degrees = numpy.arange(4, 10) + flops = [count_flops(action(form(cell, int(degree)))) for degree in degrees] + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees + 1)) + assert (rates < order).all() + + +@pytest.mark.parametrize(('cell', 'order'), + [(quadrilateral, 4), + (TensorProductCell(interval, interval), 4), + (TensorProductCell(quadrilateral, interval), 5)]) +def test_laplace(cell, order): + degrees = numpy.arange(4, 10) + flops = [count_flops(laplace(cell, int(degree))) for degree in degrees] + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees + 1)) + assert (rates < order).all() + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 284bc7e9ddd2a077b9e4ec5d29b5294174dc87fd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Aug 2017 14:41:04 +0100 Subject: [PATCH 374/809] rename: vector_transpose -> shape_innermost --- tsfc/finatinterface.py | 48 ++++++++++++++++++------------------ tsfc/kernel_interface/ufc.py | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index e71b408238..2e47dc8ac4 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -81,7 +81,7 @@ def fiat_compat(element): @singledispatch -def convert(element, vector_transpose=False): +def convert(element, shape_innermost=True): """Handler for converting UFL elements to FInAT elements. :arg element: The UFL element to convert. @@ -95,7 +95,7 @@ def convert(element, vector_transpose=False): # Base finite elements first @convert.register(ufl.FiniteElement) -def convert_finiteelement(element, vector_transpose=False): +def convert_finiteelement(element, shape_innermost=True): cell = as_fiat_cell(element.cell()) if element.family() == "Quadrature": degree = element.degree() @@ -113,7 +113,7 @@ def convert_finiteelement(element, vector_transpose=False): element.family()) # Handle quadrilateral short names like RTCF and RTCE. element = element.reconstruct(cell=quad_tpc) - return finat.QuadrilateralElement(create_element(element, vector_transpose)) + return finat.QuadrilateralElement(create_element(element, shape_innermost)) kind = element.variant() if kind is None: @@ -139,67 +139,67 @@ def convert_finiteelement(element, vector_transpose=False): # EnrichedElement case @convert.register(ufl.EnrichedElement) -def convert_enrichedelement(element, vector_transpose=False): - return finat.EnrichedElement([create_element(elem, vector_transpose) +def convert_enrichedelement(element, shape_innermost=True): + return finat.EnrichedElement([create_element(elem, shape_innermost) for elem in element._elements]) # Generic MixedElement case @convert.register(ufl.MixedElement) -def convert_mixedelement(element, vector_transpose=False): - return finat.MixedElement([create_element(elem, vector_transpose) +def convert_mixedelement(element, shape_innermost=True): + return finat.MixedElement([create_element(elem, shape_innermost) for elem in element.sub_elements()]) # VectorElement case @convert.register(ufl.VectorElement) -def convert_vectorelement(element, vector_transpose=False): - scalar_element = create_element(element.sub_elements()[0], vector_transpose) +def convert_vectorelement(element, shape_innermost=True): + scalar_element = create_element(element.sub_elements()[0], shape_innermost) return finat.TensorFiniteElement(scalar_element, (element.num_sub_elements(),), - transpose=vector_transpose) + transpose=not shape_innermost) # TensorElement case @convert.register(ufl.TensorElement) -def convert_tensorelement(element, vector_transpose=False): - scalar_element = create_element(element.sub_elements()[0], vector_transpose) +def convert_tensorelement(element, shape_innermost=True): + scalar_element = create_element(element.sub_elements()[0], shape_innermost) return finat.TensorFiniteElement(scalar_element, element.reference_value_shape(), - transpose=vector_transpose) + transpose=not shape_innermost) # TensorProductElement case @convert.register(ufl.TensorProductElement) -def convert_tensorproductelement(element, vector_transpose=False): +def convert_tensorproductelement(element, shape_innermost=True): cell = element.cell() if type(cell) is not ufl.TensorProductCell: raise ValueError("TensorProductElement not on TensorProductCell?") - return finat.TensorProductElement([create_element(elem, vector_transpose) + return finat.TensorProductElement([create_element(elem, shape_innermost) for elem in element.sub_elements()]) # HDivElement case @convert.register(ufl.HDivElement) -def convert_hdivelement(element, vector_transpose=False): - return finat.HDivElement(create_element(element._element, vector_transpose)) +def convert_hdivelement(element, shape_innermost=True): + return finat.HDivElement(create_element(element._element, shape_innermost)) # HDivElement case @convert.register(ufl.HCurlElement) -def convert_hcurlelement(element, vector_transpose=False): - return finat.HCurlElement(create_element(element._element, vector_transpose)) +def convert_hcurlelement(element, shape_innermost=True): + return finat.HCurlElement(create_element(element._element, shape_innermost)) quad_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() -def create_element(element, vector_transpose=False): +def create_element(element, shape_innermost=True): """Create a FInAT element (suitable for tabulating with) given a UFL element. :arg element: The UFL element to create a FInAT element from. - :arg vector_transpose: Vector/tensor indices come before basis function indices + :arg shape_innermost: Vector/tensor indices come after basis function indices """ try: cache = _cache[element] @@ -208,13 +208,13 @@ def create_element(element, vector_transpose=False): cache = _cache[element] try: - return cache[vector_transpose] + return cache[shape_innermost] except KeyError: pass if element.cell() is None: raise ValueError("Don't know how to build element when cell is not given") - finat_element = convert(element, vector_transpose=vector_transpose) - cache[vector_transpose] = finat_element + finat_element = convert(element, shape_innermost=shape_innermost) + cache[shape_innermost] = finat_element return finat_element diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 4c3e51875b..022f6c265b 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -18,7 +18,7 @@ def create_element(element): # UFC DoF ordering for vector/tensor elements is XXXX YYYY ZZZZ. - return _create_element(element, vector_transpose=True) + return _create_element(element, shape_innermost=False) class KernelBuilder(KernelBuilderBase): From 8d2c7ef378081b0ecf441c7b6f502794b67e0bb6 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Aug 2017 14:44:31 +0100 Subject: [PATCH 375/809] add docstring for split in split_coefficients --- tsfc/ufl_utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index d756b5b871..8149fd120b 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -201,7 +201,12 @@ def modified_terminal(self, o): def split_coefficients(expression, split): """Split mixed coefficients, so mixed elements need not be - implemented.""" + implemented. + + :arg split: A :py:class:`dict` mapping each mixed coefficient to a + sequence of subcoefficients. If None, calling this + function is a no-op. + """ if split is None: return expression From 5d7660f43f5779811fac8fcb827d9bb3db5abff3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Aug 2017 15:21:30 +0100 Subject: [PATCH 376/809] move COFFEE algorithm from TSFC to GEM Two modes share this: 'coffee' and 'spectral'. --- tsfc/coffee_mode.py | 194 ++------------------------------------------ tsfc/spectral.py | 2 +- 2 files changed, 8 insertions(+), 188 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 63fa2f23c2..2dc621748f 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -1,19 +1,16 @@ from __future__ import absolute_import, print_function, division -from six.moves import map, range, zip +from six.moves import zip -import numpy -import itertools from functools import partial, reduce -from collections import OrderedDict -from gem.optimise import make_sum, make_product, replace_division, unroll_indexsum -from gem.refactorise import Monomial, collect_monomials -from gem.unconcatenate import unconcatenate + from gem.node import traversal -from gem.gem import IndexSum, Failure, Sum, index_sum, one +from gem.gem import Failure, Sum, index_sum +from gem.optimise import replace_division, unroll_indexsum +from gem.refactorise import collect_monomials +from gem.unconcatenate import unconcatenate +from gem.coffee import optimise_monomial_sum from gem.utils import groupby -from tsfc.logging import logger - import tsfc.spectral as spectral @@ -49,11 +46,6 @@ def flatten(var_reps, index_cache): :returns: series of (return variable, GEM expression root) pairs """ - try: - from firedrake import Citations - Citations().register("Luporini2016") - except ImportError: - pass assignments = unconcatenate([(variable, reduce(Sum, reps)) for variable, reps in var_reps], cache=index_cache) @@ -89,175 +81,3 @@ def optimise_expressions(expressions, argument_indices): classifier = partial(spectral.classify, set(argument_indices)) monomial_sums = collect_monomials(expressions, classifier) return [optimise_monomial_sum(ms, argument_indices) for ms in monomial_sums] - - -def monomial_sum_to_expression(monomial_sum): - """Convert a monomial sum to a GEM expression. - - :arg monomial_sum: an iterable of :class:`Monomial`s - - :returns: GEM expression - """ - indexsums = [] # The result is summation of indexsums - # Group monomials according to their sum indices - groups = groupby(monomial_sum, key=lambda m: frozenset(m.sum_indices)) - # Create IndexSum's from each monomial group - for _, monomials in groups: - sum_indices = monomials[0].sum_indices - products = [make_product(monomial.atomics + (monomial.rest,)) for monomial in monomials] - indexsums.append(IndexSum(make_sum(products), sum_indices)) - return make_sum(indexsums) - - -def index_extent(factor, argument_indices): - """Compute the product of the extents of argument indices of a GEM expression - - :arg factor: GEM expression - :arg argument_indices: set of argument indices - - :returns: product of extents of argument indices - """ - return numpy.prod([i.extent for i in factor.free_indices if i in argument_indices]) - - -def find_optimal_atomics(monomials, argument_indices): - """Find optimal atomic common subexpressions, which produce least number of - terms in the resultant IndexSum when factorised. - - :arg monomials: A list of :class:`Monomial`s, all of which should have - the same sum indices - :arg argument_indices: tuple of argument indices - - :returns: list of atomic GEM expressions - """ - atomics = tuple(OrderedDict.fromkeys(itertools.chain(*(monomial.atomics for monomial in monomials)))) - - def cost(solution): - extent = sum(map(lambda atomic: index_extent(atomic, argument_indices), solution)) - # Prefer shorter solutions, but larger extents - return (len(solution), -extent) - - optimal_solution = set(atomics) # pessimal but feasible solution - solution = set() - - max_it = 1 << 12 - it = iter(range(max_it)) - - def solve(idx): - while idx < len(monomials) and solution.intersection(monomials[idx].atomics): - idx += 1 - - if idx < len(monomials): - if len(solution) < len(optimal_solution): - for atomic in monomials[idx].atomics: - solution.add(atomic) - solve(idx + 1) - solution.remove(atomic) - else: - if cost(solution) < cost(optimal_solution): - optimal_solution.clear() - optimal_solution.update(solution) - next(it) - - try: - solve(0) - except StopIteration: - logger.warning("Solution to ILP problem may not be optimal: search " - "interrupted after examining %d solutions.", max_it) - - return tuple(atomic for atomic in atomics if atomic in optimal_solution) - - -def factorise_atomics(monomials, optimal_atomics, argument_indices): - """Group and factorise monomials using a list of atomics as common - subexpressions. Create new monomials for each group and optimise them recursively. - - :arg monomials: an iterable of :class:`Monomial`s, all of which should have - the same sum indices - :arg optimal_atomics: list of tuples of atomics to be used as common subexpression - :arg argument_indices: tuple of argument indices - - :returns: an iterable of :class:`Monomials`s after factorisation - """ - if not optimal_atomics or len(monomials) <= 1: - return monomials - - # Group monomials with respect to each optimal atomic - def group_key(monomial): - for oa in optimal_atomics: - if oa in monomial.atomics: - return oa - assert False, "Expect at least one optimal atomic per monomial." - factor_group = groupby(monomials, key=group_key) - - # We should not drop monomials - assert sum(len(ms) for _, ms in factor_group) == len(monomials) - - sum_indices = next(iter(monomials)).sum_indices - new_monomials = [] - for oa, monomials in factor_group: - # Create new MonomialSum for the factorised out terms - sub_monomials = [] - for monomial in monomials: - atomics = list(monomial.atomics) - atomics.remove(oa) # remove common factor - sub_monomials.append(Monomial((), tuple(atomics), monomial.rest)) - # Continue to factorise the remaining expression - sub_monomials = optimise_monomials(sub_monomials, argument_indices) - if len(sub_monomials) == 1: - # Factorised part is a product, we add back the common atomics then - # add to new MonomialSum directly rather than forming a product node - # Retaining the monomial structure enables applying associativity - # when forming GEM nodes later. - sub_monomial, = sub_monomials - new_monomials.append( - Monomial(sum_indices, (oa,) + sub_monomial.atomics, sub_monomial.rest)) - else: - # Factorised part is a summation, we need to create a new GEM node - # and multiply with the common factor - node = monomial_sum_to_expression(sub_monomials) - # If the free indices of the new node intersect with argument indices, - # add to the new monomial as `atomic`, otherwise add as `rest`. - # Note: we might want to continue to factorise with the new atomics - # by running optimise_monoials twice. - if set(argument_indices) & set(node.free_indices): - new_monomials.append(Monomial(sum_indices, (oa, node), one)) - else: - new_monomials.append(Monomial(sum_indices, (oa, ), node)) - return new_monomials - - -def optimise_monomial_sum(monomial_sum, argument_indices): - """Choose optimal common atomic subexpressions and factorise a - :class:`MonomialSum` object to create a GEM expression. - - :arg monomial_sum: a :class:`MonomialSum` object - :arg argument_indices: tuple of argument indices - - :returns: factorised GEM expression - """ - groups = groupby(monomial_sum, key=lambda m: frozenset(m.sum_indices)) - new_monomials = [] - for _, monomials in groups: - new_monomials.extend(optimise_monomials(monomials, argument_indices)) - return monomial_sum_to_expression(new_monomials) - - -def optimise_monomials(monomials, argument_indices): - """Choose optimal common atomic subexpressions and factorise an iterable - of monomials. - - :arg monomials: a list of :class:`Monomial`s, all of which should have - the same sum indices - :arg argument_indices: tuple of argument indices - - :returns: an iterable of factorised :class:`Monomials`s - """ - assert len(set(frozenset(m.sum_indices) for m in monomials)) <= 1,\ - "All monomials required to have same sum indices for factorisation" - - result = [m for m in monomials if not m.atomics] # skipped monomials - active_monomials = [m for m in monomials if m.atomics] - optimal_atomics = find_optimal_atomics(active_monomials, argument_indices) - result += factorise_atomics(active_monomials, optimal_atomics, argument_indices) - return result diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 6974d8b86a..c161068d5c 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -10,6 +10,7 @@ from gem.optimise import remove_componenttensors, replace_division, unroll_indexsum from gem.refactorise import ATOMIC, COMPOUND, OTHER, MonomialSum, collect_monomials from gem.unconcatenate import unconcatenate +from gem.coffee import optimise_monomial_sum from gem.utils import groupby @@ -160,5 +161,4 @@ def sum_factorise(variable, tail_ordering, monomial_sum): new_rest = sum_factorise(variable, tail_ordering[1:], monosum) monomial_sum.add(sum_indices, atomics, new_rest) - from tsfc.coffee_mode import optimise_monomial_sum return optimise_monomial_sum(monomial_sum, variable.index_ordering()) From e18df606c0e4ae5bff99806680691ed9dbb599f7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Aug 2017 15:52:07 +0100 Subject: [PATCH 377/809] add some more comments --- tsfc/spectral.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index c161068d5c..4ad6344a52 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -73,15 +73,20 @@ def flatten(var_reps, index_cache): def group_key(pair): variable, expression = pair - return variable.free_indices + return frozenset(variable.free_indices) - # Delta cancellation for arguments + # Variable ordering after delta cancellation narrow_variables = OrderedDict() + # Assignments are variable -> MonomialSum map delta_simplified = defaultdict(MonomialSum) + # Group assignment pairs by argument indices for free_indices, pair_group in groupby(pairs, group_key): variables, expressions = zip(*pair_group) + # Argument factorise expressions classifier = partial(classify, set(free_indices)) monomial_sums = collect_monomials(expressions, classifier) + # For each monomial, apply delta cancellation and insert + # result into delta_simplified. for variable, monomial_sum in zip(variables, monomial_sums): for monomial in monomial_sum: var, s, a, r = delta_elimination(variable, *monomial) @@ -91,9 +96,17 @@ def group_key(pair): # Final factorisation for variable in narrow_variables: monomial_sum = delta_simplified[variable] + # Collect sum indices applicable to the current MonomialSum sum_indices = set().union(*[m.sum_indices for m in monomial_sum]) + # Put them in a deterministic order sum_indices = [i for i in quadrature_indices if i in sum_indices] + # Sort for increasing index extent, this obtains the good + # factorisation for triangle x interval cells. Python sort is + # stable, so in the common case when index extents are equal, + # the previous deterministic ordering applies which is good + # for getting smaller temporaries. sum_indices = sorted(sum_indices, key=lambda index: index.extent) + # Apply sum factorisation combined with COFFEE technology expression = sum_factorise(variable, sum_indices, monomial_sum) yield (variable, expression) @@ -146,6 +159,13 @@ def sum_factorise(variable, tail_ordering, monomial_sum): key_ordering = OrderedDict() sub_monosums = defaultdict(MonomialSum) for sum_indices, atomics, rest in monomial_sum: + # Pull out those sum indices that are not contained in the + # tail ordering, together with those atomics which do not + # share free indices with the tail ordering. + # + # Based on this, split the monomial sum, then recursively + # optimise each sub monomial sum with the first tail index + # removed. tail_indices = tuple(i for i in sum_indices if i in tail_ordering) tail_atomics = tuple(a for a in atomics if set(tail_indices) & set(a.free_indices)) @@ -161,4 +181,5 @@ def sum_factorise(variable, tail_ordering, monomial_sum): new_rest = sum_factorise(variable, tail_ordering[1:], monosum) monomial_sum.add(sum_indices, atomics, new_rest) + # Use COFFEE algorithm to optimise the monomial sum return optimise_monomial_sum(monomial_sum, variable.index_ordering()) From f6203fe7f41c3d74d599d5bcd0ed869bf0ff2fc7 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Aug 2017 15:52:33 +0100 Subject: [PATCH 378/809] change default mode: coffee -> spectral --- tsfc/parameters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 3d112a4182..518938072f 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -14,7 +14,7 @@ "quadrature_degree": "auto", # Default mode - "mode": "coffee", + "mode": "spectral", # Maximum extent to unroll index sums. Default is 3, so that loops # over geometric dimensions are unrolled; this improves assembly From e1947a4dcb3668cd1e105526e529d8989303761f Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 2 Aug 2017 16:22:54 +0100 Subject: [PATCH 379/809] expose delta_elimination bug in test case --- tests/test_delta_elimination.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/test_delta_elimination.py diff --git a/tests/test_delta_elimination.py b/tests/test_delta_elimination.py new file mode 100644 index 0000000000..df76629f53 --- /dev/null +++ b/tests/test_delta_elimination.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import, print_function, division + +import pytest + +from gem.gem import Delta, Identity, Index, Indexed, one +from gem.optimise import delta_elimination, remove_componenttensors + + +def test_delta_elimination(): + i = Index() + j = Index() + k = Index() + I = Identity(3) + + sum_indices = (i, j) + factors = [Delta(i, j), Delta(i, k), Indexed(I, (j, k))] + + sum_indices, factors = delta_elimination(sum_indices, factors) + factors = remove_componenttensors(factors) + + assert sum_indices == [] + assert factors == [one, one, Indexed(I, (k, k))] + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 374ba4bad22a1e575556762c7e8369561e91c890 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 3 Aug 2017 11:05:29 +0100 Subject: [PATCH 380/809] add a short comment --- tsfc/spectral.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 4ad6344a52..96f5994809 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -133,6 +133,9 @@ def delta_elimination(variable, sum_indices, args, rest): factors = list(args) + [variable, rest] # construct factors def prune(factors): + # Skip last factor (``rest``, see above) which can be + # arbitrarily complicated, so its pruning may be expensive, + # and its early pruning brings no advantages. result = remove_componenttensors(factors[:-1]) result.append(factors[-1]) return result From 70630fe82e4a83ab13acdf2189cd4c49a12fc187 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 9 Aug 2017 12:29:24 +0100 Subject: [PATCH 381/809] FInAT interface no longer needs fallback on FIAT interface --- tsfc/finatinterface.py | 34 +++++++++------------------------- tsfc/ufl_utils.py | 34 ---------------------------------- 2 files changed, 9 insertions(+), 59 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 2e47dc8ac4..9836d67dd6 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -27,12 +27,10 @@ import weakref import finat -from finat.fiat_elements import FiatElementBase import ufl from tsfc.fiatinterface import as_fiat_cell -from tsfc.ufl_utils import spanning_degree __all__ = ("create_element", "supported_elements", "as_fiat_cell") @@ -42,8 +40,15 @@ # These all map directly to FInAT elements "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": finat.BrezziDouglasFortinMarini, + "Bubble": finat.Bubble, + "Crouzeix-Raviart": finat.CrouzeixRaviart, "Discontinuous Lagrange": finat.DiscontinuousLagrange, "Discontinuous Raviart-Thomas": finat.DiscontinuousRaviartThomas, + "Discontinuous Taylor": finat.DiscontinuousTaylor, + "Gauss-Legendre": finat.GaussLegendre, + "Gauss-Lobatto-Legendre": finat.GaussLobattoLegendre, + "HDiv Trace": finat.HDivTrace, + "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, "Lagrange": finat.Lagrange, "Nedelec 1st kind H(curl)": finat.Nedelec, "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, @@ -61,25 +66,6 @@ have a direct FInAT equivalent.""" -class FiatElementWrapper(FiatElementBase): - def __init__(self, element, degree=None): - super(FiatElementWrapper, self).__init__(element) - self._degree = degree - - @property - def degree(self): - if self._degree is not None: - return self._degree - else: - return super(FiatElementWrapper, self).degree - - -def fiat_compat(element): - from tsfc.fiatinterface import create_element - return FiatElementWrapper(create_element(element), - degree=spanning_degree(element)) - - @singledispatch def convert(element, shape_innermost=True): """Handler for converting UFL elements to FInAT elements. @@ -90,7 +76,7 @@ def convert(element, shape_innermost=True): :func:`create_element`.""" if element.family() in supported_elements: raise ValueError("Element %s supported, but no handler provided" % element) - return fiat_compat(element) + raise ValueError("Unsupported element type %s" % type(element)) # Base finite elements first @@ -104,9 +90,7 @@ def convert_finiteelement(element, shape_innermost=True): raise ValueError("Quadrature scheme and degree must be specified!") return finat.QuadratureElement(cell, degree, scheme) - if element.family() not in supported_elements: - return fiat_compat(element) - lmbda = supported_elements.get(element.family()) + lmbda = supported_elements[element.family()] if lmbda is None: if element.cell().cellname() != "quadrilateral": raise ValueError("%s is supported, but handled incorrectly" % diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 8149fd120b..83875501b8 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -244,40 +244,6 @@ def modified_terminal(self, o): return o -def _spanning_degree(cell, degree): - if cell is None: - assert degree == 0 - return degree - elif cell.cellname() in ["interval", "triangle", "tetrahedron"]: - return degree - elif cell.cellname() == "quadrilateral": - # TODO: Tensor-product space assumed - return 2 * degree - elif isinstance(cell, ufl.TensorProductCell): - try: - # A component cell might be a quadrilateral, so recurse. - return sum(_spanning_degree(sub_cell, d) - for sub_cell, d in zip(cell.sub_cells(), degree)) - except TypeError: - assert degree == 0 - return 0 - else: - raise ValueError("Unknown cell %s" % cell.cellname()) - - -def spanning_degree(element): - """Determine the degree of the polynomial space spanning an element. - - :arg element: The element to determine the degree of. - - .. warning:: - - For non-simplex elements, this assumes a tensor-product - space. - """ - return _spanning_degree(element.cell(), element.degree()) - - def ufl_reuse_if_untouched(o, *ops): """Reuse object if operands are the same objects.""" if all(a is b for a, b in zip(o.ufl_operands, ops)): From cf67492badb62a9ebd6b6bcaabf2808cc3396bfa Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 9 Aug 2017 16:09:13 +0100 Subject: [PATCH 382/809] fall back to FIAT for RestrictedElement --- tsfc/finatinterface.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 9836d67dd6..9792a63374 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -66,6 +66,14 @@ have a direct FInAT equivalent.""" +def fiat_compat(element): + from tsfc.fiatinterface import create_element + from finat.fiat_elements import FiatElement + + assert element.cell().is_simplex() + return FiatElement(create_element(element)) + + @singledispatch def convert(element, shape_innermost=True): """Handler for converting UFL elements to FInAT elements. @@ -121,21 +129,24 @@ def convert_finiteelement(element, shape_innermost=True): return lmbda(cell, element.degree()) -# EnrichedElement case +# Element modifiers and compound element types +@convert.register(ufl.BrokenElement) +def convert_brokenelement(element, shape_innermost=True): + return finat.DiscontinuousElement(create_element(element._element, shape_innermost)) + + @convert.register(ufl.EnrichedElement) def convert_enrichedelement(element, shape_innermost=True): return finat.EnrichedElement([create_element(elem, shape_innermost) for elem in element._elements]) -# Generic MixedElement case @convert.register(ufl.MixedElement) def convert_mixedelement(element, shape_innermost=True): return finat.MixedElement([create_element(elem, shape_innermost) for elem in element.sub_elements()]) -# VectorElement case @convert.register(ufl.VectorElement) def convert_vectorelement(element, shape_innermost=True): scalar_element = create_element(element.sub_elements()[0], shape_innermost) @@ -144,7 +155,6 @@ def convert_vectorelement(element, shape_innermost=True): transpose=not shape_innermost) -# TensorElement case @convert.register(ufl.TensorElement) def convert_tensorelement(element, shape_innermost=True): scalar_element = create_element(element.sub_elements()[0], shape_innermost) @@ -153,7 +163,6 @@ def convert_tensorelement(element, shape_innermost=True): transpose=not shape_innermost) -# TensorProductElement case @convert.register(ufl.TensorProductElement) def convert_tensorproductelement(element, shape_innermost=True): cell = element.cell() @@ -163,18 +172,22 @@ def convert_tensorproductelement(element, shape_innermost=True): for elem in element.sub_elements()]) -# HDivElement case @convert.register(ufl.HDivElement) def convert_hdivelement(element, shape_innermost=True): return finat.HDivElement(create_element(element._element, shape_innermost)) -# HDivElement case @convert.register(ufl.HCurlElement) def convert_hcurlelement(element, shape_innermost=True): return finat.HCurlElement(create_element(element._element, shape_innermost)) +@convert.register(ufl.RestrictedElement) +def convert_restrictedelement(element, shape_innermost=True): + # Fall back on FIAT + return fiat_compat(element) + + quad_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() From 215b3a4d8c0e39d0a4e745455d332668337b4448 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 10 Aug 2017 10:48:02 +0100 Subject: [PATCH 383/809] no need for DiscontinuousRaviartThomas --- tests/test_create_finat_element.py | 1 - tsfc/finatinterface.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index ebac51c3ef..83c2b6437d 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -8,7 +8,6 @@ @pytest.fixture(params=["BDM", "BDFM", - "DRT", "Lagrange", "N1curl", "N2curl", diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 9792a63374..251acaeca8 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -43,7 +43,7 @@ "Bubble": finat.Bubble, "Crouzeix-Raviart": finat.CrouzeixRaviart, "Discontinuous Lagrange": finat.DiscontinuousLagrange, - "Discontinuous Raviart-Thomas": finat.DiscontinuousRaviartThomas, + "Discontinuous Raviart-Thomas": lambda c, d: finat.DiscontinuousElement(finat.RaviartThomas(c, d)), "Discontinuous Taylor": finat.DiscontinuousTaylor, "Gauss-Legendre": finat.GaussLegendre, "Gauss-Lobatto-Legendre": finat.GaussLobattoLegendre, From 1bfd710fbb181c0b44a9f3dae745200ff0f76acd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Tue, 8 Aug 2017 17:10:29 +0100 Subject: [PATCH 384/809] refactor element conversion --- tsfc/finatinterface.py | 119 ++++++++++++++++++----------- tsfc/kernel_interface/__init__.py | 2 +- tsfc/kernel_interface/firedrake.py | 4 +- tsfc/kernel_interface/ufc.py | 10 +-- 4 files changed, 82 insertions(+), 53 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 251acaeca8..0a794b388c 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -22,6 +22,7 @@ # along with FFC. If not, see . from __future__ import absolute_import, print_function, division +from six import iteritems from singledispatch import singledispatch import weakref @@ -75,7 +76,7 @@ def fiat_compat(element): @singledispatch -def convert(element, shape_innermost=True): +def convert(element, **kwargs): """Handler for converting UFL elements to FInAT elements. :arg element: The UFL element to convert. @@ -89,7 +90,7 @@ def convert(element, shape_innermost=True): # Base finite elements first @convert.register(ufl.FiniteElement) -def convert_finiteelement(element, shape_innermost=True): +def convert_finiteelement(element, **kwargs): cell = as_fiat_cell(element.cell()) if element.family() == "Quadrature": degree = element.degree() @@ -97,7 +98,7 @@ def convert_finiteelement(element, shape_innermost=True): if degree is None or scheme is None: raise ValueError("Quadrature scheme and degree must be specified!") - return finat.QuadratureElement(cell, degree, scheme) + return finat.QuadratureElement(cell, degree, scheme), set() lmbda = supported_elements[element.family()] if lmbda is None: if element.cell().cellname() != "quadrilateral": @@ -105,7 +106,8 @@ def convert_finiteelement(element, shape_innermost=True): element.family()) # Handle quadrilateral short names like RTCF and RTCE. element = element.reconstruct(cell=quad_tpc) - return finat.QuadrilateralElement(create_element(element, shape_innermost)) + finat_elem, deps = _create_element(element, **kwargs) + return finat.QuadrilateralElement(finat_elem), deps kind = element.variant() if kind is None: @@ -126,92 +128,119 @@ def convert_finiteelement(element, shape_innermost=True): lmbda = finat.GaussLegendre else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - return lmbda(cell, element.degree()) + return lmbda(cell, element.degree()), set() # Element modifiers and compound element types @convert.register(ufl.BrokenElement) -def convert_brokenelement(element, shape_innermost=True): - return finat.DiscontinuousElement(create_element(element._element, shape_innermost)) +def convert_brokenelement(element, **kwargs): + finat_elem, deps = _create_element(element._element, **kwargs) + return finat.DiscontinuousElement(finat_elem), deps @convert.register(ufl.EnrichedElement) -def convert_enrichedelement(element, shape_innermost=True): - return finat.EnrichedElement([create_element(elem, shape_innermost) - for elem in element._elements]) +def convert_enrichedelement(element, **kwargs): + elements, deps = zip(*[_create_element(elem, **kwargs) + for elem in element._elements]) + return finat.EnrichedElement(elements), set.union(*deps) @convert.register(ufl.MixedElement) -def convert_mixedelement(element, shape_innermost=True): - return finat.MixedElement([create_element(elem, shape_innermost) - for elem in element.sub_elements()]) +def convert_mixedelement(element, **kwargs): + elements, deps = zip(*[_create_element(elem, **kwargs) + for elem in element.sub_elements()]) + return finat.MixedElement(elements), set.union(*deps) @convert.register(ufl.VectorElement) -def convert_vectorelement(element, shape_innermost=True): - scalar_element = create_element(element.sub_elements()[0], shape_innermost) - return finat.TensorFiniteElement(scalar_element, - (element.num_sub_elements(),), - transpose=not shape_innermost) +def convert_vectorelement(element, **kwargs): + scalar_elem, deps = _create_element(element.sub_elements()[0], **kwargs) + shape = (element.num_sub_elements(),) + shape_innermost = kwargs["shape_innermost"] + return (finat.TensorFiniteElement(scalar_elem, shape, not shape_innermost), + deps | {"shape_innermost"}) @convert.register(ufl.TensorElement) -def convert_tensorelement(element, shape_innermost=True): - scalar_element = create_element(element.sub_elements()[0], shape_innermost) - return finat.TensorFiniteElement(scalar_element, - element.reference_value_shape(), - transpose=not shape_innermost) +def convert_tensorelement(element, **kwargs): + scalar_elem, deps = _create_element(element.sub_elements()[0], **kwargs) + shape = element.reference_value_shape() + shape_innermost = kwargs["shape_innermost"] + return (finat.TensorFiniteElement(scalar_elem, shape, not shape_innermost), + deps | {"shape_innermost"}) @convert.register(ufl.TensorProductElement) -def convert_tensorproductelement(element, shape_innermost=True): +def convert_tensorproductelement(element, **kwargs): cell = element.cell() if type(cell) is not ufl.TensorProductCell: raise ValueError("TensorProductElement not on TensorProductCell?") - return finat.TensorProductElement([create_element(elem, shape_innermost) - for elem in element.sub_elements()]) + elements, deps = zip(*[_create_element(elem, **kwargs) + for elem in element.sub_elements()]) + return finat.TensorProductElement(elements), set.union(*deps) @convert.register(ufl.HDivElement) -def convert_hdivelement(element, shape_innermost=True): - return finat.HDivElement(create_element(element._element, shape_innermost)) +def convert_hdivelement(element, **kwargs): + finat_elem, deps = _create_element(element._element, **kwargs) + return finat.HDivElement(finat_elem), deps @convert.register(ufl.HCurlElement) -def convert_hcurlelement(element, shape_innermost=True): - return finat.HCurlElement(create_element(element._element, shape_innermost)) +def convert_hcurlelement(element, **kwargs): + finat_elem, deps = _create_element(element._element, **kwargs) + return finat.HCurlElement(finat_elem), deps @convert.register(ufl.RestrictedElement) -def convert_restrictedelement(element, shape_innermost=True): +def convert_restrictedelement(element, **kwargs): # Fall back on FIAT - return fiat_compat(element) + return fiat_compat(element), set() quad_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() -def create_element(element, shape_innermost=True): +def create_element(ufl_element, shape_innermost=True): """Create a FInAT element (suitable for tabulating with) given a UFL element. - :arg element: The UFL element to create a FInAT element from. + :arg ufl_element: The UFL element to create a FInAT element from. :arg shape_innermost: Vector/tensor indices come after basis function indices """ - try: - cache = _cache[element] - except KeyError: - _cache[element] = {} - cache = _cache[element] + finat_element, deps = _create_element(ufl_element, + shape_innermost=shape_innermost) + return finat_element + + +def _create_element(ufl_element, **kwargs): + """A caching wrapper around :py:func:`convert`. + Takes a UFL element and an unspecified set of parameter options, + and returns the converted element with the set of keyword names + that were relevant for conversion. + """ + # Look up conversion in cache try: - return cache[shape_innermost] + cache = _cache[ufl_element] except KeyError: - pass + _cache[ufl_element] = {} + cache = _cache[ufl_element] + + for key, finat_element in iteritems(cache): + # Cache hit if all relevant parameter values match. + if all(kwargs[param] == value for param, value in key): + return finat_element, set(param for param, value in key) - if element.cell() is None: + # Convert if cache miss + if ufl_element.cell() is None: raise ValueError("Don't know how to build element when cell is not given") - finat_element = convert(element, shape_innermost=shape_innermost) - cache[shape_innermost] = finat_element - return finat_element + finat_element, deps = convert(ufl_element, **kwargs) + + # Store conversion in cache + key = frozenset((param, kwargs[param]) for param in deps) + cache[key] = finat_element + + # Forward result + return finat_element, deps diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index a81eb3426d..4e8b7a2fc8 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -24,7 +24,7 @@ def entity_number(self, restriction): """Facet or vertex number as a GEM index.""" @abstractmethod - def create_element(self, element): + def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given a UFL element.""" diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 44e0ca5453..e9756d47bb 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -91,10 +91,10 @@ def needs_cell_orientations(ir): return True return False - def create_element(self, element): + def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given a UFL element.""" - return create_element(element) + return create_element(element, **kwargs) class ExpressionKernelBuilder(KernelBuilderBase): diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 022f6c265b..7cdfb0921f 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -2,6 +2,7 @@ from six.moves import range, zip import numpy +import functools from itertools import chain, product import coffee.base as coffee @@ -16,9 +17,8 @@ from tsfc.coffee import SCALAR_TYPE -def create_element(element): - # UFC DoF ordering for vector/tensor elements is XXXX YYYY ZZZZ. - return _create_element(element, shape_innermost=False) +# UFC DoF ordering for vector/tensor elements is XXXX YYYY ZZZZ. +create_element = functools.partial(_create_element, shape_innermost=False) class KernelBuilder(KernelBuilderBase): @@ -148,10 +148,10 @@ def needs_cell_orientations(ir): # UFC tabulate_tensor always have cell orientations return True - def create_element(self, element): + def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given a UFL element.""" - return create_element(element) + return create_element(element, **kwargs) def prepare_coefficient(coefficient, num, name, interior_facet=False): From 00c64b0886ab01afbe373b9c0448ca72eb7ecb54 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 31 Aug 2017 17:09:34 +0100 Subject: [PATCH 385/809] create reference cycle between Context and Translator --- tsfc/fem.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index f341bc6453..401ef622f6 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -99,6 +99,11 @@ def entity_selector(self, callback, restriction): def index_cache(self): return {} + @cached_property + def translator(self): + # NOTE: reference cycle! + return Translator(self) + class PointSetContext(ContextBase): """Context for compile-time known evaluation points.""" @@ -153,12 +158,17 @@ def basis_evaluation(self, finat_element, local_derivatives, entity_id): class Translator(MultiFunction, ModifiedTerminalMixin, ufl2gem.Mixin): - """Contains all the context necessary to translate UFL into GEM.""" + """Multifunction for translating UFL -> GEM. Incorporates ufl2gem.Mixin, and + dispatches on terminal type when reaching modified terminals.""" def __init__(self, context): + # MultiFunction.__init__ does not call further __init__ + # methods, but ufl2gem.Mixin must be initialised. + # (ModifiedTerminalMixin requires no initialisation.) MultiFunction.__init__(self) ufl2gem.Mixin.__init__(self) + # Need context during translation! self.context = context def modified_terminal(self, o): @@ -414,8 +424,7 @@ def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): expressions = [expression] # Translate UFL to GEM, lowering finite element specific nodes - translator = Translator(context) - result = map_expr_dags(translator, expressions) + result = map_expr_dags(context.translator, expressions) if point_sum: result = [gem.index_sum(expr, context.point_indices) for expr in result] return result From 1267f08a1312f10c7fa4a2c8a6e443c6953bc9d2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Sep 2017 16:19:46 +0100 Subject: [PATCH 386/809] remove long deprecated coefficient mode argument --- tsfc/kernel_interface/ufc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 7cdfb0921f..252399d546 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -62,11 +62,10 @@ def set_arguments(self, arguments, multiindices): self.apply_glue(prepare) return expressions - def set_coordinates(self, coefficient, mode=None): + def set_coordinates(self, coefficient): """Prepare the coordinate field. :arg coefficient: :class:`ufl.Coefficient` - :arg mode: (ignored) """ self.coordinates_args, expression = prepare_coordinates( coefficient, "coordinate_dofs", interior_facet=self.interior_facet) From 7f9aa426b084077856652d6d34c81fc820bd450c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Sep 2017 17:05:00 +0100 Subject: [PATCH 387/809] accept more modified geometric terminals --- tsfc/modified_terminals.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tsfc/modified_terminals.py b/tsfc/modified_terminals.py index 85712069d9..07e9a40a5e 100644 --- a/tsfc/modified_terminals.py +++ b/tsfc/modified_terminals.py @@ -24,8 +24,8 @@ from ufl.classes import (ReferenceValue, ReferenceGrad, NegativeRestricted, PositiveRestricted, - Restricted, FacetAvg, CellAvg, - ConstantValue) + Restricted, FacetAvg, CellAvg, ConstantValue, + Jacobian, SpatialCoordinate) class ModifiedTerminal(object): @@ -157,12 +157,14 @@ def analyse_modified_terminal(expr): if reference_value is None: reference_value = False - mt = ModifiedTerminal(expr, t, local_derivatives, averaged, restriction, reference_value) + # Consistency check + if isinstance(t, (SpatialCoordinate, Jacobian)): + pass + else: + if local_derivatives and not reference_value: + raise ValueError("Local derivatives of non-local value?") - if local_derivatives and not reference_value: - raise ValueError("Local derivatives of non-local value?") - - return mt + return ModifiedTerminal(expr, t, local_derivatives, averaged, restriction, reference_value) def construct_modified_terminal(mt, terminal): From cd2ed55c205e80d3b2fd431e7fcbf448a5621beb Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Sep 2017 17:05:53 +0100 Subject: [PATCH 388/809] extend kernel interface --- tsfc/kernel_interface/__init__.py | 5 +++++ tsfc/kernel_interface/common.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index 4e8b7a2fc8..0eb52be701 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -10,6 +10,11 @@ class KernelInterface(with_metaclass(ABCMeta)): """Abstract interface for accessing the GEM expressions corresponding to kernel arguments.""" + @abstractmethod + def coordinate(self, ufl_domain): + """A function that maps :class:`ufl.Domain`s to coordinate + :class:`ufl.Coefficient`s.""" + @abstractmethod def coefficient(self, ufl_coefficient, restriction): """A function that maps :class:`ufl.Coefficient`s to GEM diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index ac01068b2e..b3fee31625 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -23,9 +23,15 @@ def __init__(self, interior_facet=False): self.prepare = [] self.finalise = [] + # Coordinates + self.domain_coordinate = {} + # Coefficients self.coefficient_map = {} + def coordinate(self, domain): + return self.domain_coordinate[domain] + def coefficient(self, ufl_coefficient, restriction): """A function that maps :class:`ufl.Coefficient`s to GEM expressions.""" From afd2cae099f5477cabadfcbb58ed4adedff1c1d9 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Sep 2017 17:06:56 +0100 Subject: [PATCH 389/809] handle SpatialCoordinate in 1st stage --- tsfc/fem.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 401ef622f6..bcb6da660a 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -18,7 +18,8 @@ CellVolume, Coefficient, FacetArea, FacetCoordinate, GeometricQuantity, QuadratureWeight, ReferenceCellVolume, - ReferenceFacetVolume, ReferenceNormal) + ReferenceFacetVolume, ReferenceNormal, + SpatialCoordinate) from FIAT.reference_element import make_affine_mapping @@ -33,9 +34,11 @@ from tsfc import ufl2gem from tsfc.finatinterface import as_fiat_cell from tsfc.kernel_interface import ProxyKernelInterface -from tsfc.modified_terminals import analyse_modified_terminal +from tsfc.modified_terminals import (analyse_modified_terminal, + construct_modified_terminal) from tsfc.parameters import NUMPY_TYPE, PARAMETERS -from tsfc.ufl_utils import ModifiedTerminalMixin, PickRestriction, simplify_abs +from tsfc.ufl_utils import (ModifiedTerminalMixin, PickRestriction, + simplify_abs, preprocess_expression) class ContextBase(ProxyKernelInterface): @@ -293,6 +296,18 @@ def translate_facet_coordinate(terminal, mt, ctx): return ctx.point_expr +@translate.register(SpatialCoordinate) +def translate_spatialcoordinate(terminal, mt, ctx): + # Replace terminal with a Coefficient + terminal = ctx.coordinate(terminal.ufl_domain()) + # Get back to reference space + terminal = preprocess_expression(terminal) + # Rebuild modified terminal + expr = construct_modified_terminal(mt, terminal) + # Translate replaced UFL snippet + return ctx.translator(expr) + + @translate.register(CellVolume) def translate_cellvolume(terminal, mt, ctx): return ctx.cellvolume(mt.restriction) From 3e7fab875afad396c175b7f32439d3a73f4003cb Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Sep 2017 17:07:43 +0100 Subject: [PATCH 390/809] switch to new kernel interface --- tsfc/driver.py | 32 +++++++++++++----------------- tsfc/kernel_interface/firedrake.py | 9 ++++++--- tsfc/kernel_interface/ufc.py | 12 +++++++---- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 0e892d9d63..6b61fbb6bb 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -14,7 +14,7 @@ import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type -from ufl.classes import Form, CellVolume +from ufl.classes import Form, GeometricQuantity from ufl.log import GREEN from ufl.utils.sequences import max_degree @@ -109,8 +109,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, for arg in arguments) return_variables = builder.set_arguments(arguments, argument_multiindices) - coordinates = ufl_utils.coordinate_coefficient(mesh) - builder.set_coordinates(coordinates) + builder.set_coordinates(mesh) builder.set_coefficients(integral_data, form_data) @@ -132,8 +131,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, argument_multiindices=argument_multiindices, index_cache=index_cache) - kernel_cfg["facetarea"] = facetarea_generator(mesh, coordinates, kernel_cfg, integral_type) - kernel_cfg["cellvolume"] = cellvolume_generator(mesh, coordinates, kernel_cfg) + kernel_cfg["facetarea"] = facetarea_generator(mesh, kernel_cfg, integral_type) + kernel_cfg["cellvolume"] = cellvolume_generator(mesh, kernel_cfg) mode_irs = collections.OrderedDict() for integral in integral_data.integrals: @@ -145,8 +144,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, mode = pick_mode(params["mode"]) mode_irs.setdefault(mode, collections.OrderedDict()) - integrand = ufl_utils.replace_coordinates(integral.integrand(), coordinates) - integrand = ufl.replace(integrand, form_data.function_replace_map) + integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) # Check if the integral has a quad degree attached, otherwise use @@ -157,7 +155,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, quadrature_degree = params["quadrature_degree"] except KeyError: quadrature_degree = params["estimated_polynomial_degree"] - functions = list(arguments) + [coordinates] + list(integral_data.integral_coefficients) + functions = list(arguments) + [builder.coordinate(mesh)] + list(integral_data.integral_coefficients) function_degrees = [f.ufl_function_space().ufl_element().degree() for f in functions] if all((asarray(quadrature_degree) > 10 * asarray(degree)).all() for degree in function_degrees): @@ -263,11 +261,10 @@ def coefficient(self, ufl_coefficient, r): return self._wrapee.coefficient(ufl_coefficient, self.restriction) -def cellvolume_generator(domain, coordinate_coefficient, kernel_config): +def cellvolume_generator(domain, kernel_config): def cellvolume(restriction): from ufl import dx integrand, degree = ufl_utils.one_times(dx(domain=domain)) - integrand = ufl_utils.replace_coordinates(integrand, coordinate_coefficient) interface = CellVolumeKernelInterface(kernel_config["interface"], restriction) config = {k: v for k, v in kernel_config.items() @@ -278,12 +275,11 @@ def cellvolume(restriction): return cellvolume -def facetarea_generator(domain, coordinate_coefficient, kernel_config, integral_type): +def facetarea_generator(domain, kernel_config, integral_type): def facetarea(): from ufl import Measure assert integral_type != 'cell' integrand, degree = ufl_utils.one_times(Measure(integral_type, domain=domain)) - integrand = ufl_utils.replace_coordinates(integrand, coordinate_coefficient) config = kernel_config.copy() config.update(quadrature_degree=degree) @@ -318,19 +314,19 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression) + # Initialise kernel builder + builder = firedrake_interface.ExpressionKernelBuilder() + # Replace coordinates (if any) domain = expression.ufl_domain() if domain: assert coordinates.ufl_domain() == domain - expression = ufl_utils.replace_coordinates(expression, coordinates) + builder.domain_coordinate[domain] = coordinates # Collect required coefficients coefficients = extract_coefficients(expression) - if coordinates not in coefficients and has_type(expression, CellVolume): + if has_type(expression, GeometricQuantity): coefficients = [coordinates] + coefficients - - # Initialise kernel builder - builder = firedrake_interface.ExpressionKernelBuilder() builder.set_coefficients(coefficients) # Split mixed coefficients @@ -342,7 +338,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non ufl_cell=coordinates.ufl_domain().ufl_cell(), precision=parameters["precision"], point_set=point_set) - config["cellvolume"] = cellvolume_generator(coordinates.ufl_domain(), coordinates, config) + config["cellvolume"] = cellvolume_generator(coordinates.ufl_domain(), config) ir, = fem.compile_ufl(expression, point_sum=False, **config) # Deal with non-scalar expressions diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index e9756d47bb..36fd22939c 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -181,12 +181,15 @@ def set_arguments(self, arguments, multiindices): arguments, multiindices, interior_facet=self.interior_facet) return expressions - def set_coordinates(self, coefficient): + def set_coordinates(self, domain): """Prepare the coordinate field. - :arg coefficient: :class:`ufl.Coefficient` + :arg domain: :class:`ufl.Domain` """ - self.coordinates_arg = self._coefficient(coefficient, "coords") + # Create a fake coordinate coefficient for a domain. + f = Coefficient(FunctionSpace(domain, domain.ufl_coordinate_element())) + self.domain_coordinate[domain] = f + self.coordinates_arg = self._coefficient(f, "coords") def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 252399d546..cbcdd2c82a 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -12,6 +12,8 @@ from finat import TensorFiniteElement +import ufl + from tsfc.kernel_interface.common import KernelBuilderBase from tsfc.finatinterface import create_element as _create_element from tsfc.coffee import SCALAR_TYPE @@ -62,14 +64,16 @@ def set_arguments(self, arguments, multiindices): self.apply_glue(prepare) return expressions - def set_coordinates(self, coefficient): + def set_coordinates(self, domain): """Prepare the coordinate field. - :arg coefficient: :class:`ufl.Coefficient` + :arg domain: :class:`ufl.Domain` """ + # Create a fake coordinate coefficient for a domain. + f = ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) self.coordinates_args, expression = prepare_coordinates( - coefficient, "coordinate_dofs", interior_facet=self.interior_facet) - self.coefficient_map[coefficient] = expression + f, "coordinate_dofs", interior_facet=self.interior_facet) + self.coefficient_map[f] = expression def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. From dbc6baed96af057a6c18f29118a6b9f72e2f6113 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 31 Aug 2017 17:26:06 +0100 Subject: [PATCH 391/809] CellVolume: driver.py -> fem.py --- tsfc/driver.py | 32 -------------------------------- tsfc/fem.py | 29 ++++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 6b61fbb6bb..eeeb41c33d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -32,7 +32,6 @@ from tsfc.logging import logger from tsfc.parameters import default_parameters -from tsfc.kernel_interface import ProxyKernelInterface import tsfc.kernel_interface.firedrake as firedrake_interface @@ -132,7 +131,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, index_cache=index_cache) kernel_cfg["facetarea"] = facetarea_generator(mesh, kernel_cfg, integral_type) - kernel_cfg["cellvolume"] = cellvolume_generator(mesh, kernel_cfg) mode_irs = collections.OrderedDict() for integral in integral_data.integrals: @@ -246,35 +244,6 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, body) -class CellVolumeKernelInterface(ProxyKernelInterface): - # Since CellVolume is evaluated as a cell integral, we must ensure - # that the right restriction is applied when it is used in an - # interior facet integral. This proxy diverts coefficient - # translation to use a specified restriction. - - def __init__(self, wrapee, restriction): - ProxyKernelInterface.__init__(self, wrapee) - self.restriction = restriction - - def coefficient(self, ufl_coefficient, r): - assert r is None - return self._wrapee.coefficient(ufl_coefficient, self.restriction) - - -def cellvolume_generator(domain, kernel_config): - def cellvolume(restriction): - from ufl import dx - integrand, degree = ufl_utils.one_times(dx(domain=domain)) - interface = CellVolumeKernelInterface(kernel_config["interface"], restriction) - - config = {k: v for k, v in kernel_config.items() - if k in ["ufl_cell", "precision", "index_cache"]} - config.update(interface=interface, quadrature_degree=degree) - expr, = fem.compile_ufl(integrand, point_sum=True, **config) - return expr - return cellvolume - - def facetarea_generator(domain, kernel_config, integral_type): def facetarea(): from ufl import Measure @@ -338,7 +307,6 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non ufl_cell=coordinates.ufl_domain().ufl_cell(), precision=parameters["precision"], point_set=point_set) - config["cellvolume"] = cellvolume_generator(coordinates.ufl_domain(), config) ir, = fem.compile_ufl(expression, point_sum=False, **config) # Deal with non-scalar expressions diff --git a/tsfc/fem.py b/tsfc/fem.py index bcb6da660a..cfa81c63ce 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -11,6 +11,7 @@ import numpy from singledispatch import singledispatch +import ufl from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction from ufl.classes import (Argument, CellCoordinate, CellEdgeVectors, @@ -38,7 +39,8 @@ construct_modified_terminal) from tsfc.parameters import NUMPY_TYPE, PARAMETERS from tsfc.ufl_utils import (ModifiedTerminalMixin, PickRestriction, - simplify_abs, preprocess_expression) + one_times, simplify_abs, + preprocess_expression) class ContextBase(ProxyKernelInterface): @@ -50,7 +52,6 @@ class ContextBase(ProxyKernelInterface): 'entity_ids', 'precision', 'argument_multiindices', - 'cellvolume', 'facetarea', 'index_cache') @@ -308,9 +309,31 @@ def translate_spatialcoordinate(terminal, mt, ctx): return ctx.translator(expr) +class CellVolumeKernelInterface(ProxyKernelInterface): + # Since CellVolume is evaluated as a cell integral, we must ensure + # that the right restriction is applied when it is used in an + # interior facet integral. This proxy diverts coefficient + # translation to use a specified restriction. + + def __init__(self, wrapee, restriction): + ProxyKernelInterface.__init__(self, wrapee) + self.restriction = restriction + + def coefficient(self, ufl_coefficient, r): + assert r is None + return self._wrapee.coefficient(ufl_coefficient, self.restriction) + + @translate.register(CellVolume) def translate_cellvolume(terminal, mt, ctx): - return ctx.cellvolume(mt.restriction) + integrand, degree = one_times(ufl.dx(domain=terminal.ufl_domain())) + interface = CellVolumeKernelInterface(ctx, mt.restriction) + + config = {name: getattr(ctx, name) + for name in ["ufl_cell", "precision", "index_cache"]} + config.update(interface=interface, quadrature_degree=degree) + expr, = compile_ufl(integrand, point_sum=True, **config) + return expr @translate.register(FacetArea) From 5b737d8e172a5bd5a06e5c7d277861be1957c2fa Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 31 Aug 2017 17:42:24 +0100 Subject: [PATCH 392/809] FacetArea: driver.py -> fem.py --- tsfc/driver.py | 16 +--------------- tsfc/fem.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index eeeb41c33d..ea6675c069 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -124,14 +124,13 @@ def compile_integral(integral_data, form_data, prefix, parameters, kernel_cfg = dict(interface=builder, ufl_cell=cell, + integral_type=integral_type, precision=parameters["precision"], integration_dim=integration_dim, entity_ids=entity_ids, argument_multiindices=argument_multiindices, index_cache=index_cache) - kernel_cfg["facetarea"] = facetarea_generator(mesh, kernel_cfg, integral_type) - mode_irs = collections.OrderedDict() for integral in integral_data.integrals: params = parameters.copy() @@ -244,19 +243,6 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, body) -def facetarea_generator(domain, kernel_config, integral_type): - def facetarea(): - from ufl import Measure - assert integral_type != 'cell' - integrand, degree = ufl_utils.one_times(Measure(integral_type, domain=domain)) - - config = kernel_config.copy() - config.update(quadrature_degree=degree) - expr, = fem.compile_ufl(integrand, point_sum=True, **config) - return expr - return facetarea - - def compile_expression_at_points(expression, points, coordinates, parameters=None): """Compiles a UFL expression to be evaluated at compile-time known reference points. Useful for interpolating UFL expressions onto diff --git a/tsfc/fem.py b/tsfc/fem.py index cfa81c63ce..01ce687f76 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -48,6 +48,7 @@ class ContextBase(ProxyKernelInterface): keywords = ('ufl_cell', 'fiat_cell', + 'integral_type', 'integration_dim', 'entity_ids', 'precision', @@ -338,7 +339,16 @@ def translate_cellvolume(terminal, mt, ctx): @translate.register(FacetArea) def translate_facetarea(terminal, mt, ctx): - return ctx.facetarea() + assert ctx.integral_type != 'cell' + domain = terminal.ufl_domain() + integrand, degree = one_times(ufl.Measure(ctx.integral_type, domain=domain)) + + config = {name: getattr(ctx, name) + for name in ["ufl_cell", "integration_dim", + "entity_ids", "precision", "index_cache"]} + config.update(interface=ctx, quadrature_degree=degree) + expr, = compile_ufl(integrand, point_sum=True, **config) + return expr def fiat_to_ufl(fiat_dict, order): From 6ea01b5065eb01cd2ae4f2949ad7c0fa66390ed3 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Sep 2017 14:41:03 +0100 Subject: [PATCH 393/809] remove some no longer required UFL utils --- tsfc/ufl_utils.py | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 83875501b8..bc53419ee5 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -19,8 +19,8 @@ from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, ComponentTensor, Expr, FloatValue, Division, MixedElement, MultiIndex, Product, - ReferenceValue, ScalarValue, Sqrt, Zero, - CellVolume, FacetArea) + ScalarValue, Sqrt, Zero, CellVolume, + FacetArea) from gem.node import MemoizerArg @@ -95,37 +95,6 @@ def preprocess_expression(expression): return expression -class SpatialCoordinateReplacer(MultiFunction): - """Replace SpatialCoordinate nodes with the ReferenceValue of a - Coefficient. Assumes that the coordinate element only needs - affine mapping. - - :arg coordinates: the coefficient to replace spatial coordinates with - """ - def __init__(self, coordinates): - self.coordinates = coordinates - MultiFunction.__init__(self) - - expr = MultiFunction.reuse_if_untouched - - def terminal(self, t): - return t - - def spatial_coordinate(self, o): - assert o.ufl_domain().ufl_coordinate_element().mapping() == "identity" - return ReferenceValue(self.coordinates) - - -def replace_coordinates(integrand, coordinate_coefficient): - """Replace SpatialCoordinate nodes with Coefficients.""" - return map_expr_dag(SpatialCoordinateReplacer(coordinate_coefficient), integrand) - - -def coordinate_coefficient(domain): - """Create a fake coordinate coefficient for a domain.""" - return ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) - - class ModifiedTerminalMixin(object): """Mixin to use with MultiFunctions that operate on modified terminals.""" From af05599dc3b95656454a32290fe8f4e63e39ad39 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Sep 2017 17:40:18 +0100 Subject: [PATCH 394/809] implement CellOrigin --- tsfc/fem.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 01ce687f76..5f569a4ee3 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -16,11 +16,11 @@ from ufl.corealg.multifunction import MultiFunction from ufl.classes import (Argument, CellCoordinate, CellEdgeVectors, CellFacetJacobian, CellOrientation, - CellVolume, Coefficient, FacetArea, - FacetCoordinate, GeometricQuantity, - QuadratureWeight, ReferenceCellVolume, - ReferenceFacetVolume, ReferenceNormal, - SpatialCoordinate) + CellOrigin, CellVolume, Coefficient, + FacetArea, FacetCoordinate, + GeometricQuantity, QuadratureWeight, + ReferenceCellVolume, ReferenceFacetVolume, + ReferenceNormal, SpatialCoordinate) from FIAT.reference_element import make_affine_mapping @@ -30,6 +30,7 @@ from gem.unconcatenate import unconcatenate from gem.utils import cached_property +from finat.point_set import PointSingleton from finat.quadrature import make_quadrature from tsfc import ufl2gem @@ -351,6 +352,20 @@ def translate_facetarea(terminal, mt, ctx): return expr +@translate.register(CellOrigin) +def translate_cellorigin(terminal, mt, ctx): + domain = terminal.ufl_domain() + coords = SpatialCoordinate(domain) + expression = construct_modified_terminal(mt, coords) + point_set = PointSingleton((0.0,) * domain.topological_dimension()) + + config = {name: getattr(ctx, name) + for name in ["ufl_cell", "precision", "index_cache"]} + config.update(interface=ctx, point_set=point_set) + context = PointSetContext(**config) + return context.translator(expression) + + def fiat_to_ufl(fiat_dict, order): # All derivative multiindices must be of the same dimension. dimension, = list(set(len(alpha) for alpha in iterkeys(fiat_dict))) From 9a06c8f7776230eccf0ba89394b64d2184dfd798 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 1 Sep 2017 17:36:12 +0100 Subject: [PATCH 395/809] WIP: draft Hermite element --- tsfc/driver.py | 3 ++- tsfc/fem.py | 42 +++++++++++++++++++++++++----- tsfc/finatinterface.py | 7 +++++ tsfc/kernel_interface/firedrake.py | 2 +- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index ea6675c069..47235a895c 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -141,7 +141,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, mode = pick_mode(params["mode"]) mode_irs.setdefault(mode, collections.OrderedDict()) - integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) + # integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) + integrand = integral.integrand() integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) # Check if the integral has a quad degree attached, otherwise use diff --git a/tsfc/fem.py b/tsfc/fem.py index 5f569a4ee3..3dcd74b2fa 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -18,9 +18,11 @@ CellFacetJacobian, CellOrientation, CellOrigin, CellVolume, Coefficient, FacetArea, FacetCoordinate, - GeometricQuantity, QuadratureWeight, - ReferenceCellVolume, ReferenceFacetVolume, - ReferenceNormal, SpatialCoordinate) + GeometricQuantity, Jacobian, + NegativeRestricted, QuadratureWeight, + PositiveRestricted, ReferenceCellVolume, + ReferenceFacetVolume, ReferenceNormal, + SpatialCoordinate) from FIAT.reference_element import make_affine_mapping @@ -28,7 +30,7 @@ from gem.node import traversal from gem.optimise import ffc_rounding from gem.unconcatenate import unconcatenate -from gem.utils import cached_property +from gem.utils import DynamicallyScoped, cached_property from finat.point_set import PointSingleton from finat.quadrature import make_quadrature @@ -44,6 +46,9 @@ preprocess_expression) +MT = DynamicallyScoped() + + class ContextBase(ProxyKernelInterface): """Common UFL -> GEM translation context.""" @@ -143,9 +148,29 @@ def weight_expr(self): return self.quadrature_rule.weight_expression def basis_evaluation(self, finat_element, local_derivatives, entity_id): + from finat.hermite import PhysicalGeometry + + class CoordinateMapping(PhysicalGeometry): + def jacobian_at(cm, point): + expr = Jacobian(MT.value.terminal.ufl_domain()) + if MT.value.restriction == '+': + expr = PositiveRestricted(expr) + elif MT.value.restriction == '-': + expr = NegativeRestricted(expr) + expr = preprocess_expression(expr) + + point_set = PointSingleton(point) + + config = {name: getattr(self, name) + for name in ["ufl_cell", "precision", "index_cache"]} + config.update(interface=self, point_set=point_set) + context = PointSetContext(**config) + return context.translator(expr) + return finat_element.basis_evaluation(local_derivatives, self.point_set, - (self.integration_dim, entity_id)) + (self.integration_dim, entity_id), + coordinate_mapping=CoordinateMapping()) class GemPointContext(ContextBase): @@ -395,7 +420,8 @@ def translate_argument(terminal, mt, ctx): element = ctx.create_element(terminal.ufl_element()) def callback(entity_id): - finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) + with MT.let(mt): + finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) # Filter out irrelevant derivatives filtered_dict = {alpha: table for alpha, table in iteritems(finat_dict) @@ -413,6 +439,7 @@ def callback(entity_id): @translate.register(Coefficient) def translate_coefficient(terminal, mt, ctx): + # import ipdb; ipdb.set_trace() vec = ctx.coefficient(terminal, mt.restriction) if terminal.ufl_element().family() == 'Real': @@ -424,7 +451,8 @@ def translate_coefficient(terminal, mt, ctx): # Collect FInAT tabulation for all entities per_derivative = collections.defaultdict(list) for entity_id in ctx.entity_ids: - finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) + with MT.let(mt): + finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) for alpha, table in iteritems(finat_dict): # Filter out irrelevant derivatives if sum(alpha) == mt.local_derivatives: diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0a794b388c..56617bb488 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -37,6 +37,12 @@ __all__ = ("create_element", "supported_elements", "as_fiat_cell") +def hermite_element(cell, degree): + assert degree == 3 + from finat.hermite import CubicHermite + return CubicHermite(cell) + + supported_elements = { # These all map directly to FInAT elements "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, @@ -50,6 +56,7 @@ "Gauss-Lobatto-Legendre": finat.GaussLobattoLegendre, "HDiv Trace": finat.HDivTrace, "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, + "Hermite": hermite_element, "Lagrange": finat.Lagrange, "Nedelec 1st kind H(curl)": finat.Nedelec, "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 36fd22939c..17947e867d 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -203,7 +203,7 @@ def set_coefficients(self, integral_data, form_data): # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: - coefficient = form_data.function_replace_map[form_data.reduced_coefficients[i]] + coefficient = form_data.reduced_coefficients[i] if type(coefficient.ufl_element()) == ufl_MixedElement: split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) for element in coefficient.ufl_element().sub_elements()] From 663a7338e2bf29fae52dac579d9b2960d94fafdd Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Fri, 8 Sep 2017 10:57:58 +0100 Subject: [PATCH 396/809] fix UFC interface --- tsfc/kernel_interface/ufc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index cbcdd2c82a..9c9b1435c8 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -71,6 +71,7 @@ def set_coordinates(self, domain): """ # Create a fake coordinate coefficient for a domain. f = ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) + self.domain_coordinate[domain] = f self.coordinates_args, expression = prepare_coordinates( f, "coordinate_dofs", interior_facet=self.interior_facet) self.coefficient_map[f] = expression From 259e498275926fee6fbd0aa0b0fb2069ae8269f5 Mon Sep 17 00:00:00 2001 From: Jan Blechta Date: Tue, 26 Sep 2017 13:57:16 +0200 Subject: [PATCH 397/809] Update for renaming internal geometry quantities in UFL UFL commit 7fd49ca3f37939dd231e7532f1b9dd4b5809b6e8 --- tsfc/fem.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 5f569a4ee3..461378b5e2 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -14,11 +14,10 @@ import ufl from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, CellCoordinate, CellEdgeVectors, - CellFacetJacobian, CellOrientation, - CellOrigin, CellVolume, Coefficient, - FacetArea, FacetCoordinate, - GeometricQuantity, QuadratureWeight, +from ufl.classes import (Argument, CellCoordinate, CellFacetJacobian, + CellOrientation, CellOrigin, CellVolume, Coefficient, + FacetArea, FacetCoordinate, GeometricQuantity, + QuadratureWeight, ReferenceCellEdgeVectors, ReferenceCellVolume, ReferenceFacetVolume, ReferenceNormal, SpatialCoordinate) @@ -260,12 +259,12 @@ def callback(facet_i): return ctx.entity_selector(callback, mt.restriction) -@translate.register(CellEdgeVectors) -def translate_cell_edge_vectors(terminal, mt, ctx): +@translate.register(ReferenceCellEdgeVectors) +def translate_reference_cell_edge_vectors(terminal, mt, ctx): from FIAT.reference_element import TensorProductCell as fiat_TensorProductCell fiat_cell = ctx.fiat_cell if isinstance(fiat_cell, fiat_TensorProductCell): - raise NotImplementedError("CellEdgeVectors not implemented on TensorProductElements yet") + raise NotImplementedError("ReferenceCellEdgeVectors not implemented on TensorProductElements yet") nedges = len(fiat_cell.get_topology()[1]) vecs = numpy.vstack(map(fiat_cell.compute_edge_tangent, range(nedges))).astype(NUMPY_TYPE) From 19eaf74c42be1d88d162578c816f3afe0dace207 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 27 Sep 2017 14:11:51 +0100 Subject: [PATCH 398/809] implement CellVertices and CellEdgeVectors --- tsfc/fem.py | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 461378b5e2..d21ed1ea4c 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -14,10 +14,12 @@ import ufl from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, CellCoordinate, CellFacetJacobian, - CellOrientation, CellOrigin, CellVolume, Coefficient, - FacetArea, FacetCoordinate, GeometricQuantity, - QuadratureWeight, ReferenceCellEdgeVectors, +from ufl.classes import (Argument, CellCoordinate, CellEdgeVectors, + CellFacetJacobian, CellOrientation, + CellOrigin, CellVertices, CellVolume, + Coefficient, FacetArea, FacetCoordinate, + GeometricQuantity, QuadratureWeight, + ReferenceCellEdgeVectors, ReferenceCellVolume, ReferenceFacetVolume, ReferenceNormal, SpatialCoordinate) @@ -29,7 +31,7 @@ from gem.unconcatenate import unconcatenate from gem.utils import cached_property -from finat.point_set import PointSingleton +from finat.point_set import PointSet, PointSingleton from finat.quadrature import make_quadrature from tsfc import ufl2gem @@ -365,6 +367,41 @@ def translate_cellorigin(terminal, mt, ctx): return context.translator(expression) +@translate.register(CellVertices) +def translate_cell_vertices(terminal, mt, ctx): + coords = SpatialCoordinate(terminal.ufl_domain()) + ufl_expr = construct_modified_terminal(mt, coords) + ps = PointSet(numpy.array(ctx.fiat_cell.get_vertices())) + + config = {name: getattr(ctx, name) + for name in ["ufl_cell", "precision", "index_cache"]} + config.update(interface=ctx, point_set=ps) + context = PointSetContext(**config) + expr = context.translator(ufl_expr) + + # Wrap up point (vertex) index + c = gem.Index() + return gem.ComponentTensor(gem.Indexed(expr, (c,)), ps.indices + (c,)) + + +@translate.register(CellEdgeVectors) +def translate_cell_edge_vectors(terminal, mt, ctx): + # WARNING: Assumes straight edges! + coords = CellVertices(terminal.ufl_domain()) + ufl_expr = construct_modified_terminal(mt, coords) + cell_vertices = ctx.translator(ufl_expr) + + e = gem.Index() + c = gem.Index() + expr = gem.ListTensor([ + gem.Sum(gem.Indexed(cell_vertices, (u, c)), + gem.Product(gem.Literal(-1), + gem.Indexed(cell_vertices, (v, c)))) + for _, (u, v) in sorted(iteritems(ctx.fiat_cell.get_topology()[1])) + ]) + return gem.ComponentTensor(gem.Indexed(expr, (e,)), (e, c)) + + def fiat_to_ufl(fiat_dict, order): # All derivative multiindices must be of the same dimension. dimension, = list(set(len(alpha) for alpha in iterkeys(fiat_dict))) From fd6ea23a99a6114874d1d0156fc52a3f8b93b4a9 Mon Sep 17 00:00:00 2001 From: David Ham Date: Fri, 6 Oct 2017 12:23:10 +0100 Subject: [PATCH 399/809] Fix multiple derivatives in modified terminals. When a modified terminal contains multiple derivatives, construct_modified_terminals iteratively differentiates it. However this can result in differentiating a Zero, which fails. This works around this. --- tsfc/modified_terminals.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tsfc/modified_terminals.py b/tsfc/modified_terminals.py index 07e9a40a5e..006124f22f 100644 --- a/tsfc/modified_terminals.py +++ b/tsfc/modified_terminals.py @@ -25,7 +25,8 @@ from ufl.classes import (ReferenceValue, ReferenceGrad, NegativeRestricted, PositiveRestricted, Restricted, FacetAvg, CellAvg, ConstantValue, - Jacobian, SpatialCoordinate) + Jacobian, SpatialCoordinate, Zero) +from ufl.checks import is_cellwise_constant class ModifiedTerminal(object): @@ -175,8 +176,16 @@ def construct_modified_terminal(mt, terminal): if mt.reference_value: expr = ReferenceValue(expr) + dim = expr.ufl_domain().topological_dimension() for n in range(mt.local_derivatives): - expr = ReferenceGrad(expr) + # Return zero if expression is trivially constant. This has to + # happen here because ReferenceGrad has no access to the + # topological dimension of a literal zero. + if is_cellwise_constant(expr): + expr = Zero(expr.ufl_shape + (dim,), expr.ufl_free_indices, + expr.ufl_index_dimensions) + else: + expr = ReferenceGrad(expr) if mt.averaged == "cell": expr = CellAvg(expr) From 29dc3d9172beabcc936572c0613ba3e279ed7a52 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 10 Nov 2017 10:47:29 +0000 Subject: [PATCH 400/809] Migrate citations import to firedrake_citations --- tsfc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/__init__.py b/tsfc/__init__.py index 5012401cd3..98a029c8c3 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -4,7 +4,7 @@ from tsfc.parameters import default_parameters # noqa: F401 try: - from firedrake import Citations + from firedrake_citations import Citations Citations().register("Homolya2017") del Citations except ImportError: From c51fe0f66ae5e465d74ece6c1a64be4ee607c6ec Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 10 Nov 2017 13:07:56 +0000 Subject: [PATCH 401/809] Register appropriate cites based on optimisation mode --- tsfc/driver.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index ea6675c069..ca630667c4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -365,6 +365,16 @@ def lower_integral_type(fiat_cell, integral_type): def pick_mode(mode): "Return one of the specialized optimisation modules from a mode string." + try: + from firedrake_citations import Citations + cites = {"vanilla": ("Homolya2017", ), + "coffee": ("Luporini2016", "Homolya2017", ), + "spectral": ("Luporini2016", "Homolya2017", "Homolya2017a"), + "tensor": ("Homolya2017", )} + for c in cites[mode]: + Citations().register(c) + except ImportError: + pass if mode == "vanilla": import tsfc.vanilla as m elif mode == "coffee": From 360c50158f39153501ff12920e44077385ddecd1 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 10 Nov 2017 17:55:38 +0000 Subject: [PATCH 402/809] Citation registration only in pick_mode Also register Kirby & Logg 2006 in tensor mode. --- tsfc/__init__.py | 15 ++++++++++++++- tsfc/driver.py | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tsfc/__init__.py b/tsfc/__init__.py index 98a029c8c3..da4cbbfa8b 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -5,7 +5,20 @@ try: from firedrake_citations import Citations - Citations().register("Homolya2017") + Citations().add("Kirby2006", """ +@Article{Kirby2006, + author = {Kirby, Robert C. and Logg, Anders}, + title = {A Compiler for Variational Forms}, + journal = {ACM Trans. Math. Softw.}, + year = 2006, + volume = 32, + number = 3, + pages = {417--444}, + month = sep, + numpages = 28, + doi = {10.1145/1163641.1163644}, + acmid = 1163644, +}""") del Citations except ImportError: pass diff --git a/tsfc/driver.py b/tsfc/driver.py index ca630667c4..8e74f4ff56 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -370,7 +370,7 @@ def pick_mode(mode): cites = {"vanilla": ("Homolya2017", ), "coffee": ("Luporini2016", "Homolya2017", ), "spectral": ("Luporini2016", "Homolya2017", "Homolya2017a"), - "tensor": ("Homolya2017", )} + "tensor": ("Kirby2006", "Homolya2017", )} for c in cites[mode]: Citations().register(c) except ImportError: From 96c62d3866734cb573b6dec613b07edb2b9ed987 Mon Sep 17 00:00:00 2001 From: tj-sun Date: Tue, 5 Dec 2017 11:10:18 +0000 Subject: [PATCH 403/809] change to value packing --- tsfc/kernel_interface/firedrake.py | 42 +++++++++++------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 36fd22939c..2bc83149a4 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -12,8 +12,6 @@ from gem.node import traversal from gem.optimise import remove_componenttensors as prune -from finat import TensorFiniteElement - from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase from tsfc.coffee import SCALAR_TYPE @@ -63,12 +61,12 @@ def __init__(self, interior_facet=False): # Cell orientation if self.interior_facet: - cell_orientations = gem.Variable("cell_orientations", (2, 1)) - self._cell_orientations = (gem.Indexed(cell_orientations, (0, 0)), - gem.Indexed(cell_orientations, (1, 0))) + cell_orientations = gem.Variable("cell_orientations", (2,)) + self._cell_orientations = (gem.Indexed(cell_orientations, (0,)), + gem.Indexed(cell_orientations, (1,))) else: - cell_orientations = gem.Variable("cell_orientations", (1, 1)) - self._cell_orientations = (gem.Indexed(cell_orientations, (0, 0)),) + cell_orientations = gem.Variable("cell_orientations", (1,)) + self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) def _coefficient(self, coefficient, name): """Prepare a coefficient. Adds glue code for the coefficient @@ -286,31 +284,21 @@ def prepare_coefficient(coefficient, name, interior_facet=False): return funarg, expression finat_element = create_element(coefficient.ufl_element()) - - if isinstance(finat_element, TensorFiniteElement): - scalar_shape = finat_element.base_element.index_shape - tensor_shape = finat_element.index_shape[len(scalar_shape):] - else: - scalar_shape = finat_element.index_shape - tensor_shape = () - scalar_size = numpy.prod(scalar_shape, dtype=int) - tensor_size = numpy.prod(tensor_shape, dtype=int) + shape = finat_element.index_shape + size = numpy.prod(shape, dtype=int) funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), - pointers=[("const", "restrict"), ("restrict",)], + pointers=[("restrict",)], qualifiers=["const"]) if not interior_facet: - expression = gem.reshape( - gem.Variable(name, (scalar_size, tensor_size)), - scalar_shape, tensor_shape - ) + expression = gem.reshape(gem.Variable(name, (size,)), shape) else: - varexp = gem.Variable(name, (2 * scalar_size, tensor_size)) - plus = gem.view(varexp, slice(scalar_size), slice(tensor_size)) - minus = gem.view(varexp, slice(scalar_size, 2 * scalar_size), slice(tensor_size)) - expression = (gem.reshape(plus, scalar_shape, tensor_shape), - gem.reshape(minus, scalar_shape, tensor_shape)) + varexp = gem.Variable(name, (2 * size,)) + plus = gem.view(varexp, slice(size)) + minus = gem.view(varexp, slice(size, 2 * size)) + expression = (gem.reshape(plus, shape), + gem.reshape(minus, shape)) return funarg, expression @@ -360,6 +348,6 @@ def expression(restricted): cell_orientations_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), - pointers=[("restrict", "const"), ("restrict",)], + pointers=[("restrict",)], qualifiers=["const"]) """COFFEE function argument for cell orientations""" From 074fea80f30f17b7021eed2f2cd883ff5cff4c6c Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Dec 2017 11:22:42 +0000 Subject: [PATCH 404/809] remove six dependency --- tests/test_flexibly_indexed.py | 1 - tests/test_pickle_gem.py | 2 +- tests/test_sum_factorisation.py | 1 - tests/test_underintegration.py | 1 - tsfc/coffee_mode.py | 1 - tsfc/driver.py | 10 ++++------ tsfc/fem.py | 18 ++++++++---------- tsfc/finatinterface.py | 3 +-- tsfc/kernel_interface/__init__.py | 3 +-- tsfc/kernel_interface/ufc.py | 1 - tsfc/spectral.py | 3 +-- tsfc/tensor.py | 4 +--- 12 files changed, 17 insertions(+), 31 deletions(-) diff --git a/tests/test_flexibly_indexed.py b/tests/test_flexibly_indexed.py index cd40b7013a..ae4a2acb92 100644 --- a/tests/test_flexibly_indexed.py +++ b/tests/test_flexibly_indexed.py @@ -1,5 +1,4 @@ from __future__ import absolute_import, print_function, division -from six.moves import range from itertools import product diff --git a/tests/test_pickle_gem.py b/tests/test_pickle_gem.py index 4d484a988b..361f6dd885 100644 --- a/tests/test_pickle_gem.py +++ b/tests/test_pickle_gem.py @@ -1,5 +1,5 @@ from __future__ import absolute_import, print_function, division -from six.moves import cPickle as pickle, range +import pickle import gem import numpy import pytest diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index 0b36d4dfa5..3c52f586b6 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -1,5 +1,4 @@ from __future__ import absolute_import, print_function, division -from six.moves import range import numpy import pytest diff --git a/tests/test_underintegration.py b/tests/test_underintegration.py index 9bd2cc1c1f..4c0d76530b 100644 --- a/tests/test_underintegration.py +++ b/tests/test_underintegration.py @@ -1,5 +1,4 @@ from __future__ import absolute_import, print_function, division -from six.moves import range from functools import reduce diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 2dc621748f..2adac2eff4 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -1,5 +1,4 @@ from __future__ import absolute_import, print_function, division -from six.moves import zip from functools import partial, reduce diff --git a/tsfc/driver.py b/tsfc/driver.py index ea6675c069..ffc032eae7 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,6 +1,4 @@ from __future__ import absolute_import, print_function, division -from six import iterkeys, iteritems, viewitems -from six.moves import range, zip import collections import operator @@ -186,8 +184,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, # Finalise mode representations into a set of assignments assignments = [] - for mode, var_reps in iteritems(mode_irs): - assignments.extend(mode.flatten(viewitems(var_reps), index_cache)) + for mode, var_reps in mode_irs.items(): + assignments.extend(mode.flatten(var_reps.items(), index_cache)) if assignments: return_variables, expressions = zip(*assignments) @@ -197,8 +195,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, # Need optimised roots for COFFEE options = dict(reduce(operator.and_, - [viewitems(mode.finalise_options) - for mode in iterkeys(mode_irs)])) + [mode.finalise_options.items() + for mode in mode_irs.keys()])) expressions = impero_utils.preprocess_gem(expressions, **options) assignments = list(zip(return_variables, expressions)) diff --git a/tsfc/fem.py b/tsfc/fem.py index d21ed1ea4c..1ac8659b3a 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -2,8 +2,6 @@ geometric quantities into GEM expressions.""" from __future__ import absolute_import, print_function, division -from six import iterkeys, iteritems, itervalues -from six.moves import map, range, zip import collections import itertools @@ -397,17 +395,17 @@ def translate_cell_edge_vectors(terminal, mt, ctx): gem.Sum(gem.Indexed(cell_vertices, (u, c)), gem.Product(gem.Literal(-1), gem.Indexed(cell_vertices, (v, c)))) - for _, (u, v) in sorted(iteritems(ctx.fiat_cell.get_topology()[1])) + for _, (u, v) in sorted(ctx.fiat_cell.get_topology()[1].items()) ]) return gem.ComponentTensor(gem.Indexed(expr, (e,)), (e, c)) def fiat_to_ufl(fiat_dict, order): # All derivative multiindices must be of the same dimension. - dimension, = list(set(len(alpha) for alpha in iterkeys(fiat_dict))) + dimension, = set(len(alpha) for alpha in fiat_dict.keys()) # All derivative tables must have the same shape. - shape, = list(set(table.shape for table in itervalues(fiat_dict))) + shape, = set(table.shape for table in fiat_dict.values()) sigma = tuple(gem.Index(extent=extent) for extent in shape) # Convert from FIAT to UFL format @@ -434,7 +432,7 @@ def callback(entity_id): finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) # Filter out irrelevant derivatives filtered_dict = {alpha: table - for alpha, table in iteritems(finat_dict) + for alpha, table in finat_dict.items() if sum(alpha) == mt.local_derivatives} # Change from FIAT to UFL arrangement @@ -461,7 +459,7 @@ def translate_coefficient(terminal, mt, ctx): per_derivative = collections.defaultdict(list) for entity_id in ctx.entity_ids: finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) - for alpha, table in iteritems(finat_dict): + for alpha, table in finat_dict.items(): # Filter out irrelevant derivatives if sum(alpha) == mt.local_derivatives: # A numerical hack that FFC used to apply on FIAT @@ -476,11 +474,11 @@ def take_singleton(xs): x, = xs # asserts singleton return x per_derivative = {alpha: take_singleton(tables) - for alpha, tables in iteritems(per_derivative)} + for alpha, tables in per_derivative.items()} else: f = ctx.entity_number(mt.restriction) per_derivative = {alpha: gem.select_expression(tables, f) - for alpha, tables in iteritems(per_derivative)} + for alpha, tables in per_derivative.items()} # Coefficient evaluation ctx.index_cache.setdefault(terminal.ufl_element(), element.get_indices()) @@ -488,7 +486,7 @@ def take_singleton(xs): zeta = element.get_value_indices() vec_beta, = gem.optimise.remove_componenttensors([gem.Indexed(vec, beta)]) value_dict = {} - for alpha, table in iteritems(per_derivative): + for alpha, table in per_derivative.items(): table_qi = gem.Indexed(table, beta + zeta) summands = [] for var, expr in unconcatenate([(vec_beta, table_qi)], ctx.index_cache): diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0a794b388c..0b4d32cdc5 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -22,7 +22,6 @@ # along with FFC. If not, see . from __future__ import absolute_import, print_function, division -from six import iteritems from singledispatch import singledispatch import weakref @@ -227,7 +226,7 @@ def _create_element(ufl_element, **kwargs): _cache[ufl_element] = {} cache = _cache[ufl_element] - for key, finat_element in iteritems(cache): + for key, finat_element in cache.items(): # Cache hit if all relevant parameter values match. if all(kwargs[param] == value for param, value in key): return finat_element, set(param for param, value in key) diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index 0eb52be701..d6e7633cc7 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -1,12 +1,11 @@ from __future__ import absolute_import, print_function, division -from six import with_metaclass from abc import ABCMeta, abstractmethod from gem.utils import make_proxy_class -class KernelInterface(with_metaclass(ABCMeta)): +class KernelInterface(metaclass=ABCMeta): """Abstract interface for accessing the GEM expressions corresponding to kernel arguments.""" diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 9c9b1435c8..f1eb116b36 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -1,5 +1,4 @@ from __future__ import absolute_import, print_function, division -from six.moves import range, zip import numpy import functools diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 96f5994809..7b707c6f8b 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -1,9 +1,8 @@ from __future__ import absolute_import, print_function, division -from six.moves import zip, zip_longest from collections import OrderedDict, defaultdict, namedtuple from functools import partial, reduce -from itertools import chain +from itertools import chain, zip_longest from gem.gem import Delta, Indexed, Sum, index_sum, one from gem.optimise import delta_elimination as _delta_elimination diff --git a/tsfc/tensor.py b/tsfc/tensor.py index a9f1ad17f3..20a8e76d44 100644 --- a/tsfc/tensor.py +++ b/tsfc/tensor.py @@ -1,6 +1,4 @@ from __future__ import absolute_import, print_function, division -from six import iteritems -from six.moves import zip from collections import defaultdict from functools import partial, reduce @@ -39,7 +37,7 @@ def einsum(factors, sum_indices): subscript_parts.append(''.join(letters)) result_pairs = sorted((letter, index) - for index, letter in iteritems(index2letter) + for index, letter in index2letter.items() if index not in sum_indices) subscripts = ','.join(subscript_parts) + '->' + ''.join(l for l, i in result_pairs) From 6fba4358e03924d3672997dbefd1bde944f2e880 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Dec 2017 11:30:33 +0000 Subject: [PATCH 405/809] remove singledispatch package dependency --- tsfc/coffee.py | 3 +-- tsfc/fem.py | 2 +- tsfc/fiatinterface.py | 3 +-- tsfc/finatinterface.py | 2 +- tsfc/ufl_utils.py | 3 ++- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 0bae0591d9..0829442495 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -5,12 +5,11 @@ from __future__ import absolute_import, print_function, division from collections import defaultdict -from functools import reduce +from functools import singledispatch, reduce from math import isnan import itertools import numpy -from singledispatch import singledispatch import coffee.base as coffee diff --git a/tsfc/fem.py b/tsfc/fem.py index 1ac8659b3a..4d851210ae 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -5,9 +5,9 @@ import collections import itertools +from functools import singledispatch import numpy -from singledispatch import singledispatch import ufl from ufl.corealg.map_dag import map_expr_dag, map_expr_dags diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 6cf579c1c8..6c629fe5dd 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -23,8 +23,7 @@ from __future__ import absolute_import, print_function, division -from singledispatch import singledispatch -from functools import partial +from functools import singledispatch, partial import weakref import FIAT diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0b4d32cdc5..4c4da9379a 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -23,7 +23,7 @@ from __future__ import absolute_import, print_function, division -from singledispatch import singledispatch +from functools import singledispatch import weakref import finat diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index bc53419ee5..a26a65a7bf 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -2,8 +2,9 @@ from __future__ import absolute_import, print_function, division +from functools import singledispatch + import numpy -from singledispatch import singledispatch import ufl from ufl import as_tensor, indices, replace From ebaa95471859077c138d85285bc244341677e7a2 Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Thu, 7 Dec 2017 11:38:46 +0000 Subject: [PATCH 406/809] remove future imports --- tests/test_codegen.py | 2 -- tests/test_coffee_optimise.py | 2 -- tests/test_create_fiat_element.py | 1 - tests/test_create_finat_element.py | 1 - tests/test_delta_elimination.py | 2 -- tests/test_estimated_degree.py | 1 - tests/test_firedrake_972.py | 1 - tests/test_flexibly_indexed.py | 2 -- tests/test_gem_failure.py | 1 - tests/test_geometry.py | 2 -- tests/test_idempotency.py | 1 - tests/test_pickle_gem.py | 1 - tests/test_refactorise.py | 2 -- tests/test_simplification.py | 2 -- tests/test_sum_factorisation.py | 2 -- tests/test_tensor.py | 2 -- tests/test_underintegration.py | 2 -- tsfc/__init__.py | 2 -- tsfc/coffee.py | 2 -- tsfc/coffee_mode.py | 2 -- tsfc/driver.py | 2 -- tsfc/fem.py | 2 -- tsfc/fiatinterface.py | 2 -- tsfc/finatinterface.py | 2 -- tsfc/kernel_interface/__init__.py | 2 -- tsfc/kernel_interface/common.py | 2 -- tsfc/kernel_interface/firedrake.py | 2 -- tsfc/kernel_interface/ufc.py | 2 -- tsfc/logging.py | 2 -- tsfc/modified_terminals.py | 2 -- tsfc/parameters.py | 2 -- tsfc/spectral.py | 2 -- tsfc/tensor.py | 2 -- tsfc/ufl2gem.py | 2 -- tsfc/ufl_utils.py | 2 -- tsfc/vanilla.py | 2 -- 36 files changed, 65 deletions(-) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index b06942814a..8d0bc79655 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import pytest from gem import impero_utils diff --git a/tests/test_coffee_optimise.py b/tests/test_coffee_optimise.py index bef6d825fa..065bb22ee4 100644 --- a/tests/test_coffee_optimise.py +++ b/tests/test_coffee_optimise.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import pytest from gem.gem import Index, Indexed, Product, Variable, Division, Literal, Sum diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 259a0c8b57..1fa2f437cd 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, print_function, division import pytest import FIAT diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index 83c2b6437d..e135576d02 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, print_function, division import pytest import ufl diff --git a/tests/test_delta_elimination.py b/tests/test_delta_elimination.py index df76629f53..3ac3f8dc28 100644 --- a/tests/test_delta_elimination.py +++ b/tests/test_delta_elimination.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import pytest from gem.gem import Delta, Identity, Index, Indexed, one diff --git a/tests/test_estimated_degree.py b/tests/test_estimated_degree.py index d07cfeca68..c3c80f02a3 100644 --- a/tests/test_estimated_degree.py +++ b/tests/test_estimated_degree.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, print_function, division import logging import pytest diff --git a/tests/test_firedrake_972.py b/tests/test_firedrake_972.py index b318233d32..b8cc992239 100644 --- a/tests/test_firedrake_972.py +++ b/tests/test_firedrake_972.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, print_function, division import numpy import pytest diff --git a/tests/test_flexibly_indexed.py b/tests/test_flexibly_indexed.py index ae4a2acb92..8390bcebcf 100644 --- a/tests/test_flexibly_indexed.py +++ b/tests/test_flexibly_indexed.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - from itertools import product import numpy diff --git a/tests/test_gem_failure.py b/tests/test_gem_failure.py index 03fb35216e..8bf313388f 100644 --- a/tests/test_gem_failure.py +++ b/tests/test_gem_failure.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, print_function, division from ufl import (triangle, tetrahedron, FiniteElement, TrialFunction, TestFunction, inner, grad, dx, dS) from tsfc import compile_form diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 6c36ce1347..458f1b5657 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import pytest import numpy as np diff --git a/tests/test_idempotency.py b/tests/test_idempotency.py index e5ce04dda7..7656592e2b 100644 --- a/tests/test_idempotency.py +++ b/tests/test_idempotency.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, print_function, division import ufl from tsfc import compile_form import pytest diff --git a/tests/test_pickle_gem.py b/tests/test_pickle_gem.py index 361f6dd885..73e39cac9f 100644 --- a/tests/test_pickle_gem.py +++ b/tests/test_pickle_gem.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, print_function, division import pickle import gem import numpy diff --git a/tests/test_refactorise.py b/tests/test_refactorise.py index c12747eaf6..8c8251dfa9 100644 --- a/tests/test_refactorise.py +++ b/tests/test_refactorise.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - from functools import partial import pytest diff --git a/tests/test_simplification.py b/tests/test_simplification.py index 6df0be4365..e5fe3d66e5 100644 --- a/tests/test_simplification.py +++ b/tests/test_simplification.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import pytest from gem.gem import Variable, Zero, Conditional, \ diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index 3c52f586b6..9e31b12501 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import numpy import pytest diff --git a/tests/test_tensor.py b/tests/test_tensor.py index c3e8950e03..f13ea79bd8 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import numpy import pytest diff --git a/tests/test_underintegration.py b/tests/test_underintegration.py index 4c0d76530b..6d7846fbba 100644 --- a/tests/test_underintegration.py +++ b/tests/test_underintegration.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - from functools import reduce import numpy diff --git a/tsfc/__init__.py b/tsfc/__init__.py index 98a029c8c3..0db465bc4b 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - from tsfc.driver import compile_form, compile_expression_at_points # noqa: F401 from tsfc.parameters import default_parameters # noqa: F401 diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 0829442495..915e60e550 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -2,8 +2,6 @@ This is the final stage of code generation in TSFC.""" -from __future__ import absolute_import, print_function, division - from collections import defaultdict from functools import singledispatch, reduce from math import isnan diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 2adac2eff4..025a9b34ef 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - from functools import partial, reduce from gem.node import traversal diff --git a/tsfc/driver.py b/tsfc/driver.py index ffc032eae7..b94cfeb583 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import collections import operator import string diff --git a/tsfc/fem.py b/tsfc/fem.py index 4d851210ae..6e7a9ecef6 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -1,8 +1,6 @@ """Functions to translate UFL finite element objects and reference geometric quantities into GEM expressions.""" -from __future__ import absolute_import, print_function, division - import collections import itertools from functools import singledispatch diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 6c629fe5dd..c106cbcced 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -21,8 +21,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with FFC. If not, see . -from __future__ import absolute_import, print_function, division - from functools import singledispatch, partial import weakref diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 4c4da9379a..6529ce3bfb 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -21,8 +21,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with FFC. If not, see . -from __future__ import absolute_import, print_function, division - from functools import singledispatch import weakref diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index d6e7633cc7..0ff4b6e9e0 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - from abc import ABCMeta, abstractmethod from gem.utils import make_proxy_class diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index b3fee31625..bf9fd4c1af 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import numpy import coffee.base as coffee diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 36fd22939c..597f16fcfb 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import numpy from collections import namedtuple from itertools import chain, product diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index f1eb116b36..c0f2ca79b5 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import numpy import functools from itertools import chain, product diff --git a/tsfc/logging.py b/tsfc/logging.py index e064838960..6bced525f4 100644 --- a/tsfc/logging.py +++ b/tsfc/logging.py @@ -1,7 +1,5 @@ """Logging for TSFC.""" -from __future__ import absolute_import, print_function, division - import logging logger = logging.getLogger('tsfc') diff --git a/tsfc/modified_terminals.py b/tsfc/modified_terminals.py index 006124f22f..1cbf41f4c3 100644 --- a/tsfc/modified_terminals.py +++ b/tsfc/modified_terminals.py @@ -20,8 +20,6 @@ """Definitions of 'modified terminals', a core concept in uflacs.""" -from __future__ import absolute_import, print_function, division - from ufl.classes import (ReferenceValue, ReferenceGrad, NegativeRestricted, PositiveRestricted, Restricted, FacetAvg, CellAvg, ConstantValue, diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 518938072f..70dc647da2 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - import numpy diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 7b707c6f8b..3db58c4686 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - from collections import OrderedDict, defaultdict, namedtuple from functools import partial, reduce from itertools import chain, zip_longest diff --git a/tsfc/tensor.py b/tsfc/tensor.py index 20a8e76d44..d4f80fa00e 100644 --- a/tsfc/tensor.py +++ b/tsfc/tensor.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - from collections import defaultdict from functools import partial, reduce from itertools import count diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index f739e0dc41..33b50723ce 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -1,7 +1,5 @@ """Translation of UFL tensor-algebra into GEM tensor-algebra.""" -from __future__ import absolute_import, print_function, division - import collections import ufl diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index a26a65a7bf..e8575347f8 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -1,7 +1,5 @@ """Utilities for preprocessing UFL objects.""" -from __future__ import absolute_import, print_function, division - from functools import singledispatch import numpy diff --git a/tsfc/vanilla.py b/tsfc/vanilla.py index c995e16210..ecf24cd5e4 100644 --- a/tsfc/vanilla.py +++ b/tsfc/vanilla.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, division - from functools import reduce from gem import index_sum, Sum From 344487ec82ad47d46130c32837eb5229538c9050 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Sun, 28 May 2017 17:35:20 +0100 Subject: [PATCH 407/809] add conj to ufl2gem mixins --- tsfc/ufl2gem.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 33b50723ce..42710fac2b 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -3,7 +3,7 @@ import collections import ufl -from gem import (Literal, Zero, Identity, Sum, Product, Division, +from gem import (Literal, Zero, Identity, Sum, Product, Division, Conj, Power, MathFunction, MinValue, MaxValue, Comparison, LogicalNot, LogicalAnd, LogicalOr, Conditional, Index, Indexed, ComponentTensor, IndexSum, @@ -44,6 +44,13 @@ def product(self, o, *ops): def division(self, o, numerator, denominator): return Division(numerator, denominator) + def conj(self, o, expr): + if o.ufl_shape: # is this necessary? + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(Conj(Indexed(expr, indices)), indices) + else: + return Conj(expr) + def abs(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) From 74aa71f85bed67542ea96cf7d6395e81e505b033 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Sun, 28 May 2017 17:51:41 +0100 Subject: [PATCH 408/809] comments --- tsfc/ufl2gem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 42710fac2b..69db814706 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -45,7 +45,7 @@ def division(self, o, numerator, denominator): return Division(numerator, denominator) def conj(self, o, expr): - if o.ufl_shape: # is this necessary? + if o.ufl_shape: # is this necessary? i guess that this does element-wise conj? indices = tuple(Index() for i in range(len(o.ufl_shape))) return ComponentTensor(Conj(Indexed(expr, indices)), indices) else: From 2bad8bfa9bfa0f924770bf763c0b045f6535a6fa Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Wed, 14 Jun 2017 18:41:38 +0100 Subject: [PATCH 409/809] simplify conj iconj in abs --- tsfc/ufl_utils.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index e8575347f8..afe9f58da0 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -19,7 +19,8 @@ ComponentTensor, Expr, FloatValue, Division, MixedElement, MultiIndex, Product, ScalarValue, Sqrt, Zero, CellVolume, - FacetArea) + ScalarValue, Sqrt, Zero, Conj, + CellVolume, FacetArea) from gem.node import MemoizerArg @@ -248,6 +249,12 @@ def _simplify_abs_expr(o, self, in_abs): def _simplify_abs_sqrt(o, self, in_abs): # Square root is always non-negative return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) + + +@_simplify_abs.register(Conj) +def _simplify_abs_conj(o, self, in_abs): + # Conjugation and abs are the same + return ufl_reuse_if_untouched(0, self(o.ufl_operands[0], False)) @_simplify_abs.register(ScalarValue) From bc544dd76db3e03af5adfa677fb310a51195ba70 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Wed, 14 Jun 2017 21:36:54 +0100 Subject: [PATCH 410/809] add conj, pow,log,abs -> complex --- tsfc/coffee.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 915e60e550..33b19e9b2a 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -237,14 +237,19 @@ def _expression_division(expr, parameters): @_expression.register(gem.Power) def _expression_power(expr, parameters): base, exponent = expr.children - return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) + return coffee.FunCall("cpow", expression(base, parameters), expression(exponent, parameters)) + + +@_expression.register(gem.Conj): +def _expression_conj(expr, parameters): + return coffee.FunCall('conj', *[expression(c, parameters) for c in expr.children]) @_expression.register(gem.MathFunction) def _expression_mathfunction(expr, parameters): name_map = { - 'abs': 'fabs', - 'ln': 'log', + 'abs': 'cabs', + 'ln': 'clog', # Bessel functions 'cyl_bessel_j': 'jn', @@ -273,6 +278,8 @@ def _expression_mathfunction(expr, parameters): return coffee.FunCall(name, *[expression(c, parameters) for c in expr.children]) +# TO DO: remove these/throw exceptions? what happens if you use > in C w/ complex nos? + @_expression.register(gem.MinValue) def _expression_minvalue(expr, parameters): return coffee.FunCall('fmin', *[expression(c, parameters) for c in expr.children]) From 686caf2c65767d5e13e92484b84ff4f7a7c8a33c Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Fri, 16 Jun 2017 10:13:25 +0100 Subject: [PATCH 411/809] add type information in parameters --- tsfc/driver.py | 5 ++++- tsfc/parameters.py | 14 ++++++++++++-- tsfc/ufl_utils.py | 4 +++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index e09aef100d..99000d928a 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -26,7 +26,7 @@ from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee from tsfc.fiatinterface import as_fiat_cell from tsfc.logging import logger -from tsfc.parameters import default_parameters +from tsfc.parameters import default_parameters, set_scalar_type import tsfc.kernel_interface.firedrake as firedrake_interface @@ -43,6 +43,7 @@ def compile_form(form, prefix="form", parameters=None): assert isinstance(form, Form) + complx = parameters and isinstance(parameters["scalar_type"], complex) fd = ufl_utils.compute_form_data(form) logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) @@ -76,6 +77,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, _.update(parameters) parameters = _ + set_scalar_type(parameters["scalar_type"]) + # Remove these here, they're handled below. if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: del parameters["quadrature_degree"] diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 70dc647da2..3c4e81545c 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -3,8 +3,7 @@ NUMPY_TYPE = numpy.dtype("double") -SCALAR_TYPE = {numpy.dtype("double"): "double", - numpy.dtype("float32"): "float"}[NUMPY_TYPE] +SCALAR_TYPE = "double" PARAMETERS = { @@ -22,8 +21,19 @@ # Precision of float printing (number of digits) "precision": numpy.finfo(NUMPY_TYPE).precision, + + "scalar_type": "double" } def default_parameters(): return PARAMETERS.copy() + + +def set_scalar_type(type_): + global NUMPY_TYPE + global SCALAR_TYPE + + SCALAR_TYPE = type_ + NUMPY_TYPE = {"double", numpy.dtype("double"), + "float", numpy.dtype("float32")}["type_"] \ No newline at end of file diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index afe9f58da0..6ed1b62e97 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -38,7 +38,8 @@ def compute_form_data(form, do_apply_geometry_lowering=True, preserve_geometry_types=preserve_geometry_types, do_apply_restrictions=True, - do_estimate_degrees=True): + do_estimate_degrees=True, + complex_mode=False): """Preprocess UFL form in a format suitable for TSFC. Return form data. @@ -54,6 +55,7 @@ def compute_form_data(form, preserve_geometry_types=preserve_geometry_types, do_apply_restrictions=do_apply_restrictions, do_estimate_degrees=do_estimate_degrees, + complex_mode=complex_mode ) return fd From c299822017ca47adc1548984d95df61a1e196429 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Fri, 16 Jun 2017 10:15:58 +0100 Subject: [PATCH 412/809] tidy some comments --- tsfc/parameters.py | 3 ++- tsfc/ufl2gem.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 3c4e81545c..564e46322f 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -36,4 +36,5 @@ def set_scalar_type(type_): SCALAR_TYPE = type_ NUMPY_TYPE = {"double", numpy.dtype("double"), - "float", numpy.dtype("float32")}["type_"] \ No newline at end of file + "float", numpy.dtype("float32")}["type_"] + \ No newline at end of file diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 69db814706..38c3b74a1a 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -45,7 +45,7 @@ def division(self, o, numerator, denominator): return Division(numerator, denominator) def conj(self, o, expr): - if o.ufl_shape: # is this necessary? i guess that this does element-wise conj? + if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) return ComponentTensor(Conj(Indexed(expr, indices)), indices) else: From cb71fb3809dc5ea74e5c1e4199dd30d4773bec25 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Fri, 16 Jun 2017 10:40:36 +0100 Subject: [PATCH 413/809] proper complex type checking --- tsfc/driver.py | 4 ++-- tsfc/parameters.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 99000d928a..e4f3358fe3 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -43,8 +43,8 @@ def compile_form(form, prefix="form", parameters=None): assert isinstance(form, Form) - complx = parameters and isinstance(parameters["scalar_type"], complex) - fd = ufl_utils.compute_form_data(form) + complx = parameters and parameters["scalar_type"] is 'complex' + fd = ufl_utils.compute_form_data(form, complex_mode=complx) logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) kernels = [] diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 564e46322f..bcc6e1f30b 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -35,6 +35,6 @@ def set_scalar_type(type_): global SCALAR_TYPE SCALAR_TYPE = type_ - NUMPY_TYPE = {"double", numpy.dtype("double"), - "float", numpy.dtype("float32")}["type_"] - \ No newline at end of file + NUMPY_TYPE = {"double": numpy.dtype("double"), + "float": numpy.dtype("float32"), + "complex": numpy.dtype("complex128")}[type_] From d1aca67b86973dfa4aadcf6b621dd9794a2c0f8d Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Fri, 16 Jun 2017 10:43:54 +0100 Subject: [PATCH 414/809] distinguish real and complex pow --- tsfc/coffee.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 33b19e9b2a..93b0599906 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -237,12 +237,18 @@ def _expression_division(expr, parameters): @_expression.register(gem.Power) def _expression_power(expr, parameters): base, exponent = expr.children - return coffee.FunCall("cpow", expression(base, parameters), expression(exponent, parameters)) + if parameters['scalar_type'] is 'complex': + return coffee.FunCall("cpow", expression(base, parameters), expression(exponent, parameters)) + else: + return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) -@_expression.register(gem.Conj): +@_expression.register(gem.Conj) def _expression_conj(expr, parameters): - return coffee.FunCall('conj', *[expression(c, parameters) for c in expr.children]) + if parameters['scalar_type'] is 'complex': + return coffee.FunCall('conj', *[expression(c, parameters) for c in expr.children]) + else: + pass @_expression.register(gem.MathFunction) From 17a64d86eea5a30931b68da135dfa4db7d95539c Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Fri, 16 Jun 2017 10:47:34 +0100 Subject: [PATCH 415/809] complex abs and log switch --- tsfc/coffee.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 93b0599906..6f545ed86c 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -254,8 +254,8 @@ def _expression_conj(expr, parameters): @_expression.register(gem.MathFunction) def _expression_mathfunction(expr, parameters): name_map = { - 'abs': 'cabs', - 'ln': 'clog', + 'abs': 'fabs', + 'ln': 'log', # Bessel functions 'cyl_bessel_j': 'jn', @@ -268,7 +268,14 @@ def _expression_mathfunction(expr, parameters): 'cyl_bessel_i': 'boost::math::cyl_bessel_i', 'cyl_bessel_k': 'boost::math::cyl_bessel_k', } - name = name_map.get(expr.name, expr.name) + complex_name_map = { + 'abs': 'cabs', + 'ln': 'clog' + } + if parameters['scalar_type'] is 'complex': + name = complex_name_map.get(expr.name, expr.name) + else: + name = name_map.get(expr.name, expr.name) if name == 'jn': nu, arg = expr.children if nu == gem.Zero(): From 68e1dfdb066d53c5428a7fdbf6b8765cb61aad01 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 19 Jun 2017 12:30:19 +0100 Subject: [PATCH 416/809] change conj to mathfunction --- tsfc/ufl2gem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 38c3b74a1a..79b71a6a36 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -47,9 +47,9 @@ def division(self, o, numerator, denominator): def conj(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(Conj(Indexed(expr, indices)), indices) + return ComponentTensor(MathFunction('conj', Indexed(expr, indices)), indices) else: - return Conj(expr) + return MathFunction('conj', expr) def abs(self, o, expr): if o.ufl_shape: From 3b5236db6224223cbe9d9625a503034356c0e592 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 19 Jun 2017 12:31:06 +0100 Subject: [PATCH 417/809] isnan to complex version --- tsfc/coffee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 6f545ed86c..a8c33535c8 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -4,7 +4,7 @@ from collections import defaultdict from functools import singledispatch, reduce -from math import isnan +from cmath import isnan import itertools import numpy From 8e0fc58f15bfdca6a388633927983dc4636d4bae Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 19 Jun 2017 12:31:31 +0100 Subject: [PATCH 418/809] need to fix simplify abs and conj --- tsfc/ufl_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 6ed1b62e97..0bbfd69415 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -253,10 +253,10 @@ def _simplify_abs_sqrt(o, self, in_abs): return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) -@_simplify_abs.register(Conj) -def _simplify_abs_conj(o, self, in_abs): - # Conjugation and abs are the same - return ufl_reuse_if_untouched(0, self(o.ufl_operands[0], False)) +# @_simplify_abs.register(Conj) +# def _simplify_abs_conj(o, self, in_abs): +# # Conjugation and abs are the same +# return ufl_reuse_if_untouched(0, self(o.ufl_operands[0], False)) @_simplify_abs.register(ScalarValue) From 598032f82f1894e0fb1ee1e1d48bf204f509bdfb Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 19 Jun 2017 15:52:42 +0100 Subject: [PATCH 419/809] use correct parameters type, fix type accessing, add complex constant --- tsfc/coffee.py | 31 ++++++++++++++++++++++--------- tsfc/driver.py | 3 ++- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index a8c33535c8..28e97a0a50 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -20,7 +20,7 @@ class Bunch(object): pass -def generate(impero_c, index_names, precision, roots=(), argument_indices=()): +def generate(impero_c, index_names, precision, scalar_type, roots=(), argument_indices=()): """Generates COFFEE code. :arg impero_c: ImperoC tuple with Impero AST and other data @@ -40,6 +40,7 @@ def generate(impero_c, index_names, precision, roots=(), argument_indices=()): params.epsilon = 10.0 * eval("1e-%d" % precision) params.roots = roots params.argument_indices = argument_indices + params.scalar_type = scalar_type params.names = {} for i, temp in enumerate(impero_c.temporaries): @@ -237,7 +238,7 @@ def _expression_division(expr, parameters): @_expression.register(gem.Power) def _expression_power(expr, parameters): base, exponent = expr.children - if parameters['scalar_type'] is 'complex': + if parameters.scalar_type is 'complex': return coffee.FunCall("cpow", expression(base, parameters), expression(exponent, parameters)) else: return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) @@ -245,7 +246,7 @@ def _expression_power(expr, parameters): @_expression.register(gem.Conj) def _expression_conj(expr, parameters): - if parameters['scalar_type'] is 'complex': + if parameters.scalar_type is 'complex': return coffee.FunCall('conj', *[expression(c, parameters) for c in expr.children]) else: pass @@ -272,7 +273,7 @@ def _expression_mathfunction(expr, parameters): 'abs': 'cabs', 'ln': 'clog' } - if parameters['scalar_type'] is 'complex': + if parameters.scalar_type is 'complex': name = complex_name_map.get(expr.name, expr.name) else: name = name_map.get(expr.name, expr.name) @@ -340,11 +341,23 @@ def _expression_scalar(expr, parameters): if isnan(expr.value): return coffee.Symbol("NAN") else: - v = expr.value - r = round(v, 1) - if r and abs(v - r) < parameters.epsilon: - v = r # round to nonzero - return coffee.Symbol(("%%.%dg" % parameters.precision) % v) + vr = expr.value.real + print(vr) + rr = round(vr, 1) + if rr and abs(vr - rr) < parameters.epsilon: + vr = rr # round to nonzero + + vi = expr.value.imag # also checks if v is purely real + if vi == 0.0: + return coffee.Symbol(("%%.%dg" % parameters.precision) % vr) + ri = round(vi, 1) + + # TO DO: determine if this is the right syntax for this coffee Symbol + + if ri and abs(vi - ri) < parameters.epsilon: + vi = ri + return coffee.Symbol(("%%.%dg" % parameters.precision) % vr + " + " + + ("%%.%dg" % parameters.precision) % vi + " * I") @_expression.register(gem.Variable) diff --git a/tsfc/driver.py b/tsfc/driver.py index e4f3358fe3..bcfc2338d5 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -237,7 +237,8 @@ def name_multiindex(multiindex, name): name_multiindex(multiindex, name) # Construct kernel - body = generate_coffee(impero_c, index_names, parameters["precision"], expressions, split_argument_indices) + body = generate_coffee(impero_c, index_names, parameters["precision"], + parameters["scalar_type"], expressions, split_argument_indices) return builder.construct_kernel(kernel_name, body) From 4ffa1d7ba2faba6d898e8f85c014c2faaa685bd2 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 19 Jun 2017 15:55:04 +0100 Subject: [PATCH 420/809] remove debugging print statement --- tsfc/coffee.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 28e97a0a50..522fa86237 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -342,7 +342,6 @@ def _expression_scalar(expr, parameters): return coffee.Symbol("NAN") else: vr = expr.value.real - print(vr) rr = round(vr, 1) if rr and abs(vr - rr) < parameters.epsilon: vr = rr # round to nonzero From b8f685f836331b30c38725f0b613c4717aef6541 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Wed, 21 Jun 2017 16:45:14 +0100 Subject: [PATCH 421/809] add ufl translations for real and imag --- tsfc/ufl2gem.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 79b71a6a36..fe98464c28 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -3,7 +3,7 @@ import collections import ufl -from gem import (Literal, Zero, Identity, Sum, Product, Division, Conj, +from gem import (Literal, Zero, Identity, Sum, Product, Division, Real, Imag, Power, MathFunction, MinValue, MaxValue, Comparison, LogicalNot, LogicalAnd, LogicalOr, Conditional, Index, Indexed, ComponentTensor, IndexSum, @@ -44,6 +44,20 @@ def product(self, o, *ops): def division(self, o, numerator, denominator): return Division(numerator, denominator) + def real(self, o, expr): + if o.ufl_shape: + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(Real(Indexed(expr, indices)), indices) + else: + return Real(expr) + + def imag(self, o, expr): + if o.ufl_shape: + indices = tuple(Index() for i in range(len(o.ufl_shape))) + return ComponentTensor(Imag(Indexed(expr, indices)), indices) + else: + return Imag(expr) + def conj(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) From 4495715c3c78a15d2816434ea1cfe6198dc4c50e Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Thu, 22 Jun 2017 11:19:34 +0100 Subject: [PATCH 422/809] trying out a fix for simplifying abs conj --- tsfc/ufl_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 0bbfd69415..55f1a31eeb 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -253,10 +253,10 @@ def _simplify_abs_sqrt(o, self, in_abs): return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) -# @_simplify_abs.register(Conj) -# def _simplify_abs_conj(o, self, in_abs): -# # Conjugation and abs are the same -# return ufl_reuse_if_untouched(0, self(o.ufl_operands[0], False)) +@_simplify_abs.register(Conj) +def _simplify_abs_conj(o, self, in_abs): + # Conjugation and abs are the same + return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) @_simplify_abs.register(ScalarValue) From 8b00033f56805cf09bfea326d9f5e060f9a1000d Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 26 Jun 2017 16:43:14 +0100 Subject: [PATCH 423/809] add real and imag to tsfc --- tsfc/coffee.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 522fa86237..393843d8cb 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -252,6 +252,22 @@ def _expression_conj(expr, parameters): pass +@_expression.register(gem.Real) +def _expression_real(expr, parameters): + if parameters.scalar_type is 'complex': + return coffee.FunCall('creal', *[expression(c, parameters) for c in expr.children]) + else: + pass + + +@_expression.register(gem.Imag) +def _expression_imag(expr, parameters): + if parameters.scalar_type is 'complex': + return coffee.FunCall('cimag', *[expression(c, parameters) for c in expr.children]) + else: + pass + + @_expression.register(gem.MathFunction) def _expression_mathfunction(expr, parameters): name_map = { From 187b7e07c813c350ab19fb13ce85fc4777766df4 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 26 Jun 2017 16:46:29 +0100 Subject: [PATCH 424/809] make conj like real and imag --- tsfc/ufl2gem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index fe98464c28..f76f6e8095 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -61,9 +61,9 @@ def imag(self, o, expr): def conj(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(MathFunction('conj', Indexed(expr, indices)), indices) + return ComponentTensor(Conj(Indexed(expr, indices)), indices) else: - return MathFunction('conj', expr) + return Conj(expr) def abs(self, o, expr): if o.ufl_shape: From d475c04af19749fe3b90dc1dec561bdc67589b3f Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Tue, 27 Jun 2017 20:01:19 +0100 Subject: [PATCH 425/809] make sure to import the right functions --- tsfc/ufl2gem.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index f76f6e8095..7a3b8f5ba2 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -3,7 +3,7 @@ import collections import ufl -from gem import (Literal, Zero, Identity, Sum, Product, Division, Real, Imag, +from gem import (Literal, Zero, Identity, Sum, Product, Division, ComplexPartsFunction, Power, MathFunction, MinValue, MaxValue, Comparison, LogicalNot, LogicalAnd, LogicalOr, Conditional, Index, Indexed, ComponentTensor, IndexSum, @@ -47,23 +47,23 @@ def division(self, o, numerator, denominator): def real(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(Real(Indexed(expr, indices)), indices) + return ComponentTensor(ComplexPartsFunction('real', Indexed(expr, indices)), indices) else: - return Real(expr) + return ComplexPartsFunction('real', expr) def imag(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(Imag(Indexed(expr, indices)), indices) + return ComponentTensor(ComplexPartsFunction('imag', Indexed(expr, indices)), indices) else: - return Imag(expr) + return ComplexPartsFunction('imag', expr) def conj(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(Conj(Indexed(expr, indices)), indices) + return ComponentTensor(ComplexPartsFunction('conj', Indexed(expr, indices)), indices) else: - return Conj(expr) + return ComplexPartsFunction('conj', expr) def abs(self, o, expr): if o.ufl_shape: From 11606806193c50209e0cf1364e250b3845bed985 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Thu, 29 Jun 2017 19:38:26 +0100 Subject: [PATCH 426/809] new numpy type and scalar type checking --- tsfc/parameters.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index bcc6e1f30b..0cf3f1f339 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -37,4 +37,13 @@ def set_scalar_type(type_): SCALAR_TYPE = type_ NUMPY_TYPE = {"double": numpy.dtype("double"), "float": numpy.dtype("float32"), - "complex": numpy.dtype("complex128")}[type_] + "double complex": numpy.dtype("complex128")}[type_] + + +def scalar_type(): + return SCALAR_TYPE + + +def numpy_type(): + return NUMPY_TYPE + From 6ae8fc8ca6228868127daa37dc1662b0f69281ce Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Thu, 29 Jun 2017 19:41:25 +0100 Subject: [PATCH 427/809] more changes in type checking --- tsfc/coffee.py | 51 ++++++++++++++++++-------------------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 393843d8cb..0bcdacf3ea 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -13,8 +13,6 @@ from gem import gem, impero as imp -from tsfc.parameters import SCALAR_TYPE - class Bunch(object): pass @@ -109,7 +107,7 @@ def statement_block(tree, parameters): statements = [statement(child, parameters) for child in tree.children] declares = [] for expr in parameters.declare[tree]: - declares.append(coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters))) + declares.append(coffee.Decl(parameters.scalar_type, _decl_symbol(expr, parameters))) return coffee.Block(declares + statements, open_scope=True) @@ -133,7 +131,7 @@ def statement_for(tree, parameters): @statement.register(imp.Initialise) def statement_initialise(leaf, parameters): if parameters.declare[leaf]: - return coffee.Decl(SCALAR_TYPE, _decl_symbol(leaf.indexsum, parameters), 0.0) + return coffee.Decl(parameters.scalar_type, _decl_symbol(leaf.indexsum, parameters), 0.0) else: return coffee.Assign(_ref_symbol(leaf.indexsum, parameters), 0.0) @@ -168,7 +166,7 @@ def statement_evaluate(leaf, parameters): if isinstance(expr, gem.ListTensor): if parameters.declare[leaf]: array_expression = numpy.vectorize(lambda v: expression(v, parameters)) - return coffee.Decl(SCALAR_TYPE, + return coffee.Decl(parameters.scalar_type, _decl_symbol(expr, parameters), coffee.ArrayInit(array_expression(expr.array), precision=parameters.precision)) @@ -180,14 +178,14 @@ def statement_evaluate(leaf, parameters): return coffee.Block(ops, open_scope=False) elif isinstance(expr, gem.Constant): assert parameters.declare[leaf] - return coffee.Decl(SCALAR_TYPE, + return coffee.Decl(parameters.scalar_type, _decl_symbol(expr, parameters), coffee.ArrayInit(expr.array, parameters.precision), qualifiers=["static", "const"]) else: code = expression(expr, parameters, top=True) if parameters.declare[leaf]: - return coffee.Decl(SCALAR_TYPE, _decl_symbol(expr, parameters), code) + return coffee.Decl(parameters.scalar_type, _decl_symbol(expr, parameters), code) else: return coffee.Assign(_ref_symbol(expr, parameters), code) @@ -238,35 +236,21 @@ def _expression_division(expr, parameters): @_expression.register(gem.Power) def _expression_power(expr, parameters): base, exponent = expr.children - if parameters.scalar_type is 'complex': + if parameters.scalar_type is 'double complex': return coffee.FunCall("cpow", expression(base, parameters), expression(exponent, parameters)) else: return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) -@_expression.register(gem.Conj) -def _expression_conj(expr, parameters): - if parameters.scalar_type is 'complex': - return coffee.FunCall('conj', *[expression(c, parameters) for c in expr.children]) - else: - pass - - -@_expression.register(gem.Real) -def _expression_real(expr, parameters): - if parameters.scalar_type is 'complex': - return coffee.FunCall('creal', *[expression(c, parameters) for c in expr.children]) - else: - pass - - -@_expression.register(gem.Imag) -def _expression_imag(expr, parameters): - if parameters.scalar_type is 'complex': - return coffee.FunCall('cimag', *[expression(c, parameters) for c in expr.children]) - else: - pass - +@_expression.register(gem.ComplexPartsFunction) +def _expression_complexpartsfunction(expr, parameters): + name_map = { + 'real': 'creal', + 'imag': 'cimag', + 'conj': 'conj', + } + name = name_map.get(expr.name, expr.name) + return coffee.FunCall(name, *[expression(c, parameters) for c in expr.children]) @_expression.register(gem.MathFunction) def _expression_mathfunction(expr, parameters): @@ -287,9 +271,10 @@ def _expression_mathfunction(expr, parameters): } complex_name_map = { 'abs': 'cabs', - 'ln': 'clog' + 'ln': 'clog', + 'conj': 'conj' } - if parameters.scalar_type is 'complex': + if parameters.scalar_type == 'double complex': name = complex_name_map.get(expr.name, expr.name) else: name = name_map.get(expr.name, expr.name) From 649829491f6a2cd80c32cabd875379f155153187 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Thu, 29 Jun 2017 19:42:45 +0100 Subject: [PATCH 428/809] even more type checking --- tsfc/driver.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index bcfc2338d5..c4b5f062d4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -23,10 +23,10 @@ from finat.quadrature import AbstractQuadratureRule, make_quadrature from tsfc import fem, ufl_utils -from tsfc.coffee import SCALAR_TYPE, generate as generate_coffee +from tsfc.coffee import generate as generate_coffee from tsfc.fiatinterface import as_fiat_cell from tsfc.logging import logger -from tsfc.parameters import default_parameters, set_scalar_type +from tsfc.parameters import default_parameters, set_scalar_type, scalar_type import tsfc.kernel_interface.firedrake as firedrake_interface @@ -42,8 +42,7 @@ def compile_form(form, prefix="form", parameters=None): cpu_time = time.time() assert isinstance(form, Form) - - complx = parameters and parameters["scalar_type"] is 'complex' + complx = parameters and parameters["scalar_type"] is 'double complex' fd = ufl_utils.compute_form_data(form, complex_mode=complx) logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) @@ -305,7 +304,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non return_shape = (len(points),) + value_shape return_indices = point_set.indices + tensor_indices return_var = gem.Variable('A', return_shape) - return_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('A', rank=return_shape)) + return_arg = ast.Decl(scalar_type(), ast.Symbol('A', rank=return_shape)) return_expr = gem.Indexed(return_var, return_indices) ir, = impero_utils.preprocess_gem([ir]) impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) From 1f133f5c824bbbc8a892e4b7b8c415cdee50295e Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Thu, 29 Jun 2017 19:43:13 +0100 Subject: [PATCH 429/809] conj<->abs simplifications --- tsfc/ufl_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 55f1a31eeb..aea16626cb 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -255,8 +255,9 @@ def _simplify_abs_sqrt(o, self, in_abs): @_simplify_abs.register(Conj) def _simplify_abs_conj(o, self, in_abs): - # Conjugation and abs are the same - return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) + # Conj(Abs()) is same as Abs() + # Abs(Conj()) is same as Abs() + return self(o.ufl_operands[0], True) @_simplify_abs.register(ScalarValue) From 068732307b67c07f8711ac93fedec3ba140780b0 Mon Sep 17 00:00:00 2001 From: S Nicholas Barton Date: Tue, 4 Jul 2017 13:53:10 +0100 Subject: [PATCH 430/809] update kernel interfaces to use new scalar type --- tsfc/kernel_interface/firedrake.py | 10 +++++----- tsfc/kernel_interface/ufc.py | 13 +++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 0325b64084..2eac9894a5 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -12,7 +12,7 @@ from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase -from tsfc.coffee import SCALAR_TYPE +from tsfc.parameters import scalar_type # Expression kernel description type @@ -272,7 +272,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): if coefficient.ufl_element().family() == 'Real': # Constant - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + funarg = coffee.Decl(scalar_type(), coffee.Symbol(name), pointers=[("restrict",)], qualifiers=["const"]) @@ -285,7 +285,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): shape = finat_element.index_shape size = numpy.prod(shape, dtype=int) - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + funarg = coffee.Decl(scalar_type(), coffee.Symbol(name), pointers=[("restrict",)], qualifiers=["const"]) @@ -317,7 +317,7 @@ def prepare_arguments(arguments, multiindices, interior_facet=False): if len(arguments) == 0: # No arguments - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=(1,))) + funarg = coffee.Decl(scalar_type(), coffee.Symbol("A", rank=(1,))) expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) return funarg, [expression] @@ -339,7 +339,7 @@ def expression(restricted): c_shape = tuple(u_shape) slicez = [[slice(s) for s in u_shape]] - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A", rank=c_shape)) + funarg = coffee.Decl(scalar_type(), coffee.Symbol("A", rank=c_shape)) varexp = gem.Variable("A", c_shape) expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] return funarg, prune(expressions) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index c0f2ca79b5..1dc0645bee 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -13,7 +13,8 @@ from tsfc.kernel_interface.common import KernelBuilderBase from tsfc.finatinterface import create_element as _create_element -from tsfc.coffee import SCALAR_TYPE +from tsfc.parameters import scalar_type + # UFC DoF ordering for vector/tensor elements is XXXX YYYY ZZZZ. @@ -81,7 +82,7 @@ def set_coefficients(self, integral_data, form_data): """ name = "w" self.coefficient_args = [ - coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + coffee.Decl(scalar_type(), coffee.Symbol(name), pointers=[("const",), ()], qualifiers=["const"]) ] @@ -218,16 +219,16 @@ def transpose(expr): transposed_indices) if not interior_facet: - funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name), + funargs = [coffee.Decl(scalar_type(), coffee.Symbol(name), pointers=[("",)], qualifiers=["const"])] variable = gem.Variable(name, (size,)) expression = transpose(gem.reshape(variable, transposed_shape)) else: - funargs = [coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_0"), + funargs = [coffee.Decl(scalar_type(), coffee.Symbol(name+"_0"), pointers=[("",)], qualifiers=["const"]), - coffee.Decl(SCALAR_TYPE, coffee.Symbol(name+"_1"), + coffee.Decl(scalar_type(), coffee.Symbol(name+"_1"), pointers=[("",)], qualifiers=["const"])] variable0 = gem.Variable(name+"_0", (size,)) @@ -253,7 +254,7 @@ def prepare_arguments(arguments, multiindices, interior_facet=False): expressions - GEM expressions referring to the argument tensor """ - funarg = coffee.Decl(SCALAR_TYPE, coffee.Symbol("A"), pointers=[()]) + funarg = coffee.Decl(scalar_type(), coffee.Symbol("A"), pointers=[()]) varexp = gem.Variable("A", (None,)) if len(arguments) == 0: From cf8ad511855c45188c6a26d109a3684440b68706 Mon Sep 17 00:00:00 2001 From: S Nicholas Barton Date: Tue, 4 Jul 2017 14:11:18 +0100 Subject: [PATCH 431/809] add scalar type to expression --- tsfc/driver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index c4b5f062d4..ad1967f5b1 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -76,6 +76,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, _.update(parameters) parameters = _ + # Set the scalar type set_scalar_type(parameters["scalar_type"]) # Remove these here, they're handled below. @@ -265,6 +266,9 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non if extract_arguments(expression): return ValueError("Cannot interpolate UFL expression with Arguments!") + # Set the scalar type + set_scalar_type(parameters["scalar_type"]) + # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression) From 7654d820dd72c9cdad937cde8087455fd6243751 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Sun, 16 Jul 2017 19:09:02 +0100 Subject: [PATCH 432/809] remove old comments --- tsfc/coffee.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 0bcdacf3ea..de77f88fe2 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -293,8 +293,6 @@ def _expression_mathfunction(expr, parameters): return coffee.FunCall(name, *[expression(c, parameters) for c in expr.children]) -# TO DO: remove these/throw exceptions? what happens if you use > in C w/ complex nos? - @_expression.register(gem.MinValue) def _expression_minvalue(expr, parameters): return coffee.FunCall('fmin', *[expression(c, parameters) for c in expr.children]) From d3f390aeafa6320a5be73b3690bf8ac980b85f8c Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 17 Jul 2017 13:21:47 +0100 Subject: [PATCH 433/809] make sure tsfc in the right mode and avoid Literaly dtype problem for now --- tsfc/driver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index ad1967f5b1..fd0117b9ff 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -42,7 +42,11 @@ def compile_form(form, prefix="form", parameters=None): cpu_time = time.time() assert isinstance(form, Form) + + # determine if we're in complex mode; coffee mode does not support complex complx = parameters and parameters["scalar_type"] is 'double complex' + if complx: + parameters["mode"] = 'vanilla' fd = ufl_utils.compute_form_data(form, complex_mode=complx) logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) From 1276ebfaf6c26530febedd68d3276b97b6175df4 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 24 Jul 2017 17:39:28 +0100 Subject: [PATCH 434/809] add new complex preprocessing for expressions --- tsfc/ufl_utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index aea16626cb..07d13a3d29 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -12,6 +12,8 @@ from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering +from ufl.algorithms.comparison_checker import do_comparison_check +from ufl.algorithms.remove_complex_nodes import remove_complex_nodes from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction from ufl.geometry import QuadratureWeight @@ -81,12 +83,14 @@ def one_times(measure): return integrand, degree -def preprocess_expression(expression): +def preprocess_expression(expression, complex_mode=False): """Imitates the compute_form_data processing pipeline. Useful, for example, to preprocess non-scalar expressions, which are not and cannot be forms. """ + if complex_mode: + expression = do_comparison_check(expression) expression = apply_algebra_lowering(expression) expression = apply_derivatives(expression) expression = apply_function_pullbacks(expression) @@ -94,6 +98,8 @@ def preprocess_expression(expression): expression = apply_derivatives(expression) expression = apply_geometry_lowering(expression, preserve_geometry_types) expression = apply_derivatives(expression) + if not complex_mode: + expression = remove_complex_nodes(expression) return expression From 4f9db71bc8a20bbbfa186ec6422fc23b06abf9c8 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 24 Jul 2017 17:43:15 +0100 Subject: [PATCH 435/809] more complex processing for expressions --- tsfc/driver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index fd0117b9ff..efd516c45f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -273,8 +273,11 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non # Set the scalar type set_scalar_type(parameters["scalar_type"]) + # determine if we're in complex mode + complx = parameters and parameters["scalar_type"] is 'double complex' + # Apply UFL preprocessing - expression = ufl_utils.preprocess_expression(expression) + expression = ufl_utils.preprocess_expression(expression, complex_mode=complx) # Initialise kernel builder builder = firedrake_interface.ExpressionKernelBuilder() From 248e1f6d80c168f90bfe18bf6a71493ba6ed87c7 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Tue, 25 Jul 2017 12:12:32 +0100 Subject: [PATCH 436/809] added scalar type to coffee generation --- tsfc/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index efd516c45f..aea38b95d2 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -320,7 +320,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non ir, = impero_utils.preprocess_gem([ir]) impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) point_index, = point_set.indices - body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"]) + body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"], scalar_type()) # Handle cell orientations if builder.needs_cell_orientations([ir]): From 75941f838430323671dfaa6a3c11a1093ec1b22b Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Thu, 27 Jul 2017 15:07:21 +0100 Subject: [PATCH 437/809] syntax fix --- tsfc/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index aea38b95d2..d30663a492 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -44,7 +44,7 @@ def compile_form(form, prefix="form", parameters=None): assert isinstance(form, Form) # determine if we're in complex mode; coffee mode does not support complex - complx = parameters and parameters["scalar_type"] is 'double complex' + complx = parameters and parameters["scalar_type"] == 'double complex' if complx: parameters["mode"] = 'vanilla' fd = ufl_utils.compute_form_data(form, complex_mode=complx) From 1cf55713551837e45aa160f8eb420684336f38af Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Sat, 29 Jul 2017 16:39:30 +0100 Subject: [PATCH 438/809] add complex support for more mathfunctions --- tsfc/coffee.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index de77f88fe2..24aa06cb10 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -270,12 +270,15 @@ def _expression_mathfunction(expr, parameters): 'cyl_bessel_k': 'boost::math::cyl_bessel_k', } complex_name_map = { - 'abs': 'cabs', - 'ln': 'clog', - 'conj': 'conj' + 'ln': 'clog' + + # TODO: Are there different complex Bessel Functions? } if parameters.scalar_type == 'double complex': name = complex_name_map.get(expr.name, expr.name) + if name in {'sin', 'cos', 'tan', 'sqrt', 'exp', 'abs', 'sinh', 'cosh', 'tanh', + 'sinh', 'acos', 'asin', 'atan'}: + name = 'c' + expr.name else: name = name_map.get(expr.name, expr.name) if name == 'jn': From 3612693b4d466d4e75988de3bb36123767e96d02 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Mon, 31 Jul 2017 18:31:32 +0100 Subject: [PATCH 439/809] abs fix --- tsfc/coffee.py | 2 -- tsfc/ufl_utils.py | 7 ------- 2 files changed, 9 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 24aa06cb10..e962eb8429 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -352,8 +352,6 @@ def _expression_scalar(expr, parameters): if vi == 0.0: return coffee.Symbol(("%%.%dg" % parameters.precision) % vr) ri = round(vi, 1) - - # TO DO: determine if this is the right syntax for this coffee Symbol if ri and abs(vi - ri) < parameters.epsilon: vi = ri diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 07d13a3d29..c78e712a58 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -257,13 +257,6 @@ def _simplify_abs_expr(o, self, in_abs): def _simplify_abs_sqrt(o, self, in_abs): # Square root is always non-negative return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) - - -@_simplify_abs.register(Conj) -def _simplify_abs_conj(o, self, in_abs): - # Conj(Abs()) is same as Abs() - # Abs(Conj()) is same as Abs() - return self(o.ufl_operands[0], True) @_simplify_abs.register(ScalarValue) From 33b456b8076368f589630e4e3775ac9cb373bf7f Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Fri, 4 Aug 2017 20:02:37 +0200 Subject: [PATCH 440/809] better type checking --- tsfc/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index d30663a492..6633e8293e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -274,7 +274,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non set_scalar_type(parameters["scalar_type"]) # determine if we're in complex mode - complx = parameters and parameters["scalar_type"] is 'double complex' + complx = parameters and parameters["scalar_type"] == 'double complex' # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, complex_mode=complx) From fd2ec710fd53809721ac209b6b10a96223e5e5d4 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Wed, 9 Aug 2017 18:38:14 +0200 Subject: [PATCH 441/809] better type checking --- tsfc/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 6633e8293e..c6c537283b 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -44,7 +44,7 @@ def compile_form(form, prefix="form", parameters=None): assert isinstance(form, Form) # determine if we're in complex mode; coffee mode does not support complex - complx = parameters and parameters["scalar_type"] == 'double complex' + complx = parameters and "scalar_type" in parameters and parameters["scalar_type"] == 'double complex' if complx: parameters["mode"] = 'vanilla' fd = ufl_utils.compute_form_data(form, complex_mode=complx) From 6dad6511dd744aa5b4987cc6b0e18c7478906db0 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Wed, 16 Aug 2017 22:11:35 +0100 Subject: [PATCH 442/809] move complex fxns to mathfunction --- tsfc/coffee.py | 16 +++------------- tsfc/ufl2gem.py | 14 +++++++------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index e962eb8429..3a5538e2e4 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -242,16 +242,6 @@ def _expression_power(expr, parameters): return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) -@_expression.register(gem.ComplexPartsFunction) -def _expression_complexpartsfunction(expr, parameters): - name_map = { - 'real': 'creal', - 'imag': 'cimag', - 'conj': 'conj', - } - name = name_map.get(expr.name, expr.name) - return coffee.FunCall(name, *[expression(c, parameters) for c in expr.children]) - @_expression.register(gem.MathFunction) def _expression_mathfunction(expr, parameters): name_map = { @@ -270,14 +260,14 @@ def _expression_mathfunction(expr, parameters): 'cyl_bessel_k': 'boost::math::cyl_bessel_k', } complex_name_map = { - 'ln': 'clog' - + 'ln': 'clog', + 'conj': 'conj' # TODO: Are there different complex Bessel Functions? } if parameters.scalar_type == 'double complex': name = complex_name_map.get(expr.name, expr.name) if name in {'sin', 'cos', 'tan', 'sqrt', 'exp', 'abs', 'sinh', 'cosh', 'tanh', - 'sinh', 'acos', 'asin', 'atan'}: + 'sinh', 'acos', 'asin', 'atan', 'real', 'imag'}: name = 'c' + expr.name else: name = name_map.get(expr.name, expr.name) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index 7a3b8f5ba2..fc1c760dc8 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -3,7 +3,7 @@ import collections import ufl -from gem import (Literal, Zero, Identity, Sum, Product, Division, ComplexPartsFunction, +from gem import (Literal, Zero, Identity, Sum, Product, Division, Power, MathFunction, MinValue, MaxValue, Comparison, LogicalNot, LogicalAnd, LogicalOr, Conditional, Index, Indexed, ComponentTensor, IndexSum, @@ -47,23 +47,23 @@ def division(self, o, numerator, denominator): def real(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(ComplexPartsFunction('real', Indexed(expr, indices)), indices) + return ComponentTensor(MathFunction('real', Indexed(expr, indices)), indices) else: - return ComplexPartsFunction('real', expr) + return MathFunction('real', expr) def imag(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(ComplexPartsFunction('imag', Indexed(expr, indices)), indices) + return ComponentTensor(MathFunction('imag', Indexed(expr, indices)), indices) else: - return ComplexPartsFunction('imag', expr) + return MathFunction('imag', expr) def conj(self, o, expr): if o.ufl_shape: indices = tuple(Index() for i in range(len(o.ufl_shape))) - return ComponentTensor(ComplexPartsFunction('conj', Indexed(expr, indices)), indices) + return ComponentTensor(MathFunction('conj', Indexed(expr, indices)), indices) else: - return ComplexPartsFunction('conj', expr) + return MathFunction('conj', expr) def abs(self, o, expr): if o.ufl_shape: From 8700b50f9fe6640692f8f23050c4c9a47fa6abc7 Mon Sep 17 00:00:00 2001 From: NicholasBermuda Date: Wed, 16 Aug 2017 22:18:44 +0100 Subject: [PATCH 443/809] add new complex node optimisation to preprocess expression --- tsfc/ufl_utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index c78e712a58..bb851a1c5a 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -13,6 +13,7 @@ from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.algorithms.comparison_checker import do_comparison_check +from ufl.algorithms.optimise_complex_nodes import optimise_complex_nodes from ufl.algorithms.remove_complex_nodes import remove_complex_nodes from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction @@ -20,9 +21,7 @@ from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, ComponentTensor, Expr, FloatValue, Division, MixedElement, MultiIndex, Product, - ScalarValue, Sqrt, Zero, CellVolume, - ScalarValue, Sqrt, Zero, Conj, - CellVolume, FacetArea) + ScalarValue, Sqrt, Zero, CellVolume, FacetArea) from gem.node import MemoizerArg @@ -91,6 +90,8 @@ def preprocess_expression(expression, complex_mode=False): """ if complex_mode: expression = do_comparison_check(expression) + if not complex_mode: + expression = remove_complex_nodes(expression) expression = apply_algebra_lowering(expression) expression = apply_derivatives(expression) expression = apply_function_pullbacks(expression) @@ -100,6 +101,8 @@ def preprocess_expression(expression, complex_mode=False): expression = apply_derivatives(expression) if not complex_mode: expression = remove_complex_nodes(expression) + if complex_mode: + expression = optimise_complex_nodes(expression) return expression From 2a5d30e4336b989d687af63ff57e2cc984563f30 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Wed, 25 Oct 2017 15:17:15 -0500 Subject: [PATCH 444/809] Fix rounding --- tsfc/coffee.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 3a5538e2e4..69966d429d 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -334,14 +334,14 @@ def _expression_scalar(expr, parameters): return coffee.Symbol("NAN") else: vr = expr.value.real - rr = round(vr, 1) + rr = vr.round(1) if rr and abs(vr - rr) < parameters.epsilon: vr = rr # round to nonzero vi = expr.value.imag # also checks if v is purely real if vi == 0.0: return coffee.Symbol(("%%.%dg" % parameters.precision) % vr) - ri = round(vi, 1) + ri = vi.round(1) if ri and abs(vi - ri) < parameters.epsilon: vi = ri From 1924ccf817c51a3dc49320c0c390c3f3ec1cb207 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Thu, 30 Nov 2017 15:34:47 -0600 Subject: [PATCH 445/809] fix scalar rounding --- tsfc/coffee.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 69966d429d..fe5925210d 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -5,6 +5,7 @@ from collections import defaultdict from functools import singledispatch, reduce from cmath import isnan + import itertools import numpy @@ -334,14 +335,14 @@ def _expression_scalar(expr, parameters): return coffee.Symbol("NAN") else: vr = expr.value.real - rr = vr.round(1) + rr = round(1) if rr and abs(vr - rr) < parameters.epsilon: vr = rr # round to nonzero vi = expr.value.imag # also checks if v is purely real if vi == 0.0: return coffee.Symbol(("%%.%dg" % parameters.precision) % vr) - ri = vi.round(1) + ri = round(1) if ri and abs(vi - ri) < parameters.epsilon: vi = ri From 9050de8fb020fa2216c6e5cea8833bd04537a356 Mon Sep 17 00:00:00 2001 From: David Ham Date: Mon, 11 Dec 2017 17:44:43 +0000 Subject: [PATCH 446/809] remove obsolete complex optimisations --- tsfc/ufl_utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index bb851a1c5a..66647e01b4 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -13,7 +13,6 @@ from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.algorithms.comparison_checker import do_comparison_check -from ufl.algorithms.optimise_complex_nodes import optimise_complex_nodes from ufl.algorithms.remove_complex_nodes import remove_complex_nodes from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction @@ -101,8 +100,6 @@ def preprocess_expression(expression, complex_mode=False): expression = apply_derivatives(expression) if not complex_mode: expression = remove_complex_nodes(expression) - if complex_mode: - expression = optimise_complex_nodes(expression) return expression From 3411a01e882d206c0c27f2540556f57217504524 Mon Sep 17 00:00:00 2001 From: David Ham Date: Tue, 16 Jan 2018 11:08:02 +0000 Subject: [PATCH 447/809] Flake8 --- tsfc/coffee.py | 6 +++--- tsfc/driver.py | 6 +++--- tsfc/kernel_interface/ufc.py | 1 - tsfc/parameters.py | 5 ++--- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index fe5925210d..6ee6e891fb 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -339,15 +339,15 @@ def _expression_scalar(expr, parameters): if rr and abs(vr - rr) < parameters.epsilon: vr = rr # round to nonzero - vi = expr.value.imag # also checks if v is purely real + vi = expr.value.imag # also checks if v is purely real if vi == 0.0: return coffee.Symbol(("%%.%dg" % parameters.precision) % vr) ri = round(1) if ri and abs(vi - ri) < parameters.epsilon: vi = ri - return coffee.Symbol(("%%.%dg" % parameters.precision) % vr + " + " - + ("%%.%dg" % parameters.precision) % vi + " * I") + return coffee.Symbol(("%%.%dg" % parameters.precision) % vr + " + " + + ("%%.%dg" % parameters.precision) % vi + " * I") @_expression.register(gem.Variable) diff --git a/tsfc/driver.py b/tsfc/driver.py index c6c537283b..824ab40ca1 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -42,7 +42,7 @@ def compile_form(form, prefix="form", parameters=None): cpu_time = time.time() assert isinstance(form, Form) - + # determine if we're in complex mode; coffee mode does not support complex complx = parameters and "scalar_type" in parameters and parameters["scalar_type"] == 'double complex' if complx: @@ -241,8 +241,8 @@ def name_multiindex(multiindex, name): name_multiindex(multiindex, name) # Construct kernel - body = generate_coffee(impero_c, index_names, parameters["precision"], - parameters["scalar_type"], expressions, split_argument_indices) + body = generate_coffee(impero_c, index_names, parameters["precision"], + parameters["scalar_type"], expressions, split_argument_indices) return builder.construct_kernel(kernel_name, body) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 1dc0645bee..ff6bfca38f 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -16,7 +16,6 @@ from tsfc.parameters import scalar_type - # UFC DoF ordering for vector/tensor elements is XXXX YYYY ZZZZ. create_element = functools.partial(_create_element, shape_innermost=False) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 0cf3f1f339..480eeb7dde 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -36,8 +36,8 @@ def set_scalar_type(type_): SCALAR_TYPE = type_ NUMPY_TYPE = {"double": numpy.dtype("double"), - "float": numpy.dtype("float32"), - "double complex": numpy.dtype("complex128")}[type_] + "float": numpy.dtype("float32"), + "double complex": numpy.dtype("complex128")}[type_] def scalar_type(): @@ -46,4 +46,3 @@ def scalar_type(): def numpy_type(): return NUMPY_TYPE - From 6c0f806eeb660f922fa6b01d949aebe619afa867 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 14 Feb 2018 19:07:17 +0000 Subject: [PATCH 448/809] Fix occasionally failing fiat element test The element cache is weakly keyed on the UFL element. The test might end up collecting in between two constructions in such a way that we fail. Avoid this by scoping fixtures at module level. --- tests/test_create_fiat_element.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 1fa2f437cd..c898973329 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -29,14 +29,15 @@ def test_triangle_basic(ufl_element): assert isinstance(element, supported_elements[ufl_element.family()]) -@pytest.fixture(params=["CG", "DG"]) +@pytest.fixture(params=["CG", "DG"], scope="module") def tensor_name(request): return request.param @pytest.fixture(params=[ufl.interval, ufl.triangle, ufl.quadrilateral], - ids=lambda x: x.cellname()) + ids=lambda x: x.cellname(), + scope="module") def ufl_A(request, tensor_name): return ufl.FiniteElement(tensor_name, request.param, 1) From f90975e70a08e6fbbc37dd4d514d5f7aaf6f299e Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 14 Feb 2018 18:53:09 +0000 Subject: [PATCH 449/809] Support ReferenceFacetVolume on non-simplex cells Compute the volume by summing the quadrature weights. --- tsfc/fem.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 6e7a9ecef6..6a4d17b676 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -215,10 +215,10 @@ def translate_reference_cell_volume(terminal, mt, ctx): @translate.register(ReferenceFacetVolume) def translate_reference_facet_volume(terminal, mt, ctx): - # FIXME: simplex only code path - dim = ctx.fiat_cell.get_spatial_dimension() - facet_cell = ctx.fiat_cell.construct_subelement(dim - 1) - return gem.Literal(facet_cell.volume()) + assert ctx.integral_type != "cell" + # Sum of quadrature weights is entity volume + return gem.optimise.aggressive_unroll(gem.index_sum(ctx.weight_expr, + ctx.point_indices)) @translate.register(CellFacetJacobian) From be5cf92bec198932822d0199e3ef8fec388548b2 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 14 Feb 2018 16:53:15 +0000 Subject: [PATCH 450/809] Support averaging operators (fixes #107) Done by just integrating the averaged expression over the relevant entity. --- tsfc/fem.py | 26 ++++++++++++++++++++++++++ tsfc/modified_terminals.py | 32 +++++--------------------------- tsfc/ufl_utils.py | 3 --- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 6a4d17b676..4b11d56117 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -174,6 +174,32 @@ def __init__(self, context): # Need context during translation! self.context = context + # XXXAvg is just (\sum_q w_q expr_q) / reference_XXX_volume + # We just use the provided quadrature rule to + # perform the integration. + # Can't put these in the ufl2gem mixin, since they (unlike + # everything else) want access to the translation context. + def cell_avg(self, o, expr): + if self.context.integral_type != "cell": + # Need to create a cell-based quadrature rule and + # translate the expression using that (c.f. CellVolume + # below). + raise NotImplementedError("CellAvg on non-cell integrals not yet implemented") + indices = tuple(gem.Index() for i in range(len(o.ufl_shape))) + expr = gem.Product(gem.Indexed(expr, indices), self.context.weight_expr) + expr = gem.index_sum(expr, self.context.point_indices) + cellvolume = translate(ReferenceCellVolume(o.ufl_domain()), None, self.context) + return gem.ComponentTensor(gem.Product(expr, gem.Division(gem.Literal(1), cellvolume)), indices) + + def facet_avg(self, o, expr): + if self.context.integral_type == "cell": + raise ValueError("Can't take FacetAvg in cell integral") + indices = tuple(gem.Index() for i in range(len(o.ufl_shape))) + expr = gem.Product(gem.Indexed(expr, indices), self.context.weight_expr) + expr = gem.index_sum(expr, self.context.point_indices) + facetvolume = translate(ReferenceFacetVolume(o.ufl_domain()), None, self.context) + return gem.ComponentTensor(gem.Product(expr, gem.Division(gem.Literal(1), facetvolume)), indices) + def modified_terminal(self, o): """Overrides the modified terminal handler from :class:`ModifiedTerminalMixin`.""" diff --git a/tsfc/modified_terminals.py b/tsfc/modified_terminals.py index 1cbf41f4c3..ebb00704ef 100644 --- a/tsfc/modified_terminals.py +++ b/tsfc/modified_terminals.py @@ -22,7 +22,7 @@ from ufl.classes import (ReferenceValue, ReferenceGrad, NegativeRestricted, PositiveRestricted, - Restricted, FacetAvg, CellAvg, ConstantValue, + Restricted, ConstantValue, Jacobian, SpatialCoordinate, Zero) from ufl.checks import is_cellwise_constant @@ -38,11 +38,10 @@ class ModifiedTerminal(object): terminal - the underlying Terminal object local_derivatives - tuple of ints, each meaning derivative in that local direction reference_value - bool, whether this is represented in reference frame - averaged - None, 'facet' or 'cell' restriction - None, '+' or '-' """ - def __init__(self, expr, terminal, local_derivatives, averaged, restriction, reference_value): + def __init__(self, expr, terminal, local_derivatives, restriction, reference_value): # The original expression self.expr = expr @@ -56,16 +55,12 @@ def __init__(self, expr, terminal, local_derivatives, averaged, restriction, ref # Derivatives self.local_derivatives = local_derivatives - # Evaluation method (alternative: { None, 'facet_midpoint', 'cell_midpoint', 'facet_avg', 'cell_avg' }) - self.averaged = averaged - def as_tuple(self): t = self.terminal rv = self.reference_value ld = self.local_derivatives - a = self.averaged r = self.restriction - return (t, rv, ld, a, r) + return (t, rv, ld, r) def __hash__(self): return hash(self.as_tuple()) @@ -80,7 +75,6 @@ def __str__(self): s = [] s += ["terminal: {0}".format(self.terminal)] s += ["local_derivatives: {0}".format(self.local_derivatives)] - s += ["averaged: {0}".format(self.averaged)] s += ["restriction: {0}".format(self.restriction)] return '\n'.join(s) @@ -111,13 +105,12 @@ def analyse_modified_terminal(expr): A modified terminal expression is an object of a Terminal subtype, wrapped in terminal modifier types. The wrapper types can include 0-* Grad or ReferenceGrad objects, - and 0-1 ReferenceValue, 0-1 Restricted, 0-1 Indexed, and 0-1 FacetAvg or CellAvg objects. + and 0-1 ReferenceValue, 0-1 Restricted, and 0-1 Indexed objects. """ # Data to determine local_derivatives = 0 reference_value = None restriction = None - averaged = None # Start with expr and strip away layers of modifiers t = expr @@ -136,16 +129,6 @@ def analyse_modified_terminal(expr): restriction = t._side t, = t.ufl_operands - elif isinstance(t, CellAvg): - assert averaged is None, "Got twice averaged terminal!" - averaged = "cell" - t, = t.ufl_operands - - elif isinstance(t, FacetAvg): - assert averaged is None, "Got twice averaged terminal!" - averaged = "facet" - t, = t.ufl_operands - elif t._ufl_terminal_modifiers_: raise ValueError("Missing handler for terminal modifier type %s, object is %s." % (type(t), repr(t))) @@ -163,7 +146,7 @@ def analyse_modified_terminal(expr): if local_derivatives and not reference_value: raise ValueError("Local derivatives of non-local value?") - return ModifiedTerminal(expr, t, local_derivatives, averaged, restriction, reference_value) + return ModifiedTerminal(expr, t, local_derivatives, restriction, reference_value) def construct_modified_terminal(mt, terminal): @@ -185,11 +168,6 @@ def construct_modified_terminal(mt, terminal): else: expr = ReferenceGrad(expr) - if mt.averaged == "cell": - expr = CellAvg(expr) - elif mt.averaged == "facet": - expr = FacetAvg(expr) - # No need to apply restrictions to ConstantValue terminals if not isinstance(expr, ConstantValue): if mt.restriction == '+': diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index e8575347f8..b6f92f3f74 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -120,9 +120,6 @@ def _modified_terminal(self, o): positive_restricted = _modified_terminal negative_restricted = _modified_terminal - cell_avg = _modified_terminal - facet_avg = _modified_terminal - reference_grad = _modified_terminal reference_value = _modified_terminal From 3f31e5d7bbbb9783b1f2502a4d1d2c52f75e158d Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 19 Feb 2018 18:56:28 +0000 Subject: [PATCH 451/809] WIP: do cell_avg properly Also gets around a bug (?) in the optimisation step. --- tsfc/fem.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 4b11d56117..2d691f2f83 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -179,17 +179,37 @@ def __init__(self, context): # perform the integration. # Can't put these in the ufl2gem mixin, since they (unlike # everything else) want access to the translation context. - def cell_avg(self, o, expr): + def cell_avg(self, o): if self.context.integral_type != "cell": # Need to create a cell-based quadrature rule and # translate the expression using that (c.f. CellVolume # below). raise NotImplementedError("CellAvg on non-cell integrals not yet implemented") - indices = tuple(gem.Index() for i in range(len(o.ufl_shape))) - expr = gem.Product(gem.Indexed(expr, indices), self.context.weight_expr) - expr = gem.index_sum(expr, self.context.point_indices) - cellvolume = translate(ReferenceCellVolume(o.ufl_domain()), None, self.context) - return gem.ComponentTensor(gem.Product(expr, gem.Division(gem.Literal(1), cellvolume)), indices) + from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree + from ufl.algorithms.analysis import extract_arguments + from tsfc.ufl_utils import compute_form_data + integrand, = o.ufl_operands + + arguments = extract_arguments(integrand) + if len(arguments) == 1: + argument, = arguments + integrand = ufl.replace(integrand, {argument: ufl.Argument(argument.function_space(), number=0, part=argument.part())}) + degree = estimate_total_polynomial_degree(integrand) + form = (integrand / CellVolume(o.ufl_domain()))*ufl.dx(domain=o.ufl_domain()) + fd = compute_form_data(form, + do_apply_function_pullbacks=False, + do_apply_geometry_lowering=True, + do_estimate_degrees=False) + itg_data, = fd.integral_data + integral, = itg_data.integrals + integrand = integral.integrand() + + config = {name: getattr(self.context, name) + for name in ["ufl_cell", "precision", "index_cache", + "argument_multiindices"]} + config.update(quadrature_degree=degree, interface=self.context) + expr, = compile_ufl(integrand, point_sum=True, **config) + return expr def facet_avg(self, o, expr): if self.context.integral_type == "cell": From c81da76ea58ecb04721be08fd239ca764ebde8ca Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 19 Feb 2018 19:00:32 +0000 Subject: [PATCH 452/809] WIP: and facet_avg in the same way --- tsfc/fem.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 2d691f2f83..c5d4797259 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -211,14 +211,34 @@ def cell_avg(self, o): expr, = compile_ufl(integrand, point_sum=True, **config) return expr - def facet_avg(self, o, expr): + def facet_avg(self, o): if self.context.integral_type == "cell": raise ValueError("Can't take FacetAvg in cell integral") - indices = tuple(gem.Index() for i in range(len(o.ufl_shape))) - expr = gem.Product(gem.Indexed(expr, indices), self.context.weight_expr) - expr = gem.index_sum(expr, self.context.point_indices) - facetvolume = translate(ReferenceFacetVolume(o.ufl_domain()), None, self.context) - return gem.ComponentTensor(gem.Product(expr, gem.Division(gem.Literal(1), facetvolume)), indices) + from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree + from ufl.algorithms.analysis import extract_arguments + from tsfc.ufl_utils import compute_form_data + integrand, = o.ufl_operands + + arguments = extract_arguments(integrand) + if len(arguments) == 1: + argument, = arguments + integrand = ufl.replace(integrand, {argument: ufl.Argument(argument.function_space(), number=0, part=argument.part())}) + degree = estimate_total_polynomial_degree(integrand) + form = (integrand / FacetArea(o.ufl_domain()))*ufl.Measure(self.context.integral_type, domain=o.ufl_domain()) + fd = compute_form_data(form, + do_apply_function_pullbacks=False, + do_apply_geometry_lowering=True, + do_estimate_degrees=False) + itg_data, = fd.integral_data + integral, = itg_data.integrals + integrand = integral.integrand() + + config = {name: getattr(self.context, name) + for name in ["ufl_cell", "precision", "index_cache", + "argument_multiindices"]} + config.update(quadrature_degree=degree, interface=self.context) + expr, = compile_ufl(integrand, point_sum=True, **config) + return expr def modified_terminal(self, o): """Overrides the modified terminal handler from From f532d13cface8bf972ac1ebb0356d8cc718a63da Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 19 Feb 2018 19:12:24 +0000 Subject: [PATCH 453/809] Nearly done. --- tsfc/fem.py | 43 +++++++------------------------------------ tsfc/ufl_utils.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index c5d4797259..23fb625ebe 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -37,7 +37,7 @@ construct_modified_terminal) from tsfc.parameters import NUMPY_TYPE, PARAMETERS from tsfc.ufl_utils import (ModifiedTerminalMixin, PickRestriction, - one_times, simplify_abs, + entity_avg, one_times, simplify_abs, preprocess_expression) @@ -174,7 +174,6 @@ def __init__(self, context): # Need context during translation! self.context = context - # XXXAvg is just (\sum_q w_q expr_q) / reference_XXX_volume # We just use the provided quadrature rule to # perform the integration. # Can't put these in the ufl2gem mixin, since they (unlike @@ -185,24 +184,10 @@ def cell_avg(self, o): # translate the expression using that (c.f. CellVolume # below). raise NotImplementedError("CellAvg on non-cell integrals not yet implemented") - from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree - from ufl.algorithms.analysis import extract_arguments - from tsfc.ufl_utils import compute_form_data integrand, = o.ufl_operands - - arguments = extract_arguments(integrand) - if len(arguments) == 1: - argument, = arguments - integrand = ufl.replace(integrand, {argument: ufl.Argument(argument.function_space(), number=0, part=argument.part())}) - degree = estimate_total_polynomial_degree(integrand) - form = (integrand / CellVolume(o.ufl_domain()))*ufl.dx(domain=o.ufl_domain()) - fd = compute_form_data(form, - do_apply_function_pullbacks=False, - do_apply_geometry_lowering=True, - do_estimate_degrees=False) - itg_data, = fd.integral_data - integral, = itg_data.integrals - integrand = integral.integrand() + domain = o.ufl_domain() + measure = ufl.Measure(self.context.integral_type, domain=domain) + integrand, degree = entity_avg(integrand / CellVolume(domain), measure) config = {name: getattr(self.context, name) for name in ["ufl_cell", "precision", "index_cache", @@ -214,24 +199,10 @@ def cell_avg(self, o): def facet_avg(self, o): if self.context.integral_type == "cell": raise ValueError("Can't take FacetAvg in cell integral") - from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree - from ufl.algorithms.analysis import extract_arguments - from tsfc.ufl_utils import compute_form_data integrand, = o.ufl_operands - - arguments = extract_arguments(integrand) - if len(arguments) == 1: - argument, = arguments - integrand = ufl.replace(integrand, {argument: ufl.Argument(argument.function_space(), number=0, part=argument.part())}) - degree = estimate_total_polynomial_degree(integrand) - form = (integrand / FacetArea(o.ufl_domain()))*ufl.Measure(self.context.integral_type, domain=o.ufl_domain()) - fd = compute_form_data(form, - do_apply_function_pullbacks=False, - do_apply_geometry_lowering=True, - do_estimate_degrees=False) - itg_data, = fd.integral_data - integral, = itg_data.integrals - integrand = integral.integrand() + domain = o.ufl_domain() + measure = ufl.Measure(self.context.integral_type, domain=domain) + integrand, degree = entity_avg(integrand / FacetArea(domain), measure) config = {name: getattr(self.context, name) for name in ["ufl_cell", "precision", "index_cache", diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index b6f92f3f74..6a51f9da4c 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -8,6 +8,7 @@ from ufl import as_tensor, indices, replace from ufl.algorithms import compute_form_data as ufl_compute_form_data from ufl.algorithms import estimate_total_polynomial_degree +from ufl.algorithms.analysis import extract_arguments from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives @@ -78,6 +79,24 @@ def one_times(measure): return integrand, degree +def entity_avg(integrand, measure): + arguments = extract_arguments(integrand) + if len(arguments) == 1: + a, = arguments + integrand = ufl.replace(integrand, {a: ufl.Argument(a.function_space(), + number=0, + part=a.part())}) + + degree = estimate_total_polynomial_degree(integrand) + form = integrand * measure + fd = compute_form_data(form, do_estimate_degrees=False, + do_apply_function_pullbacks=False) + itg_data, = fd.integral_data + integral, = itg_data.integrals + integrand = integral.integrand() + return integrand, degree + + def preprocess_expression(expression): """Imitates the compute_form_data processing pipeline. From c58e75d2e4d469a335192a8ab08fd6e571740bcf Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 20 Feb 2018 17:30:07 +0000 Subject: [PATCH 454/809] Pass correct argument multiindices --- tsfc/fem.py | 16 ++++++++-------- tsfc/ufl_utils.py | 5 +++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 23fb625ebe..661c6983c7 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -187,12 +187,12 @@ def cell_avg(self, o): integrand, = o.ufl_operands domain = o.ufl_domain() measure = ufl.Measure(self.context.integral_type, domain=domain) - integrand, degree = entity_avg(integrand / CellVolume(domain), measure) + integrand, degree, argument_multiindices = entity_avg(integrand / CellVolume(domain), measure, self.context.argument_multiindices) config = {name: getattr(self.context, name) - for name in ["ufl_cell", "precision", "index_cache", - "argument_multiindices"]} - config.update(quadrature_degree=degree, interface=self.context) + for name in ["ufl_cell", "precision", "index_cache"]} + config.update(quadrature_degree=degree, interface=self.context, + argument_multiindices=argument_multiindices) expr, = compile_ufl(integrand, point_sum=True, **config) return expr @@ -202,12 +202,12 @@ def facet_avg(self, o): integrand, = o.ufl_operands domain = o.ufl_domain() measure = ufl.Measure(self.context.integral_type, domain=domain) - integrand, degree = entity_avg(integrand / FacetArea(domain), measure) + integrand, degree, argument_multiindices = entity_avg(integrand / CellVolume(domain), measure, self.context.argument_multiindices) config = {name: getattr(self.context, name) - for name in ["ufl_cell", "precision", "index_cache", - "argument_multiindices"]} - config.update(quadrature_degree=degree, interface=self.context) + for name in ["ufl_cell", "precision", "index_cache"]}, + config.update(quadrature_degree=degree, interface=self.context, + argument_multiindices=argument_multiindices) expr, = compile_ufl(integrand, point_sum=True, **config) return expr diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 6a51f9da4c..5eb237294e 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -79,13 +79,14 @@ def one_times(measure): return integrand, degree -def entity_avg(integrand, measure): +def entity_avg(integrand, measure, argument_multiindices): arguments = extract_arguments(integrand) if len(arguments) == 1: a, = arguments integrand = ufl.replace(integrand, {a: ufl.Argument(a.function_space(), number=0, part=a.part())}) + argument_multiindices = (argument_multiindices[a.number()], ) degree = estimate_total_polynomial_degree(integrand) form = integrand * measure @@ -94,7 +95,7 @@ def entity_avg(integrand, measure): itg_data, = fd.integral_data integral, = itg_data.integrals integrand = integral.integrand() - return integrand, degree + return integrand, degree, argument_multiindices def preprocess_expression(expression): From 1dbab08b637787a8b75969c6e37f91eef06f7080 Mon Sep 17 00:00:00 2001 From: David Ham Date: Sat, 24 Feb 2018 12:39:14 +0000 Subject: [PATCH 455/809] Clean up complex code --- tsfc/ufl_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 66647e01b4..4469f2438b 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -89,7 +89,7 @@ def preprocess_expression(expression, complex_mode=False): """ if complex_mode: expression = do_comparison_check(expression) - if not complex_mode: + else: expression = remove_complex_nodes(expression) expression = apply_algebra_lowering(expression) expression = apply_derivatives(expression) From fa6dd34a6e6cab427a9e4a4f75c02e58d5a5206c Mon Sep 17 00:00:00 2001 From: David Ham Date: Sat, 24 Feb 2018 16:59:56 +0000 Subject: [PATCH 456/809] Remove global scalar type and pass through parameters instead --- tsfc/driver.py | 20 +++++++++----------- tsfc/fem.py | 7 +++---- tsfc/kernel_interface/common.py | 3 ++- tsfc/kernel_interface/firedrake.py | 30 +++++++++++++++--------------- tsfc/kernel_interface/ufc.py | 23 +++++++++++------------ tsfc/parameters.py | 27 ++++----------------------- 6 files changed, 44 insertions(+), 66 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 824ab40ca1..5fab7f0c2f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -26,7 +26,7 @@ from tsfc.coffee import generate as generate_coffee from tsfc.fiatinterface import as_fiat_cell from tsfc.logging import logger -from tsfc.parameters import default_parameters, set_scalar_type, scalar_type +from tsfc.parameters import default_parameters, numpy_type_map import tsfc.kernel_interface.firedrake as firedrake_interface @@ -80,8 +80,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, _.update(parameters) parameters = _ - # Set the scalar type - set_scalar_type(parameters["scalar_type"]) # Remove these here, they're handled below. if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: @@ -106,7 +104,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() builder = interface.KernelBuilder(integral_type, integral_data.subdomain_id, - domain_numbering[integral_data.domain]) + domain_numbering[integral_data.domain], + parameters["scalar_type"]) argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() for arg in arguments) return_variables = builder.set_arguments(arguments, argument_multiindices) @@ -129,6 +128,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, ufl_cell=cell, integral_type=integral_type, precision=parameters["precision"], + numpy_type=numpy_type_map[parameters["scalar_type"]], integration_dim=integration_dim, entity_ids=entity_ids, argument_multiindices=argument_multiindices, @@ -270,17 +270,14 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non if extract_arguments(expression): return ValueError("Cannot interpolate UFL expression with Arguments!") - # Set the scalar type - set_scalar_type(parameters["scalar_type"]) - # determine if we're in complex mode - complx = parameters and parameters["scalar_type"] == 'double complex' + complx = parameters["scalar_type"] == 'double complex' # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, complex_mode=complx) # Initialise kernel builder - builder = firedrake_interface.ExpressionKernelBuilder() + builder = firedrake_interface.ExpressionKernelBuilder(parameters["scalar_type"]) # Replace coordinates (if any) domain = expression.ufl_domain() @@ -302,6 +299,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non config = dict(interface=builder, ufl_cell=coordinates.ufl_domain().ufl_cell(), precision=parameters["precision"], + numpy_type=numpy_type_map[parameters["scalar_type"]], point_set=point_set) ir, = fem.compile_ufl(expression, point_sum=False, **config) @@ -315,12 +313,12 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non return_shape = (len(points),) + value_shape return_indices = point_set.indices + tensor_indices return_var = gem.Variable('A', return_shape) - return_arg = ast.Decl(scalar_type(), ast.Symbol('A', rank=return_shape)) + return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) return_expr = gem.Indexed(return_var, return_indices) ir, = impero_utils.preprocess_gem([ir]) impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) point_index, = point_set.indices - body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"], scalar_type()) + body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"], parameters["scalar_type"]) # Handle cell orientations if builder.needs_cell_orientations([ir]): diff --git a/tsfc/fem.py b/tsfc/fem.py index 6e7a9ecef6..94a05d7ada 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -35,7 +35,7 @@ from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import (analyse_modified_terminal, construct_modified_terminal) -from tsfc.parameters import NUMPY_TYPE, PARAMETERS +from tsfc.parameters import numpy_type_map from tsfc.ufl_utils import (ModifiedTerminalMixin, PickRestriction, one_times, simplify_abs, preprocess_expression) @@ -50,6 +50,7 @@ class ContextBase(ProxyKernelInterface): 'integration_dim', 'entity_ids', 'precision', + 'numpy_type', 'argument_multiindices', 'facetarea', 'index_cache') @@ -72,8 +73,6 @@ def integration_dim(self): entity_ids = [0] - precision = PARAMETERS["precision"] - @cached_property def epsilon(self): # Rounding tolerance mimicking FFC @@ -265,7 +264,7 @@ def translate_reference_cell_edge_vectors(terminal, mt, ctx): raise NotImplementedError("ReferenceCellEdgeVectors not implemented on TensorProductElements yet") nedges = len(fiat_cell.get_topology()[1]) - vecs = numpy.vstack(map(fiat_cell.compute_edge_tangent, range(nedges))).astype(NUMPY_TYPE) + vecs = numpy.vstack(map(fiat_cell.compute_edge_tangent, range(nedges))).astype(ctx["numpy_type"]) assert vecs.shape == terminal.ufl_shape return gem.Literal(vecs) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index bf9fd4c1af..c37573ca39 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -10,12 +10,13 @@ class KernelBuilderBase(KernelInterface): """Helper class for building local assembly kernels.""" - def __init__(self, interior_facet=False): + def __init__(self, scalar_type, interior_facet=False): """Initialise a kernel builder. :arg interior_facet: kernel accesses two cells """ assert isinstance(interior_facet, bool) + self.scalar_type = scalar_type self.interior_facet = interior_facet self.prepare = [] diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 2eac9894a5..b2589c2815 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -12,7 +12,6 @@ from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase -from tsfc.parameters import scalar_type # Expression kernel description type @@ -50,12 +49,13 @@ def __init__(self, ast=None, integral_type=None, oriented=False, class KernelBuilderBase(_KernelBuilderBase): - def __init__(self, interior_facet=False): + def __init__(self, scalar_type, interior_facet=False): """Initialise a kernel builder. :arg interior_facet: kernel accesses two cells """ - super(KernelBuilderBase, self).__init__(interior_facet=interior_facet) + super(KernelBuilderBase, self).__init__(scalar_type=scalar_type, + interior_facet=interior_facet) # Cell orientation if self.interior_facet: @@ -74,7 +74,7 @@ def _coefficient(self, coefficient, name): :arg name: coefficient name :returns: COFFEE function argument for the coefficient """ - funarg, expression = prepare_coefficient(coefficient, name, interior_facet=self.interior_facet) + funarg, expression = prepare_coefficient(coefficient, name, self.scalar_type, interior_facet=self.interior_facet) self.coefficient_map[coefficient] = expression return funarg @@ -96,8 +96,8 @@ def create_element(self, element, **kwargs): class ExpressionKernelBuilder(KernelBuilderBase): """Builds expression kernels for UFL interpolation in Firedrake.""" - def __init__(self): - super(ExpressionKernelBuilder, self).__init__() + def __init__(self, scalar_type): + super(ExpressionKernelBuilder, self).__init__(scalar_type) self.oriented = False def set_coefficients(self, coefficients): @@ -142,9 +142,9 @@ def construct_kernel(self, return_arg, body): class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number): + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type): """Initialise a kernel builder.""" - super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) + super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) @@ -174,7 +174,7 @@ def set_arguments(self, arguments, multiindices): :returns: GEM expression representing the return variable """ self.local_tensor, expressions = prepare_arguments( - arguments, multiindices, interior_facet=self.interior_facet) + arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet) return expressions def set_coordinates(self, domain): @@ -256,7 +256,7 @@ def construct_empty_kernel(self, name): return None -def prepare_coefficient(coefficient, name, interior_facet=False): +def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. @@ -272,7 +272,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): if coefficient.ufl_element().family() == 'Real': # Constant - funarg = coffee.Decl(scalar_type(), coffee.Symbol(name), + funarg = coffee.Decl(scalar_type, coffee.Symbol(name), pointers=[("restrict",)], qualifiers=["const"]) @@ -285,7 +285,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): shape = finat_element.index_shape size = numpy.prod(shape, dtype=int) - funarg = coffee.Decl(scalar_type(), coffee.Symbol(name), + funarg = coffee.Decl(scalar_type, coffee.Symbol(name), pointers=[("restrict",)], qualifiers=["const"]) @@ -300,7 +300,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): return funarg, expression -def prepare_arguments(arguments, multiindices, interior_facet=False): +def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. @@ -317,7 +317,7 @@ def prepare_arguments(arguments, multiindices, interior_facet=False): if len(arguments) == 0: # No arguments - funarg = coffee.Decl(scalar_type(), coffee.Symbol("A", rank=(1,))) + funarg = coffee.Decl(scalar_type, coffee.Symbol("A", rank=(1,))) expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) return funarg, [expression] @@ -339,7 +339,7 @@ def expression(restricted): c_shape = tuple(u_shape) slicez = [[slice(s) for s in u_shape]] - funarg = coffee.Decl(scalar_type(), coffee.Symbol("A", rank=c_shape)) + funarg = coffee.Decl(scalar_type, coffee.Symbol("A", rank=c_shape)) varexp = gem.Variable("A", c_shape) expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] return funarg, prune(expressions) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index ff6bfca38f..c5893a1f8c 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -13,7 +13,6 @@ from tsfc.kernel_interface.common import KernelBuilderBase from tsfc.finatinterface import create_element as _create_element -from tsfc.parameters import scalar_type # UFC DoF ordering for vector/tensor elements is XXXX YYYY ZZZZ. @@ -23,9 +22,9 @@ class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number): + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type): """Initialise a kernel builder.""" - super(KernelBuilder, self).__init__(integral_type.startswith("interior_facet")) + super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) self.integral_type = integral_type self.local_tensor = None @@ -57,7 +56,7 @@ def set_arguments(self, arguments, multiindices): :returns: GEM expression representing the return variable """ self.local_tensor, prepare, expressions = prepare_arguments( - arguments, multiindices, interior_facet=self.interior_facet) + arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet) self.apply_glue(prepare) return expressions @@ -70,7 +69,7 @@ def set_coordinates(self, domain): f = ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) self.domain_coordinate[domain] = f self.coordinates_args, expression = prepare_coordinates( - f, "coordinate_dofs", interior_facet=self.interior_facet) + f, "coordinate_dofs", self.scalar_type, interior_facet=self.interior_facet) self.coefficient_map[f] = expression def set_coefficients(self, integral_data, form_data): @@ -81,7 +80,7 @@ def set_coefficients(self, integral_data, form_data): """ name = "w" self.coefficient_args = [ - coffee.Decl(scalar_type(), coffee.Symbol(name), + coffee.Decl(self.scalar_type, coffee.Symbol(name), pointers=[("const",), ()], qualifiers=["const"]) ] @@ -189,7 +188,7 @@ def expression(data): expression(gem.reshape(data_m, (), (size,)))) -def prepare_coordinates(coefficient, name, interior_facet=False): +def prepare_coordinates(coefficient, name, scalar_type, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for coordinates. @@ -218,16 +217,16 @@ def transpose(expr): transposed_indices) if not interior_facet: - funargs = [coffee.Decl(scalar_type(), coffee.Symbol(name), + funargs = [coffee.Decl(scalar_type, coffee.Symbol(name), pointers=[("",)], qualifiers=["const"])] variable = gem.Variable(name, (size,)) expression = transpose(gem.reshape(variable, transposed_shape)) else: - funargs = [coffee.Decl(scalar_type(), coffee.Symbol(name+"_0"), + funargs = [coffee.Decl(scalar_type, coffee.Symbol(name+"_0"), pointers=[("",)], qualifiers=["const"]), - coffee.Decl(scalar_type(), coffee.Symbol(name+"_1"), + coffee.Decl(scalar_type, coffee.Symbol(name+"_1"), pointers=[("",)], qualifiers=["const"])] variable0 = gem.Variable(name+"_0", (size,)) @@ -238,7 +237,7 @@ def transpose(expr): return funargs, expression -def prepare_arguments(arguments, multiindices, interior_facet=False): +def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. @@ -253,7 +252,7 @@ def prepare_arguments(arguments, multiindices, interior_facet=False): expressions - GEM expressions referring to the argument tensor """ - funarg = coffee.Decl(scalar_type(), coffee.Symbol("A"), pointers=[()]) + funarg = coffee.Decl(scalar_type, coffee.Symbol("A"), pointers=[()]) varexp = gem.Variable("A", (None,)) if len(arguments) == 0: diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 480eeb7dde..71f1fe93d4 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -1,11 +1,5 @@ import numpy - -NUMPY_TYPE = numpy.dtype("double") - -SCALAR_TYPE = "double" - - PARAMETERS = { "quadrature_rule": "auto", "quadrature_degree": "auto", @@ -19,8 +13,9 @@ # that makes compilation time much shorter. "unroll_indexsum": 3, + "scalar_type": "double", # Precision of float printing (number of digits) - "precision": numpy.finfo(NUMPY_TYPE).precision, + "precision": numpy.finfo(numpy.dtype("double")).precision, "scalar_type": "double" } @@ -29,20 +24,6 @@ def default_parameters(): return PARAMETERS.copy() - -def set_scalar_type(type_): - global NUMPY_TYPE - global SCALAR_TYPE - - SCALAR_TYPE = type_ - NUMPY_TYPE = {"double": numpy.dtype("double"), +numpy_type_map = {"double": numpy.dtype("double"), "float": numpy.dtype("float32"), - "double complex": numpy.dtype("complex128")}[type_] - - -def scalar_type(): - return SCALAR_TYPE - - -def numpy_type(): - return NUMPY_TYPE + "double complex": numpy.dtype("complex128")} From ea84bca34470c35911b2268ab347fca2b9243ade Mon Sep 17 00:00:00 2001 From: David Ham Date: Sat, 24 Feb 2018 17:01:43 +0000 Subject: [PATCH 457/809] lindt --- tsfc/driver.py | 1 - tsfc/fem.py | 1 - tsfc/parameters.py | 2 ++ 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 5fab7f0c2f..b3ce8f9b8a 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -80,7 +80,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, _.update(parameters) parameters = _ - # Remove these here, they're handled below. if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: del parameters["quadrature_degree"] diff --git a/tsfc/fem.py b/tsfc/fem.py index 94a05d7ada..8fa9bdce60 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -35,7 +35,6 @@ from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import (analyse_modified_terminal, construct_modified_terminal) -from tsfc.parameters import numpy_type_map from tsfc.ufl_utils import (ModifiedTerminalMixin, PickRestriction, one_times, simplify_abs, preprocess_expression) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 71f1fe93d4..89863f29eb 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -1,5 +1,6 @@ import numpy + PARAMETERS = { "quadrature_rule": "auto", "quadrature_degree": "auto", @@ -24,6 +25,7 @@ def default_parameters(): return PARAMETERS.copy() + numpy_type_map = {"double": numpy.dtype("double"), "float": numpy.dtype("float32"), "double complex": numpy.dtype("complex128")} From 72cf8335ae5eccbd8c5d5f7e5ea3903733ff0bc2 Mon Sep 17 00:00:00 2001 From: David Ham Date: Tue, 6 Mar 2018 14:59:45 +0000 Subject: [PATCH 458/809] slightly less dubious complex mode test --- tsfc/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b3ce8f9b8a..81560acb99 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -44,7 +44,7 @@ def compile_form(form, prefix="form", parameters=None): assert isinstance(form, Form) # determine if we're in complex mode; coffee mode does not support complex - complx = parameters and "scalar_type" in parameters and parameters["scalar_type"] == 'double complex' + complx = parameters and "scalar_type" in parameters and 'complex' in parameters["scalar_type"] if complx: parameters["mode"] = 'vanilla' fd = ufl_utils.compute_form_data(form, complex_mode=complx) From 299aca84f0b1724969fb0825228396b9e1ea48cb Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Fri, 16 Mar 2018 14:32:34 -0500 Subject: [PATCH 459/809] Fix error giving an unset variable exception --- tsfc/fem.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 3ccafdde3c..d53eb0ab1a 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -484,14 +484,9 @@ def translate_coefficient(terminal, mt, ctx): # Collect FInAT tabulation for all entities per_derivative = collections.defaultdict(list) for entity_id in ctx.entity_ids: -# <<<<<<< HEAD -# with MT.let(mt): -# finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) -# for alpha, table in iteritems(finat_dict): -# ======= - finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) - for alpha, table in finat_dict.items(): -# >>>>>>> origin/master + with MT.let(mt): + finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) + for alpha, table in finat_dict.items(): # Filter out irrelevant derivatives if sum(alpha) == mt.local_derivatives: # A numerical hack that FFC used to apply on FIAT From 5395d38b6eee269b48b02250f075a0c39e6f3b72 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Fri, 16 Mar 2018 15:13:41 -0500 Subject: [PATCH 460/809] fix PhysicalGeometry and whitespace --- tsfc/driver.py | 2 +- tsfc/fem.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 0ca1d92a49..34d216680d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -7,7 +7,7 @@ from numpy import asarray -import ufl +# import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type from ufl.classes import Form, GeometricQuantity diff --git a/tsfc/fem.py b/tsfc/fem.py index d53eb0ab1a..baaef25566 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -30,6 +30,7 @@ from gem.unconcatenate import unconcatenate from gem.utils import DynamicallyScoped, cached_property +from finat.physical_geometry import PhysicalGeometry from finat.point_set import PointSet, PointSingleton from finat.quadrature import make_quadrature @@ -146,7 +147,6 @@ def weight_expr(self): return self.quadrature_rule.weight_expression def basis_evaluation(self, finat_element, local_derivatives, entity_id): - from finat.hermite import PhysicalGeometry class CoordinateMapping(PhysicalGeometry): def jacobian_at(cm, point): @@ -484,9 +484,9 @@ def translate_coefficient(terminal, mt, ctx): # Collect FInAT tabulation for all entities per_derivative = collections.defaultdict(list) for entity_id in ctx.entity_ids: - with MT.let(mt): - finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) - for alpha, table in finat_dict.items(): + with MT.let(mt): + finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) + for alpha, table in finat_dict.items(): # Filter out irrelevant derivatives if sum(alpha) == mt.local_derivatives: # A numerical hack that FFC used to apply on FIAT From 996ed203040c5edb5e1f409730695f577bd15d5c Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 18 Apr 2018 13:44:27 +0100 Subject: [PATCH 461/809] Allow KernelBuilder to control unsummed coefficient free indices For performing macro-cell integration, when evaluating a coefficient, we do not necessarily immediately want to sum over all of its free indices. Allow the KernelBuilder to specify a set of indices that should be removed from a coefficient's index_ordering before summing over them. --- tsfc/fem.py | 5 +++-- tsfc/kernel_interface/__init__.py | 7 ++++++- tsfc/kernel_interface/common.py | 6 ++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 661c6983c7..a019092579 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -525,7 +525,8 @@ def take_singleton(xs): table_qi = gem.Indexed(table, beta + zeta) summands = [] for var, expr in unconcatenate([(vec_beta, table_qi)], ctx.index_cache): - value = gem.IndexSum(gem.Product(expr, var), var.index_ordering()) + indices = tuple(i for i in var.index_ordering() if i not in ctx.unsummed_coefficient_indices) + value = gem.IndexSum(gem.Product(expr, var), indices) summands.append(gem.optimise.contraction(value)) optimised_value = gem.optimise.make_sum(summands) value_dict[alpha] = gem.ComponentTensor(optimised_value, zeta) @@ -533,7 +534,7 @@ def take_singleton(xs): # Change from FIAT to UFL arrangement result = fiat_to_ufl(value_dict, mt.local_derivatives) assert result.shape == mt.expr.ufl_shape - assert set(result.free_indices) <= set(ctx.point_indices) + assert set(result.free_indices) - ctx.unsummed_coefficient_indices <= set(ctx.point_indices) # Detect Jacobian of affine cells if not result.free_indices and all(numpy.count_nonzero(node.array) <= 2 diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index 0ff4b6e9e0..2ea8bb717e 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -1,4 +1,4 @@ -from abc import ABCMeta, abstractmethod +from abc import ABCMeta, abstractmethod, abstractproperty from gem.utils import make_proxy_class @@ -30,5 +30,10 @@ def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given a UFL element.""" + @abstractproperty + def unsummed_coefficient_indices(self): + """A set of indices that coefficient evaluation should not sum over. + Used for macro-cell integration.""" + ProxyKernelInterface = make_proxy_class('ProxyKernelInterface', KernelInterface) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index bf9fd4c1af..f4e895a925 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -4,6 +4,8 @@ import gem +from gem.utils import cached_property + from tsfc.kernel_interface import KernelInterface @@ -27,6 +29,10 @@ def __init__(self, interior_facet=False): # Coefficients self.coefficient_map = {} + @cached_property + def unsummed_coefficient_indices(self): + return frozenset() + def coordinate(self, domain): return self.domain_coordinate[domain] From 605da83382f8c92443252ecfd8c0041255ae7e8f Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 2 May 2018 14:40:06 +0100 Subject: [PATCH 462/809] Support NodalEnrichedElement --- tsfc/fiatinterface.py | 13 ++++++++----- tsfc/finatinterface.py | 5 +++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index c106cbcced..8a8176e706 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -142,11 +142,14 @@ def _(element, vector_is_mixed): @convert.register(ufl.EnrichedElement) # noqa def _(element, vector_is_mixed): - if len(element._elements) != 2: - raise ValueError("Enriched elements with more than two components not handled") - A, B = element._elements - return FIAT.EnrichedElement(create_element(A, vector_is_mixed), - create_element(B, vector_is_mixed)) + return FIAT.EnrichedElement(*(create_element(e, vector_is_mixed) + for e in element._elements)) + + +@convert.register(ufl.NodalEnrichedElement) # noqa +def _(element, vector_is_mixed): + return FIAT.NodalEnrichedElement(*(create_element(e, vector_is_mixed) + for e in element._elements)) @convert.register(ufl.BrokenElement) # noqa diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 6529ce3bfb..94c3b86bae 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -195,6 +195,11 @@ def convert_restrictedelement(element, **kwargs): return fiat_compat(element), set() +@convert.register(ufl.NodalEnrichedElement) +def convert_nodalenrichedelement(element, **kwargs): + return fiat_compat(element), set() + + quad_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() From 5a79da525fc1f124704f64ecf8084d646e6cc87d Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 3 May 2018 13:50:30 +0100 Subject: [PATCH 463/809] Remove need for noqa --- tsfc/fiatinterface.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 8a8176e706..a77d5fe8ab 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -88,8 +88,8 @@ def convert(element, vector_is_mixed): # Base finite elements first -@convert.register(ufl.FiniteElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.FiniteElement) +def convert_finiteelement(element, vector_is_mixed): if element.family() == "Real": # Real element is just DG0 cell = element.cell() @@ -134,32 +134,32 @@ def _(element, vector_is_mixed): # Element modifiers -@convert.register(ufl.RestrictedElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.RestrictedElement) +def convert_restrictedelement(element, vector_is_mixed): return FIAT.RestrictedElement(create_element(element.sub_element(), vector_is_mixed), restriction_domain=element.restriction_domain()) -@convert.register(ufl.EnrichedElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.EnrichedElement) +def convert_enrichedelement(element, vector_is_mixed): return FIAT.EnrichedElement(*(create_element(e, vector_is_mixed) for e in element._elements)) -@convert.register(ufl.NodalEnrichedElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.NodalEnrichedElement) +def convert_nodalenrichedelement(element, vector_is_mixed): return FIAT.NodalEnrichedElement(*(create_element(e, vector_is_mixed) for e in element._elements)) -@convert.register(ufl.BrokenElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.BrokenElement) +def convert_brokenelement(element, vector_is_mixed): return FIAT.DiscontinuousElement(create_element(element._element, vector_is_mixed)) # Now for the TPE-specific stuff -@convert.register(ufl.TensorProductElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.TensorProductElement) +def convert_tensorproductelement(element, vector_is_mixed): cell = element.cell() if type(cell) is not ufl.TensorProductCell: raise ValueError("TPE not on TPC?") @@ -168,19 +168,19 @@ def _(element, vector_is_mixed): create_element(B, vector_is_mixed)) -@convert.register(ufl.HDivElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.HDivElement) +def convert_hdivelement(element, vector_is_mixed): return FIAT.Hdiv(create_element(element._element, vector_is_mixed)) -@convert.register(ufl.HCurlElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.HCurlElement) +def convert_hcurlelement(element, vector_is_mixed): return FIAT.Hcurl(create_element(element._element, vector_is_mixed)) # Finally the MixedElement case -@convert.register(ufl.MixedElement) # noqa -def _(element, vector_is_mixed): +@convert.register(ufl.MixedElement) +def convert_mixedelement(element, vector_is_mixed): # If we're just trying to get the scalar part of a vector element? if not vector_is_mixed: assert isinstance(element, (ufl.VectorElement, From 2eb65b0bb76591ecca17676500455eb16d672a6f Mon Sep 17 00:00:00 2001 From: Miklos Homolya Date: Wed, 30 Aug 2017 10:10:35 +0100 Subject: [PATCH 464/809] FIAT and FInAT interface: add bindings for elements on hexahedra --- tsfc/fiatinterface.py | 15 +++++++++++---- tsfc/finatinterface.py | 17 ++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index a77d5fe8ab..de6a1e6f93 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -56,6 +56,8 @@ "Q": None, "RTCE": None, "RTCF": None, + "NCE": None, + "NCF": None, } """A :class:`.dict` mapping UFL element family names to their FIAT-equivalent constructors. If the value is ``None``, the UFL @@ -105,11 +107,15 @@ def convert_finiteelement(element, vector_is_mixed): return FIAT.QuadratureElement(cell, quad_rule.get_points()) lmbda = supported_elements[element.family()] if lmbda is None: - if element.cell().cellname() != "quadrilateral": + if element.cell().cellname() == "quadrilateral": + # Handle quadrilateral short names like RTCF and RTCE. + element = element.reconstruct(cell=quadrilateral_tpc) + elif element.cell().cellname() == "hexahedron": + # Handle hexahedron short names like NCF and NCE. + element = element.reconstruct(cell=hexahedron_tpc) + else: raise ValueError("%s is supported, but handled incorrectly" % element.family()) - # Handle quadrilateral short names like RTCF and RTCE. - element = element.reconstruct(cell=quad_tpc) return FlattenedDimensions(create_element(element, vector_is_mixed)) kind = element.variant() @@ -202,7 +208,8 @@ def rec(eles): return FIAT.MixedElement(fiat_elements) -quad_tpc = ufl.TensorProductCell(ufl.Cell("interval"), ufl.Cell("interval")) +hexahedron_tpc = ufl.TensorProductCell(ufl.quadrilateral, ufl.interval) +quadrilateral_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 94c3b86bae..44bf69a6c1 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -57,6 +57,8 @@ "Q": None, "RTCE": None, "RTCF": None, + "NCE": None, + "NCF": None, } """A :class:`.dict` mapping UFL element family names to their FInAT-equivalent constructors. If the value is ``None``, the UFL @@ -98,13 +100,17 @@ def convert_finiteelement(element, **kwargs): return finat.QuadratureElement(cell, degree, scheme), set() lmbda = supported_elements[element.family()] if lmbda is None: - if element.cell().cellname() != "quadrilateral": + if element.cell().cellname() == "quadrilateral": + # Handle quadrilateral short names like RTCF and RTCE. + element = element.reconstruct(cell=quadrilateral_tpc) + elif element.cell().cellname() == "hexahedron": + # Handle hexahedron short names like NCF and NCE. + element = element.reconstruct(cell=hexahedron_tpc) + else: raise ValueError("%s is supported, but handled incorrectly" % element.family()) - # Handle quadrilateral short names like RTCF and RTCE. - element = element.reconstruct(cell=quad_tpc) finat_elem, deps = _create_element(element, **kwargs) - return finat.QuadrilateralElement(finat_elem), deps + return finat.FlattenedDimensions(finat_elem), deps kind = element.variant() if kind is None: @@ -200,7 +206,8 @@ def convert_nodalenrichedelement(element, **kwargs): return fiat_compat(element), set() -quad_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) +hexahedron_tpc = ufl.TensorProductCell(ufl.quadrilateral, ufl.interval) +quadrilateral_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() From f1d320960e89cb2538aa7cea562ff02f70a035d6 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 4 May 2018 12:44:20 +0100 Subject: [PATCH 465/809] Add support for FacetBubble in fiat/finatinterface --- tsfc/fiatinterface.py | 1 + tsfc/finatinterface.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index de6a1e6f93..4d38341440 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -38,6 +38,7 @@ "Brezzi-Douglas-Marini": FIAT.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": FIAT.BrezziDouglasFortinMarini, "Bubble": FIAT.Bubble, + "FacetBubble": FIAT.FacetBubble, "Crouzeix-Raviart": FIAT.CrouzeixRaviart, "Discontinuous Lagrange": FIAT.DiscontinuousLagrange, "Discontinuous Taylor": FIAT.DiscontinuousTaylor, diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 44bf69a6c1..46df2b6fc6 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -39,6 +39,7 @@ "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": finat.BrezziDouglasFortinMarini, "Bubble": finat.Bubble, + "FacetBubble": finat.FacetBubble, "Crouzeix-Raviart": finat.CrouzeixRaviart, "Discontinuous Lagrange": finat.DiscontinuousLagrange, "Discontinuous Raviart-Thomas": lambda c, d: finat.DiscontinuousElement(finat.RaviartThomas(c, d)), From 22d99737b58f01de45f45627fe56c3a852d3307f Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 5 Jun 2018 13:18:44 +0100 Subject: [PATCH 466/809] Ensure literals have correct SCALAR_TYPE Otherwise we turn 1.0 -> 1. --- tsfc/coffee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 915e60e550..bbe12eb96a 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -324,7 +324,7 @@ def _expression_scalar(expr, parameters): r = round(v, 1) if r and abs(v - r) < parameters.epsilon: v = r # round to nonzero - return coffee.Symbol(("%%.%dg" % parameters.precision) % v) + return coffee.Symbol(("(%s)(%%.%dg)" % (SCALAR_TYPE, parameters.precision)) % v) @_expression.register(gem.Variable) From 61f94e171774c0d497694db73f31f4114fda7429 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 8 Jun 2018 18:23:58 +0100 Subject: [PATCH 467/809] Hack in Morley as supported element --- tsfc/finatinterface.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 7936ef764c..eb5fafd8b7 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -40,6 +40,12 @@ def hermite_element(cell, degree): return CubicHermite(cell) +def morley_element(cell, degree): + assert degree in {2, None} + from finat.morley import Morley + return Morley(cell) + + supported_elements = { # These all map directly to FInAT elements "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, @@ -55,6 +61,7 @@ def hermite_element(cell, degree): "HDiv Trace": finat.HDivTrace, "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, "Hermite": hermite_element, + "Morley": morley_element, "Lagrange": finat.Lagrange, "Nedelec 1st kind H(curl)": finat.Nedelec, "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, From cbb7ef2455a80600724b1508cba9bd4b5d79728e Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 8 Jun 2018 18:24:16 +0100 Subject: [PATCH 468/809] Add more physical geometry callbacks (for Morley) --- tsfc/fem.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 9b0b2735a2..70b051f029 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -163,7 +163,57 @@ def jacobian_at(cm, point): for name in ["ufl_cell", "precision", "index_cache"]} config.update(interface=self, point_set=point_set) context = PointSetContext(**config) - return context.translator(expr) + return map_expr_dag(context.translator, expr) + + def reference_normal(cm, facet): + assert 0 <= facet < 3 + n = self.fiat_cell.compute_normal(facet) + return gem.Literal(n) + + def reference_tangent(cm, facet): + assert 0 <= facet < 3 + t = self.fiat_cell.compute_normalized_edge_tangent(facet) + return gem.Literal(t) + + def physical_tangent(cm, facet): + n = cm.physical_normal(facet) + # R = [[0, -1], [1, 0]] + # t = R . n + return gem.ListTensor([gem.Product(gem.Literal(-1), gem.Indexed(n, (1, ))), + gem.Indexed(n, (0, ))]) + + def physical_normal(cm, facet): + assert 0 <= facet < 3 + expr = ufl.classes.FacetNormal(MT.value.terminal.ufl_domain()) + if MT.value.restriction is not None: + expr = expr(MT.value.restriction) + expr = preprocess_expression(expr) + + def entity_selector(callback, restriction=None): + return callback(facet) + + point_set = PointSingleton([0.5]) + config = {name: getattr(self, name) + for name in ["ufl_cell", "precision", "index_cache"]} + config.update(interface=self, point_set=point_set) + context = PointSetContext(**config) + context.entity_selector = entity_selector + context.integration_dim = 1 + return map_expr_dag(context.translator, expr) + + def physical_edge_lengths(cm): + expr = ufl.classes.CellEdgeVectors(MT.value.terminal.ufl_domain()) + if MT.value.restriction is not None: + expr = expr(MT.value.restriction) + + expr = ufl.as_vector([ufl.sqrt(ufl.dot(expr[i, :], expr[i, :])) for i in range(3)]) + expr = preprocess_expression(expr) + point_set = PointSingleton([1/3, 1/3]) + config = {name: getattr(self, name) + for name in ["ufl_cell", "precision", "index_cache"]} + config.update(interface=self, point_set=point_set) + context = PointSetContext(**config) + return map_expr_dag(context.translator, expr) return finat_element.basis_evaluation(local_derivatives, self.point_set, From 59c6f37c7855ec41f0294349d56f3b7ee8cc6aca Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Mon, 18 Jun 2018 12:45:00 -0500 Subject: [PATCH 469/809] Rework normals and tangents in CoordinateMapping to agree with the zany element paper. --- tsfc/fem.py | 57 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 70b051f029..cb36af9b8c 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -172,34 +172,41 @@ def reference_normal(cm, facet): def reference_tangent(cm, facet): assert 0 <= facet < 3 - t = self.fiat_cell.compute_normalized_edge_tangent(facet) + t = self.fiat_cell.compute_tangents(1, facet)[0] return gem.Literal(t) - def physical_tangent(cm, facet): - n = cm.physical_normal(facet) - # R = [[0, -1], [1, 0]] - # t = R . n - return gem.ListTensor([gem.Product(gem.Literal(-1), gem.Indexed(n, (1, ))), - gem.Indexed(n, (0, ))]) + def physical_tangents(cm): + rts = [self.fiat_cell.compute_tangents(1, f)[0] for f in range(3)] + # this is gem: + jac = cm.jacobian_at([1/3, 1/3]) + + # this too + els = cm.physical_edge_lengths() + + return gem.ListTensor( + [[gem.Division(gem.Sum( + gem.Product(gem.Indexed(jac, (0, 0)), + gem.Literal(rts[i][0])), + gem.Product(gem.Indexed(jac, (0, 1)), + gem.Literal(rts[i][1])) + ), gem.Indexed(els, (i,))), + gem.Division(gem.Sum( + gem.Product(gem.Indexed(jac, (1, 0)), + gem.Literal(rts[i][0])), + gem.Product(gem.Indexed(jac, (1, 1)), + gem.Literal(rts[i][1])) + ), gem.Indexed(els, (i,)))] + for i in range(3)]) + + def physical_normals(cm): + pts = cm.physical_tangents() + return gem.ListTensor( + [[gem.Indexed(pts, (i, 1)), + gem.Product(gem.Literal(-1), + gem.Indexed(pts, (i, 0)))] + for i in range(3)] + ) - def physical_normal(cm, facet): - assert 0 <= facet < 3 - expr = ufl.classes.FacetNormal(MT.value.terminal.ufl_domain()) - if MT.value.restriction is not None: - expr = expr(MT.value.restriction) - expr = preprocess_expression(expr) - - def entity_selector(callback, restriction=None): - return callback(facet) - - point_set = PointSingleton([0.5]) - config = {name: getattr(self, name) - for name in ["ufl_cell", "precision", "index_cache"]} - config.update(interface=self, point_set=point_set) - context = PointSetContext(**config) - context.entity_selector = entity_selector - context.integration_dim = 1 - return map_expr_dag(context.translator, expr) def physical_edge_lengths(cm): expr = ufl.classes.CellEdgeVectors(MT.value.terminal.ufl_domain()) From 5642b30c61cd1befcb119b057f2c46a77bd7534e Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Wed, 20 Jun 2018 13:28:21 -0500 Subject: [PATCH 470/809] Add argyris, fix some white space issues --- tsfc/fem.py | 21 ++++++++++----------- tsfc/finatinterface.py | 7 +++++++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index cb36af9b8c..934320cd1b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -188,14 +188,15 @@ def physical_tangents(cm): gem.Product(gem.Indexed(jac, (0, 0)), gem.Literal(rts[i][0])), gem.Product(gem.Indexed(jac, (0, 1)), - gem.Literal(rts[i][1])) - ), gem.Indexed(els, (i,))), - gem.Division(gem.Sum( - gem.Product(gem.Indexed(jac, (1, 0)), - gem.Literal(rts[i][0])), - gem.Product(gem.Indexed(jac, (1, 1)), - gem.Literal(rts[i][1])) - ), gem.Indexed(els, (i,)))] + gem.Literal(rts[i][1]))), + gem.Indexed(els, (i,))), + gem.Division( + gem.Sum( + gem.Product(gem.Indexed(jac, (1, 0)), + gem.Literal(rts[i][0])), + gem.Product(gem.Indexed(jac, (1, 1)), + gem.Literal(rts[i][1])) + ), gem.Indexed(els, (i,)))] for i in range(3)]) def physical_normals(cm): @@ -204,9 +205,7 @@ def physical_normals(cm): [[gem.Indexed(pts, (i, 1)), gem.Product(gem.Literal(-1), gem.Indexed(pts, (i, 0)))] - for i in range(3)] - ) - + for i in range(3)]) def physical_edge_lengths(cm): expr = ufl.classes.CellEdgeVectors(MT.value.terminal.ufl_domain()) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index eb5fafd8b7..ecd514e09d 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -46,6 +46,12 @@ def morley_element(cell, degree): return Morley(cell) +def argyris_element(cell, degree): + assert degree in {5, None} + from finat.argyris import Argyris + return Argyris(cell) + + supported_elements = { # These all map directly to FInAT elements "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, @@ -62,6 +68,7 @@ def morley_element(cell, degree): "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, "Hermite": hermite_element, "Morley": morley_element, + "Argyris": argyris_element, "Lagrange": finat.Lagrange, "Nedelec 1st kind H(curl)": finat.Nedelec, "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, From 2d35d482d0fea1d33a19d03e925547835c0e9ccb Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Thu, 21 Jun 2018 16:21:02 -0500 Subject: [PATCH 471/809] Bell --- tsfc/finatinterface.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index ecd514e09d..2f7fb20de6 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -52,6 +52,12 @@ def argyris_element(cell, degree): return Argyris(cell) +def bell_element(cell, degree): + assert degree in {5, None} + from finat.bell import Bell + return Bell(cell) + + supported_elements = { # These all map directly to FInAT elements "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, @@ -67,8 +73,9 @@ def argyris_element(cell, degree): "HDiv Trace": finat.HDivTrace, "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, "Hermite": hermite_element, - "Morley": morley_element, "Argyris": argyris_element, + "Morley": morley_element, + "Bell": bell_element, "Lagrange": finat.Lagrange, "Nedelec 1st kind H(curl)": finat.Nedelec, "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, From c1ea88c2399945d1e80f2561e6c8be761cd9e4e1 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 26 Jun 2018 16:33:26 +0100 Subject: [PATCH 472/809] Simplify finatinterface --- tsfc/finatinterface.py | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 2f7fb20de6..f2ee2d06ec 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -34,30 +34,6 @@ __all__ = ("create_element", "supported_elements", "as_fiat_cell") -def hermite_element(cell, degree): - assert degree == 3 - from finat.hermite import CubicHermite - return CubicHermite(cell) - - -def morley_element(cell, degree): - assert degree in {2, None} - from finat.morley import Morley - return Morley(cell) - - -def argyris_element(cell, degree): - assert degree in {5, None} - from finat.argyris import Argyris - return Argyris(cell) - - -def bell_element(cell, degree): - assert degree in {5, None} - from finat.bell import Bell - return Bell(cell) - - supported_elements = { # These all map directly to FInAT elements "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, @@ -72,10 +48,10 @@ def bell_element(cell, degree): "Gauss-Lobatto-Legendre": finat.GaussLobattoLegendre, "HDiv Trace": finat.HDivTrace, "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, - "Hermite": hermite_element, - "Argyris": argyris_element, - "Morley": morley_element, - "Bell": bell_element, + "Hermite": finat.Hermite, + "Argyris": finat.Argyris, + "Morley": finat.Morley, + "Bell": finat.Bell, "Lagrange": finat.Lagrange, "Nedelec 1st kind H(curl)": finat.Nedelec, "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, From 0d05cef027e76db0ac0d3d77139807988e733559 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 26 Jun 2018 16:33:37 +0100 Subject: [PATCH 473/809] Update geometry class for new FInAT interface Everything returns GEM now. --- tsfc/fem.py | 47 +++++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 934320cd1b..e5387bb4cd 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -165,47 +165,34 @@ def jacobian_at(cm, point): context = PointSetContext(**config) return map_expr_dag(context.translator, expr) - def reference_normal(cm, facet): - assert 0 <= facet < 3 - n = self.fiat_cell.compute_normal(facet) - return gem.Literal(n) - - def reference_tangent(cm, facet): - assert 0 <= facet < 3 - t = self.fiat_cell.compute_tangents(1, facet)[0] - return gem.Literal(t) + def reference_normals(cm): + return gem.Literal(numpy.asarray([self.fiat_cell.compute_normal(i) for i in range(3)])) def physical_tangents(cm): rts = [self.fiat_cell.compute_tangents(1, f)[0] for f in range(3)] # this is gem: jac = cm.jacobian_at([1/3, 1/3]) - # this too els = cm.physical_edge_lengths() - return gem.ListTensor( - [[gem.Division(gem.Sum( - gem.Product(gem.Indexed(jac, (0, 0)), - gem.Literal(rts[i][0])), - gem.Product(gem.Indexed(jac, (0, 1)), - gem.Literal(rts[i][1]))), - gem.Indexed(els, (i,))), - gem.Division( - gem.Sum( - gem.Product(gem.Indexed(jac, (1, 0)), - gem.Literal(rts[i][0])), - gem.Product(gem.Indexed(jac, (1, 1)), - gem.Literal(rts[i][1])) - ), gem.Indexed(els, (i,)))] - for i in range(3)]) + return gem.ListTensor([[gem.Division(gem.Sum(gem.Product(gem.Indexed(jac, (0, 0)), + gem.Literal(rts[i][0])), + gem.Product(gem.Indexed(jac, (0, 1)), + gem.Literal(rts[i][1]))), + gem.Indexed(els, (i,))), + gem.Division(gem.Sum(gem.Product(gem.Indexed(jac, (1, 0)), + gem.Literal(rts[i][0])), + gem.Product(gem.Indexed(jac, (1, 1)), + gem.Literal(rts[i][1]))), + gem.Indexed(els, (i, )))] + for i in range(3)]) def physical_normals(cm): pts = cm.physical_tangents() - return gem.ListTensor( - [[gem.Indexed(pts, (i, 1)), - gem.Product(gem.Literal(-1), - gem.Indexed(pts, (i, 0)))] - for i in range(3)]) + return gem.ListTensor([[gem.Indexed(pts, (i, 1)), + gem.Product(gem.Literal(-1), + gem.Indexed(pts, (i, 0)))] + for i in range(3)]) def physical_edge_lengths(cm): expr = ufl.classes.CellEdgeVectors(MT.value.terminal.ufl_domain()) From 1917a7097dc61f443811077d32dd0318d5927407 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 26 Jun 2018 17:56:06 +0100 Subject: [PATCH 474/809] Refactor coordinate mapping removing need for dynamic scoping Carry around the modified terminal and interface in the CoordinateMapping object. --- tsfc/fem.py | 163 ++++++++++++++++++++++++++++------------------------ 1 file changed, 88 insertions(+), 75 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index e5387bb4cd..16d830e572 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -28,9 +28,9 @@ from gem.node import traversal from gem.optimise import ffc_rounding from gem.unconcatenate import unconcatenate -from gem.utils import DynamicallyScoped, cached_property +from gem.utils import cached_property -from finat.physical_geometry import PhysicalGeometry +from finat.physically_mapped import PhysicalGeometry from finat.point_set import PointSet, PointSingleton from finat.quadrature import make_quadrature @@ -45,9 +45,6 @@ preprocess_expression) -MT = DynamicallyScoped() - - class ContextBase(ProxyKernelInterface): """Common UFL -> GEM translation context.""" @@ -115,6 +112,85 @@ def translator(self): return Translator(self) +class CoordinateMapping(PhysicalGeometry): + """Callback class that provides physical geometry to FInAT elements. + + Required for elements whose basis transformation requires physical + geometry such as Argyris and Hermite. + + :arg mt: The modified terminal whose element will be tabulated. + :arg interface: The interface carrying information (generally a + :class:`PointSetContext`). + """ + def __init__(self, mt, interface): + super().__init__() + self.mt = mt + self.interface = interface + + @property + def config(self): + config = {name: getattr(self.interface, name) + for name in ["ufl_cell", "precision", "index_cache"]} + config["interface"] = self.interface + return config + + def jacobian_at(self, point): + expr = Jacobian(self.mt.terminal.ufl_domain()) + if self.mt.restriction == '+': + expr = PositiveRestricted(expr) + elif self.mt.restriction == '-': + expr = NegativeRestricted(expr) + expr = preprocess_expression(expr) + + config = {"point_set": PointSingleton(point)} + config.update(self.config) + context = PointSetContext(**config) + return map_expr_dag(context.translator, expr) + + def reference_normals(self): + return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) + + def physical_tangents(self): + rts = [self.interface.fiat_cell.compute_tangents(1, f)[0] for f in range(3)] + # this is gem: + jac = self.jacobian_at([1/3, 1/3]) + + els = self.physical_edge_lengths() + + return gem.ListTensor([[gem.Division(gem.Sum(gem.Product(gem.Indexed(jac, (0, 0)), + gem.Literal(rts[i][0])), + gem.Product(gem.Indexed(jac, (0, 1)), + gem.Literal(rts[i][1]))), + gem.Indexed(els, (i,))), + gem.Division(gem.Sum(gem.Product(gem.Indexed(jac, (1, 0)), + gem.Literal(rts[i][0])), + gem.Product(gem.Indexed(jac, (1, 1)), + gem.Literal(rts[i][1]))), + gem.Indexed(els, (i, )))] + for i in range(3)]) + + def physical_normals(self): + pts = self.physical_tangents() + return gem.ListTensor([[gem.Indexed(pts, (i, 1)), + gem.Product(gem.Literal(-1), + gem.Indexed(pts, (i, 0)))] + for i in range(3)]) + + def physical_edge_lengths(self): + expr = ufl.classes.CellEdgeVectors(self.mt.terminal.ufl_domain()) + if self.mt.restriction == '+': + expr = PositiveRestricted(expr) + elif self.mt.restriction == '-': + expr = NegativeRestricted(expr) + + expr = ufl.as_vector([ufl.sqrt(ufl.dot(expr[i, :], expr[i, :])) for i in range(3)]) + expr = preprocess_expression(expr) + config = {"point_set": PointSingleton([1/3, 1/3])} + config.update(self.config) + context = PointSetContext(**config) + return map_expr_dag(context.translator, expr) + + class PointSetContext(ContextBase): """Context for compile-time known evaluation points.""" @@ -146,72 +222,11 @@ def point_expr(self): def weight_expr(self): return self.quadrature_rule.weight_expression - def basis_evaluation(self, finat_element, local_derivatives, entity_id): - - class CoordinateMapping(PhysicalGeometry): - def jacobian_at(cm, point): - expr = Jacobian(MT.value.terminal.ufl_domain()) - if MT.value.restriction == '+': - expr = PositiveRestricted(expr) - elif MT.value.restriction == '-': - expr = NegativeRestricted(expr) - expr = preprocess_expression(expr) - - point_set = PointSingleton(point) - - config = {name: getattr(self, name) - for name in ["ufl_cell", "precision", "index_cache"]} - config.update(interface=self, point_set=point_set) - context = PointSetContext(**config) - return map_expr_dag(context.translator, expr) - - def reference_normals(cm): - return gem.Literal(numpy.asarray([self.fiat_cell.compute_normal(i) for i in range(3)])) - - def physical_tangents(cm): - rts = [self.fiat_cell.compute_tangents(1, f)[0] for f in range(3)] - # this is gem: - jac = cm.jacobian_at([1/3, 1/3]) - - els = cm.physical_edge_lengths() - - return gem.ListTensor([[gem.Division(gem.Sum(gem.Product(gem.Indexed(jac, (0, 0)), - gem.Literal(rts[i][0])), - gem.Product(gem.Indexed(jac, (0, 1)), - gem.Literal(rts[i][1]))), - gem.Indexed(els, (i,))), - gem.Division(gem.Sum(gem.Product(gem.Indexed(jac, (1, 0)), - gem.Literal(rts[i][0])), - gem.Product(gem.Indexed(jac, (1, 1)), - gem.Literal(rts[i][1]))), - gem.Indexed(els, (i, )))] - for i in range(3)]) - - def physical_normals(cm): - pts = cm.physical_tangents() - return gem.ListTensor([[gem.Indexed(pts, (i, 1)), - gem.Product(gem.Literal(-1), - gem.Indexed(pts, (i, 0)))] - for i in range(3)]) - - def physical_edge_lengths(cm): - expr = ufl.classes.CellEdgeVectors(MT.value.terminal.ufl_domain()) - if MT.value.restriction is not None: - expr = expr(MT.value.restriction) - - expr = ufl.as_vector([ufl.sqrt(ufl.dot(expr[i, :], expr[i, :])) for i in range(3)]) - expr = preprocess_expression(expr) - point_set = PointSingleton([1/3, 1/3]) - config = {name: getattr(self, name) - for name in ["ufl_cell", "precision", "index_cache"]} - config.update(interface=self, point_set=point_set) - context = PointSetContext(**config) - return map_expr_dag(context.translator, expr) - - return finat_element.basis_evaluation(local_derivatives, + def basis_evaluation(self, finat_element, mt, entity_id): + return finat_element.basis_evaluation(mt.local_derivatives, self.point_set, (self.integration_dim, entity_id), - coordinate_mapping=CoordinateMapping()) + coordinate_mapping=CoordinateMapping(mt, self)) class GemPointContext(ContextBase): @@ -223,8 +238,8 @@ class GemPointContext(ContextBase): 'weight_expr', ) - def basis_evaluation(self, finat_element, local_derivatives, entity_id): - return finat_element.point_evaluation(local_derivatives, + def basis_evaluation(self, finat_element, mt, entity_id): + return finat_element.point_evaluation(mt.local_derivatives, self.point_expr, (self.integration_dim, entity_id)) @@ -533,8 +548,7 @@ def translate_argument(terminal, mt, ctx): element = ctx.create_element(terminal.ufl_element()) def callback(entity_id): - with MT.let(mt): - finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) + finat_dict = ctx.basis_evaluation(element, mt, entity_id) # Filter out irrelevant derivatives filtered_dict = {alpha: table for alpha, table in finat_dict.items() @@ -564,8 +578,7 @@ def translate_coefficient(terminal, mt, ctx): # Collect FInAT tabulation for all entities per_derivative = collections.defaultdict(list) for entity_id in ctx.entity_ids: - with MT.let(mt): - finat_dict = ctx.basis_evaluation(element, mt.local_derivatives, entity_id) + finat_dict = ctx.basis_evaluation(element, mt, entity_id) for alpha, table in finat_dict.items(): # Filter out irrelevant derivatives if sum(alpha) == mt.local_derivatives: From d514bb65d1ce34b066fd1783b497b86455815418 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 27 Jun 2018 11:33:56 +0100 Subject: [PATCH 475/809] Pass cell_size through kernel interface Now available in CoordinateMapping class for physical geometry callbacks and scaling. --- tsfc/driver.py | 4 +++ tsfc/fem.py | 3 +++ tsfc/kernel_interface/__init__.py | 4 +++ tsfc/kernel_interface/common.py | 5 ++++ tsfc/kernel_interface/firedrake.py | 39 +++++++++++++++++++++++++++--- tsfc/kernel_interface/ufc.py | 16 ++++++++++++ 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 34d216680d..31c6584638 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -105,6 +105,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, return_variables = builder.set_arguments(arguments, argument_multiindices) builder.set_coordinates(mesh) + builder.set_cell_sizes(mesh) builder.set_coefficients(integral_data, form_data) @@ -203,6 +204,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, if builder.needs_cell_orientations(expressions): builder.require_cell_orientations() + if builder.needs_cell_sizes(expressions): + builder.require_cell_sizes() + # Construct ImperoC split_argument_indices = tuple(chain(*[var.index_ordering() for var in return_variables])) diff --git a/tsfc/fem.py b/tsfc/fem.py index 16d830e572..be2a1397d4 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -134,6 +134,9 @@ def config(self): config["interface"] = self.interface return config + def cell_size(self): + return self.interface.cell_size(self.mt.restriction) + def jacobian_at(self, point): expr = Jacobian(self.mt.terminal.ufl_domain()) if self.mt.restriction == '+': diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index 2ea8bb717e..bf81360906 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -21,6 +21,10 @@ def coefficient(self, ufl_coefficient, restriction): def cell_orientation(self, restriction): """Cell orientation as a GEM expression.""" + @abstractmethod + def cell_size(self, restriction): + """Mesh cell size as a GEM expression. Shape (nvertex, ) in FIAT vertex ordering.""" + @abstractmethod def entity_number(self, restriction): """Facet or vertex number as a GEM index.""" diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index f4e895a925..1df61253c6 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -58,6 +58,11 @@ def cell_orientation(self, restriction): gem.Literal(1), gem.Literal(numpy.nan))) + def cell_size(self, restriction): + f = {None: (), '+': (0, ), '-': (1, )}[restriction] + # cell_sizes expression must have been set up by now. + return gem.partial_indexed(self._cell_sizes, f) + def entity_number(self, restriction): """Facet or vertex number as a GEM index.""" # Assume self._entity_number dict is set up at this point. diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 89dde7165e..62b5cda150 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -2,7 +2,7 @@ from collections import namedtuple from itertools import chain, product -from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace +from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import coffee.base as coffee @@ -21,7 +21,7 @@ class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", - "domain_number", + "domain_number", "needs_cell_sizes", "coefficient_numbers", "__weakref__") """A compiled Kernel object. @@ -34,10 +34,12 @@ class Kernel(object): original_form.ufl_domains() to get the correct domain). :kwarg coefficient_numbers: A list of which coefficients from the form the kernel needs. + :kwarg needs_cell_sizes: Does the kernel require cell sizes. """ def __init__(self, ast=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, - coefficient_numbers=()): + coefficient_numbers=(), + needs_cell_sizes=False): # Defaults self.ast = ast self.integral_type = integral_type @@ -45,6 +47,7 @@ def __init__(self, ast=None, integral_type=None, oriented=False, self.domain_number = domain_number self.subdomain_id = subdomain_id self.coefficient_numbers = coefficient_numbers + self.needs_cell_sizes = needs_cell_sizes super(Kernel, self).__init__() @@ -87,6 +90,15 @@ def needs_cell_orientations(ir): return True return False + @staticmethod + def needs_cell_sizes(ir): + """Does a multi-root GEM expression DAG references cell + orientations?""" + for node in traversal(ir): + if isinstance(node, gem.Variable) and node.name == "cell_sizes": + return True + return False + def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given a UFL element.""" @@ -124,6 +136,15 @@ def require_cell_orientations(self): """Set that the kernel requires cell orientations.""" self.oriented = True + @staticmethod + def needs_cell_sizes(ir): + # Not hooked up + return False + + @staticmethod + def require_cell_sizes(): + pass + def construct_kernel(self, return_arg, body): """Constructs an :class:`ExpressionKernel`. @@ -221,6 +242,16 @@ def require_cell_orientations(self): """Set that the kernel requires cell orientations.""" self.kernel.oriented = True + def require_cell_sizes(self): + """Set that the kernel requires cell sizes.""" + self.kernel.needs_cell_sizes = True + + def set_cell_sizes(self, domain): + f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) + funarg, expression = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) + self.cell_sizes_arg = funarg + self._cell_sizes = expression + def construct_kernel(self, name, body): """Construct a fully built :class:`Kernel`. @@ -234,6 +265,8 @@ def construct_kernel(self, name, body): args = [self.local_tensor, self.coordinates_arg] if self.kernel.oriented: args.append(cell_orientations_coffee_arg) + if self.kernel.needs_cell_sizes: + args.append(self.cell_sizes_arg) args.extend(self.coefficient_args) if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: args.append(coffee.Decl("unsigned int", diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index c0f2ca79b5..5cafd80139 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -73,6 +73,13 @@ def set_coordinates(self, domain): f, "coordinate_dofs", interior_facet=self.interior_facet) self.coefficient_map[f] = expression + def set_cell_sizes(self, domain): + """Prepare cell sizes field. + + :arg domain: :class:`ufl.Domain` + """ + pass + def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -149,6 +156,15 @@ def needs_cell_orientations(ir): # UFC tabulate_tensor always have cell orientations return True + @staticmethod + def require_cell_sizes(): + pass + + @staticmethod + def needs_cell_sizes(ir): + # Not hooked up right now. + return False + def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given a UFL element.""" From e6a57a85f59d07876738b946c02c63936d1107fc Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 4 Jul 2018 10:01:09 +0100 Subject: [PATCH 476/809] gem: Add some minimal sugar Support +, -, *, /, and __getitem__ magic methods. Fixes #168. --- tests/test_syntax_sugar.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/test_syntax_sugar.py diff --git a/tests/test_syntax_sugar.py b/tests/test_syntax_sugar.py new file mode 100644 index 0000000000..4dc4a2ec18 --- /dev/null +++ b/tests/test_syntax_sugar.py @@ -0,0 +1,26 @@ +import pytest +from gem import gem + + +def test_expressions(): + x = gem.Variable("x", (3, 4)) + y = gem.Variable("y", (4, )) + i = gem.Index() + j = gem.Index() + + xij = x[i, j] + yj = y[j] + + assert xij == gem.Indexed(x, (i, j)) + assert yj == gem.Indexed(y, (j, )) + + assert xij + yj == gem.Sum(xij, yj) + assert xij * yj == gem.Product(xij, yj) + assert xij - yj == gem.Sum(xij, gem.Product(gem.Literal(-1), yj)) + assert xij / yj == gem.Division(xij, yj) + + with pytest.raises(AssertionError): + xij + 1 + + with pytest.raises(AssertionError): + xij + y From 78ec5a6a81fe23d432521b857f13420e12a84a4c Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 4 Jul 2018 12:03:20 +0100 Subject: [PATCH 477/809] A little sweeter --- tests/test_syntax_sugar.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/test_syntax_sugar.py b/tests/test_syntax_sugar.py index 4dc4a2ec18..a2615e84ed 100644 --- a/tests/test_syntax_sugar.py +++ b/tests/test_syntax_sugar.py @@ -1,12 +1,11 @@ import pytest -from gem import gem +import gem def test_expressions(): x = gem.Variable("x", (3, 4)) y = gem.Variable("y", (4, )) - i = gem.Index() - j = gem.Index() + i, j = gem.indices(2) xij = x[i, j] yj = y[j] @@ -19,8 +18,18 @@ def test_expressions(): assert xij - yj == gem.Sum(xij, gem.Product(gem.Literal(-1), yj)) assert xij / yj == gem.Division(xij, yj) - with pytest.raises(AssertionError): - xij + 1 + assert xij + 1 == gem.Sum(xij, gem.Literal(1)) + assert 1 + xij == gem.Sum(gem.Literal(1), xij) with pytest.raises(AssertionError): xij + y + + with pytest.raises(ValueError): + xij + "foo" + + +def test_as_gem(): + with pytest.raises(ValueError): + gem.as_gem([1, 2]) + + assert gem.as_gem(1) == gem.Literal(1) From a705bbdfae675bd2eff5f537a03de8e5e37a6bb6 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 4 Jul 2018 09:31:24 +0100 Subject: [PATCH 478/809] Add explanatory comment to set_cell_sizes --- tsfc/kernel_interface/firedrake.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 62b5cda150..1f29f8335c 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -247,6 +247,15 @@ def require_cell_sizes(self): self.kernel.needs_cell_sizes = True def set_cell_sizes(self, domain): + """Setup a fake coefficient for "cell sizes". + + :arg domain: The domain of the integral. + + This is required for scaling of derivative basis functions on + physically mapped elements (Argyris, Bell, etc...). We need a + measure of the mesh size around each vertex (hence this lives + in P1). + """ f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) funarg, expression = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) self.cell_sizes_arg = funarg From 06a4076835851f84cdcdf422c5e2593739787ccf Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 4 Jul 2018 14:08:12 +0100 Subject: [PATCH 479/809] Remove debug code --- tsfc/fem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index be2a1397d4..28e4f5be22 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -569,7 +569,6 @@ def callback(entity_id): @translate.register(Coefficient) def translate_coefficient(terminal, mt, ctx): - # import ipdb; ipdb.set_trace() vec = ctx.coefficient(terminal, mt.restriction) if terminal.ufl_element().family() == 'Real': From 9fd190b50e60e935ac6266fd241b4ed71c148469 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 4 Jul 2018 14:08:23 +0100 Subject: [PATCH 480/809] Fix copy-paste error --- tsfc/kernel_interface/firedrake.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 1f29f8335c..17d30ab699 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -92,8 +92,7 @@ def needs_cell_orientations(ir): @staticmethod def needs_cell_sizes(ir): - """Does a multi-root GEM expression DAG references cell - orientations?""" + """Does a multi-root GEM expression DAG reference cell sizes?""" for node in traversal(ir): if isinstance(node, gem.Variable) and node.name == "cell_sizes": return True From 12215edc7a8a82d9b51c5036af6085a9e14f346f Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 4 Jul 2018 14:08:36 +0100 Subject: [PATCH 481/809] Reinstate use of function_replace_map I think the reasons for removing it have been avoided. --- tsfc/driver.py | 5 ++--- tsfc/kernel_interface/firedrake.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 31c6584638..52b419f007 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -7,7 +7,7 @@ from numpy import asarray -# import ufl +import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type from ufl.classes import Form, GeometricQuantity @@ -138,8 +138,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, mode = pick_mode(params["mode"]) mode_irs.setdefault(mode, collections.OrderedDict()) - # integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) - integrand = integral.integrand() + integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) # Check if the integral has a quad degree attached, otherwise use diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 17d30ab699..36384e92f4 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -219,7 +219,7 @@ def set_coefficients(self, integral_data, form_data): # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: - coefficient = form_data.reduced_coefficients[i] + coefficient = form_data.function_replace_map[form_data.reduced_coefficients[i]] if type(coefficient.ufl_element()) == ufl_MixedElement: split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) for element in coefficient.ufl_element().sub_elements()] From 79cd2d368cfb20a012dc3af7b3c0b482473dce94 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 5 Jul 2018 17:09:43 +0100 Subject: [PATCH 482/809] fem: Use new sugar to prettyify physical geometry --- tsfc/fem.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 28e4f5be22..99a4d4b10c 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -155,29 +155,17 @@ def reference_normals(self): def physical_tangents(self): rts = [self.interface.fiat_cell.compute_tangents(1, f)[0] for f in range(3)] - # this is gem: jac = self.jacobian_at([1/3, 1/3]) els = self.physical_edge_lengths() - return gem.ListTensor([[gem.Division(gem.Sum(gem.Product(gem.Indexed(jac, (0, 0)), - gem.Literal(rts[i][0])), - gem.Product(gem.Indexed(jac, (0, 1)), - gem.Literal(rts[i][1]))), - gem.Indexed(els, (i,))), - gem.Division(gem.Sum(gem.Product(gem.Indexed(jac, (1, 0)), - gem.Literal(rts[i][0])), - gem.Product(gem.Indexed(jac, (1, 1)), - gem.Literal(rts[i][1]))), - gem.Indexed(els, (i, )))] + return gem.ListTensor([[(jac[0, 0]*rts[i][0] + jac[0, 1]*rts[i][1]) / els[i], + (jac[1, 0]*rts[i][0] + jac[1, 1]*rts[i][1]) / els[i]] for i in range(3)]) def physical_normals(self): pts = self.physical_tangents() - return gem.ListTensor([[gem.Indexed(pts, (i, 1)), - gem.Product(gem.Literal(-1), - gem.Indexed(pts, (i, 0)))] - for i in range(3)]) + return gem.ListTensor([[pts[i, 1], -1*pts[i, 0]] for i in range(3)]) def physical_edge_lengths(self): expr = ufl.classes.CellEdgeVectors(self.mt.terminal.ufl_domain()) From 44b83f1ab785366aa59753fddf61a7cf6acc422f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Fri, 13 Jul 2018 21:32:57 +0200 Subject: [PATCH 483/809] fix rounding --- tsfc/coffee.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 6ee6e891fb..e21ba87edd 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -3,9 +3,8 @@ This is the final stage of code generation in TSFC.""" from collections import defaultdict -from functools import singledispatch, reduce from cmath import isnan - +from functools import singledispatch, reduce import itertools import numpy @@ -335,19 +334,19 @@ def _expression_scalar(expr, parameters): return coffee.Symbol("NAN") else: vr = expr.value.real - rr = round(1) + rr = round(vr, 1) if rr and abs(vr - rr) < parameters.epsilon: vr = rr # round to nonzero vi = expr.value.imag # also checks if v is purely real if vi == 0.0: return coffee.Symbol(("%%.%dg" % parameters.precision) % vr) - ri = round(1) + ri = round(vi, 1) if ri and abs(vi - ri) < parameters.epsilon: vi = ri - return coffee.Symbol(("%%.%dg" % parameters.precision) % vr + " + " + - ("%%.%dg" % parameters.precision) % vi + " * I") + return coffee.Symbol("({real:.{prec}g} + {imag:.{prec}g} * I)".format( + real=vr, imag=vi, prec=parameters.precision)) @_expression.register(gem.Variable) From 4db5b2339af3c73908fbb386fb28dc39bc1aed19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Wed, 25 Jul 2018 11:05:19 +0100 Subject: [PATCH 484/809] eliminate FutureWarning --- tsfc/tensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/tensor.py b/tsfc/tensor.py index d4f80fa00e..8902b470a2 100644 --- a/tsfc/tensor.py +++ b/tsfc/tensor.py @@ -31,7 +31,7 @@ def einsum(factors, sum_indices): else: selectors.append(slice(None)) letters.append(index2letter[index]) - operands.append(literal.array.__getitem__(selectors)) + operands.append(literal.array.__getitem__(tuple(selectors))) subscript_parts.append(''.join(letters)) result_pairs = sorted((letter, index) From cf4b135a40d61fe4fb2b2eab18d2727a137b6c84 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 3 Aug 2018 11:58:17 +0100 Subject: [PATCH 485/809] Support physically mapped elements in compile_expression_at_points We just need to hook up the same cell size and stuff, and ensure that we provide coordinates. --- tsfc/driver.py | 7 ++-- tsfc/fem.py | 12 +++++-- tsfc/kernel_interface/common.py | 2 ++ tsfc/kernel_interface/firedrake.py | 52 ++++++++++++++---------------- 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 52b419f007..e91238f951 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -277,10 +277,11 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non if domain: assert coordinates.ufl_domain() == domain builder.domain_coordinate[domain] = coordinates + builder.set_cell_sizes(domain) # Collect required coefficients coefficients = extract_coefficients(expression) - if has_type(expression, GeometricQuantity): + if has_type(expression, GeometricQuantity) or any(fem.needs_coordinate_mapping(c.ufl_element()) for c in coefficients): coefficients = [coordinates] + coefficients builder.set_coefficients(coefficients) @@ -315,7 +316,9 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non # Handle cell orientations if builder.needs_cell_orientations([ir]): builder.require_cell_orientations() - + # Handle cell sizes (physically mapped elements) + if builder.needs_cell_sizes([ir]): + builder.require_cell_sizes() # Build kernel tuple return builder.construct_kernel(return_arg, body) diff --git a/tsfc/fem.py b/tsfc/fem.py index 99a4d4b10c..52833a452b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -30,12 +30,12 @@ from gem.unconcatenate import unconcatenate from gem.utils import cached_property -from finat.physically_mapped import PhysicalGeometry +from finat.physically_mapped import PhysicalGeometry, PhysicallyMappedElement from finat.point_set import PointSet, PointSingleton from finat.quadrature import make_quadrature from tsfc import ufl2gem -from tsfc.finatinterface import as_fiat_cell +from tsfc.finatinterface import as_fiat_cell, create_element from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import (analyse_modified_terminal, construct_modified_terminal) @@ -182,6 +182,14 @@ def physical_edge_lengths(self): return map_expr_dag(context.translator, expr) +def needs_coordinate_mapping(element): + """Does this UFL element require a CoordinateMapping for translation?""" + if element.family() == 'Real': + return False + else: + return isinstance(create_element(element), PhysicallyMappedElement) + + class PointSetContext(ContextBase): """Context for compile-time known evaluation points.""" diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 1df61253c6..0931cd11bf 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -59,6 +59,8 @@ def cell_orientation(self, restriction): gem.Literal(numpy.nan))) def cell_size(self, restriction): + if not hasattr(self, "_cell_sizes"): + raise RuntimeError("Haven't called set_cell_sizes") f = {None: (), '+': (0, ), '-': (1, )}[restriction] # cell_sizes expression must have been set up by now. return gem.partial_indexed(self._cell_sizes, f) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 36384e92f4..1ac5a4f3b8 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -16,7 +16,7 @@ # Expression kernel description type -ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'coefficients']) +ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients']) class Kernel(object): @@ -81,6 +81,21 @@ def _coefficient(self, coefficient, name): self.coefficient_map[coefficient] = expression return funarg + def set_cell_sizes(self, domain): + """Setup a fake coefficient for "cell sizes". + + :arg domain: The domain of the integral. + + This is required for scaling of derivative basis functions on + physically mapped elements (Argyris, Bell, etc...). We need a + measure of the mesh size around each vertex (hence this lives + in P1). + """ + f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) + funarg, expression = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) + self.cell_sizes_arg = funarg + self._cell_sizes = expression + @staticmethod def needs_cell_orientations(ir): """Does a multi-root GEM expression DAG references cell @@ -110,6 +125,7 @@ class ExpressionKernelBuilder(KernelBuilderBase): def __init__(self): super(ExpressionKernelBuilder, self).__init__() self.oriented = False + self.cell_sizes = False def set_coefficients(self, coefficients): """Prepare the coefficients of the expression. @@ -135,14 +151,8 @@ def require_cell_orientations(self): """Set that the kernel requires cell orientations.""" self.oriented = True - @staticmethod - def needs_cell_sizes(ir): - # Not hooked up - return False - - @staticmethod - def require_cell_sizes(): - pass + def require_cell_sizes(self): + self.cell_sizes = True def construct_kernel(self, return_arg, body): """Constructs an :class:`ExpressionKernel`. @@ -151,12 +161,15 @@ def construct_kernel(self, return_arg, body): :arg body: function body (:class:`coffee.Block` node) :returns: :class:`ExpressionKernel` object """ - args = [return_arg] + self.kernel_args + args = [return_arg] if self.oriented: - args.insert(1, cell_orientations_coffee_arg) + args.append(cell_orientations_coffee_arg) + if self.cell_sizes: + args.append(self.cell_sizes_arg) + args.extend(self.kernel_args) kernel_code = super(ExpressionKernelBuilder, self).construct_kernel("expression_kernel", args, body) - return ExpressionKernel(kernel_code, self.oriented, self.coefficients) + return ExpressionKernel(kernel_code, self.oriented, self.cell_sizes, self.coefficients) class KernelBuilder(KernelBuilderBase): @@ -245,21 +258,6 @@ def require_cell_sizes(self): """Set that the kernel requires cell sizes.""" self.kernel.needs_cell_sizes = True - def set_cell_sizes(self, domain): - """Setup a fake coefficient for "cell sizes". - - :arg domain: The domain of the integral. - - This is required for scaling of derivative basis functions on - physically mapped elements (Argyris, Bell, etc...). We need a - measure of the mesh size around each vertex (hence this lives - in P1). - """ - f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - funarg, expression = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) - self.cell_sizes_arg = funarg - self._cell_sizes = expression - def construct_kernel(self, name, body): """Construct a fully built :class:`Kernel`. From a7766a2bef46c652d2fd764d7189780e004389fe Mon Sep 17 00:00:00 2001 From: Tuomas Karna Date: Thu, 27 Sep 2018 12:10:46 +0300 Subject: [PATCH 486/809] support foo x Real tensorproduct --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index f2ee2d06ec..ca977a59b9 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -64,6 +64,7 @@ "RTCF": None, "NCE": None, "NCF": None, + "Real": finat.DiscontinuousLagrange, } """A :class:`.dict` mapping UFL element family names to their FInAT-equivalent constructors. If the value is ``None``, the UFL From b1fe4fd9f85709bd98f5c500646567256202fd24 Mon Sep 17 00:00:00 2001 From: David Ham Date: Thu, 27 Sep 2018 15:24:13 +0100 Subject: [PATCH 487/809] merge fail --- tsfc/kernel_interface/firedrake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index be6ff57784..2f339e1867 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -92,7 +92,7 @@ def set_cell_sizes(self, domain): in P1). """ f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - funarg, expression = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) + funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) self.cell_sizes_arg = funarg self._cell_sizes = expression From 1e54d0bba17ba4d62919bb84ae6e4944e6fdfa24 Mon Sep 17 00:00:00 2001 From: David Ham Date: Wed, 3 Oct 2018 13:40:53 +0100 Subject: [PATCH 488/809] Drop complex nodes when interpolating. This is needed because UFL is already complex but TSFC not yet. --- tsfc/driver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index e91238f951..ef981e24aa 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -10,6 +10,7 @@ import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type +from ufl.algorithms.remove_complex_nodes import remove_complex_nodes from ufl.classes import Form, GeometricQuantity from ufl.log import GREEN from ufl.utils.sequences import max_degree @@ -288,6 +289,9 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non # Split mixed coefficients expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) + # Change this before complex lands. + expression = remove_complex_nodes(expression) + # Translate to GEM point_set = PointSet(points) config = dict(interface=builder, From 670832bd6f0c4d809e49b8bf7bd5fb4502adaa94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Wed, 10 Oct 2018 21:51:00 +0200 Subject: [PATCH 489/809] register Bernstein element in FIAT interface --- tsfc/fiatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 4d38341440..cc71aa5c88 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -35,6 +35,7 @@ supported_elements = { # These all map directly to FIAT elements + "Bernstein": FIAT.Bernstein, "Brezzi-Douglas-Marini": FIAT.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": FIAT.BrezziDouglasFortinMarini, "Bubble": FIAT.Bubble, From f71c60ca59f6fbbc2e3666dcd21f746cbd411658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Wed, 10 Oct 2018 21:51:23 +0200 Subject: [PATCH 490/809] add temporary FIAT compatibility for Bernstein --- tsfc/finatinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index ca977a59b9..64204b82f5 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -104,6 +104,8 @@ def convert_finiteelement(element, **kwargs): raise ValueError("Quadrature scheme and degree must be specified!") return finat.QuadratureElement(cell, degree, scheme), set() + elif element.family() == "Bernstein": + return fiat_compat(element), set() lmbda = supported_elements[element.family()] if lmbda is None: if element.cell().cellname() == "quadrilateral": From ec3c3e50508fc7e8e593749790a1f61c1e30a01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Tue, 6 Nov 2018 11:41:31 +0100 Subject: [PATCH 491/809] Revert "Drop complex nodes when interpolating." This reverts commit 1e54d0bba17ba4d62919bb84ae6e4944e6fdfa24. --- tsfc/driver.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index ef981e24aa..e91238f951 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -10,7 +10,6 @@ import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type -from ufl.algorithms.remove_complex_nodes import remove_complex_nodes from ufl.classes import Form, GeometricQuantity from ufl.log import GREEN from ufl.utils.sequences import max_degree @@ -289,9 +288,6 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non # Split mixed coefficients expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) - # Change this before complex lands. - expression = remove_complex_nodes(expression) - # Translate to GEM point_set = PointSet(points) config = dict(interface=builder, From cefcb17c2ed89c04b4fd3bbc7aa80cc00a6d64d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Tue, 6 Nov 2018 16:20:55 +0100 Subject: [PATCH 492/809] remove unnecessary numpy type Remove duplicated "scalar_type" entry in default PARAMETERS. --- tsfc/driver.py | 4 +--- tsfc/fem.py | 3 +-- tsfc/parameters.py | 9 ++------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index d02653d0c8..9b07d201c0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -26,7 +26,7 @@ from tsfc.coffee import generate as generate_coffee from tsfc.fiatinterface import as_fiat_cell from tsfc.logging import logger -from tsfc.parameters import default_parameters, numpy_type_map +from tsfc.parameters import default_parameters import tsfc.kernel_interface.firedrake as firedrake_interface @@ -128,7 +128,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, ufl_cell=cell, integral_type=integral_type, precision=parameters["precision"], - numpy_type=numpy_type_map[parameters["scalar_type"]], integration_dim=integration_dim, entity_ids=entity_ids, argument_multiindices=argument_multiindices, @@ -303,7 +302,6 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non config = dict(interface=builder, ufl_cell=coordinates.ufl_domain().ufl_cell(), precision=parameters["precision"], - numpy_type=numpy_type_map[parameters["scalar_type"]], point_set=point_set) ir, = fem.compile_ufl(expression, point_sum=False, **config) diff --git a/tsfc/fem.py b/tsfc/fem.py index d33049000f..3d7aac76e1 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -53,7 +53,6 @@ class ContextBase(ProxyKernelInterface): 'integration_dim', 'entity_ids', 'precision', - 'numpy_type', 'argument_multiindices', 'facetarea', 'index_cache') @@ -383,7 +382,7 @@ def translate_reference_cell_edge_vectors(terminal, mt, ctx): raise NotImplementedError("ReferenceCellEdgeVectors not implemented on TensorProductElements yet") nedges = len(fiat_cell.get_topology()[1]) - vecs = numpy.vstack(map(fiat_cell.compute_edge_tangent, range(nedges))).astype(ctx["numpy_type"]) + vecs = numpy.vstack(map(fiat_cell.compute_edge_tangent, range(nedges))) assert vecs.shape == terminal.ufl_shape return gem.Literal(vecs) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 89863f29eb..f46ddf608c 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -14,18 +14,13 @@ # that makes compilation time much shorter. "unroll_indexsum": 3, + # Scalar type (C typename string) "scalar_type": "double", + # Precision of float printing (number of digits) "precision": numpy.finfo(numpy.dtype("double")).precision, - - "scalar_type": "double" } def default_parameters(): return PARAMETERS.copy() - - -numpy_type_map = {"double": numpy.dtype("double"), - "float": numpy.dtype("float32"), - "double complex": numpy.dtype("complex128")} From b5e20b6cc86607cdd6b76c41e07d9c6e09ba5336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Tue, 6 Nov 2018 17:29:49 +0100 Subject: [PATCH 493/809] add missing docstring update --- tsfc/coffee.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index e21ba87edd..5feb263531 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -24,6 +24,7 @@ def generate(impero_c, index_names, precision, scalar_type, roots=(), argument_i :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names :arg precision: floating-point precision for printing + :arg scalar_type: type of scalars as C typename string :arg roots: list of expression DAG roots for attaching #pragma coffee expression :arg argument_indices: argument indices for attaching From fa76d37f77fd1a26368dd05bc9e5953054a430fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Wed, 7 Nov 2018 08:26:48 +0100 Subject: [PATCH 494/809] undo unnecessary change --- tsfc/fem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsfc/fem.py b/tsfc/fem.py index 3d7aac76e1..2a7b85fed0 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -39,6 +39,7 @@ from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import (analyse_modified_terminal, construct_modified_terminal) +from tsfc.parameters import PARAMETERS from tsfc.ufl_utils import (ModifiedTerminalMixin, PickRestriction, entity_avg, one_times, simplify_abs, preprocess_expression) @@ -75,6 +76,8 @@ def integration_dim(self): entity_ids = [0] + precision = PARAMETERS["precision"] + @cached_property def epsilon(self): # Rounding tolerance mimicking FFC From ac698872d75d5558a48a4548de743d1436be8ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Wed, 7 Nov 2018 10:06:27 +0100 Subject: [PATCH 495/809] use gloves to determine complex mode --- tsfc/coffee.py | 6 ++++-- tsfc/driver.py | 22 ++++++++++++++-------- tsfc/parameters.py | 5 +++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 5feb263531..3cfd84db04 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -13,6 +13,8 @@ from gem import gem, impero as imp +from tsfc.parameters import is_complex + class Bunch(object): pass @@ -237,7 +239,7 @@ def _expression_division(expr, parameters): @_expression.register(gem.Power) def _expression_power(expr, parameters): base, exponent = expr.children - if parameters.scalar_type is 'double complex': + if is_complex(parameters.scalar_type): return coffee.FunCall("cpow", expression(base, parameters), expression(exponent, parameters)) else: return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) @@ -265,7 +267,7 @@ def _expression_mathfunction(expr, parameters): 'conj': 'conj' # TODO: Are there different complex Bessel Functions? } - if parameters.scalar_type == 'double complex': + if is_complex(parameters.scalar_type): name = complex_name_map.get(expr.name, expr.name) if name in {'sin', 'cos', 'tan', 'sqrt', 'exp', 'abs', 'sinh', 'cosh', 'tanh', 'sinh', 'acos', 'asin', 'atan', 'real', 'imag'}: diff --git a/tsfc/driver.py b/tsfc/driver.py index 9b07d201c0..7023dca6db 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -26,7 +26,7 @@ from tsfc.coffee import generate as generate_coffee from tsfc.fiatinterface import as_fiat_cell from tsfc.logging import logger -from tsfc.parameters import default_parameters +from tsfc.parameters import default_parameters, is_complex import tsfc.kernel_interface.firedrake as firedrake_interface @@ -43,11 +43,16 @@ def compile_form(form, prefix="form", parameters=None): assert isinstance(form, Form) - # determine if we're in complex mode; coffee mode does not support complex - complx = parameters and "scalar_type" in parameters and 'complex' in parameters["scalar_type"] - if complx: + # Determine whether in complex mode: + # complex nodes would break the refactoriser. + complex_mode = parameters and is_complex(parameters.get("scalar_type")) + if complex_mode: + logger.warning("Disabling whole expression optimisations" + " in GEM for supporting complex mode.") + parameters = parameters.copy() parameters["mode"] = 'vanilla' - fd = ufl_utils.compute_form_data(form, complex_mode=complx) + + fd = ufl_utils.compute_form_data(form, complex_mode=complex_mode) logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) kernels = [] @@ -272,11 +277,12 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non if extract_arguments(expression): return ValueError("Cannot interpolate UFL expression with Arguments!") - # determine if we're in complex mode - complx = parameters["scalar_type"] == 'double complex' + # Determine whether in complex mode + complex_mode = is_complex(parameters["scalar_type"]) # Apply UFL preprocessing - expression = ufl_utils.preprocess_expression(expression, complex_mode=complx) + expression = ufl_utils.preprocess_expression(expression, + complex_mode=complex_mode) # Initialise kernel builder builder = firedrake_interface.ExpressionKernelBuilder(parameters["scalar_type"]) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index f46ddf608c..8cd98be42b 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -24,3 +24,8 @@ def default_parameters(): return PARAMETERS.copy() + + +def is_complex(scalar_type): + """Decides complex mode based on scalar type.""" + return scalar_type and 'complex' in scalar_type From 8f9ced4322b13c7f34fa1f90ffc240a00d85426e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Tue, 6 Nov 2018 16:32:40 +0100 Subject: [PATCH 496/809] resolve compatibility issues with Firedrake master - revive SCALAR_TYPE in tsfc.coffee and tsfc.parameters - make scalar_type optional for tsfc.coffee.generate - make scalar_type optional for tsfc.kernel_interface.firedrake.KernelBuilderBase --- tsfc/coffee.py | 7 +++++-- tsfc/kernel_interface/firedrake.py | 7 +++++-- tsfc/parameters.py | 3 +++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 3cfd84db04..285ec3cb5f 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -15,12 +15,15 @@ from tsfc.parameters import is_complex +# Satisfy import demands until complex branch is merged in Firedrake +from tsfc.parameters import SCALAR_TYPE + class Bunch(object): pass -def generate(impero_c, index_names, precision, scalar_type, roots=(), argument_indices=()): +def generate(impero_c, index_names, precision, scalar_type=None, roots=(), argument_indices=()): """Generates COFFEE code. :arg impero_c: ImperoC tuple with Impero AST and other data @@ -41,7 +44,7 @@ def generate(impero_c, index_names, precision, scalar_type, roots=(), argument_i params.epsilon = 10.0 * eval("1e-%d" % precision) params.roots = roots params.argument_indices = argument_indices - params.scalar_type = scalar_type + params.scalar_type = scalar_type or SCALAR_TYPE params.names = {} for i, temp in enumerate(impero_c.temporaries): diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 2f339e1867..2b4bee6287 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -52,11 +52,14 @@ def __init__(self, ast=None, integral_type=None, oriented=False, class KernelBuilderBase(_KernelBuilderBase): - def __init__(self, scalar_type, interior_facet=False): + def __init__(self, scalar_type=None, interior_facet=False): """Initialise a kernel builder. :arg interior_facet: kernel accesses two cells """ + if scalar_type is None: + from tsfc.parameters import SCALAR_TYPE + scalar_type = SCALAR_TYPE super(KernelBuilderBase, self).__init__(scalar_type=scalar_type, interior_facet=interior_facet) @@ -175,7 +178,7 @@ def construct_kernel(self, return_arg, body): class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type): + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 8cd98be42b..df9d66bbff 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -21,6 +21,9 @@ "precision": numpy.finfo(numpy.dtype("double")).precision, } +# Satisfy import demands until complex branch is merged in Firedrake +SCALAR_TYPE = PARAMETERS["scalar_type"] + def default_parameters(): return PARAMETERS.copy() From b99ded53d47b267ef267c562b211eb03932b7767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Wed, 7 Nov 2018 21:41:19 +0100 Subject: [PATCH 497/809] refactor math function handling --- tsfc/coffee.py | 104 +++++++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 37 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 285ec3cb5f..23f93d2e86 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -239,56 +239,86 @@ def _expression_division(expr, parameters): for c in expr.children]) +# Table of handled math functions in real and complex modes +# Copied from FFCX (ffc/language/ufl_to_cnodes.py) +math_table = { + 'sqrt': ('sqrt', 'csqrt'), + 'abs': ('fabs', 'cabs'), + 'cos': ('cos', 'ccos'), + 'sin': ('sin', 'csin'), + 'tan': ('tan', 'ctan'), + 'acos': ('acos', 'cacos'), + 'asin': ('asin', 'casin'), + 'atan': ('atan', 'catan'), + 'cosh': ('cosh', 'ccosh'), + 'sinh': ('sinh', 'csinh'), + 'tanh': ('tanh', 'ctanh'), + 'acosh': ('acosh', 'cacosh'), + 'asinh': ('asinh', 'casinh'), + 'atanh': ('atanh', 'catanh'), + 'power': ('pow', 'cpow'), + 'exp': ('exp', 'cexp'), + 'ln': ('log', 'clog'), + 'real': (None, 'creal'), + 'imag': (None, 'cimag'), + 'conj': (None, 'conj'), + 'erf': ('erf', None), + 'atan_2': ('atan2', None), + 'min_value': ('fmin', None), + 'max_value': ('fmax', None) +} + + @_expression.register(gem.Power) def _expression_power(expr, parameters): base, exponent = expr.children - if is_complex(parameters.scalar_type): - return coffee.FunCall("cpow", expression(base, parameters), expression(exponent, parameters)) - else: - return coffee.FunCall("pow", expression(base, parameters), expression(exponent, parameters)) + complex_mode = int(is_complex(parameters.scalar_type)) + return coffee.FunCall(math_table['power'][complex_mode], + expression(base, parameters), + expression(exponent, parameters)) @_expression.register(gem.MathFunction) def _expression_mathfunction(expr, parameters): - name_map = { - 'abs': 'fabs', - 'ln': 'log', + complex_mode = int(is_complex(parameters.scalar_type)) - # Bessel functions - 'cyl_bessel_j': 'jn', - 'cyl_bessel_y': 'yn', + # Bessel functions + if expr.name.startswith('cyl_bessel_'): + if complex_mode: + msg = "Bessel functions for complex numbers: missing implementation" + raise NotImplementedError(msg) + nu, arg = expr.children + nu_thunk = lambda: expression(nu, parameters) + arg_coffee = expression(arg, parameters) + if expr.name == 'cyl_bessel_j': + if nu == gem.Zero(): + return coffee.FunCall('j0', arg_coffee) + elif nu == gem.one: + return coffee.FunCall('j1', arg_coffee) + else: + return coffee.FunCall('jn', nu_thunk(), arg_coffee) + if expr.name == 'cyl_bessel_y': + if nu == gem.Zero(): + return coffee.FunCall('y0', arg_coffee) + elif nu == gem.one: + return coffee.FunCall('y1', arg_coffee) + else: + return coffee.FunCall('yn', nu_thunk(), arg_coffee) # Modified Bessel functions (C++ only) # # These mappings work for FEniCS only, and fail with Firedrake # since no Boost available. - 'cyl_bessel_i': 'boost::math::cyl_bessel_i', - 'cyl_bessel_k': 'boost::math::cyl_bessel_k', - } - complex_name_map = { - 'ln': 'clog', - 'conj': 'conj' - # TODO: Are there different complex Bessel Functions? - } - if is_complex(parameters.scalar_type): - name = complex_name_map.get(expr.name, expr.name) - if name in {'sin', 'cos', 'tan', 'sqrt', 'exp', 'abs', 'sinh', 'cosh', 'tanh', - 'sinh', 'acos', 'asin', 'atan', 'real', 'imag'}: - name = 'c' + expr.name - else: - name = name_map.get(expr.name, expr.name) - if name == 'jn': - nu, arg = expr.children - if nu == gem.Zero(): - return coffee.FunCall('j0', expression(arg, parameters)) - elif nu == gem.one: - return coffee.FunCall('j1', expression(arg, parameters)) - if name == 'yn': - nu, arg = expr.children - if nu == gem.Zero(): - return coffee.FunCall('y0', expression(arg, parameters)) - elif nu == gem.one: - return coffee.FunCall('y1', expression(arg, parameters)) + if expr.name in ['cyl_bessel_i', 'cyl_bessel_k']: + name = 'boost::math::' + expr.name + return coffee.FunCall(name, nu_thunk(), arg_coffee) + + assert False, "Unknown Bessel function: {}".format(expr.name) + + # Other math functions + name = math_table[expr.name][complex_mode] + if name is None: + raise RuntimeError("{} not supported in complex mode".format(expr.name)) return coffee.FunCall(name, *[expression(c, parameters) for c in expr.children]) From 19448738d256e2e597959ee47d41d53b4b48add7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Fri, 9 Nov 2018 15:38:32 +0100 Subject: [PATCH 498/809] fix typo --- tsfc/fem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 52833a452b..1133b8bfec 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -288,7 +288,7 @@ def facet_avg(self, o): integrand, degree, argument_multiindices = entity_avg(integrand / CellVolume(domain), measure, self.context.argument_multiindices) config = {name: getattr(self.context, name) - for name in ["ufl_cell", "precision", "index_cache"]}, + for name in ["ufl_cell", "precision", "index_cache"]} config.update(quadrature_degree=degree, interface=self.context, argument_multiindices=argument_multiindices) expr, = compile_ufl(integrand, point_sum=True, **config) From 9cd3a5dd81549c2215c10ca0628eaa875f4a8f42 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 9 Nov 2018 14:59:40 +0000 Subject: [PATCH 499/809] Perhaps fix translation of facet_avg --- tsfc/fem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 1133b8bfec..31f7434f61 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -288,7 +288,8 @@ def facet_avg(self, o): integrand, degree, argument_multiindices = entity_avg(integrand / CellVolume(domain), measure, self.context.argument_multiindices) config = {name: getattr(self.context, name) - for name in ["ufl_cell", "precision", "index_cache"]} + for name in ["ufl_cell", "precision", "index_cache", + "integration_dim", "entity_ids"]} config.update(quadrature_degree=degree, interface=self.context, argument_multiindices=argument_multiindices) expr, = compile_ufl(integrand, point_sum=True, **config) From e4eec6498cd931e5d510c28a77314ed3d42e272c Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 16 Nov 2018 14:57:22 +0000 Subject: [PATCH 500/809] Possible more right? --- tsfc/fem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index b9f8486a6b..5b3e76f345 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -285,11 +285,12 @@ def facet_avg(self, o): integrand, = o.ufl_operands domain = o.ufl_domain() measure = ufl.Measure(self.context.integral_type, domain=domain) - integrand, degree, argument_multiindices = entity_avg(integrand / CellVolume(domain), measure, self.context.argument_multiindices) + integrand, degree, argument_multiindices = entity_avg(integrand / FacetArea(domain), measure, self.context.argument_multiindices) config = {name: getattr(self.context, name) for name in ["ufl_cell", "precision", "index_cache", - "integration_dim", "entity_ids"]} + "integration_dim", "entity_ids", + "integral_type"]} config.update(quadrature_degree=degree, interface=self.context, argument_multiindices=argument_multiindices) expr, = compile_ufl(integrand, point_sum=True, **config) From e6f0b055342fb9ada6cc01154812e7ad8ff7a718 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 19 Nov 2018 15:02:34 +0000 Subject: [PATCH 501/809] Add alternate name for 'atan2' --- tsfc/coffee.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 23f93d2e86..6da94a2974 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -264,6 +264,7 @@ def _expression_division(expr, parameters): 'conj': (None, 'conj'), 'erf': ('erf', None), 'atan_2': ('atan2', None), + 'atan2': ('atan2', None), 'min_value': ('fmin', None), 'max_value': ('fmax', None) } From a0cfe1a9f95f9b953f55d1d5a80bf4bed30f4c40 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 14 Dec 2018 18:17:42 +0000 Subject: [PATCH 502/809] Allow caller to pass kernel interface to compile_form --- tsfc/driver.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7023dca6db..b7b0b07506 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -31,7 +31,7 @@ import tsfc.kernel_interface.firedrake as firedrake_interface -def compile_form(form, prefix="form", parameters=None): +def compile_form(form, prefix="form", parameters=None, interface=None): """Compiles a UFL form into a set of assembly kernels. :arg form: UFL form @@ -43,6 +43,8 @@ def compile_form(form, prefix="form", parameters=None): assert isinstance(form, Form) + if interface is None: + interface = firedrake_interface # Determine whether in complex mode: # complex nodes would break the refactoriser. complex_mode = parameters and is_complex(parameters.get("scalar_type")) @@ -58,7 +60,7 @@ def compile_form(form, prefix="form", parameters=None): kernels = [] for integral_data in fd.integral_data: start = time.time() - kernel = compile_integral(integral_data, fd, prefix, parameters) + kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface) if kernel is not None: kernels.append(kernel) logger.info(GREEN % "compile_integral finished in %g seconds.", time.time() - start) From 423c51819bf45431dc2df2951ee0891383385181 Mon Sep 17 00:00:00 2001 From: cyruscycheng21 Date: Tue, 12 Feb 2019 18:03:42 +0000 Subject: [PATCH 503/809] Added DP to dict --- tsfc/fiatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index cc71aa5c88..65c307d527 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -55,6 +55,7 @@ "Hellan-Herrmann-Johnson": FIAT.HellanHerrmannJohnson, # These require special treatment below "DQ": None, + "DP": None, "Q": None, "RTCE": None, "RTCF": None, From c4a72dbaf3a9b4b2de2d60a7d4b87b701c5aeb90 Mon Sep 17 00:00:00 2001 From: cyruscycheng21 Date: Tue, 12 Feb 2019 18:06:25 +0000 Subject: [PATCH 504/809] Added DP to dict --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 64204b82f5..0aa6796493 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -59,6 +59,7 @@ "Regge": finat.Regge, # These require special treatment below "DQ": None, + "DP": None, "Q": None, "RTCE": None, "RTCF": None, From 8a11ba5122a1f2d301e2f00d069f0accdd5383cd Mon Sep 17 00:00:00 2001 From: cyruscycheng21 Date: Thu, 14 Feb 2019 10:24:09 +0000 Subject: [PATCH 505/809] Added DPC to supported_elements --- tsfc/fiatinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 65c307d527..a9b04ce8cc 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -42,6 +42,7 @@ "FacetBubble": FIAT.FacetBubble, "Crouzeix-Raviart": FIAT.CrouzeixRaviart, "Discontinuous Lagrange": FIAT.DiscontinuousLagrange, + "DPC": FIAT.DPC, "Discontinuous Taylor": FIAT.DiscontinuousTaylor, "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, "Gauss-Lobatto-Legendre": FIAT.GaussLobattoLegendre, @@ -55,7 +56,6 @@ "Hellan-Herrmann-Johnson": FIAT.HellanHerrmannJohnson, # These require special treatment below "DQ": None, - "DP": None, "Q": None, "RTCE": None, "RTCF": None, From b1b0b9c6f938dc0de310d2ab10ec08bea3d9e402 Mon Sep 17 00:00:00 2001 From: cyruscycheng21 Date: Thu, 14 Feb 2019 10:24:25 +0000 Subject: [PATCH 506/809] Added DPC to supported_elements --- tsfc/finatinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0aa6796493..22e3d9136f 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -42,6 +42,7 @@ "FacetBubble": finat.FacetBubble, "Crouzeix-Raviart": finat.CrouzeixRaviart, "Discontinuous Lagrange": finat.DiscontinuousLagrange, + "DPC": finat.DPC, "Discontinuous Raviart-Thomas": lambda c, d: finat.DiscontinuousElement(finat.RaviartThomas(c, d)), "Discontinuous Taylor": finat.DiscontinuousTaylor, "Gauss-Legendre": finat.GaussLegendre, @@ -59,7 +60,6 @@ "Regge": finat.Regge, # These require special treatment below "DQ": None, - "DP": None, "Q": None, "RTCE": None, "RTCF": None, From 2917ae302e31e6b54d13816f0d3b347a7d19bd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Wed, 27 Feb 2019 14:54:02 -0800 Subject: [PATCH 507/809] Tighten classifier for delta elimination If we have a Delta node inside a ListTensor, we can't consider an Indexed node to be ATOMIC. Fixes #182. --- tsfc/spectral.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tsfc/spectral.py b/tsfc/spectral.py index 3db58c4686..8ee838481d 100644 --- a/tsfc/spectral.py +++ b/tsfc/spectral.py @@ -3,6 +3,7 @@ from itertools import chain, zip_longest from gem.gem import Delta, Indexed, Sum, index_sum, one +from gem.node import Memoizer from gem.optimise import delta_elimination as _delta_elimination from gem.optimise import remove_componenttensors, replace_division, unroll_indexsum from gem.refactorise import ATOMIC, COMPOUND, OTHER, MonomialSum, collect_monomials @@ -44,6 +45,12 @@ def predicate(index): return [Integral(e, quadrature_multiindex, argument_indices) for e in expressions] +def _delta_inside(node, self): + """Does node contain a Delta?""" + return any(isinstance(child, Delta) or self(child) + for child in node.children) + + def flatten(var_reps, index_cache): quadrature_indices = OrderedDict() @@ -72,6 +79,7 @@ def group_key(pair): variable, expression = pair return frozenset(variable.free_indices) + delta_inside = Memoizer(_delta_inside) # Variable ordering after delta cancellation narrow_variables = OrderedDict() # Assignments are variable -> MonomialSum map @@ -80,7 +88,7 @@ def group_key(pair): for free_indices, pair_group in groupby(pairs, group_key): variables, expressions = zip(*pair_group) # Argument factorise expressions - classifier = partial(classify, set(free_indices)) + classifier = partial(classify, set(free_indices), delta_inside=delta_inside) monomial_sums = collect_monomials(expressions, classifier) # For each monomial, apply delta cancellation and insert # result into delta_simplified. @@ -111,13 +119,13 @@ def group_key(pair): finalise_options = dict(replace_delta=False) -def classify(argument_indices, expression): +def classify(argument_indices, expression, delta_inside): """Classifier for argument factorisation""" n = len(argument_indices.intersection(expression.free_indices)) if n == 0: return OTHER elif n == 1: - if isinstance(expression, (Delta, Indexed)): + if isinstance(expression, (Delta, Indexed)) and not delta_inside(expression): return ATOMIC else: return COMPOUND From 210365c007fed263160855840e1f6486a3d5dda0 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 27 Feb 2019 15:37:44 -0800 Subject: [PATCH 508/809] Update coffee_mode for new spectral.classify --- tsfc/coffee_mode.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/coffee_mode.py b/tsfc/coffee_mode.py index 025a9b34ef..632b915b41 100644 --- a/tsfc/coffee_mode.py +++ b/tsfc/coffee_mode.py @@ -1,6 +1,6 @@ from functools import partial, reduce -from gem.node import traversal +from gem.node import traversal, Memoizer from gem.gem import Failure, Sum, index_sum from gem.optimise import replace_division, unroll_indexsum from gem.refactorise import collect_monomials @@ -75,6 +75,7 @@ def optimise_expressions(expressions, argument_indices): return expressions # Apply argument factorisation unconditionally - classifier = partial(spectral.classify, set(argument_indices)) + classifier = partial(spectral.classify, set(argument_indices), + delta_inside=Memoizer(spectral._delta_inside)) monomial_sums = collect_monomials(expressions, classifier) return [optimise_monomial_sum(ms, argument_indices) for ms in monomial_sums] From 5938743deeb6684fc7ae9807daa917bc227d9e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Fri, 1 Mar 2019 14:29:58 +0100 Subject: [PATCH 509/809] Add test case --- tests/test_tsfc_182.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_tsfc_182.py diff --git a/tests/test_tsfc_182.py b/tests/test_tsfc_182.py new file mode 100644 index 0000000000..477356301b --- /dev/null +++ b/tests/test_tsfc_182.py @@ -0,0 +1,33 @@ +import pytest + +from ufl import (Coefficient, FiniteElement, MixedElement, + TestFunction, VectorElement, dx, inner, tetrahedron) + +from tsfc import compile_form + + +@pytest.mark.parametrize('mode', ['vanilla', 'coffee', 'spectral']) +def test_delta_elimination(mode): + # Code sample courtesy of Marco Morandini: + # https://github.com/firedrakeproject/tsfc/issues/182 + scheme = "default" + degree = 3 + + element_lambda = FiniteElement("Quadrature", tetrahedron, degree, + quad_scheme=scheme) + element_eps_p = VectorElement("Quadrature", tetrahedron, degree, + dim=6, quad_scheme=scheme) + + element_chi_lambda = MixedElement(element_eps_p, element_lambda) + + chi_lambda = Coefficient(element_chi_lambda) + delta_chi_lambda = TestFunction(element_chi_lambda) + + L = inner(delta_chi_lambda, chi_lambda) * dx(degree=degree, scheme=scheme) + kernel, = compile_form(L, parameters={'mode': mode}) + + +if __name__ == "__main__": + import os + import sys + pytest.main(args=[os.path.abspath(__file__)] + sys.argv[1:]) From 8556c5b403b4c70de3531aabab6376021110bbe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Thu, 21 Feb 2019 15:33:12 +0000 Subject: [PATCH 510/809] kernel_interface: Pass class constructor, not module --- tsfc/driver.py | 13 ++++++------- tsfc/kernel_interface/firedrake.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b7b0b07506..ce3f0790bb 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -43,8 +43,6 @@ def compile_form(form, prefix="form", parameters=None, interface=None): assert isinstance(form, Form) - if interface is None: - interface = firedrake_interface # Determine whether in complex mode: # complex nodes would break the refactoriser. complex_mode = parameters and is_complex(parameters.get("scalar_type")) @@ -69,8 +67,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None): return kernels -def compile_integral(integral_data, form_data, prefix, parameters, - interface=firedrake_interface): +def compile_integral(integral_data, form_data, prefix, parameters, interface): """Compiles a UFL integral into an assembly kernel. :arg integral_data: UFL integral data @@ -86,6 +83,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, _ = default_parameters() _.update(parameters) parameters = _ + if interface is None: + interface = firedrake_interface.KernelBuilder # Remove these here, they're handled below. if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: @@ -109,9 +108,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() - builder = interface.KernelBuilder(integral_type, integral_data.subdomain_id, - domain_numbering[integral_data.domain], - parameters["scalar_type"]) + builder = interface(integral_type, integral_data.subdomain_id, + domain_numbering[integral_data.domain], + parameters["scalar_type"]) argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() for arg in arguments) return_variables = builder.set_arguments(arguments, argument_multiindices) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 2b4bee6287..99a84802e2 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -1,6 +1,7 @@ import numpy from collections import namedtuple from itertools import chain, product +from functools import partial from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement @@ -18,6 +19,10 @@ ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients']) +def make_builder(*args, **kwargs): + return partial(KernelBuilder, *args, **kwargs) + + class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", @@ -178,7 +183,8 @@ def construct_kernel(self, return_arg, body): class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None): + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None, + dont_split=()): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) @@ -188,6 +194,7 @@ def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None) self.coordinates_arg = None self.coefficient_args = [] self.coefficient_split = {} + self.dont_split = frozenset(dont_split) # Facet number if integral_type in ['exterior_facet', 'exterior_facet_vert']: @@ -236,7 +243,7 @@ def set_coefficients(self, integral_data, form_data): for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: coefficient = form_data.function_replace_map[form_data.reduced_coefficients[i]] - if type(coefficient.ufl_element()) == ufl_MixedElement: + if type(coefficient.ufl_element()) == ufl_MixedElement and coefficient not in self.dont_split: split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) for element in coefficient.ufl_element().sub_elements()] coefficients.extend(split) From c20b45cba21d93b782831258c2285cc0149e655e Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 21 Feb 2019 15:52:24 +0000 Subject: [PATCH 511/809] kernel_interface: Look up original coefficient in dont_split The replaced coefficient will not match. --- tsfc/kernel_interface/firedrake.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 99a84802e2..984822fc82 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -242,12 +242,17 @@ def set_coefficients(self, integral_data, form_data): # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: - coefficient = form_data.function_replace_map[form_data.reduced_coefficients[i]] - if type(coefficient.ufl_element()) == ufl_MixedElement and coefficient not in self.dont_split: - split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) - for element in coefficient.ufl_element().sub_elements()] - coefficients.extend(split) - self.coefficient_split[coefficient] = split + original = form_data.reduced_coefficients[i] + coefficient = form_data.function_replace_map[original] + if type(coefficient.ufl_element()) == ufl_MixedElement: + if original in self.dont_split: + coefficients.append(coefficient) + self.coefficient_split[coefficient] = [coefficient] + else: + split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) + for element in coefficient.ufl_element().sub_elements()] + coefficients.extend(split) + self.coefficient_split[coefficient] = split else: coefficients.append(coefficient) # This is which coefficient in the original form the From 3d2720849cd2345744e51721dd4f18963a4f1937 Mon Sep 17 00:00:00 2001 From: Chris Eldred Date: Fri, 22 Mar 2019 18:11:07 +0000 Subject: [PATCH 512/809] Add ability to provide tabulations at runtime This extends the Firedrake kernel interface to support Themis kernels, where the tabulations are mesh-position dependent and arrive as extra kernel arguments. --- tsfc/driver.py | 16 ++++++++++--- tsfc/fem.py | 4 ++-- tsfc/finatinterface.py | 34 +++++++++++++++++++++++---- tsfc/kernel_interface/common.py | 4 ++++ tsfc/kernel_interface/firedrake.py | 37 ++++++++++++++++++++++++++---- tsfc/kernel_interface/ufc.py | 6 ++++- 6 files changed, 85 insertions(+), 16 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index ce3f0790bb..af9d0e5c60 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -210,6 +210,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface): expressions = impero_utils.preprocess_gem(expressions, **options) assignments = list(zip(return_variables, expressions)) + # Register tabulations for runtime tabulated elements (used by Themis) + builder.register_tabulations(expressions) + # Look for cell orientations in the IR if builder.needs_cell_orientations(expressions): builder.require_cell_orientations() @@ -252,10 +255,10 @@ def name_multiindex(multiindex, name): body = generate_coffee(impero_c, index_names, parameters["precision"], parameters["scalar_type"], expressions, split_argument_indices) - return builder.construct_kernel(kernel_name, body) + return builder.construct_kernel(kernel_name, body, quad_rule) -def compile_expression_at_points(expression, points, coordinates, parameters=None): +def compile_expression_at_points(expression, points, coordinates, interface=None, parameters=None): """Compiles a UFL expression to be evaluated at compile-time known reference points. Useful for interpolating UFL expressions onto function spaces with only point evaluation nodes. @@ -264,6 +267,7 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non :arg points: reference coordinates of the evaluation points :arg coordinates: the coordinate function :arg parameters: parameters object + :arg interface: backend module for the kernel interface """ import coffee.base as ast @@ -286,7 +290,10 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non complex_mode=complex_mode) # Initialise kernel builder - builder = firedrake_interface.ExpressionKernelBuilder(parameters["scalar_type"]) + if interface is None: + interface = firedrake_interface.ExpressionKernelBuilder + + builder = interface(parameters["scalar_type"]) # Replace coordinates (if any) domain = expression.ufl_domain() @@ -318,6 +325,9 @@ def compile_expression_at_points(expression, points, coordinates, parameters=Non if value_shape: ir = gem.Indexed(ir, tensor_indices) + # Register tabulations for runtime tabulated elements (used by Themis) + builder.register_tabulations([ir]) + # Build kernel body return_shape = (len(points),) + value_shape return_indices = point_set.indices + tensor_indices diff --git a/tsfc/fem.py b/tsfc/fem.py index 5b3e76f345..5c226384d0 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -546,7 +546,7 @@ def fiat_to_ufl(fiat_dict, order): def translate_argument(terminal, mt, ctx): argument_multiindex = ctx.argument_multiindices[terminal.number()] sigma = tuple(gem.Index(extent=d) for d in mt.expr.ufl_shape) - element = ctx.create_element(terminal.ufl_element()) + element = ctx.create_element(terminal.ufl_element(), restriction=mt.restriction) def callback(entity_id): finat_dict = ctx.basis_evaluation(element, mt, entity_id) @@ -573,7 +573,7 @@ def translate_coefficient(terminal, mt, ctx): assert mt.local_derivatives == 0 return vec - element = ctx.create_element(terminal.ufl_element()) + element = ctx.create_element(terminal.ufl_element(), restriction=mt.restriction) # Collect FInAT tabulation for all entities per_derivative = collections.defaultdict(list) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 22e3d9136f..33c6acf85a 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -130,6 +130,12 @@ def convert_finiteelement(element, **kwargs): lmbda = finat.Lagrange elif kind == 'spectral' and element.cell().cellname() == 'interval': lmbda = finat.GaussLobattoLegendre + elif kind in ['mgd', 'feec', 'qb', 'mse']: + degree = element.degree() + shift_axes = kwargs["shift_axes"] + restriction = kwargs["restriction"] + deps = {"shift_axes", "restriction"} + return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction), deps else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) elif element.family() == "Discontinuous Lagrange": @@ -138,6 +144,12 @@ def convert_finiteelement(element, **kwargs): lmbda = finat.DiscontinuousLagrange elif kind == 'spectral' and element.cell().cellname() == 'interval': lmbda = finat.GaussLegendre + elif kind in ['mgd', 'feec', 'qb', 'mse']: + degree = element.degree() + shift_axes = kwargs["shift_axes"] + restriction = kwargs["restriction"] + deps = {"shift_axes", "restriction"} + return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction, continuous=False), deps else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) return lmbda(cell, element.degree()), set() @@ -187,9 +199,17 @@ def convert_tensorproductelement(element, **kwargs): cell = element.cell() if type(cell) is not ufl.TensorProductCell: raise ValueError("TensorProductElement not on TensorProductCell?") - elements, deps = zip(*[_create_element(elem, **kwargs) - for elem in element.sub_elements()]) - return finat.TensorProductElement(elements), set.union(*deps) + shift_axes = kwargs["shift_axes"] + dim_offset = 0 + elements = [] + deps = set() + for elem in element.sub_elements(): + kwargs["shift_axes"] = shift_axes + dim_offset + dim_offset += elem.cell().topological_dimension() + finat_elem, ds = _create_element(elem, **kwargs) + elements.append(finat_elem) + deps.update(ds) + return finat.TensorProductElement(elements), deps @convert.register(ufl.HDivElement) @@ -220,14 +240,18 @@ def convert_nodalenrichedelement(element, **kwargs): _cache = weakref.WeakKeyDictionary() -def create_element(ufl_element, shape_innermost=True): +def create_element(ufl_element, shape_innermost=True, shift_axes=0, restriction=None): """Create a FInAT element (suitable for tabulating with) given a UFL element. :arg ufl_element: The UFL element to create a FInAT element from. :arg shape_innermost: Vector/tensor indices come after basis function indices + :arg restriction: cell restriction in interior facet integrals + (only for runtime tabulated elements) """ finat_element, deps = _create_element(ufl_element, - shape_innermost=shape_innermost) + shape_innermost=shape_innermost, + shift_axes=shift_axes, + restriction=restriction) return finat_element diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 3b512f9059..587facec1b 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -97,3 +97,7 @@ def construct_kernel(self, name, args, body): assert isinstance(body, coffee.Block) body_ = coffee.Block(self.prepare + body.children + self.finalise) return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) + + # stubbed out for firedrake and ufc kernel builders + def register_tabulations(self, expressions): + pass diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 984822fc82..8cd7246a97 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -16,7 +16,7 @@ # Expression kernel description type -ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients']) +ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', 'tabulations']) def make_builder(*args, **kwargs): @@ -25,7 +25,7 @@ def make_builder(*args, **kwargs): class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", - "domain_number", "needs_cell_sizes", + "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", "coefficient_numbers", "__weakref__") """A compiled Kernel object. @@ -38,10 +38,12 @@ class Kernel(object): original_form.ufl_domains() to get the correct domain). :kwarg coefficient_numbers: A list of which coefficients from the form the kernel needs. + :kwarg quadrature_rule: The finat quadrature rule used to generate this kernel + :kwarg tabulations: The runtime tabulations this kernel requires :kwarg needs_cell_sizes: Does the kernel require cell sizes. """ def __init__(self, ast=None, integral_type=None, oriented=False, - subdomain_id=None, domain_number=None, + subdomain_id=None, domain_number=None, quadrature_rule=None, coefficient_numbers=(), needs_cell_sizes=False): # Defaults @@ -155,6 +157,13 @@ def set_coefficients(self, coefficients): self.coefficients.append(coefficient) self.kernel_args.append(self._coefficient(coefficient, "w_%d" % (i,))) + def register_tabulations(self, expressions): + tabulations = {} + for node in traversal(expressions): + if isinstance(node, gem.Variable) and node.name.startswith("rt_"): + tabulations[node.name] = node.shape + self.tabulations = tuple(sorted(tabulations.items())) + def require_cell_orientations(self): """Set that the kernel requires cell orientations.""" self.oriented = True @@ -176,8 +185,12 @@ def construct_kernel(self, return_arg, body): args.append(self.cell_sizes_arg) args.extend(self.kernel_args) + for name_, shape in self.tabulations: + args.append(coffee.Decl(self.scalar_type, coffee.Symbol( + name_, rank=shape), qualifiers=["const"])) + kernel_code = super(ExpressionKernelBuilder, self).construct_kernel("expression_kernel", args, body) - return ExpressionKernel(kernel_code, self.oriented, self.cell_sizes, self.coefficients) + return ExpressionKernel(kernel_code, self.oriented, self.cell_sizes, self.coefficients, self.tabulations) class KernelBuilder(KernelBuilderBase): @@ -265,6 +278,13 @@ def set_coefficients(self, integral_data, form_data): self._coefficient(coefficient, "w_%d" % i)) self.kernel.coefficient_numbers = tuple(coefficient_numbers) + def register_tabulations(self, expressions): + tabulations = {} + for node in traversal(expressions): + if isinstance(node, gem.Variable) and node.name.startswith("rt_"): + tabulations[node.name] = node.shape + self.kernel.tabulations = tuple(sorted(tabulations.items())) + def require_cell_orientations(self): """Set that the kernel requires cell orientations.""" self.kernel.oriented = True @@ -273,7 +293,7 @@ def require_cell_sizes(self): """Set that the kernel requires cell sizes.""" self.kernel.needs_cell_sizes = True - def construct_kernel(self, name, body): + def construct_kernel(self, name, body, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -281,6 +301,7 @@ def construct_kernel(self, name, body): :arg name: function name :arg body: function body (:class:`coffee.Block` node) + :arg quadrature rule: quadrature rule (not used, stubbed out for Themis integration) :returns: :class:`Kernel` object """ args = [self.local_tensor, self.coordinates_arg] @@ -298,6 +319,12 @@ def construct_kernel(self, name, body): coffee.Symbol("facet", rank=(2,)), qualifiers=["const"])) + for name_, shape in self.kernel.tabulations: + args.append(coffee.Decl(self.scalar_type, coffee.Symbol( + name_, rank=shape), qualifiers=["const"])) + + self.kernel.quadrature_rule = quadrature_rule + self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 0a05d517e2..9d71b899db 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -102,7 +102,7 @@ def set_coefficients(self, integral_data, form_data): expression = prepare_coefficient(coefficient, n, name, self.interior_facet) self.coefficient_map[coefficient] = expression - def construct_kernel(self, name, body): + def construct_kernel(self, name, body, quadrature_rule): """Construct a fully built kernel function. This function contains the logic for building the argument @@ -110,8 +110,12 @@ def construct_kernel(self, name, body): :arg name: function name :arg body: function body (:class:`coffee.Block` node) + :arg quadrature rule: quadrature rule (not used, stubbed out for Themis integration) :returns: a COFFEE function definition object """ + + assert quadrature_rule is None + args = [self.local_tensor] args.extend(self.coefficient_args) args.extend(self.coordinates_args) From 22de37eb95c74cc77533ff26215c8decf0fa17ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Mon, 25 Mar 2019 21:36:08 +0100 Subject: [PATCH 513/809] Refactor kernel interface requirements The driver does not need to know about cell orientations, cell sizes, runtime tabulated elements, etc. --- tsfc/driver.py | 24 +++------ tsfc/kernel_interface/common.py | 9 +++- tsfc/kernel_interface/firedrake.py | 81 ++++++++++++++---------------- tsfc/kernel_interface/ufc.py | 19 ------- 4 files changed, 50 insertions(+), 83 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index af9d0e5c60..74e4e8238d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -210,15 +210,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface): expressions = impero_utils.preprocess_gem(expressions, **options) assignments = list(zip(return_variables, expressions)) - # Register tabulations for runtime tabulated elements (used by Themis) - builder.register_tabulations(expressions) - - # Look for cell orientations in the IR - if builder.needs_cell_orientations(expressions): - builder.require_cell_orientations() - - if builder.needs_cell_sizes(expressions): - builder.require_cell_sizes() + # Let the kernel interface inspect the optimised IR to register + # what kind of external data is required (e.g., cell orientations, + # cell sizes, etc.). + builder.register_requirements(expressions) # Construct ImperoC split_argument_indices = tuple(chain(*[var.index_ordering() @@ -325,9 +320,6 @@ def compile_expression_at_points(expression, points, coordinates, interface=None if value_shape: ir = gem.Indexed(ir, tensor_indices) - # Register tabulations for runtime tabulated elements (used by Themis) - builder.register_tabulations([ir]) - # Build kernel body return_shape = (len(points),) + value_shape return_indices = point_set.indices + tensor_indices @@ -339,12 +331,8 @@ def compile_expression_at_points(expression, points, coordinates, interface=None point_index, = point_set.indices body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"], parameters["scalar_type"]) - # Handle cell orientations - if builder.needs_cell_orientations([ir]): - builder.require_cell_orientations() - # Handle cell sizes (physically mapped elements) - if builder.needs_cell_sizes([ir]): - builder.require_cell_sizes() + # Handle kernel interface requirements + builder.register_requirements([ir]) # Build kernel tuple return builder.construct_kernel(return_arg, body) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 587facec1b..72e825e48f 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -98,6 +98,11 @@ def construct_kernel(self, name, args, body): body_ = coffee.Block(self.prepare + body.children + self.finalise) return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) - # stubbed out for firedrake and ufc kernel builders - def register_tabulations(self, expressions): + def register_requirements(self, ir): + """Inspect what is referenced by the IR that needs to be + provided by the kernel interface. + + :arg ir: multi-root GEM expression DAG + """ + # Nothing is required by default pass diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 8cd7246a97..79c7604e98 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -106,23 +106,6 @@ def set_cell_sizes(self, domain): self.cell_sizes_arg = funarg self._cell_sizes = expression - @staticmethod - def needs_cell_orientations(ir): - """Does a multi-root GEM expression DAG references cell - orientations?""" - for node in traversal(ir): - if isinstance(node, gem.Variable) and node.name == "cell_orientations": - return True - return False - - @staticmethod - def needs_cell_sizes(ir): - """Does a multi-root GEM expression DAG reference cell sizes?""" - for node in traversal(ir): - if isinstance(node, gem.Variable) and node.name == "cell_sizes": - return True - return False - def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given a UFL element.""" @@ -157,19 +140,12 @@ def set_coefficients(self, coefficients): self.coefficients.append(coefficient) self.kernel_args.append(self._coefficient(coefficient, "w_%d" % (i,))) - def register_tabulations(self, expressions): - tabulations = {} - for node in traversal(expressions): - if isinstance(node, gem.Variable) and node.name.startswith("rt_"): - tabulations[node.name] = node.shape - self.tabulations = tuple(sorted(tabulations.items())) - - def require_cell_orientations(self): - """Set that the kernel requires cell orientations.""" - self.oriented = True - - def require_cell_sizes(self): - self.cell_sizes = True + def register_requirements(self, ir): + """Inspect what is referenced by the IR that needs to be + provided by the kernel interface.""" + self.oriented = needs_cell_orientations(ir) + self.cell_sizes = needs_cell_sizes(ir) + self.tabulations = collect_tabulations(ir) def construct_kernel(self, return_arg, body): """Constructs an :class:`ExpressionKernel`. @@ -278,20 +254,12 @@ def set_coefficients(self, integral_data, form_data): self._coefficient(coefficient, "w_%d" % i)) self.kernel.coefficient_numbers = tuple(coefficient_numbers) - def register_tabulations(self, expressions): - tabulations = {} - for node in traversal(expressions): - if isinstance(node, gem.Variable) and node.name.startswith("rt_"): - tabulations[node.name] = node.shape - self.kernel.tabulations = tuple(sorted(tabulations.items())) - - def require_cell_orientations(self): - """Set that the kernel requires cell orientations.""" - self.kernel.oriented = True - - def require_cell_sizes(self): - """Set that the kernel requires cell sizes.""" - self.kernel.needs_cell_sizes = True + def register_requirements(self, ir): + """Inspect what is referenced by the IR that needs to be + provided by the kernel interface.""" + self.kernel.oriented = needs_cell_orientations(ir) + self.kernel.needs_cell_sizes = needs_cell_sizes(ir) + self.kernel.tabulations = collect_tabulations(ir) def construct_kernel(self, name, body, quadrature_rule): """Construct a fully built :class:`Kernel`. @@ -337,6 +305,31 @@ def construct_empty_kernel(self, name): return None +def needs_cell_orientations(ir): + """Does a multi-root GEM expression DAG references cell + orientations?""" + for node in traversal(ir): + if isinstance(node, gem.Variable) and node.name == "cell_orientations": + return True + return False + + +def needs_cell_sizes(ir): + """Does a multi-root GEM expression DAG reference cell sizes?""" + for node in traversal(ir): + if isinstance(node, gem.Variable) and node.name == "cell_sizes": + return True + return False + + +def collect_tabulations(ir): + """Collect tabulations that Themis needs to provide.""" + tabs = {node.name: node.shape + for node in traversal(ir) + if isinstance(node, gem.Variable) and node.name.startswith("rt_")} + return tuple(sorted(tabs.items())) + + def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 9d71b899db..8c4cdd3093 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -149,25 +149,6 @@ def construct_empty_kernel(self, name): body = coffee.Block([]) # empty block return self.construct_kernel(name, body) - @staticmethod - def require_cell_orientations(): - # Nothing to do - pass - - @staticmethod - def needs_cell_orientations(ir): - # UFC tabulate_tensor always have cell orientations - return True - - @staticmethod - def require_cell_sizes(): - pass - - @staticmethod - def needs_cell_sizes(ir): - # Not hooked up right now. - return False - def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given a UFL element.""" From 392a0c57f36af8bfbab1cf1351c86121488022e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Tue, 26 Mar 2019 09:15:39 +0100 Subject: [PATCH 514/809] Collect requirements in a single pass --- tsfc/kernel_interface/firedrake.py | 45 +++++++++++------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 79c7604e98..cd692ad6f7 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -143,9 +143,7 @@ def set_coefficients(self, coefficients): def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be provided by the kernel interface.""" - self.oriented = needs_cell_orientations(ir) - self.cell_sizes = needs_cell_sizes(ir) - self.tabulations = collect_tabulations(ir) + self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) def construct_kernel(self, return_arg, body): """Constructs an :class:`ExpressionKernel`. @@ -257,9 +255,8 @@ def set_coefficients(self, integral_data, form_data): def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be provided by the kernel interface.""" - self.kernel.oriented = needs_cell_orientations(ir) - self.kernel.needs_cell_sizes = needs_cell_sizes(ir) - self.kernel.tabulations = collect_tabulations(ir) + knl = self.kernel + knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) def construct_kernel(self, name, body, quadrature_rule): """Construct a fully built :class:`Kernel`. @@ -305,29 +302,21 @@ def construct_empty_kernel(self, name): return None -def needs_cell_orientations(ir): - """Does a multi-root GEM expression DAG references cell - orientations?""" +def check_requirements(ir): + """Look for cell orientations, cell sizes, and collect tabulations + in one pass.""" + cell_orientations = False + cell_sizes = False + rt_tabs = {} for node in traversal(ir): - if isinstance(node, gem.Variable) and node.name == "cell_orientations": - return True - return False - - -def needs_cell_sizes(ir): - """Does a multi-root GEM expression DAG reference cell sizes?""" - for node in traversal(ir): - if isinstance(node, gem.Variable) and node.name == "cell_sizes": - return True - return False - - -def collect_tabulations(ir): - """Collect tabulations that Themis needs to provide.""" - tabs = {node.name: node.shape - for node in traversal(ir) - if isinstance(node, gem.Variable) and node.name.startswith("rt_")} - return tuple(sorted(tabs.items())) + if isinstance(node, gem.Variable): + if node.name == "cell_orientations": + cell_orientations = True + elif node.name == "cell_sizes": + cell_sizes = True + elif node.name.startswith("rt_"): + rt_tabs[node.name] = node.shape + return cell_orientations, cell_sizes, tuple(sorted(rt_tabs.items())) def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): From 9cf3bfcd762f90a650d61f29f662a86250a3e018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Tue, 26 Mar 2019 13:37:02 +0000 Subject: [PATCH 515/809] Fix UFC interface --- tsfc/kernel_interface/ufc.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 8c4cdd3093..ea175dc013 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -102,7 +102,7 @@ def set_coefficients(self, integral_data, form_data): expression = prepare_coefficient(coefficient, n, name, self.interior_facet) self.coefficient_map[coefficient] = expression - def construct_kernel(self, name, body, quadrature_rule): + def construct_kernel(self, name, body, quadrature_rule=None): """Construct a fully built kernel function. This function contains the logic for building the argument @@ -110,12 +110,9 @@ def construct_kernel(self, name, body, quadrature_rule): :arg name: function name :arg body: function body (:class:`coffee.Block` node) - :arg quadrature rule: quadrature rule (not used, stubbed out for Themis integration) + :arg quadrature rule: quadrature rule (ignored) :returns: a COFFEE function definition object """ - - assert quadrature_rule is None - args = [self.local_tensor] args.extend(self.coefficient_args) args.extend(self.coordinates_args) From 3e116812144699bbcf712757172936bf4e69381b Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 3 Apr 2019 16:28:09 +0100 Subject: [PATCH 516/809] tests: Update underintegration test in light of UFL changes The estimated quadrature degree for non-affine cells now gets a contribution from detJ, so we need to specify the rule for the DG mass matrix too. --- tests/test_underintegration.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/test_underintegration.py b/tests/test_underintegration.py index 6d7846fbba..e502ecb5d6 100644 --- a/tests/test_underintegration.py +++ b/tests/test_underintegration.py @@ -10,9 +10,9 @@ action, interval, quadrilateral, dot, grad) from FIAT import ufc_cell -from FIAT.quadrature import GaussLobattoLegendreQuadratureLineRule +from FIAT.quadrature import GaussLobattoLegendreQuadratureLineRule, GaussLegendreQuadratureLineRule -from finat.point_set import GaussLobattoLegendrePointSet +from finat.point_set import GaussLobattoLegendrePointSet, GaussLegendrePointSet from finat.quadrature import QuadratureRule, TensorProductQuadratureRule from tsfc import compile_form @@ -28,12 +28,22 @@ def gll_quadrature_rule(cell, elem_deg): return finat_rule +def gl_quadrature_rule(cell, elem_deg): + fiat_cell = ufc_cell("interval") + fiat_rule = GaussLegendreQuadratureLineRule(fiat_cell, elem_deg + 1) + line_rules = [QuadratureRule(GaussLegendrePointSet(fiat_rule.get_points()), + fiat_rule.get_weights()) + for _ in range(cell.topological_dimension())] + finat_rule = reduce(lambda a, b: TensorProductQuadratureRule([a, b]), line_rules) + return finat_rule + + def mass_cg(cell, degree): m = Mesh(VectorElement('Q', cell, 1)) V = FunctionSpace(m, FiniteElement('Q', cell, degree, variant='spectral')) u = TrialFunction(V) v = TestFunction(V) - return u*v*dx(rule=gll_quadrature_rule(cell, degree)) + return u*v*dx(scheme=gll_quadrature_rule(cell, degree)) def mass_dg(cell, degree): @@ -41,9 +51,7 @@ def mass_dg(cell, degree): V = FunctionSpace(m, FiniteElement('DQ', cell, degree, variant='spectral')) u = TrialFunction(V) v = TestFunction(V) - # In this case, the estimated quadrature degree will give the - # correct number of quadrature points by luck. - return u*v*dx + return u*v*dx(scheme=gl_quadrature_rule(cell, degree)) def laplace(cell, degree): @@ -51,7 +59,7 @@ def laplace(cell, degree): V = FunctionSpace(m, FiniteElement('Q', cell, degree, variant='spectral')) u = TrialFunction(V) v = TestFunction(V) - return dot(grad(u), grad(v))*dx(rule=gll_quadrature_rule(cell, degree)) + return dot(grad(u), grad(v))*dx(scheme=gll_quadrature_rule(cell, degree)) def count_flops(form): From a22cb27a3ddcf7f3787c0a19fa0226c56d1cf891 Mon Sep 17 00:00:00 2001 From: Tianjiao Sun Date: Wed, 3 Apr 2019 16:44:05 +0100 Subject: [PATCH 517/809] Add kernel interface for loopy codegen --- tests/test_idempotency.py | 7 + tsfc/coffee.py | 37 +-- tsfc/driver.py | 48 +-- tsfc/kernel_interface/firedrake.py | 16 +- tsfc/kernel_interface/firedrake_loopy.py | 387 +++++++++++++++++++++++ tsfc/kernel_interface/ufc.py | 23 +- tsfc/loopy.py | 372 ++++++++++++++++++++++ 7 files changed, 833 insertions(+), 57 deletions(-) create mode 100644 tsfc/kernel_interface/firedrake_loopy.py create mode 100644 tsfc/loopy.py diff --git a/tests/test_idempotency.py b/tests/test_idempotency.py index 7656592e2b..a5243e9145 100644 --- a/tests/test_idempotency.py +++ b/tests/test_idempotency.py @@ -63,6 +63,13 @@ def test_idempotency(form): assert k1.ast.gencode() == k2.ast.gencode() + # Test loopy backend + import loopy + k1 = compile_form(form, coffee=False)[0] + k2 = compile_form(form, coffee=False)[0] + + assert loopy.generate_code_v2(k1.ast).device_code() == loopy.generate_code_v2(k2.ast).device_code() + if __name__ == "__main__": import os diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 6da94a2974..5c4fd65a92 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -23,18 +23,13 @@ class Bunch(object): pass -def generate(impero_c, index_names, precision, scalar_type=None, roots=(), argument_indices=()): +def generate(impero_c, index_names, precision, scalar_type=None): """Generates COFFEE code. :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names :arg precision: floating-point precision for printing :arg scalar_type: type of scalars as C typename string - :arg roots: list of expression DAG roots for attaching - #pragma coffee expression - :arg argument_indices: argument indices for attaching - #pragma coffee linear loop - to the argument loops :returns: COFFEE function body """ params = Bunch() @@ -42,8 +37,6 @@ def generate(impero_c, index_names, precision, scalar_type=None, roots=(), argum params.indices = impero_c.indices params.precision = precision params.epsilon = 10.0 * eval("1e-%d" % precision) - params.roots = roots - params.argument_indices = argument_indices params.scalar_type = scalar_type or SCALAR_TYPE params.names = {} @@ -87,15 +80,6 @@ def _ref_symbol(expr, parameters): return _coffee_symbol(parameters.names[expr], rank=tuple(rank)) -def _root_pragma(expr, parameters): - """Decides whether to annonate the expression with - #pragma coffee expression""" - if expr in parameters.roots: - return "#pragma coffee expression" - else: - return None - - @singledispatch def statement(tree, parameters): """Translates an Impero (sub)tree into a COFFEE AST corresponding @@ -119,10 +103,6 @@ def statement_block(tree, parameters): @statement.register(imp.For) def statement_for(tree, parameters): - if tree.index in parameters.argument_indices: - pragma = "#pragma coffee linear loop" - else: - pragma = None extent = tree.index.extent assert extent i = _coffee_symbol(parameters.index_names[tree.index]) @@ -130,8 +110,7 @@ def statement_for(tree, parameters): return coffee.For(coffee.Decl("int", i, init=0), coffee.Less(i, extent), coffee.Incr(i, 1), - statement(tree.children[0], parameters), - pragma=pragma) + statement(tree.children[0], parameters)) @statement.register(imp.Initialise) @@ -144,26 +123,20 @@ def statement_initialise(leaf, parameters): @statement.register(imp.Accumulate) def statement_accumulate(leaf, parameters): - pragma = _root_pragma(leaf.indexsum, parameters) return coffee.Incr(_ref_symbol(leaf.indexsum, parameters), - expression(leaf.indexsum.children[0], parameters), - pragma=pragma) + expression(leaf.indexsum.children[0], parameters)) @statement.register(imp.Return) def statement_return(leaf, parameters): - pragma = _root_pragma(leaf.expression, parameters) return coffee.Incr(expression(leaf.variable, parameters), - expression(leaf.expression, parameters), - pragma=pragma) + expression(leaf.expression, parameters)) @statement.register(imp.ReturnAccumulate) def statement_returnaccumulate(leaf, parameters): - pragma = _root_pragma(leaf.indexsum, parameters) return coffee.Incr(expression(leaf.variable, parameters), - expression(leaf.indexsum.children[0], parameters), - pragma=pragma) + expression(leaf.indexsum.children[0], parameters)) @statement.register(imp.Evaluate) diff --git a/tsfc/driver.py b/tsfc/driver.py index 74e4e8238d..87acd806fd 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -2,6 +2,7 @@ import operator import string import time +import sys from functools import reduce from itertools import chain @@ -23,20 +24,24 @@ from finat.quadrature import AbstractQuadratureRule, make_quadrature from tsfc import fem, ufl_utils -from tsfc.coffee import generate as generate_coffee from tsfc.fiatinterface import as_fiat_cell from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex -import tsfc.kernel_interface.firedrake as firedrake_interface +import tsfc.kernel_interface.firedrake as firedrake_interface_coffee +import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy +# To handle big forms. The various transformations might need a deeper stack +sys.setrecursionlimit(3000) -def compile_form(form, prefix="form", parameters=None, interface=None): + +def compile_form(form, prefix="form", parameters=None, interface=None, coffee=True): """Compiles a UFL form into a set of assembly kernels. :arg form: UFL form :arg prefix: kernel name will start with this string :arg parameters: parameters object + :arg coffee: compile coffee kernel instead of loopy kernel :returns: list of kernels """ cpu_time = time.time() @@ -58,7 +63,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None): kernels = [] for integral_data in fd.integral_data: start = time.time() - kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface) + kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface, coffee=coffee) if kernel is not None: kernels.append(kernel) logger.info(GREEN % "compile_integral finished in %g seconds.", time.time() - start) @@ -67,7 +72,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None): return kernels -def compile_integral(integral_data, form_data, prefix, parameters, interface): +def compile_integral(integral_data, form_data, prefix, parameters, interface, coffee): """Compiles a UFL integral into an assembly kernel. :arg integral_data: UFL integral data @@ -84,7 +89,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface): _.update(parameters) parameters = _ if interface is None: - interface = firedrake_interface.KernelBuilder + if coffee: + interface = firedrake_interface_coffee.KernelBuilder + else: + interface = firedrake_interface_loopy.KernelBuilder # Remove these here, they're handled below. if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: @@ -203,7 +211,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface): return_variables = [] expressions = [] - # Need optimised roots for COFFEE + # Need optimised roots options = dict(reduce(operator.and_, [mode.finalise_options.items() for mode in mode_irs.keys()])) @@ -246,14 +254,10 @@ def name_multiindex(multiindex, name): for multiindex, name in zip(argument_multiindices, ['j', 'k']): name_multiindex(multiindex, name) - # Construct kernel - body = generate_coffee(impero_c, index_names, parameters["precision"], - parameters["scalar_type"], expressions, split_argument_indices) + return builder.construct_kernel(kernel_name, impero_c, parameters["precision"], index_names, quad_rule) - return builder.construct_kernel(kernel_name, body, quad_rule) - -def compile_expression_at_points(expression, points, coordinates, interface=None, parameters=None): +def compile_expression_at_points(expression, points, coordinates, interface=None, parameters=None, coffee=True): """Compiles a UFL expression to be evaluated at compile-time known reference points. Useful for interpolating UFL expressions onto function spaces with only point evaluation nodes. @@ -261,10 +265,12 @@ def compile_expression_at_points(expression, points, coordinates, interface=None :arg expression: UFL expression :arg points: reference coordinates of the evaluation points :arg coordinates: the coordinate function - :arg parameters: parameters object :arg interface: backend module for the kernel interface + :arg parameters: parameters object + :arg coffee: compile coffee kernel instead of loopy kernel """ import coffee.base as ast + import loopy as lp if parameters is None: parameters = default_parameters() @@ -286,7 +292,10 @@ def compile_expression_at_points(expression, points, coordinates, interface=None # Initialise kernel builder if interface is None: - interface = firedrake_interface.ExpressionKernelBuilder + if coffee: + interface = firedrake_interface_coffee.ExpressionKernelBuilder + else: + interface = firedrake_interface_loopy.ExpressionKernelBuilder builder = interface(parameters["scalar_type"]) @@ -324,17 +333,20 @@ def compile_expression_at_points(expression, points, coordinates, interface=None return_shape = (len(points),) + value_shape return_indices = point_set.indices + tensor_indices return_var = gem.Variable('A', return_shape) - return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) + if coffee: + return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) + else: + return_arg = lp.GlobalArg("A", dtype=parameters["scalar_type"], shape=return_shape) + return_expr = gem.Indexed(return_var, return_indices) ir, = impero_utils.preprocess_gem([ir]) impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) point_index, = point_set.indices - body = generate_coffee(impero_c, {point_index: 'p'}, parameters["precision"], parameters["scalar_type"]) # Handle kernel interface requirements builder.register_requirements([ir]) # Build kernel tuple - return builder.construct_kernel(return_arg, body) + return builder.construct_kernel(return_arg, impero_c, parameters["precision"], {point_index: 'p'}) def lower_integral_type(fiat_cell, integral_type): diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index cd692ad6f7..a1d77b0e17 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -13,6 +13,7 @@ from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase +from tsfc.coffee import generate as generate_coffee # Expression kernel description type @@ -145,7 +146,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) - def construct_kernel(self, return_arg, body): + def construct_kernel(self, return_arg, impero_c, precision, index_names): """Constructs an :class:`ExpressionKernel`. :arg return_arg: COFFEE argument for the return value @@ -159,6 +160,8 @@ def construct_kernel(self, return_arg, body): args.append(self.cell_sizes_arg) args.extend(self.kernel_args) + body = generate_coffee(impero_c, index_names, precision) + for name_, shape in self.tabulations: args.append(coffee.Decl(self.scalar_type, coffee.Symbol( name_, rank=shape), qualifiers=["const"])) @@ -258,17 +261,22 @@ def register_requirements(self, ir): knl = self.kernel knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) - def construct_kernel(self, name, body, quadrature_rule): + def construct_kernel(self, name, impero_c, precision, index_names, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument list for assembly kernels. :arg name: function name - :arg body: function body (:class:`coffee.Block` node) - :arg quadrature rule: quadrature rule (not used, stubbed out for Themis integration) + :arg impero_c: ImperoC tuple with Impero AST and other data + :arg precision: floating-point precision for printing + :arg index_names: pre-assigned index names + :arg quadrature rule: quadrature rule + :returns: :class:`Kernel` object """ + body = generate_coffee(impero_c, index_names, precision, self.scalar_type) + args = [self.local_tensor, self.coordinates_arg] if self.kernel.oriented: args.append(cell_orientations_coffee_arg) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py new file mode 100644 index 0000000000..b9bacc0184 --- /dev/null +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -0,0 +1,387 @@ +import numpy +from collections import namedtuple +from itertools import chain, product +from functools import partial + +from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement + +import gem +from gem.optimise import remove_componenttensors as prune + +import loopy as lp + +from tsfc.finatinterface import create_element +from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase +from tsfc.kernel_interface.firedrake import check_requirements +from tsfc.loopy import generate as generate_loopy + + +# Expression kernel description type +ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', 'tabulations']) + + +def make_builder(*args, **kwargs): + return partial(KernelBuilder, *args, **kwargs) + + +class Kernel(object): + __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", + "coefficient_numbers", "__weakref__") + """A compiled Kernel object. + + :kwarg ast: The loopy kernel object. + :kwarg integral_type: The type of integral. + :kwarg oriented: Does the kernel require cell_orientations. + :kwarg subdomain_id: What is the subdomain id for this kernel. + :kwarg domain_number: Which domain number in the original form + does this kernel correspond to (can be used to index into + original_form.ufl_domains() to get the correct domain). + :kwarg coefficient_numbers: A list of which coefficients from the + form the kernel needs. + :kwarg quadrature_rule: The finat quadrature rule used to generate this kernel + :kwarg tabulations: The runtime tabulations this kernel requires + :kwarg needs_cell_sizes: Does the kernel require cell sizes. + """ + def __init__(self, ast=None, integral_type=None, oriented=False, + subdomain_id=None, domain_number=None, quadrature_rule=None, + coefficient_numbers=(), + needs_cell_sizes=False): + # Defaults + self.ast = ast + self.integral_type = integral_type + self.oriented = oriented + self.domain_number = domain_number + self.subdomain_id = subdomain_id + self.coefficient_numbers = coefficient_numbers + self.needs_cell_sizes = needs_cell_sizes + super(Kernel, self).__init__() + + +class KernelBuilderBase(_KernelBuilderBase): + + def __init__(self, scalar_type=None, interior_facet=False): + """Initialise a kernel builder. + + :arg interior_facet: kernel accesses two cells + """ + if scalar_type is None: + from tsfc.parameters import SCALAR_TYPE + scalar_type = SCALAR_TYPE + super(KernelBuilderBase, self).__init__(scalar_type=scalar_type, + interior_facet=interior_facet) + + # Cell orientation + if self.interior_facet: + shape = (2,) + cell_orientations = gem.Variable("cell_orientations", shape) + self._cell_orientations = (gem.Indexed(cell_orientations, (0,)), + gem.Indexed(cell_orientations, (1,))) + else: + shape = (1,) + cell_orientations = gem.Variable("cell_orientations", shape) + self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) + self.cell_orientations_loopy_arg = lp.GlobalArg("cell_orientations", dtype=numpy.int32, shape=shape) + + def _coefficient(self, coefficient, name): + """Prepare a coefficient. Adds glue code for the coefficient + and adds the coefficient to the coefficient map. + + :arg coefficient: :class:`ufl.Coefficient` + :arg name: coefficient name + :returns: loopy argument for the coefficient + """ + funarg, expression = prepare_coefficient(coefficient, name, self.scalar_type, interior_facet=self.interior_facet) + self.coefficient_map[coefficient] = expression + return funarg + + def set_cell_sizes(self, domain): + """Setup a fake coefficient for "cell sizes". + + :arg domain: The domain of the integral. + + This is required for scaling of derivative basis functions on + physically mapped elements (Argyris, Bell, etc...). We need a + measure of the mesh size around each vertex (hence this lives + in P1). + """ + f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) + funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) + self.cell_sizes_arg = funarg + self._cell_sizes = expression + + def create_element(self, element, **kwargs): + """Create a FInAT element (suitable for tabulating with) given + a UFL element.""" + return create_element(element, **kwargs) + + +class ExpressionKernelBuilder(KernelBuilderBase): + """Builds expression kernels for UFL interpolation in Firedrake.""" + + def __init__(self, scalar_type=None): + super(ExpressionKernelBuilder, self).__init__(scalar_type=None) + self.oriented = False + self.cell_sizes = False + + def set_coefficients(self, coefficients): + """Prepare the coefficients of the expression. + + :arg coefficients: UFL coefficients from Firedrake + """ + self.coefficients = [] # Firedrake coefficients for calling the kernel + self.coefficient_split = {} + self.kernel_args = [] + + for i, coefficient in enumerate(coefficients): + if type(coefficient.ufl_element()) == ufl_MixedElement: + subcoeffs = coefficient.split() # Firedrake-specific + self.coefficients.extend(subcoeffs) + self.coefficient_split[coefficient] = subcoeffs + self.kernel_args += [self._coefficient(subcoeff, "w_%d_%d" % (i, j)) + for j, subcoeff in enumerate(subcoeffs)] + else: + self.coefficients.append(coefficient) + self.kernel_args.append(self._coefficient(coefficient, "w_%d" % (i,))) + + def register_requirements(self, ir): + """Inspect what is referenced by the IR that needs to be + provided by the kernel interface.""" + self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) + + def construct_kernel(self, return_arg, impero_c, precision, index_names): + """Constructs an :class:`ExpressionKernel`. + + :arg return_arg: loopy.GlobalArg for the return value + :arg impero_c: gem.ImperoC object that represents the kernel + :arg precision: floating point precision for code generation + :arg index_names: pre-assigned index names + :returns: :class:`ExpressionKernel` object + """ + args = [return_arg] + if self.oriented: + args.append(self.cell_orientations_loopy_arg) + if self.cell_sizes: + args.append(self.cell_sizes_arg) + args.extend(self.kernel_args) + for name_, shape in self.tabulations: + args.append(lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape)) + + loopy_kernel = generate_loopy(impero_c, args, precision, "expression_kernel", index_names, self.scalar_type) + return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, self.coefficients, self.tabulations) + + +class KernelBuilder(KernelBuilderBase): + """Helper class for building a :class:`Kernel` object.""" + + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None, dont_split=()): + """Initialise a kernel builder.""" + super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) + + self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, + domain_number=domain_number) + self.local_tensor = None + self.coordinates_arg = None + self.coefficient_args = [] + self.coefficient_split = {} + self.dont_split = frozenset(dont_split) + + # Facet number + if integral_type in ['exterior_facet', 'exterior_facet_vert']: + facet = gem.Variable('facet', (1,)) + self._entity_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} + elif integral_type in ['interior_facet', 'interior_facet_vert']: + facet = gem.Variable('facet', (2,)) + self._entity_number = { + '+': gem.VariableIndex(gem.Indexed(facet, (0,))), + '-': gem.VariableIndex(gem.Indexed(facet, (1,))) + } + elif integral_type == 'interior_facet_horiz': + self._entity_number = {'+': 1, '-': 0} + + def set_arguments(self, arguments, multiindices): + """Process arguments. + + :arg arguments: :class:`ufl.Argument`s + :arg multiindices: GEM argument multiindices + :returns: GEM expression representing the return variable + """ + self.local_tensor, expressions = prepare_arguments( + arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet) + return expressions + + def set_coordinates(self, domain): + """Prepare the coordinate field. + + :arg domain: :class:`ufl.Domain` + """ + # Create a fake coordinate coefficient for a domain. + f = Coefficient(FunctionSpace(domain, domain.ufl_coordinate_element())) + self.domain_coordinate[domain] = f + self.coordinates_arg = self._coefficient(f, "coords") + + def set_coefficients(self, integral_data, form_data): + """Prepare the coefficients of the form. + + :arg integral_data: UFL integral data + :arg form_data: UFL form data + """ + coefficients = [] + coefficient_numbers = [] + # enabled_coefficients is a boolean array that indicates which + # of reduced_coefficients the integral requires. + for i in range(len(integral_data.enabled_coefficients)): + if integral_data.enabled_coefficients[i]: + original = form_data.reduced_coefficients[i] + coefficient = form_data.function_replace_map[original] + if type(coefficient.ufl_element()) == ufl_MixedElement: + if original in self.dont_split: + coefficients.append(coefficient) + self.coefficient_split[coefficient] = [coefficient] + else: + split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) + for element in coefficient.ufl_element().sub_elements()] + coefficients.extend(split) + self.coefficient_split[coefficient] = split + else: + coefficients.append(coefficient) + # This is which coefficient in the original form the + # current coefficient is. + # Consider f*v*dx + g*v*ds, the full form contains two + # coefficients, but each integral only requires one. + coefficient_numbers.append(form_data.original_coefficient_positions[i]) + for i, coefficient in enumerate(coefficients): + self.coefficient_args.append( + self._coefficient(coefficient, "w_%d" % i)) + self.kernel.coefficient_numbers = tuple(coefficient_numbers) + + def register_requirements(self, ir): + """Inspect what is referenced by the IR that needs to be + provided by the kernel interface.""" + knl = self.kernel + knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) + + def construct_kernel(self, name, impero_c, precision, index_names, quadrature_rule): + """Construct a fully built :class:`Kernel`. + + This function contains the logic for building the argument + list for assembly kernels. + + :arg name: function name + :arg impero_c: ImperoC tuple with Impero AST and other data + :arg precision: floating-point precision for printing + :arg index_names: pre-assigned index names + :arg quadrature rule: quadrature rule + :returns: :class:`Kernel` object + """ + + args = [self.local_tensor, self.coordinates_arg] + if self.kernel.oriented: + args.append(self.cell_orientations_loopy_arg) + if self.kernel.needs_cell_sizes: + args.append(self.cell_sizes_arg) + args.extend(self.coefficient_args) + if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: + args.append(lp.GlobalArg("facet", dtype=numpy.uint32, shape=(1,))) + elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: + args.append(lp.GlobalArg("facet", dtype=numpy.uint32, shape=(2,))) + + for name_, shape in self.kernel.tabulations: + args.append(lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape)) + + self.kernel.quadrature_rule = quadrature_rule + self.kernel.ast = generate_loopy(impero_c, args, precision, name, index_names, self.scalar_type) + return self.kernel + + def construct_empty_kernel(self, name): + """Return None, since Firedrake needs no empty kernels. + + :arg name: function name + :returns: None + """ + return None + + +def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + Coefficients. + + :arg coefficient: UFL Coefficient + :arg name: unique name to refer to the Coefficient in the kernel + :arg interior_facet: interior facet integral? + :returns: (funarg, expression) + funarg - :class:`loopy.GlobalArg` function argument + expression - GEM expression referring to the Coefficient + values + """ + assert isinstance(interior_facet, bool) + + if coefficient.ufl_element().family() == 'Real': + # Constant + funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(coefficient.ufl_element().value_size(),)) + expression = gem.reshape(gem.Variable(name, (None,)), + coefficient.ufl_shape) + + return funarg, expression + + finat_element = create_element(coefficient.ufl_element()) + + shape = finat_element.index_shape + size = numpy.prod(shape, dtype=int) + + if not interior_facet: + expression = gem.reshape(gem.Variable(name, (size,)), shape) + else: + varexp = gem.Variable(name, (2*size,)) + plus = gem.view(varexp, slice(size)) + minus = gem.view(varexp, slice(size, 2*size)) + expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) + size = size * 2 + funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(size,)) + return funarg, expression + + +def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + Arguments. Vector Arguments are rearranged here for interior + facet integrals. + + :arg arguments: UFL Arguments + :arg multiindices: Argument multiindices + :arg interior_facet: interior facet integral? + :returns: (funarg, expression) + funarg - :class:`loopy.GlobalArg` function argument + expressions - GEM expressions referring to the argument + tensor + """ + + assert isinstance(interior_facet, bool) + + if len(arguments) == 0: + # No arguments + funarg = lp.GlobalArg("A", dtype=scalar_type, shape=(1,)) + expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) + + return funarg, [expression] + + elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + shapes = tuple(element.index_shape for element in elements) + + def expression(restricted): + return gem.Indexed(gem.reshape(restricted, *shapes), + tuple(chain(*multiindices))) + + u_shape = numpy.array([numpy.prod(shape, dtype=int) for shape in shapes]) + if interior_facet: + c_shape = tuple(2 * u_shape) + slicez = [[slice(r * s, (r + 1) * s) + for r, s in zip(restrictions, u_shape)] + for restrictions in product((0, 1), repeat=len(arguments))] + else: + c_shape = tuple(u_shape) + slicez = [[slice(s) for s in u_shape]] + + funarg = lp.GlobalArg("A", dtype=scalar_type, shape=c_shape) + varexp = gem.Variable("A", c_shape) + expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] + return funarg, prune(expressions) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index ea175dc013..2237964676 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -22,7 +22,7 @@ class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type): + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) self.integral_type = integral_type @@ -102,7 +102,24 @@ def set_coefficients(self, integral_data, form_data): expression = prepare_coefficient(coefficient, n, name, self.interior_facet) self.coefficient_map[coefficient] = expression - def construct_kernel(self, name, body, quadrature_rule=None): + def construct_kernel(self, name, impero_c, precision, index_names, quadrature_rule=None): + """Construct a fully built kernel function. + + This function contains the logic for building the argument + list for assembly kernels. + + :arg name: function name + :arg impero_c: ImperoC tuple with Impero AST and other data + :arg precision: floating-point precision for printing + :arg index_names: pre-assigned index names + :arg quadrature rule: quadrature rule (not used, stubbed out for Themis integration) + :returns: a COFFEE function definition object + """ + from tsfc.coffee import generate as generate_coffee + body = generate_coffee(impero_c, index_names, precision, scalar_type=self.scalar_type) + return self._construct_kernel_from_body(name, body) + + def _construct_kernel_from_body(self, name, body, quadrature_rule): """Construct a fully built kernel function. This function contains the logic for building the argument @@ -144,7 +161,7 @@ def construct_empty_kernel(self, name): :returns: a COFFEE function definition object """ body = coffee.Block([]) # empty block - return self.construct_kernel(name, body) + return self._construct_kernel_from_body(name, body) def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given diff --git a/tsfc/loopy.py b/tsfc/loopy.py new file mode 100644 index 0000000000..e21c1f280b --- /dev/null +++ b/tsfc/loopy.py @@ -0,0 +1,372 @@ +"""Generate loopy kernel from ImperoC tuple data. + +This is the final stage of code generation in TSFC.""" + +from math import isnan + +import numpy +from functools import singledispatch +from collections import defaultdict, OrderedDict + +from gem import gem, impero as imp + +import islpy as isl +import loopy as lp + +import pymbolic.primitives as p + +from pytools import UniqueNameGenerator + +from tsfc.parameters import is_complex + +# Satisfy import demands until complex branch is merged in Firedrake +from tsfc.parameters import SCALAR_TYPE + + +class LoopyContext(object): + def __init__(self): + self.indices = {} # indices for declarations and referencing values, from ImperoC + self.active_indices = {} # gem index -> pymbolic variable + self.index_extent = OrderedDict() # pymbolic variable for indices -> extent + self.gem_to_pymbolic = {} # gem node -> pymbolic variable + self.name_gen = UniqueNameGenerator() + + def pym_multiindex(self, multiindex): + indices = [] + for index in multiindex: + if isinstance(index, gem.Index): + indices.append(self.active_indices[index]) + elif isinstance(index, gem.VariableIndex): + indices.append(expression(index.expression, self)) + else: + assert isinstance(index, int) + indices.append(index) + return tuple(indices) + + def pymbolic_variable(self, node): + try: + pym = self.gem_to_pymbolic[node] + except KeyError: + name = self.name_gen(node.name) + pym = p.Variable(name) + self.gem_to_pymbolic[node] = pym + if node in self.indices: + indices = self.pym_multiindex(self.indices[node]) + if indices: + return p.Subscript(pym, indices) + else: + return pym + else: + return pym + + def active_inames(self): + # Return all active indices + return frozenset([i.name for i in self.active_indices.values()]) + + +def generate(impero_c, args, precision, kernel_name="loopy_kernel", index_names=[], scalar_type=None): + """Generates loopy code. + + :arg impero_c: ImperoC tuple with Impero AST and other data + :arg args: list of loopy.GlobalArgs + :arg precision: floating-point precision for printing + :arg scalar_type: type of scalars as C typename string + :arg kernel_name: function name of the kernel + :arg index_names: pre-assigned index names + :returns: loopy kernel + """ + ctx = LoopyContext() + ctx.indices = impero_c.indices + ctx.index_names = defaultdict(lambda: "i", index_names) + ctx.precision = precision + ctx.scalar_type = scalar_type or SCALAR_TYPE + ctx.epsilon = 10.0 ** (-precision) + + # Create arguments + data = list(args) + for i, temp in enumerate(impero_c.temporaries): + name = "t%d" % i + if isinstance(temp, gem.Constant): + data.append(lp.TemporaryVariable(name, shape=temp.shape, dtype=temp.array.dtype, initializer=temp.array, address_space=lp.AddressSpace.GLOBAL, read_only=True)) + else: + shape = tuple([i.extent for i in ctx.indices[temp]]) + temp.shape + data.append(lp.TemporaryVariable(name, shape=shape, dtype=numpy.float64, initializer=None, address_space=lp.AddressSpace.LOCAL, read_only=False)) + ctx.gem_to_pymbolic[temp] = p.Variable(name) + + # Create instructions + instructions = statement(impero_c.tree, ctx) + + # Create domains + domains = [] + for idx, extent in ctx.index_extent.items(): + inames = isl.make_zero_and_vars([idx]) + domains.append(((inames[0].le_set(inames[idx])) & (inames[idx].lt_set(inames[0] + extent)))) + + if not domains: + domains = [isl.BasicSet("[] -> {[]}")] + + # Create loopy kernel + knl = lp.make_function(domains, instructions, data, name=kernel_name, target=lp.CTarget(), + seq_dependencies=True, silenced_warnings=["summing_if_branches_ops"]) + + # Prevent loopy interchange by loopy + knl = lp.prioritize_loops(knl, ",".join(ctx.index_extent.keys())) + + # Help loopy in scheduling by assigning priority to instructions + insn_new = [] + for i, insn in enumerate(knl.instructions): + insn_new.append(insn.copy(priority=len(knl.instructions) - i)) + knl = knl.copy(instructions=insn_new) + + return knl + + +@singledispatch +def statement(tree, ctx): + """Translates an Impero (sub)tree into a loopy instructions corresponding + to a C statement. + + :arg tree: Impero (sub)tree + :arg ctx: miscellaneous code generation data + :returns: list of loopy instructions + """ + raise AssertionError("cannot generate loopy from %s" % type(tree)) + + +@statement.register(imp.Block) +def statement_block(tree, ctx): + from itertools import chain + return list(chain(*(statement(child, ctx) for child in tree.children))) + + +@statement.register(imp.For) +def statement_for(tree, ctx): + extent = tree.index.extent + assert extent + idx = ctx.name_gen(ctx.index_names[tree.index]) + ctx.active_indices[tree.index] = p.Variable(idx) + ctx.index_extent[idx] = extent + + statements = statement(tree.children[0], ctx) + + ctx.active_indices.pop(tree.index) + return statements + + +@statement.register(imp.Initialise) +def statement_initialise(leaf, ctx): + return [lp.Assignment(expression(leaf.indexsum, ctx), 0.0, within_inames=ctx.active_inames())] + + +@statement.register(imp.Accumulate) +def statement_accumulate(leaf, ctx): + lhs = expression(leaf.indexsum, ctx) + rhs = lhs + expression(leaf.indexsum.children[0], ctx) + return [lp.Assignment(lhs, rhs, within_inames=ctx.active_inames())] + + +@statement.register(imp.Return) +def statement_return(leaf, ctx): + lhs = expression(leaf.variable, ctx) + rhs = lhs + expression(leaf.expression, ctx) + return [lp.Assignment(lhs, rhs, within_inames=ctx.active_inames())] + + +@statement.register(imp.ReturnAccumulate) +def statement_returnaccumulate(leaf, ctx): + lhs = expression(leaf.variable, ctx) + rhs = lhs + expression(leaf.indexsum.children[0], ctx) + return [lp.Assignment(lhs, rhs, within_inames=ctx.active_inames())] + + +@statement.register(imp.Evaluate) +def statement_evaluate(leaf, ctx): + expr = leaf.expression + if isinstance(expr, gem.ListTensor): + ops = [] + var = ctx.pymbolic_variable(expr) + index = () + if isinstance(var, p.Subscript): + var, index = var.aggregate, var.index_tuple + for multiindex, value in numpy.ndenumerate(expr.array): + ops.append(lp.Assignment(p.Subscript(var, index + multiindex), expression(value, ctx), within_inames=ctx.active_inames())) + return ops + elif isinstance(expr, gem.Constant): + return [] + else: + return [lp.Assignment(ctx.pymbolic_variable(expr), expression(expr, ctx, top=True), within_inames=ctx.active_inames())] + + +def expression(expr, ctx, top=False): + """Translates GEM expression into a pymbolic expression + + :arg expr: GEM expression + :arg ctx: miscellaneous code generation data + :arg top: do not generate temporary reference for the root node + :returns: pymbolic expression + """ + if not top and expr in ctx.gem_to_pymbolic: + return ctx.pymbolic_variable(expr) + else: + return _expression(expr, ctx) + + +@singledispatch +def _expression(expr, parameters): + raise AssertionError("cannot generate expression from %s" % type(expr)) + + +@_expression.register(gem.Failure) +def _expression_failure(expr, parameters): + raise expr.exception + + +@_expression.register(gem.Product) +def _expression_product(expr, ctx): + return p.Product(tuple(expression(c, ctx) for c in expr.children)) + + +@_expression.register(gem.Sum) +def _expression_sum(expr, ctx): + return p.Sum(tuple(expression(c, ctx) for c in expr.children)) + + +@_expression.register(gem.Division) +def _expression_division(expr, ctx): + return p.Quotient(*(expression(c, ctx) for c in expr.children)) + + +@_expression.register(gem.Power) +def _expression_power(expr, ctx): + return p.Variable("pow")(*(expression(c, ctx) for c in expr.children)) + + +@_expression.register(gem.MathFunction) +def _expression_mathfunction(expr, ctx): + + from tsfc.coffee import math_table + + math_table = math_table.copy() + math_table['abs'] = ('abs', 'cabs') + + complex_mode = int(is_complex(ctx.scalar_type)) + + # Bessel functions + if expr.name.startswith('cyl_bessel_'): + if complex_mode: + msg = "Bessel functions for complex numbers: missing implementation" + raise NotImplementedError(msg) + nu, arg = expr.children + nu_thunk = lambda: expression(nu, ctx) + arg_loopy = expression(arg, ctx) + if expr.name == 'cyl_bessel_j': + if nu == gem.Zero(): + return p.Variable("j0")(arg_loopy) + elif nu == gem.one: + return p.Variable("j1")(arg_loopy) + else: + return p.Variable("jn")(nu_thunk(), arg_loopy) + if expr.name == 'cyl_bessel_y': + if nu == gem.Zero(): + return p.Variable("y0")(arg_loopy) + elif nu == gem.one: + return p.Variable("y1")(arg_loopy) + else: + return p.Variable("yn")(nu_thunk(), arg_loopy) + + # Modified Bessel functions (C++ only) + # + # These mappings work for FEniCS only, and fail with Firedrake + # since no Boost available. + if expr.name in ['cyl_bessel_i', 'cyl_bessel_k']: + name = 'boost::math::' + expr.name + return p.Variable(name)(nu_thunk(), arg_loopy) + + assert False, "Unknown Bessel function: {}".format(expr.name) + + # Other math functions + name = math_table[expr.name][complex_mode] + if name is None: + raise RuntimeError("{} not supported in complex mode".format(expr.name)) + + return p.Variable(name)(*[expression(c, ctx) for c in expr.children]) + + +@_expression.register(gem.MinValue) +def _expression_minvalue(expr, ctx): + return p.Variable("min")(*[expression(c, ctx) for c in expr.children]) + + +@_expression.register(gem.MaxValue) +def _expression_maxvalue(expr, ctx): + return p.Variable("max")(*[expression(c, ctx) for c in expr.children]) + + +@_expression.register(gem.Comparison) +def _expression_comparison(expr, ctx): + left, right = [expression(c, ctx) for c in expr.children] + return p.Comparison(left, expr.operator, right) + + +@_expression.register(gem.LogicalNot) +def _expression_logicalnot(expr, ctx): + return p.LogicalNot(tuple([expression(c, ctx) for c in expr.children])) + + +@_expression.register(gem.LogicalAnd) +def _expression_logicaland(expr, ctx): + return p.LogicalAnd(tuple([expression(c, ctx) for c in expr.children])) + + +@_expression.register(gem.LogicalOr) +def _expression_logicalor(expr, ctx): + return p.LogicalOr(tuple([expression(c, ctx) for c in expr.children])) + + +@_expression.register(gem.Conditional) +def _expression_conditional(expr, ctx): + return p.If(*[expression(c, ctx) for c in expr.children]) + + +@_expression.register(gem.Constant) +def _expression_scalar(expr, parameters): + assert not expr.shape + v = expr.value + if isnan(v): + return p.Variable("NAN") + r = round(v, 1) + if r and abs(v - r) < parameters.epsilon: + return r + return v + + +@_expression.register(gem.Variable) +def _expression_variable(expr, ctx): + return ctx.pymbolic_variable(expr) + + +@_expression.register(gem.Indexed) +def _expression_indexed(expr, ctx): + rank = ctx.pym_multiindex(expr.multiindex) + var = expression(expr.children[0], ctx) + if isinstance(var, p.Subscript): + rank = var.index + rank + var = var.aggregate + return p.Subscript(var, rank) + + +@_expression.register(gem.FlexiblyIndexed) +def _expression_flexiblyindexed(expr, ctx): + var = expression(expr.children[0], ctx) + + rank = [] + for off, idxs in expr.dim2idxs: + for index, stride in idxs: + assert isinstance(index, gem.Index) + + rank_ = [off] + for index, stride in idxs: + rank_.append(p.Product((ctx.active_indices[index], stride))) + rank.append(p.Sum(tuple(rank_))) + + return p.Subscript(var, tuple(rank)) From 5c5eb85ec073b4404509cdc8b723153c05e72cbe Mon Sep 17 00:00:00 2001 From: David Ham Date: Wed, 10 Apr 2019 16:10:33 +0100 Subject: [PATCH 518/809] Extend expression compiler to support arguments --- tsfc/driver.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 74e4e8238d..39de88680d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -253,13 +253,14 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, body, quad_rule) -def compile_expression_at_points(expression, points, coordinates, interface=None, parameters=None): +def compile_expression_at_points(expression, points, to_element, coordinates, interface=None, parameters=None): """Compiles a UFL expression to be evaluated at compile-time known reference points. Useful for interpolating UFL expressions onto function spaces with only point evaluation nodes. :arg expression: UFL expression :arg points: reference coordinates of the evaluation points + :arg to_element: UFL element to interpolate onto :arg coordinates: the coordinate function :arg parameters: parameters object :arg interface: backend module for the kernel interface @@ -273,10 +274,6 @@ def compile_expression_at_points(expression, points, coordinates, interface=None _.update(parameters) parameters = _ - # No arguments, please! - if extract_arguments(expression): - return ValueError("Cannot interpolate UFL expression with Arguments!") - # Determine whether in complex mode complex_mode = is_complex(parameters["scalar_type"]) @@ -289,6 +286,9 @@ def compile_expression_at_points(expression, points, coordinates, interface=None interface = firedrake_interface.ExpressionKernelBuilder builder = interface(parameters["scalar_type"]) + arguments = extract_arguments(expression) + argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() + for arg in arguments) # Replace coordinates (if any) domain = expression.ufl_domain() @@ -311,7 +311,8 @@ def compile_expression_at_points(expression, points, coordinates, interface=None config = dict(interface=builder, ufl_cell=coordinates.ufl_domain().ufl_cell(), precision=parameters["precision"], - point_set=point_set) + point_set=point_set, + argument_multiindices=argument_multiindices) ir, = fem.compile_ufl(expression, point_sum=False, **config) # Deal with non-scalar expressions @@ -321,8 +322,8 @@ def compile_expression_at_points(expression, points, coordinates, interface=None ir = gem.Indexed(ir, tensor_indices) # Build kernel body - return_shape = (len(points),) + value_shape - return_indices = point_set.indices + tensor_indices + return_indices = point_set.indices + tensor_indices + tuple(chain(*argument_multiindices)) + return_shape = tuple(i.extent for i in return_indices) return_var = gem.Variable('A', return_shape) return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) return_expr = gem.Indexed(return_var, return_indices) From db47f98c5dc5dea54ef1b50f9aba209e97baa438 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 10 Apr 2019 17:10:56 +0100 Subject: [PATCH 519/809] loopy: Use local (function) scope for static arrays --- tsfc/loopy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index e21c1f280b..a8619e3142 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -87,7 +87,7 @@ def generate(impero_c, args, precision, kernel_name="loopy_kernel", index_names= for i, temp in enumerate(impero_c.temporaries): name = "t%d" % i if isinstance(temp, gem.Constant): - data.append(lp.TemporaryVariable(name, shape=temp.shape, dtype=temp.array.dtype, initializer=temp.array, address_space=lp.AddressSpace.GLOBAL, read_only=True)) + data.append(lp.TemporaryVariable(name, shape=temp.shape, dtype=temp.array.dtype, initializer=temp.array, address_space=lp.AddressSpace.LOCAL, read_only=True)) else: shape = tuple([i.extent for i in ctx.indices[temp]]) + temp.shape data.append(lp.TemporaryVariable(name, shape=shape, dtype=numpy.float64, initializer=None, address_space=lp.AddressSpace.LOCAL, read_only=False)) From 28ef43b2d7131b04cc8191d9157007d237fb0854 Mon Sep 17 00:00:00 2001 From: cyruscycheng21 Date: Fri, 12 Apr 2019 19:55:21 +0100 Subject: [PATCH 520/809] reconstruct tpc as hypercubes for dpc and s --- tsfc/fiatinterface.py | 10 ++++++++++ tsfc/finatinterface.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index a9b04ce8cc..a4af66ed20 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -139,6 +139,16 @@ def convert_finiteelement(element, vector_is_mixed): lmbda = FIAT.GaussLegendre else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) + elif element.family() == "DPC": + if element.get_spatial_dimension() == 2: + element = element.reconstruct(cell=ufl.hypercube(2)) + elif element.get_spatial_dimension() == 3: + element = element.reconstruct(cell=ufl.hypercube(3)) + elif element.family() == "S": + if element.get_spatial_dimension() == 2: + element = element.reconstruct(cell=ufl.hypercube(2)) + elif element.get_spatial_dimension() == 3: + element = element.reconstruct(cell=ufl.hypercube(3)) return lmbda(cell, element.degree()) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 22e3d9136f..267b1635cf 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -140,6 +140,16 @@ def convert_finiteelement(element, **kwargs): lmbda = finat.GaussLegendre else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) + elif element.family() == "DPC": + if element.get_spatial_dimension() == 2: + element = element.reconstruct(cell=ufl.hypercube(2)) + elif element.get_spatial_dimension() == 3: + element = element.reconstruct(cell=ufl.hypercube(3)) + elif element.family() == "S": + if element.get_spatial_dimension() == 2: + element = element.reconstruct(cell=ufl.hypercube(2)) + elif element.get_spatial_dimension() == 3: + element = element.reconstruct(cell=ufl.hypercube(3)) return lmbda(cell, element.degree()), set() From e842d702771eb428873e8ad12a2f29198a84370c Mon Sep 17 00:00:00 2001 From: cyruscycheng21 Date: Fri, 12 Apr 2019 20:12:51 +0100 Subject: [PATCH 521/809] reconstruct tpc for dpc and s --- tsfc/fiatinterface.py | 11 ++++++----- tsfc/finatinterface.py | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index a4af66ed20..e0c70f780c 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -42,7 +42,6 @@ "FacetBubble": FIAT.FacetBubble, "Crouzeix-Raviart": FIAT.CrouzeixRaviart, "Discontinuous Lagrange": FIAT.DiscontinuousLagrange, - "DPC": FIAT.DPC, "Discontinuous Taylor": FIAT.DiscontinuousTaylor, "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, "Gauss-Lobatto-Legendre": FIAT.GaussLobattoLegendre, @@ -61,6 +60,8 @@ "RTCF": None, "NCE": None, "NCF": None, + "DPC": FIAT.DPC, + "S": FIAT.Serendipity } """A :class:`.dict` mapping UFL element family names to their FIAT-equivalent constructors. If the value is ``None``, the UFL @@ -140,14 +141,14 @@ def convert_finiteelement(element, vector_is_mixed): else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) elif element.family() == "DPC": - if element.get_spatial_dimension() == 2: + if element.cell().geometric_dimension() == 2: element = element.reconstruct(cell=ufl.hypercube(2)) - elif element.get_spatial_dimension() == 3: + elif element.cell.geometric_dimension() == 3: element = element.reconstruct(cell=ufl.hypercube(3)) elif element.family() == "S": - if element.get_spatial_dimension() == 2: + if element.cell().geometric_dimension() == 2: element = element.reconstruct(cell=ufl.hypercube(2)) - elif element.get_spatial_dimension() == 3: + elif element.cell().geometric_dimension() == 3: element = element.reconstruct(cell=ufl.hypercube(3)) return lmbda(cell, element.degree()) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 267b1635cf..d10c372936 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -42,7 +42,6 @@ "FacetBubble": finat.FacetBubble, "Crouzeix-Raviart": finat.CrouzeixRaviart, "Discontinuous Lagrange": finat.DiscontinuousLagrange, - "DPC": finat.DPC, "Discontinuous Raviart-Thomas": lambda c, d: finat.DiscontinuousElement(finat.RaviartThomas(c, d)), "Discontinuous Taylor": finat.DiscontinuousTaylor, "Gauss-Legendre": finat.GaussLegendre, @@ -66,6 +65,8 @@ "NCE": None, "NCF": None, "Real": finat.DiscontinuousLagrange, + "DPC": finat.DPC, + "S": finat.Serendipity } """A :class:`.dict` mapping UFL element family names to their FInAT-equivalent constructors. If the value is ``None``, the UFL @@ -141,14 +142,14 @@ def convert_finiteelement(element, **kwargs): else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) elif element.family() == "DPC": - if element.get_spatial_dimension() == 2: + if element.cell().geometric_dimension() == 2: element = element.reconstruct(cell=ufl.hypercube(2)) - elif element.get_spatial_dimension() == 3: + elif element.cell().geometric_dimension() == 3: element = element.reconstruct(cell=ufl.hypercube(3)) elif element.family() == "S": - if element.get_spatial_dimension() == 2: + if element.cell().geometric_dimension() == 2: element = element.reconstruct(cell=ufl.hypercube(2)) - elif element.get_spatial_dimension() == 3: + elif element.cell().geometric_dimension() == 3: element = element.reconstruct(cell=ufl.hypercube(3)) return lmbda(cell, element.degree()), set() From 42dc8405dca4e8893d955466d7a044dc150a79b0 Mon Sep 17 00:00:00 2001 From: cyruscycheng21 Date: Mon, 29 Apr 2019 12:32:40 +0100 Subject: [PATCH 522/809] fix flake8 rules, add tpc to quad transformation --- tsfc/finatinterface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index d10c372936..7f76ba50d4 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -143,14 +143,14 @@ def convert_finiteelement(element, **kwargs): raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) elif element.family() == "DPC": if element.cell().geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.hypercube(2)) + element = element.reconstruct(cell=ufl.cell.hypercube(2)) elif element.cell().geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.hypercube(3)) + element = element.reconstruct(cell=ufl.cell.hypercube(3)) elif element.family() == "S": if element.cell().geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.hypercube(2)) + element = element.reconstruct(cell=ufl.cell.hypercube(2)) elif element.cell().geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.hypercube(3)) + element = element.reconstruct(cell=ufl.cell.hypercube(3)) return lmbda(cell, element.degree()), set() From b2339c70a3f84e929fccae6052246996160f7c4b Mon Sep 17 00:00:00 2001 From: David Ham Date: Tue, 30 Apr 2019 11:11:45 +0100 Subject: [PATCH 523/809] Switch to spectral representation by default --- tsfc/fiatinterface.py | 2 +- tsfc/finatinterface.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index e0c70f780c..65426c796c 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -124,7 +124,7 @@ def convert_finiteelement(element, vector_is_mixed): kind = element.variant() if kind is None: - kind = 'equispaced' # default variant + kind = 'spectral' if element.cell().cellname() == 'interval' else 'equispaced' # default variant if element.family() == "Lagrange": if kind == 'equispaced': diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index d371605ff8..b3a30b338c 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -124,7 +124,7 @@ def convert_finiteelement(element, **kwargs): kind = element.variant() if kind is None: - kind = 'equispaced' # default variant + kind = 'spectral' if element.cell().cellname() == 'interval' else 'equispaced' # default variant if element.family() == "Lagrange": if kind == 'equispaced': From 8e95277d1c7867675c90c392c1e430dc353cea94 Mon Sep 17 00:00:00 2001 From: David Ham Date: Thu, 2 May 2019 13:51:26 +0100 Subject: [PATCH 524/809] Remove erroneous element variant check --- tsfc/finatinterface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index b3a30b338c..45551c17ce 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -140,7 +140,6 @@ def convert_finiteelement(element, **kwargs): else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) elif element.family() == "Discontinuous Lagrange": - kind = element.variant() or 'equispaced' if kind == 'equispaced': lmbda = finat.DiscontinuousLagrange elif kind == 'spectral' and element.cell().cellname() == 'interval': From 0adbacb73bf66505510e10e989882d521995178c Mon Sep 17 00:00:00 2001 From: David Ham Date: Thu, 9 May 2019 10:28:18 +0100 Subject: [PATCH 525/809] Change default variant in tests --- tests/test_create_fiat_element.py | 4 ++-- tests/test_create_finat_element.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index c898973329..608a9c6613 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -61,8 +61,8 @@ def test_tensor_prod_simple(ufl_A, ufl_B): @pytest.mark.parametrize(('family', 'expected_cls'), - [('P', FIAT.Lagrange), - ('DP', FIAT_DiscontinuousLagrange)]) + [('P', FIAT.GaussLobattoLegendre), + ('DP', FIAT.GaussLegendre)]) def test_interval_variant_default(family, expected_cls): ufl_element = ufl.FiniteElement(family, ufl.interval, 3) assert isinstance(create_element(ufl_element), expected_cls) diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index e135576d02..f40255545e 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -69,8 +69,8 @@ def test_tensor_prod_simple(ufl_A, ufl_B): @pytest.mark.parametrize(('family', 'expected_cls'), - [('P', finat.Lagrange), - ('DP', finat.DiscontinuousLagrange)]) + [('P', finat.GaussLobattoLegendre), + ('DP', finat.GaussLegendre)]) def test_interval_variant_default(family, expected_cls): ufl_element = ufl.FiniteElement(family, ufl.interval, 3) assert isinstance(create_element(ufl_element), expected_cls) From f523c9eb8b9f84e412889df2d9b580c693d7c7dc Mon Sep 17 00:00:00 2001 From: Michal Habera Date: Mon, 13 May 2019 23:31:14 +0200 Subject: [PATCH 526/809] Defer loopy import --- tsfc/driver.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 87acd806fd..7c5064e176 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -28,9 +28,6 @@ from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex -import tsfc.kernel_interface.firedrake as firedrake_interface_coffee -import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy - # To handle big forms. The various transformations might need a deeper stack sys.setrecursionlimit(3000) @@ -90,8 +87,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co parameters = _ if interface is None: if coffee: + import tsfc.kernel_interface.firedrake as firedrake_interface_coffee interface = firedrake_interface_coffee.KernelBuilder else: + import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy interface = firedrake_interface_loopy.KernelBuilder # Remove these here, they're handled below. @@ -293,8 +292,10 @@ def compile_expression_at_points(expression, points, coordinates, interface=None # Initialise kernel builder if interface is None: if coffee: + import tsfc.kernel_interface.firedrake as firedrake_interface_coffee interface = firedrake_interface_coffee.ExpressionKernelBuilder else: + import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy interface = firedrake_interface_loopy.ExpressionKernelBuilder builder = interface(parameters["scalar_type"]) From fdf70b88d22d68e9184ee98152f8d58e454007c6 Mon Sep 17 00:00:00 2001 From: Michal Habera Date: Tue, 14 May 2019 00:16:47 +0200 Subject: [PATCH 527/809] Add spoiled import --- tests/test_flexibly_indexed.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_flexibly_indexed.py b/tests/test_flexibly_indexed.py index 8390bcebcf..0bc833b02a 100644 --- a/tests/test_flexibly_indexed.py +++ b/tests/test_flexibly_indexed.py @@ -5,6 +5,7 @@ import gem import tsfc +import tsfc.coffee parameters = tsfc.coffee.Bunch() From e47337834e863a6588c4d1e2c6fcc547d8abcf65 Mon Sep 17 00:00:00 2001 From: Michal Habera Date: Tue, 14 May 2019 17:39:56 +0200 Subject: [PATCH 528/809] Comment delayed import --- tsfc/driver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7c5064e176..70cf67d3d6 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -90,6 +90,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co import tsfc.kernel_interface.firedrake as firedrake_interface_coffee interface = firedrake_interface_coffee.KernelBuilder else: + # Delayed import, loopy is a runtime dependency import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy interface = firedrake_interface_loopy.KernelBuilder @@ -295,6 +296,7 @@ def compile_expression_at_points(expression, points, coordinates, interface=None import tsfc.kernel_interface.firedrake as firedrake_interface_coffee interface = firedrake_interface_coffee.ExpressionKernelBuilder else: + # Delayed import, loopy is a runtime dependency import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy interface = firedrake_interface_loopy.ExpressionKernelBuilder From 115e1c488282c29c2a815dd23e68dc7e59e0a4cc Mon Sep 17 00:00:00 2001 From: Chris Eldred Date: Thu, 20 Jun 2019 14:57:10 +0100 Subject: [PATCH 529/809] add l2 pullbacks to discontinuous elements --- tsfc/fiatinterface.py | 10 +++++++--- tsfc/finatinterface.py | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index e0c70f780c..7ba8bcd9ff 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -61,7 +61,11 @@ "NCE": None, "NCF": None, "DPC": FIAT.DPC, - "S": FIAT.Serendipity + "S": FIAT.Serendipity, + "DPC L2": FIAT.DPC, + "Discontinuous Lagrange L2": FIAT.DiscontinuousLagrange, + "Gauss-Legendre L2": FIAT.GaussLegendre, + "DQ L2": None, } """A :class:`.dict` mapping UFL element family names to their FIAT-equivalent constructors. If the value is ``None``, the UFL @@ -133,14 +137,14 @@ def convert_finiteelement(element, vector_is_mixed): lmbda = FIAT.GaussLobattoLegendre else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() == "Discontinuous Lagrange": + elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: if kind == 'equispaced': lmbda = FIAT.DiscontinuousLagrange elif kind == 'spectral' and element.cell().cellname() == 'interval': lmbda = FIAT.GaussLegendre else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() == "DPC": + elif element.family() in ["DPC", "DPC L2"]: if element.cell().geometric_dimension() == 2: element = element.reconstruct(cell=ufl.hypercube(2)) elif element.cell.geometric_dimension() == 3: diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index d371605ff8..b079dcf937 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -66,7 +66,11 @@ "NCF": None, "Real": finat.DiscontinuousLagrange, "DPC": finat.DPC, - "S": finat.Serendipity + "S": finat.Serendipity, + "DPC L2": finat.DPC, + "Discontinuous Lagrange L2": finat.DiscontinuousLagrange, + "Gauss-Legendre L2": finat.GaussLegendre, + "DQ L2": None, } """A :class:`.dict` mapping UFL element family names to their FInAT-equivalent constructors. If the value is ``None``, the UFL @@ -139,7 +143,7 @@ def convert_finiteelement(element, **kwargs): return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction), deps else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() == "Discontinuous Lagrange": + elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: kind = element.variant() or 'equispaced' if kind == 'equispaced': lmbda = finat.DiscontinuousLagrange @@ -153,7 +157,7 @@ def convert_finiteelement(element, **kwargs): return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction, continuous=False), deps else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() == "DPC": + elif element.family() == ["DPC", "DPC L2"]: if element.cell().geometric_dimension() == 2: element = element.reconstruct(cell=ufl.cell.hypercube(2)) elif element.cell().geometric_dimension() == 3: From 5abc9492b3aae658d235e4c2b7dd163f0f90ca6e Mon Sep 17 00:00:00 2001 From: Chris Eldred Date: Thu, 20 Jun 2019 15:20:21 +0100 Subject: [PATCH 530/809] add testing --- tests/test_create_fiat_element.py | 17 ++++++++++++++--- tests/test_create_finat_element.py | 17 ++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index c898973329..8f3f6ddaac 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -29,7 +29,7 @@ def test_triangle_basic(ufl_element): assert isinstance(element, supported_elements[ufl_element.family()]) -@pytest.fixture(params=["CG", "DG"], scope="module") +@pytest.fixture(params=["CG", "DG", "DG L2"], scope="module") def tensor_name(request): return request.param @@ -62,7 +62,8 @@ def test_tensor_prod_simple(ufl_A, ufl_B): @pytest.mark.parametrize(('family', 'expected_cls'), [('P', FIAT.Lagrange), - ('DP', FIAT_DiscontinuousLagrange)]) + ('DP', FIAT_DiscontinuousLagrange), + ('DP L2', FIAT_DiscontinuousLagrange)]) def test_interval_variant_default(family, expected_cls): ufl_element = ufl.FiniteElement(family, ufl.interval, 3) assert isinstance(create_element(ufl_element), expected_cls) @@ -72,7 +73,9 @@ def test_interval_variant_default(family, expected_cls): [('P', 'equispaced', FIAT.Lagrange), ('P', 'spectral', FIAT.GaussLobattoLegendre), ('DP', 'equispaced', FIAT_DiscontinuousLagrange), - ('DP', 'spectral', FIAT.GaussLegendre)]) + ('DP', 'spectral', FIAT.GaussLegendre), + ('DP L2', 'equispaced', FIAT_DiscontinuousLagrange), + ('DP L2', 'spectral', FIAT.GaussLegendre)]) def test_interval_variant(family, variant, expected_cls): ufl_element = ufl.FiniteElement(family, ufl.interval, 3, variant=variant) assert isinstance(create_element(ufl_element), expected_cls) @@ -83,6 +86,10 @@ def test_triangle_variant_spectral_fail(): with pytest.raises(ValueError): create_element(ufl_element) +def test_triangle_variant_spectral_fail_l2(): + ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') + with pytest.raises(ValueError): + create_element(ufl_element) def test_quadrilateral_variant_spectral_q(): element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) @@ -95,6 +102,10 @@ def test_quadrilateral_variant_spectral_dq(): assert isinstance(element.element.A, FIAT.GaussLegendre) assert isinstance(element.element.B, FIAT.GaussLegendre) +def test_quadrilateral_variant_spectral_dq_l2(): + element = create_element(ufl.FiniteElement('DQ L2', ufl.quadrilateral, 1, variant='spectral')) + assert isinstance(element.element.A, FIAT.GaussLegendre) + assert isinstance(element.element.B, FIAT.GaussLegendre) def test_quadrilateral_variant_spectral_rtcf(): element = create_element(ufl.FiniteElement('RTCF', ufl.quadrilateral, 2, variant='spectral')) diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index e135576d02..e1ccedd2ed 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -39,7 +39,7 @@ def test_triangle_vector(ufl_element, ufl_vector_element): assert scalar == vector.base_element -@pytest.fixture(params=["CG", "DG"]) +@pytest.fixture(params=["CG", "DG", "DG L2"]) def tensor_name(request): return request.param @@ -70,7 +70,8 @@ def test_tensor_prod_simple(ufl_A, ufl_B): @pytest.mark.parametrize(('family', 'expected_cls'), [('P', finat.Lagrange), - ('DP', finat.DiscontinuousLagrange)]) + ('DP', finat.DiscontinuousLagrange), + ('DP L2', finat.DiscontinuousLagrange)]) def test_interval_variant_default(family, expected_cls): ufl_element = ufl.FiniteElement(family, ufl.interval, 3) assert isinstance(create_element(ufl_element), expected_cls) @@ -80,7 +81,9 @@ def test_interval_variant_default(family, expected_cls): [('P', 'equispaced', finat.Lagrange), ('P', 'spectral', finat.GaussLobattoLegendre), ('DP', 'equispaced', finat.DiscontinuousLagrange), - ('DP', 'spectral', finat.GaussLegendre)]) + ('DP', 'spectral', finat.GaussLegendre), + ('DP L2', 'equispaced', finat.DiscontinuousLagrange), + ('DP L2', 'spectral', finat.GaussLegendre)]) def test_interval_variant(family, variant, expected_cls): ufl_element = ufl.FiniteElement(family, ufl.interval, 3, variant=variant) assert isinstance(create_element(ufl_element), expected_cls) @@ -91,6 +94,10 @@ def test_triangle_variant_spectral_fail(): with pytest.raises(ValueError): create_element(ufl_element) +def test_triangle_variant_spectral_fail_l2(): + ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') + with pytest.raises(ValueError): + create_element(ufl_element) def test_quadrilateral_variant_spectral_q(): element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) @@ -103,6 +110,10 @@ def test_quadrilateral_variant_spectral_dq(): assert isinstance(element.product.factors[0], finat.GaussLegendre) assert isinstance(element.product.factors[1], finat.GaussLegendre) +def test_quadrilateral_variant_spectral_dq_l2(): + element = create_element(ufl.FiniteElement('DQ L2', ufl.quadrilateral, 1, variant='spectral')) + assert isinstance(element.product.factors[0], finat.GaussLegendre) + assert isinstance(element.product.factors[1], finat.GaussLegendre) def test_cache_hit(ufl_element): A = create_element(ufl_element) From 8c77c1f5ffef706c1c774d77070f0b2f251a4a37 Mon Sep 17 00:00:00 2001 From: Chris Eldred Date: Thu, 20 Jun 2019 16:33:54 +0100 Subject: [PATCH 531/809] flake8 --- tests/test_create_fiat_element.py | 4 ++++ tests/test_create_finat_element.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 8f3f6ddaac..b47facdfb6 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -86,11 +86,13 @@ def test_triangle_variant_spectral_fail(): with pytest.raises(ValueError): create_element(ufl_element) + def test_triangle_variant_spectral_fail_l2(): ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') with pytest.raises(ValueError): create_element(ufl_element) + def test_quadrilateral_variant_spectral_q(): element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) assert isinstance(element.element.A, FIAT.GaussLobattoLegendre) @@ -102,11 +104,13 @@ def test_quadrilateral_variant_spectral_dq(): assert isinstance(element.element.A, FIAT.GaussLegendre) assert isinstance(element.element.B, FIAT.GaussLegendre) + def test_quadrilateral_variant_spectral_dq_l2(): element = create_element(ufl.FiniteElement('DQ L2', ufl.quadrilateral, 1, variant='spectral')) assert isinstance(element.element.A, FIAT.GaussLegendre) assert isinstance(element.element.B, FIAT.GaussLegendre) + def test_quadrilateral_variant_spectral_rtcf(): element = create_element(ufl.FiniteElement('RTCF', ufl.quadrilateral, 2, variant='spectral')) assert isinstance(element.element._elements[0].A, FIAT.GaussLobattoLegendre) diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index e1ccedd2ed..04b6a2a602 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -94,11 +94,13 @@ def test_triangle_variant_spectral_fail(): with pytest.raises(ValueError): create_element(ufl_element) + def test_triangle_variant_spectral_fail_l2(): ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') with pytest.raises(ValueError): create_element(ufl_element) + def test_quadrilateral_variant_spectral_q(): element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) assert isinstance(element.product.factors[0], finat.GaussLobattoLegendre) @@ -110,11 +112,13 @@ def test_quadrilateral_variant_spectral_dq(): assert isinstance(element.product.factors[0], finat.GaussLegendre) assert isinstance(element.product.factors[1], finat.GaussLegendre) + def test_quadrilateral_variant_spectral_dq_l2(): element = create_element(ufl.FiniteElement('DQ L2', ufl.quadrilateral, 1, variant='spectral')) assert isinstance(element.product.factors[0], finat.GaussLegendre) assert isinstance(element.product.factors[1], finat.GaussLegendre) + def test_cache_hit(ufl_element): A = create_element(ufl_element) B = create_element(ufl_element) From eee427ead4daf1bc78b516b0576aba7530df1cef Mon Sep 17 00:00:00 2001 From: David Ham Date: Thu, 20 Jun 2019 16:42:16 +0100 Subject: [PATCH 532/809] atone for earlier sins --- tsfc/fiatinterface.py | 11 +---------- tsfc/finatinterface.py | 11 +---------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index e0c70f780c..00eb402df7 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -140,16 +140,7 @@ def convert_finiteelement(element, vector_is_mixed): lmbda = FIAT.GaussLegendre else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() == "DPC": - if element.cell().geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.hypercube(2)) - elif element.cell.geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.hypercube(3)) - elif element.family() == "S": - if element.cell().geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.hypercube(2)) - elif element.cell().geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.hypercube(3)) + return lmbda(cell, element.degree()) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index d371605ff8..805f670581 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -153,16 +153,7 @@ def convert_finiteelement(element, **kwargs): return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction, continuous=False), deps else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() == "DPC": - if element.cell().geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.cell.hypercube(2)) - elif element.cell().geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.cell.hypercube(3)) - elif element.family() == "S": - if element.cell().geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.cell.hypercube(2)) - elif element.cell().geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.cell.hypercube(3)) + return lmbda(cell, element.degree()), set() From ad0f2fb41a4d49765b27d28898ab76537dab5c4f Mon Sep 17 00:00:00 2001 From: Thomas Gibson Date: Wed, 21 Aug 2019 11:20:48 +0100 Subject: [PATCH 533/809] Remove hypercube reconstruction --- tsfc/fiatinterface.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 54e00c78a1..a4b6f3603c 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -144,17 +144,6 @@ def convert_finiteelement(element, vector_is_mixed): lmbda = FIAT.GaussLegendre else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() in ["DPC", "DPC L2"]: - if element.cell().geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.hypercube(2)) - elif element.cell.geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.hypercube(3)) - elif element.family() == "S": - if element.cell().geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.hypercube(2)) - elif element.cell().geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.hypercube(3)) - return lmbda(cell, element.degree()) From e9664e06c33d4d865501ce4ccd9ca03766f681c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Tue, 20 Nov 2018 15:35:45 +0100 Subject: [PATCH 534/809] Revert "resolve compatibility issues with Firedrake master" This reverts commit 8f9ced4322b13c7f34fa1f90ffc240a00d85426e. --- tsfc/coffee.py | 7 ++----- tsfc/kernel_interface/firedrake.py | 7 ++----- tsfc/parameters.py | 3 --- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 5c4fd65a92..0629f062e3 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -15,15 +15,12 @@ from tsfc.parameters import is_complex -# Satisfy import demands until complex branch is merged in Firedrake -from tsfc.parameters import SCALAR_TYPE - class Bunch(object): pass -def generate(impero_c, index_names, precision, scalar_type=None): +def generate(impero_c, index_names, precision, scalar_type): """Generates COFFEE code. :arg impero_c: ImperoC tuple with Impero AST and other data @@ -37,7 +34,7 @@ def generate(impero_c, index_names, precision, scalar_type=None): params.indices = impero_c.indices params.precision = precision params.epsilon = 10.0 * eval("1e-%d" % precision) - params.scalar_type = scalar_type or SCALAR_TYPE + params.scalar_type = scalar_type params.names = {} for i, temp in enumerate(impero_c.temporaries): diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index a1d77b0e17..30f16b5c19 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -60,14 +60,11 @@ def __init__(self, ast=None, integral_type=None, oriented=False, class KernelBuilderBase(_KernelBuilderBase): - def __init__(self, scalar_type=None, interior_facet=False): + def __init__(self, scalar_type, interior_facet=False): """Initialise a kernel builder. :arg interior_facet: kernel accesses two cells """ - if scalar_type is None: - from tsfc.parameters import SCALAR_TYPE - scalar_type = SCALAR_TYPE super(KernelBuilderBase, self).__init__(scalar_type=scalar_type, interior_facet=interior_facet) @@ -173,7 +170,7 @@ def construct_kernel(self, return_arg, impero_c, precision, index_names): class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None, + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont_split=()): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index df9d66bbff..8cd98be42b 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -21,9 +21,6 @@ "precision": numpy.finfo(numpy.dtype("double")).precision, } -# Satisfy import demands until complex branch is merged in Firedrake -SCALAR_TYPE = PARAMETERS["scalar_type"] - def default_parameters(): return PARAMETERS.copy() From 7d9d9bb74b9b0c474d4c300fdf7d010d255fb822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Mon, 26 Aug 2019 22:13:24 +0200 Subject: [PATCH 535/809] Extirpate SCALAR_TYPE from loo.py codegen --- tsfc/kernel_interface/firedrake_loopy.py | 20 ++++++++++---------- tsfc/loopy.py | 7 ++----- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index b9bacc0184..bd6fa0ee8c 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -60,14 +60,11 @@ def __init__(self, ast=None, integral_type=None, oriented=False, class KernelBuilderBase(_KernelBuilderBase): - def __init__(self, scalar_type=None, interior_facet=False): + def __init__(self, scalar_type, interior_facet=False): """Initialise a kernel builder. :arg interior_facet: kernel accesses two cells """ - if scalar_type is None: - from tsfc.parameters import SCALAR_TYPE - scalar_type = SCALAR_TYPE super(KernelBuilderBase, self).__init__(scalar_type=scalar_type, interior_facet=interior_facet) @@ -119,8 +116,8 @@ def create_element(self, element, **kwargs): class ExpressionKernelBuilder(KernelBuilderBase): """Builds expression kernels for UFL interpolation in Firedrake.""" - def __init__(self, scalar_type=None): - super(ExpressionKernelBuilder, self).__init__(scalar_type=None) + def __init__(self, scalar_type): + super(ExpressionKernelBuilder, self).__init__(scalar_type=scalar_type) self.oriented = False self.cell_sizes = False @@ -167,14 +164,16 @@ def construct_kernel(self, return_arg, impero_c, precision, index_names): for name_, shape in self.tabulations: args.append(lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape)) - loopy_kernel = generate_loopy(impero_c, args, precision, "expression_kernel", index_names, self.scalar_type) - return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, self.coefficients, self.tabulations) + loopy_kernel = generate_loopy(impero_c, args, precision, self.scalar_type, + "expression_kernel", index_names) + return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, + self.coefficients, self.tabulations) class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None, dont_split=()): + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont_split=()): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) @@ -290,7 +289,8 @@ def construct_kernel(self, name, impero_c, precision, index_names, quadrature_ru args.append(lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape)) self.kernel.quadrature_rule = quadrature_rule - self.kernel.ast = generate_loopy(impero_c, args, precision, name, index_names, self.scalar_type) + self.kernel.ast = generate_loopy(impero_c, args, precision, + self.scalar_type, name, index_names) return self.kernel def construct_empty_kernel(self, name): diff --git a/tsfc/loopy.py b/tsfc/loopy.py index a8619e3142..2df1e379c6 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -19,9 +19,6 @@ from tsfc.parameters import is_complex -# Satisfy import demands until complex branch is merged in Firedrake -from tsfc.parameters import SCALAR_TYPE - class LoopyContext(object): def __init__(self): @@ -64,7 +61,7 @@ def active_inames(self): return frozenset([i.name for i in self.active_indices.values()]) -def generate(impero_c, args, precision, kernel_name="loopy_kernel", index_names=[], scalar_type=None): +def generate(impero_c, args, precision, scalar_type, kernel_name="loopy_kernel", index_names=[]): """Generates loopy code. :arg impero_c: ImperoC tuple with Impero AST and other data @@ -79,7 +76,7 @@ def generate(impero_c, args, precision, kernel_name="loopy_kernel", index_names= ctx.indices = impero_c.indices ctx.index_names = defaultdict(lambda: "i", index_names) ctx.precision = precision - ctx.scalar_type = scalar_type or SCALAR_TYPE + ctx.scalar_type = scalar_type ctx.epsilon = 10.0 ** (-precision) # Create arguments From e793323d6bafcc29a55391f9eac5d3dbd20ff9c0 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 18 Oct 2019 10:06:17 +0100 Subject: [PATCH 536/809] Add option to build diagonal of rank-2 element tensor Useful for matrix-free multigrid with diagonal smoothing. --- tsfc/driver.py | 18 ++++++++++++++---- tsfc/kernel_interface/firedrake.py | 21 ++++++++++++++++++--- tsfc/kernel_interface/firedrake_loopy.py | 22 +++++++++++++++++++--- tsfc/kernel_interface/ufc.py | 4 +++- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 70cf67d3d6..3da8ba9198 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -32,13 +32,14 @@ sys.setrecursionlimit(3000) -def compile_form(form, prefix="form", parameters=None, interface=None, coffee=True): +def compile_form(form, prefix="form", parameters=None, interface=None, coffee=True, diagonal=False): """Compiles a UFL form into a set of assembly kernels. :arg form: UFL form :arg prefix: kernel name will start with this string :arg parameters: parameters object :arg coffee: compile coffee kernel instead of loopy kernel + :arg diagonal: Are we building a kernel for the diagonal of a rank-2 element tensor? :returns: list of kernels """ cpu_time = time.time() @@ -60,7 +61,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr kernels = [] for integral_data in fd.integral_data: start = time.time() - kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface, coffee=coffee) + kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface, coffee=coffee, diagonal=diagonal) if kernel is not None: kernels.append(kernel) logger.info(GREEN % "compile_integral finished in %g seconds.", time.time() - start) @@ -69,7 +70,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr return kernels -def compile_integral(integral_data, form_data, prefix, parameters, interface, coffee): +def compile_integral(integral_data, form_data, prefix, parameters, interface, coffee, *, diagonal=False): """Compiles a UFL integral into an assembly kernel. :arg integral_data: UFL integral data @@ -77,6 +78,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co :arg prefix: kernel name will start with this string :arg parameters: parameters object :arg interface: backend module for the kernel interface + :arg diagonal: Are we building a kernel for the diagonal of a rank-2 element tensor? :returns: a kernel constructed by the kernel interface """ if parameters is None: @@ -118,9 +120,17 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co domain_numbering = form_data.original_form.domain_numbering() builder = interface(integral_type, integral_data.subdomain_id, domain_numbering[integral_data.domain], - parameters["scalar_type"]) + parameters["scalar_type"], + diagonal=diagonal) argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() for arg in arguments) + if diagonal: + # Error checking occurs in the builder constructor. + # Diagonal assembly is obtained by using the test indices for + # the trial space as well. + a, _ = argument_multiindices + argument_multiindices = (a, a) + return_variables = builder.set_arguments(arguments, argument_multiindices) builder.set_coordinates(mesh) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 30f16b5c19..7906e39c4e 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -171,12 +171,13 @@ class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, - dont_split=()): + dont_split=(), diagonal=False): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) + self.diagonal = diagonal self.local_tensor = None self.coordinates_arg = None self.coefficient_args = [] @@ -204,7 +205,8 @@ def set_arguments(self, arguments, multiindices): :returns: GEM expression representing the return variable """ self.local_tensor, expressions = prepare_arguments( - arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet) + arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet, + diagonal=self.diagonal) return expressions def set_coordinates(self, domain): @@ -368,7 +370,7 @@ def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): return funarg, expression -def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False): +def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. @@ -376,6 +378,7 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False :arg arguments: UFL Arguments :arg multiindices: Argument multiindices :arg interior_facet: interior facet integral? + :arg diagonal: Are we assembling the diagonal of a rank-2 element tensor? :returns: (funarg, expression) funarg - :class:`coffee.Decl` function argument expressions - GEM expressions referring to the argument @@ -393,6 +396,18 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False elements = tuple(create_element(arg.ufl_element()) for arg in arguments) shapes = tuple(element.index_shape for element in elements) + if diagonal: + if len(arguments) != 2: + raise ValueError("Diagonal only for 2-forms") + try: + element, = set(elements) + except ValueError: + raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") + + elements = (element, ) + shapes = tuple(element.index_shape for element in elements) + multiindices = multiindices[:1] + def expression(restricted): return gem.Indexed(gem.reshape(restricted, *shapes), tuple(chain(*multiindices))) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index bd6fa0ee8c..4eb86afe68 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -173,12 +173,14 @@ def construct_kernel(self, return_arg, impero_c, precision, index_names): class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont_split=()): + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont_split=(), + diagonal=False): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) + self.diagonal = diagonal self.local_tensor = None self.coordinates_arg = None self.coefficient_args = [] @@ -206,7 +208,8 @@ def set_arguments(self, arguments, multiindices): :returns: GEM expression representing the return variable """ self.local_tensor, expressions = prepare_arguments( - arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet) + arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet, + diagonal=self.diagonal) return expressions def set_coordinates(self, domain): @@ -341,7 +344,7 @@ def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): return funarg, expression -def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False): +def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. @@ -349,6 +352,7 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False :arg arguments: UFL Arguments :arg multiindices: Argument multiindices :arg interior_facet: interior facet integral? + :arg diagonal: Are we assembling the diagonal of a rank-2 element tensor? :returns: (funarg, expression) funarg - :class:`loopy.GlobalArg` function argument expressions - GEM expressions referring to the argument @@ -367,6 +371,18 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False elements = tuple(create_element(arg.ufl_element()) for arg in arguments) shapes = tuple(element.index_shape for element in elements) + if diagonal: + if len(arguments) != 2: + raise ValueError("Diagonal only for 2-forms") + try: + element, = set(elements) + except ValueError: + raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") + + elements = (element, ) + shapes = tuple(element.index_shape for element in elements) + multiindices = multiindices[:1] + def expression(restricted): return gem.Indexed(gem.reshape(restricted, *shapes), tuple(chain(*multiindices))) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 2237964676..6335e92c61 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -22,8 +22,10 @@ class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None): + def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None, diagonal=False): """Initialise a kernel builder.""" + if diagonal: + raise NotImplementedError("Assembly of diagonal not implemented yet, sorry") super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) self.integral_type = integral_type From 399aa4bae1104ae0069827cabc27e3b938ec332a Mon Sep 17 00:00:00 2001 From: David Ham Date: Thu, 7 Nov 2019 17:19:40 -0300 Subject: [PATCH 537/809] fix tests --- tests/test_create_fiat_element.py | 2 +- tests/test_create_finat_element.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index c1bb563742..0271e8ba9c 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -63,7 +63,7 @@ def test_tensor_prod_simple(ufl_A, ufl_B): @pytest.mark.parametrize(('family', 'expected_cls'), [('P', FIAT.GaussLobattoLegendre), ('DP', FIAT.GaussLegendre), - ('DP L2', FIAT_DiscontinuousLagrange)]) + ('DP L2', FIAT.GaussLegendre)]) def test_interval_variant_default(family, expected_cls): ufl_element = ufl.FiniteElement(family, ufl.interval, 3) assert isinstance(create_element(ufl_element), expected_cls) diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index 55e1780437..2f937a8677 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -71,7 +71,7 @@ def test_tensor_prod_simple(ufl_A, ufl_B): @pytest.mark.parametrize(('family', 'expected_cls'), [('P', finat.GaussLobattoLegendre), ('DP', finat.GaussLegendre), - ('DP L2', finat.DiscontinuousLagrange)]) + ('DP L2', finat.GaussLegendre)]) def test_interval_variant_default(family, expected_cls): ufl_element = ufl.FiniteElement(family, ufl.interval, 3) assert isinstance(create_element(ufl_element), expected_cls) From ddbcf1026c66184f21a3675d902147acb56b18a0 Mon Sep 17 00:00:00 2001 From: David Ham Date: Mon, 9 Dec 2019 14:55:15 +0000 Subject: [PATCH 538/809] remove spurious argument --- tsfc/driver.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 165f2b4f72..f252597406 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -257,7 +257,7 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, impero_c, parameters["precision"], index_names, quad_rule) -def compile_expression_at_points(expression, points, to_element, coordinates, interface=None, +def compile_expression_at_points(expression, points, coordinates, interface=None, parameters=None, coffee=True): """Compiles a UFL expression to be evaluated at compile-time known reference points. Useful for interpolating UFL expressions onto @@ -265,7 +265,6 @@ def compile_expression_at_points(expression, points, to_element, coordinates, in :arg expression: UFL expression :arg points: reference coordinates of the evaluation points - :arg to_element: UFL element to interpolate onto :arg coordinates: the coordinate function :arg interface: backend module for the kernel interface :arg parameters: parameters object From afbf86ace6f85370441da2124b3688df4412ade1 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Mon, 3 Feb 2020 13:49:34 -0600 Subject: [PATCH 539/809] physical coords --- tsfc/fem.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 5c226384d0..c76cd62348 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -23,6 +23,7 @@ from FIAT.reference_element import make_affine_mapping +from FIAT import ufc_simplex import gem from gem.node import traversal @@ -151,9 +152,19 @@ def jacobian_at(self, point): return map_expr_dag(context.translator, expr) def reference_normals(self): - return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) + if isinstance(self.interface.fiat_cell, FIAT.ufc_simplex) \ + and self.interface.fiat_cell.get_spatial_dimension() == 2: + return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) + else: + raise NotImplementedError("Only works for triangles for now") def physical_tangents(self): + if isinstance(self.interface.fiat_cell, FIAT.ufc_simplex) \ + and self.interface.fiat_cell.get_spatial_dimension() == 2: + return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) + else: + raise NotImplementedError("Only works for triangles for now") + rts = [self.interface.fiat_cell.compute_tangents(1, f)[0] for f in range(3)] jac = self.jacobian_at([1/3, 1/3]) @@ -181,6 +192,24 @@ def physical_edge_lengths(self): context = PointSetContext(**config) return map_expr_dag(context.translator, expr) + def physical_points(self, point_set): + """Converts point_set from reference to physical space""" + expr = SpatialCoordinate(self.mt.terminal.ufl_domain()) + if self.mt.restriction == '+': + expr = PositiveRestricted(expr) + elif self.mt.restriction == '-': + expr = NegativeRestricted(expr) + expr = preprocess_expression(expr) + config = {"point_set": point_set} + config.update(self.config) + context = PointSetContext(**config) + return map_expr_dag(context.translator, expr) + + def physical_vertices(self): + vs = PointSet(self.interface.fiat_cell.vertices) + return self.physical_points(vs) + + def needs_coordinate_mapping(element): """Does this UFL element require a CoordinateMapping for translation?""" From 48080c5af0c3a5f8839c9adbb85ff65cf62041ab Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 4 Feb 2020 11:02:59 +0000 Subject: [PATCH 540/809] Fix indexing of cell_size on interior face terms Fixes #204. --- tsfc/kernel_interface/common.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 72e825e48f..9b78817c3e 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -62,9 +62,10 @@ def cell_orientation(self, restriction): def cell_size(self, restriction): if not hasattr(self, "_cell_sizes"): raise RuntimeError("Haven't called set_cell_sizes") - f = {None: (), '+': (0, ), '-': (1, )}[restriction] - # cell_sizes expression must have been set up by now. - return gem.partial_indexed(self._cell_sizes, f) + if self.interior_facet: + return self._cell_sizes[{'+': 0, '-': 1}[restriction]] + else: + return self._cell_sizes def entity_number(self, restriction): """Facet or vertex number as a GEM index.""" From 8707cf9d101bffa6e8bc36bcfa2a80e0f0607ec4 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 4 Feb 2020 16:45:48 +0000 Subject: [PATCH 541/809] Add test --- tests/test_tsfc_204.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_tsfc_204.py diff --git a/tests/test_tsfc_204.py b/tests/test_tsfc_204.py new file mode 100644 index 0000000000..e41e9c0c89 --- /dev/null +++ b/tests/test_tsfc_204.py @@ -0,0 +1,33 @@ +from tsfc import compile_form +from ufl import (BrokenElement, Coefficient, FacetNormal, FiniteElement, + FunctionSpace, Mesh, MixedElement, VectorElement, as_matrix, + dot, dS, ds, dx, facet, grad, inner, outer, split, triangle) + + +def test_physically_mapped_facet(): + mesh = Mesh(VectorElement("P", triangle, 1)) + + # set up variational problem + U = FiniteElement("Morley", mesh.ufl_cell(), 2) + V = FiniteElement("P", mesh.ufl_cell(), 1) + R = FiniteElement("P", mesh.ufl_cell(), 1) + Vv = VectorElement(BrokenElement(V)) + Qhat = VectorElement(BrokenElement(V[facet])) + Vhat = VectorElement(V[facet]) + Z = FunctionSpace(mesh, MixedElement(U, Vv, Qhat, Vhat, R)) + + z = Coefficient(Z) + u, d, qhat, dhat, lam = split(z) + + s = FacetNormal(mesh) + trans = as_matrix([[1, 0], [0, 1]]) + mat = trans*grad(grad(u))*trans + outer(d, d) * u + J = (u**2*dx + + u**3*dx + + u**4*dx + + inner(mat, mat)*dx + + inner(grad(d), grad(d))*dx + + dot(s, d)**2*ds) + L_match = inner(qhat, dhat - d) + L = J + inner(lam, inner(d, d)-1)*dx + (L_match('+') + L_match('-'))*dS + L_match*ds + compile_form(L) From 473db143921014f4095e2d47d7304c40c94fbc39 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Wed, 5 Feb 2020 16:55:52 -0600 Subject: [PATCH 542/809] work on interface --- tsfc/fem.py | 11 ++++++++--- tsfc/finatinterface.py | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index c76cd62348..959494cfb5 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -31,7 +31,7 @@ from gem.unconcatenate import unconcatenate from gem.utils import cached_property -from finat.physically_mapped import PhysicalGeometry, PhysicallyMappedElement +from finat.physically_mapped import PhysicalGeometry, NeedsCoordinateMappingElement from finat.point_set import PointSet, PointSingleton from finat.quadrature import make_quadrature @@ -207,7 +207,12 @@ def physical_points(self, point_set): def physical_vertices(self): vs = PointSet(self.interface.fiat_cell.vertices) - return self.physical_points(vs) + pvs = self.physical_points(vs) + return pvs + # print(pvs) + # i = gem.Index() + # j = pvs.free_indices + # return gem.ComponentTensor(gem.Indexed(pvs, (i,)), j + (i,)) @@ -216,7 +221,7 @@ def needs_coordinate_mapping(element): if element.family() == 'Real': return False else: - return isinstance(create_element(element), PhysicallyMappedElement) + return isinstance(create_element(element), NeedsCoordinateMappingElement) class PointSetContext(ContextBase): diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 84c9fddf04..a8cd845781 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -71,6 +71,7 @@ "Discontinuous Lagrange L2": finat.DiscontinuousLagrange, "Gauss-Legendre L2": finat.GaussLegendre, "DQ L2": None, + "Sphys": finat.DirectSerendipity, } """A :class:`.dict` mapping UFL element family names to their FInAT-equivalent constructors. If the value is ``None``, the UFL From 400bd0342060941e28efd01fc0991a882d21078a Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Fri, 7 Feb 2020 12:56:42 -0600 Subject: [PATCH 543/809] Work on mapping point sets --- tsfc/fem.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 959494cfb5..e3ee0ff00b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -203,16 +203,13 @@ def physical_points(self, point_set): config = {"point_set": point_set} config.update(self.config) context = PointSetContext(**config) - return map_expr_dag(context.translator, expr) - + mapped = map_expr_dag(context.translator, expr) + indices = tuple(gem.Index() for _ in mapped.shape) + return gem.ComponentTensor(gem.Indexed(mapped, indices), point_set.indices + indices) + def physical_vertices(self): vs = PointSet(self.interface.fiat_cell.vertices) - pvs = self.physical_points(vs) - return pvs - # print(pvs) - # i = gem.Index() - # j = pvs.free_indices - # return gem.ComponentTensor(gem.Indexed(pvs, (i,)), j + (i,)) + return self.physical_points(vs) From b606d646ffbcf07263085562437bc233c946e4e6 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Sat, 8 Feb 2020 13:03:29 +0000 Subject: [PATCH 544/809] Fix incorrect logic --- tsfc/fem.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index e3ee0ff00b..3f9ac24da8 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -152,17 +152,14 @@ def jacobian_at(self, point): return map_expr_dag(context.translator, expr) def reference_normals(self): - if isinstance(self.interface.fiat_cell, FIAT.ufc_simplex) \ - and self.interface.fiat_cell.get_spatial_dimension() == 2: - return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) - else: + if not (isinstance(self.interface.fiat_cell, ufc_simplex) and + self.interface.fiat_cell.get_spatial_dimension() == 2): raise NotImplementedError("Only works for triangles for now") + return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) def physical_tangents(self): - if isinstance(self.interface.fiat_cell, FIAT.ufc_simplex) \ - and self.interface.fiat_cell.get_spatial_dimension() == 2: - return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) - else: + if not (isinstance(self.interface.fiat_cell, ufc_simplex) and + self.interface.fiat_cell.get_spatial_dimension() == 2): raise NotImplementedError("Only works for triangles for now") rts = [self.interface.fiat_cell.compute_tangents(1, f)[0] for f in range(3)] From 7620fe9efe9e2a40313f70298a6251b21cbe2901 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Sat, 8 Feb 2020 13:04:15 +0000 Subject: [PATCH 545/809] Add error checking in jacobian_at --- tsfc/fem.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/fem.py b/tsfc/fem.py index 3f9ac24da8..bb6d17e6ca 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -139,7 +139,9 @@ def cell_size(self): return self.interface.cell_size(self.mt.restriction) def jacobian_at(self, point): + ps = PointSingleton(point) expr = Jacobian(self.mt.terminal.ufl_domain()) + assert ps.expression.shape == (expr.ufl_domain().topological_dimension(), ) if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': From 2a7362768dbb2f0037bd2349a3c90d5597462421 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Sat, 8 Feb 2020 13:04:26 +0000 Subject: [PATCH 546/809] Implement push-forward of subentity point sets Just need to send the integration_dim and entity_ids through the PointSetContext. --- tsfc/fem.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index bb6d17e6ca..187330d2b0 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -147,8 +147,7 @@ def jacobian_at(self, point): elif self.mt.restriction == '-': expr = NegativeRestricted(expr) expr = preprocess_expression(expr) - - config = {"point_set": PointSingleton(point)} + config = {"point_set": ps} config.update(self.config) context = PointSetContext(**config) return map_expr_dag(context.translator, expr) @@ -191,9 +190,15 @@ def physical_edge_lengths(self): context = PointSetContext(**config) return map_expr_dag(context.translator, expr) - def physical_points(self, point_set): + def physical_points(self, point_set, entity=None): """Converts point_set from reference to physical space""" expr = SpatialCoordinate(self.mt.terminal.ufl_domain()) + point_shape, = point_set.expression.shape + if entity is not None: + e, _ = entity + assert point_shape == e + else: + assert point_shape == expr.ufl_domain().topological_dimension() if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': @@ -201,17 +206,19 @@ def physical_points(self, point_set): expr = preprocess_expression(expr) config = {"point_set": point_set} config.update(self.config) + if entity is not None: + config.update({name: getattr(self.interface, name) + for name in ["integration_dim", "entity_ids"]}) context = PointSetContext(**config) mapped = map_expr_dag(context.translator, expr) indices = tuple(gem.Index() for _ in mapped.shape) - return gem.ComponentTensor(gem.Indexed(mapped, indices), point_set.indices + indices) - + return gem.ComponentTensor(gem.Indexed(mapped, indices), point_set.indices + indices) + def physical_vertices(self): vs = PointSet(self.interface.fiat_cell.vertices) return self.physical_points(vs) - def needs_coordinate_mapping(element): """Does this UFL element require a CoordinateMapping for translation?""" if element.family() == 'Real': From 7ee5562b2d9a2b1abdf8f7c231c31a5326b00207 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Thu, 27 Feb 2020 11:18:56 -0600 Subject: [PATCH 547/809] Add interface to trimmed serendipity --- tsfc/fiatinterface.py | 2 ++ tsfc/finatinterface.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index c35171f475..21998cff8d 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -62,6 +62,8 @@ "NCF": None, "DPC": FIAT.DPC, "S": FIAT.Serendipity, + "SminusFace": FIAT.TrimmedSerendipityFace, + "SminusEdge": FIAT.TrimmedSerendipityEdge, "DPC L2": FIAT.DPC, "Discontinuous Lagrange L2": FIAT.DiscontinuousLagrange, "Gauss-Legendre L2": FIAT.GaussLegendre, diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 84c9fddf04..10643c4742 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -67,6 +67,8 @@ "Real": finat.DiscontinuousLagrange, "DPC": finat.DPC, "S": finat.Serendipity, + "SminusFace": finat.TrimmedSerendipityFace, + "SminusEdge": finat.TrimmedSerendipityEdge, "DPC L2": finat.DPC, "Discontinuous Lagrange L2": finat.DiscontinuousLagrange, "Gauss-Legendre L2": finat.GaussLegendre, From 400c9eb03a89f60062a62cee36690d2fb6821f68 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Thu, 27 Feb 2020 12:43:04 -0600 Subject: [PATCH 548/809] Fix names --- tsfc/fiatinterface.py | 4 ++-- tsfc/finatinterface.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 21998cff8d..909ece4290 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -62,8 +62,8 @@ "NCF": None, "DPC": FIAT.DPC, "S": FIAT.Serendipity, - "SminusFace": FIAT.TrimmedSerendipityFace, - "SminusEdge": FIAT.TrimmedSerendipityEdge, + "SminusF": FIAT.TrimmedSerendipityFace, + "SminusE": FIAT.TrimmedSerendipityEdge, "DPC L2": FIAT.DPC, "Discontinuous Lagrange L2": FIAT.DiscontinuousLagrange, "Gauss-Legendre L2": FIAT.GaussLegendre, diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 10643c4742..a84fe6623c 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -67,8 +67,8 @@ "Real": finat.DiscontinuousLagrange, "DPC": finat.DPC, "S": finat.Serendipity, - "SminusFace": finat.TrimmedSerendipityFace, - "SminusEdge": finat.TrimmedSerendipityEdge, + "SminusF": finat.TrimmedSerendipityFace, + "SminusE": finat.TrimmedSerendipityEdge, "DPC L2": finat.DPC, "Discontinuous Lagrange L2": finat.DiscontinuousLagrange, "Gauss-Legendre L2": finat.GaussLegendre, From 5d484ca057b4e59322b11f771539d84853b817e0 Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Wed, 18 Mar 2020 10:12:34 +0000 Subject: [PATCH 549/809] Start a skeleton of compile_expression_dual_evaluation --- tsfc/__init__.py | 2 +- tsfc/driver.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/tsfc/__init__.py b/tsfc/__init__.py index e69abac5a8..cffa0a716a 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -1,4 +1,4 @@ -from tsfc.driver import compile_form, compile_expression_at_points # noqa: F401 +from tsfc.driver import compile_form, compile_expression_at_points, compile_expression_dual_evaluation # noqa: F401 from tsfc.parameters import default_parameters # noqa: F401 try: diff --git a/tsfc/driver.py b/tsfc/driver.py index 7297d19c08..9414956946 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -21,7 +21,7 @@ from FIAT.reference_element import TensorProductCell from finat.point_set import PointSet -from finat.quadrature import AbstractQuadratureRule, make_quadrature +from finat.quadrature import AbstractQuadratureRule, make_quadrature, QuadratureRule from tsfc import fem, ufl_utils from tsfc.fiatinterface import as_fiat_cell @@ -363,6 +363,118 @@ def compile_expression_at_points(expression, points, coordinates, interface=None return builder.construct_kernel(return_arg, impero_c, parameters["precision"], {point_index: 'p'}) +def compile_expression_dual_evaluation(expression, dual_functionals, coordinates, interface=None, + parameters=None, coffee=True): + """Compiles a UFL expression to be evaluated against specified + dual functionals. Useful for interpolating UFL expressions into + e.g. N1curl spaces. + + :arg expression: UFL expression + :arg dual_functionals: FIAT dual functionals + :arg coordinates: the coordinate function + :arg interface: backend module for the kernel interface + :arg parameters: parameters object + :arg coffee: compile coffee kernel instead of loopy kernel + """ + import coffee.base as ast + import loopy as lp + + if parameters is None: + parameters = default_parameters() + else: + _ = default_parameters() + _.update(parameters) + parameters = _ + + # Determine whether in complex mode + complex_mode = is_complex(parameters["scalar_type"]) + + # Apply UFL preprocessing + expression = ufl_utils.preprocess_expression(expression, + complex_mode=complex_mode) + + # Initialise kernel builder + if interface is None: + if coffee: + import tsfc.kernel_interface.firedrake as firedrake_interface_coffee + interface = firedrake_interface_coffee.ExpressionKernelBuilder + else: + # Delayed import, loopy is a runtime dependency + import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy + interface = firedrake_interface_loopy.ExpressionKernelBuilder + + builder = interface(parameters["scalar_type"]) + arguments = extract_arguments(expression) + argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() + for arg in arguments) + + # Replace coordinates (if any) + domain = expression.ufl_domain() + if domain: + assert coordinates.ufl_domain() == domain + builder.domain_coordinate[domain] = coordinates + builder.set_cell_sizes(domain) + + # Collect required coefficients + coefficients = extract_coefficients(expression) + if has_type(expression, GeometricQuantity) or any(fem.needs_coordinate_mapping(c.ufl_element()) for c in coefficients): + coefficients = [coordinates] + coefficients + builder.set_coefficients(coefficients) + + # Split mixed coefficients + expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) + + # Translate to GEM + kernel_cfg = dict(interface=builder, + ufl_cell=coordinates.ufl_domain().ufl_cell(), + precision=parameters["precision"], + integration_dim=coordinates.ufl_domain().ufl_cell().topological_dimension(), + argument_multiindices=argument_multiindices) + + dual_expressions = [] + for dual in dual_functionals: + # Extract associated quadrature rule representation + # Make a quad_rule + + quad_points = [*dual.pt_dict.keys()] + # get it working for scalar functions first; how do we deal with components?? + weights = [dual.pt_dict[pt][0][0] for pt in quad_points] + quad_rule = QuadratureRule(PointSet(quad_points), weights) + + config = kernel_cfg.copy() + config.update(quadrature_rule=quad_rule) + expressions = fem.compile_ufl(expression, **config) + dual_expressions.append(expressions) + + import IPython; IPython.embed() + + ir, = fem.compile_ufl(expression, point_sum=False, **config) + + # Deal with non-scalar expressions + value_shape = ir.shape + tensor_indices = tuple(gem.Index() for s in value_shape) + if value_shape: + ir = gem.Indexed(ir, tensor_indices) + + # Build kernel body + return_indices = point_set.indices + tensor_indices + tuple(chain(*argument_multiindices)) + return_shape = tuple(i.extent for i in return_indices) + return_var = gem.Variable('A', return_shape) + if coffee: + return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) + else: + return_arg = lp.GlobalArg("A", dtype=parameters["scalar_type"], shape=return_shape) + + return_expr = gem.Indexed(return_var, return_indices) + ir, = impero_utils.preprocess_gem([ir]) + impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) + point_index, = point_set.indices + + # Handle kernel interface requirements + builder.register_requirements([ir]) + # Build kernel tuple + return builder.construct_kernel(return_arg, impero_c, parameters["precision"], {point_index: 'p'}) + def lower_integral_type(fiat_cell, integral_type): """Lower integral type into the dimension of the integration subentity and a list of entity numbers for that dimension. From 29ddd147008ab4da47a241e90666c4c015ecba42 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 18 Mar 2020 10:36:59 +0000 Subject: [PATCH 550/809] Plausible step closer to working --- tsfc/driver.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9414956946..adc38d42bd 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -443,12 +443,13 @@ def compile_expression_dual_evaluation(expression, dual_functionals, coordinates config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule) - expressions = fem.compile_ufl(expression, **config) + expressions = [gem.index_sum(e, quad_rule.point_set.indices) + for e in fem.compile_ufl(expression, **config)] dual_expressions.append(expressions) - import IPython; IPython.embed() - - ir, = fem.compile_ufl(expression, point_sum=False, **config) + basis_indices = (gem.Index(), ) + ir = gem.ListTensor(dual_expressions) + ir = gem.partial_indexed(ir, basis_indices) # Deal with non-scalar expressions value_shape = ir.shape @@ -457,7 +458,7 @@ def compile_expression_dual_evaluation(expression, dual_functionals, coordinates ir = gem.Indexed(ir, tensor_indices) # Build kernel body - return_indices = point_set.indices + tensor_indices + tuple(chain(*argument_multiindices)) + return_indices = basis_indices + tensor_indices + tuple(chain(*argument_multiindices)) return_shape = tuple(i.extent for i in return_indices) return_var = gem.Variable('A', return_shape) if coffee: @@ -468,7 +469,7 @@ def compile_expression_dual_evaluation(expression, dual_functionals, coordinates return_expr = gem.Indexed(return_var, return_indices) ir, = impero_utils.preprocess_gem([ir]) impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) - point_index, = point_set.indices + point_index, = basis_indices # Handle kernel interface requirements builder.register_requirements([ir]) From 5ad69fff9854a204ba46ab1084612e1e0b953e1c Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Mon, 23 Mar 2020 10:33:36 +0000 Subject: [PATCH 551/809] Try to implement code for vector expressions also. Working for [CG1]^3, not for Nedelec N1curl --- tsfc/driver.py | 57 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index adc38d42bd..45233ad49a 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -432,20 +432,51 @@ def compile_expression_dual_evaluation(expression, dual_functionals, coordinates argument_multiindices=argument_multiindices) dual_expressions = [] - for dual in dual_functionals: - # Extract associated quadrature rule representation - # Make a quad_rule - quad_points = [*dual.pt_dict.keys()] - # get it working for scalar functions first; how do we deal with components?? - weights = [dual.pt_dict[pt][0][0] for pt in quad_points] - quad_rule = QuadratureRule(PointSet(quad_points), weights) - - config = kernel_cfg.copy() - config.update(quadrature_rule=quad_rule) - expressions = [gem.index_sum(e, quad_rule.point_set.indices) - for e in fem.compile_ufl(expression, **config)] - dual_expressions.append(expressions) + # We need to do different things based on the shape of the expression, unfortunately + + # Case 1: scalars + if len(expression.ufl_shape) == 0: + for dual in dual_functionals: + quad_points = [*dual.pt_dict.keys()] + weights = [dual.pt_dict[pt][0][0] for pt in quad_points] + quad_rule = QuadratureRule(PointSet(quad_points), weights) + + config = kernel_cfg.copy() + config.update(quadrature_rule=quad_rule) + expressions = [gem.index_sum(e, quad_rule.point_set.indices) + for e in fem.compile_ufl(expression, **config)] + dual_expressions.append(expressions) + + # Case 2: vectors + elif len(expression.ufl_shape) == 1: + for dual in dual_functionals: + quad_points = [*dual.pt_dict.keys()] + dual_expression = [] + + for comp, exp in enumerate(expression): + quad_points_comp = [q for q in quad_points if dual.pt_dict[q][0][1] == () or comp in [w[1][0] for w in dual.pt_dict[q]]] + weights_comp = [] + for q in quad_points_comp: + weights_comp.append([w[0] for w in dual.pt_dict[q] if dual.pt_dict[q][0][1] == () or comp == w[1][0] ]) + if len(quad_points_comp) > 0: + quad_rule = QuadratureRule(PointSet(quad_points_comp), weights_comp) + config = kernel_cfg.copy() + config.update(quadrature_rule=quad_rule) + expressions = [gem.index_sum(e, quad_rule.point_set.indices) + for e in fem.compile_ufl(exp, **config)] + dual_expression.append(expressions) + + #index = gem.Index() + #dual_expr = gem.index_sum(gem.ListTensor([x[0][index] for x in dual_expression]), (index,)) + #dual_expressions.append(dual_expr) + [dual_expressions.append(dual_expr) for dual_expr in dual_expression] + + # Case 3: tensors + elif len(expression.ufl_shape) == 2: + raise NotImplementedError + else: + raise ValueError("Only know how to interpolate expressions in 1-3 dimensions") basis_indices = (gem.Index(), ) ir = gem.ListTensor(dual_expressions) From fbdfad0bd7a36448ea70876123a1423bdd6368ad Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Mon, 23 Mar 2020 11:15:19 +0000 Subject: [PATCH 552/809] Lawrence's suggestion; runs through for both vector CG and Nedelec, but doesn't give the right answer in either case. Still thinking. --- tsfc/driver.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 45233ad49a..4144af21ea 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -465,12 +465,13 @@ def compile_expression_dual_evaluation(expression, dual_functionals, coordinates config.update(quadrature_rule=quad_rule) expressions = [gem.index_sum(e, quad_rule.point_set.indices) for e in fem.compile_ufl(exp, **config)] - dual_expression.append(expressions) + dual_expression.extend(expressions) + #dual_expression.append(expressions) - #index = gem.Index() - #dual_expr = gem.index_sum(gem.ListTensor([x[0][index] for x in dual_expression]), (index,)) - #dual_expressions.append(dual_expr) - [dual_expressions.append(dual_expr) for dual_expr in dual_expression] + i = gem.Index() + dual_expr = gem.index_sum(gem.ListTensor(dual_expression)[i], (i, )) + dual_expressions.append(dual_expr) + #[dual_expressions.append(dual_expr) for dual_expr in dual_expression] # Case 3: tensors elif len(expression.ufl_shape) == 2: From cecc86ea1002ba343e37e5c76a9f76f126b6ac34 Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Mon, 23 Mar 2020 11:58:46 +0000 Subject: [PATCH 553/809] Implement broadcasting semantics for scalar element applied to vector expression --- tsfc/driver.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 4144af21ea..ac1ae71b96 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -3,6 +3,7 @@ import string import time import sys +import copy from functools import reduce from itertools import chain @@ -363,14 +364,14 @@ def compile_expression_at_points(expression, points, coordinates, interface=None return builder.construct_kernel(return_arg, impero_c, parameters["precision"], {point_index: 'p'}) -def compile_expression_dual_evaluation(expression, dual_functionals, coordinates, interface=None, +def compile_expression_dual_evaluation(expression, to_element, coordinates, interface=None, parameters=None, coffee=True): """Compiles a UFL expression to be evaluated against specified dual functionals. Useful for interpolating UFL expressions into e.g. N1curl spaces. :arg expression: UFL expression - :arg dual_functionals: FIAT dual functionals + :arg to_element: FiniteElement for the target space :arg coordinates: the coordinate function :arg interface: backend module for the kernel interface :arg parameters: parameters object @@ -437,7 +438,8 @@ def compile_expression_dual_evaluation(expression, dual_functionals, coordinates # Case 1: scalars if len(expression.ufl_shape) == 0: - for dual in dual_functionals: + assert to_element.value_shape() == () + for dual in to_element.dual_basis(): quad_points = [*dual.pt_dict.keys()] weights = [dual.pt_dict[pt][0][0] for pt in quad_points] quad_rule = QuadratureRule(PointSet(quad_points), weights) @@ -450,6 +452,29 @@ def compile_expression_dual_evaluation(expression, dual_functionals, coordinates # Case 2: vectors elif len(expression.ufl_shape) == 1: + + # check we have a [scalar]^d or vector element + assert len(to_element.value_shape()) in [0, 1] + + # if we have a native vector element, just use those dual functionals + if len(to_element.value_shape()) == 1: + dual_functionals = to_element.dual_basis() + # otherwise, implement broadcasting semantics + else: + orig_functionals = to_element.dual_basis() + dual_functionals = [] + for dual in orig_functionals: + for component in range(len(expression)): + # reconstruct the functional, but with an index + new_dual = copy.copy(dual) + new_pt_dict = {} + for quad_pt in dual.pt_dict: + weights = [x[0] for x in dual.pt_dict[quad_pt]] + new_weights_and_indices = [(weight, (component,)) for weight in weights] + new_pt_dict[quad_pt] = new_weights_and_indices + new_dual.pt_dict = new_pt_dict + dual_functionals.append(new_dual) + for dual in dual_functionals: quad_points = [*dual.pt_dict.keys()] dual_expression = [] @@ -466,7 +491,6 @@ def compile_expression_dual_evaluation(expression, dual_functionals, coordinates expressions = [gem.index_sum(e, quad_rule.point_set.indices) for e in fem.compile_ufl(exp, **config)] dual_expression.extend(expressions) - #dual_expression.append(expressions) i = gem.Index() dual_expr = gem.index_sum(gem.ListTensor(dual_expression)[i], (i, )) From 42ae2848db4280600c4336860882d05d91f74ba7 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 23 Mar 2020 12:03:39 +0000 Subject: [PATCH 554/809] interpolation: Incorporate quad weight in expression to be compiled --- tsfc/driver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index ac1ae71b96..33ec06e7e0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -483,19 +483,18 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte quad_points_comp = [q for q in quad_points if dual.pt_dict[q][0][1] == () or comp in [w[1][0] for w in dual.pt_dict[q]]] weights_comp = [] for q in quad_points_comp: - weights_comp.append([w[0] for w in dual.pt_dict[q] if dual.pt_dict[q][0][1] == () or comp == w[1][0] ]) + weights_comp.extend([w[0] for w in dual.pt_dict[q] if dual.pt_dict[q][0][1] == () or comp == w[1][0] ]) if len(quad_points_comp) > 0: quad_rule = QuadratureRule(PointSet(quad_points_comp), weights_comp) config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule) expressions = [gem.index_sum(e, quad_rule.point_set.indices) - for e in fem.compile_ufl(exp, **config)] + for e in fem.compile_ufl(ufl.classes.QuadratureWeight(exp.ufl_domain())*exp, **config)] dual_expression.extend(expressions) i = gem.Index() dual_expr = gem.index_sum(gem.ListTensor(dual_expression)[i], (i, )) dual_expressions.append(dual_expr) - #[dual_expressions.append(dual_expr) for dual_expr in dual_expression] # Case 3: tensors elif len(expression.ufl_shape) == 2: @@ -532,6 +531,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Build kernel tuple return builder.construct_kernel(return_arg, impero_c, parameters["precision"], {point_index: 'p'}) + def lower_integral_type(fiat_cell, integral_type): """Lower integral type into the dimension of the integration subentity and a list of entity numbers for that dimension. From 419c27453450488da093b3ab387c6a5a885a1016 Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Mon, 23 Mar 2020 16:14:01 +0000 Subject: [PATCH 555/809] Get N2curl working on meshes that aren't just the unit cell! --- tsfc/driver.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 33ec06e7e0..8e6eaca7a8 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -12,6 +12,7 @@ import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type +from ufl.geometry import Jacobian from ufl.classes import Form, GeometricQuantity from ufl.log import GREEN from ufl.utils.sequences import max_degree @@ -390,6 +391,20 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Determine whether in complex mode complex_mode = is_complex(parameters["scalar_type"]) + # Find out which mapping to apply + mappings = set(to_element.mapping()) + if len(mappings) != 1: + raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") + mapping = mappings.pop() + if mapping == "affine": + pass # do nothing + elif mapping == "covariant piola": + mesh = expression.ufl_domain() + J = Jacobian(mesh) + expression = J.T * expression + else: + raise NotImplementedError + # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, complex_mode=complex_mode) From b493fc453d60f80868e683d44f7a24850aba48b1 Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Mon, 23 Mar 2020 16:19:02 +0000 Subject: [PATCH 556/809] Handle contravariant Piola, also --- tsfc/driver.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 8e6eaca7a8..c8664f0db8 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -12,7 +12,7 @@ import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type -from ufl.geometry import Jacobian +from ufl.geometry import Jacobian, JacobianDeterminant, JacobianInverse from ufl.classes import Form, GeometricQuantity from ufl.log import GREEN from ufl.utils.sequences import max_degree @@ -402,8 +402,13 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte mesh = expression.ufl_domain() J = Jacobian(mesh) expression = J.T * expression + elif mapping == "contravariant piola": + mesh = expression.ufl_domain() + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + expression = detJ * K * expression else: - raise NotImplementedError + raise NotImplementedError("Don't know how to handle mapping type " + mapping) # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, From 2ef475813afccac6f7518ce78ac3b3a27d038d6c Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Mon, 23 Mar 2020 16:22:32 +0000 Subject: [PATCH 557/809] Implement Lawrence's suggestion --- tsfc/driver.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index c8664f0db8..54795ff37e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -392,10 +392,11 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte complex_mode = is_complex(parameters["scalar_type"]) # Find out which mapping to apply - mappings = set(to_element.mapping()) - if len(mappings) != 1: + try: + mapping, = set(to_element.mapping()) + except ValueError: raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") - mapping = mappings.pop() + if mapping == "affine": pass # do nothing elif mapping == "covariant piola": From 48b077cc7349edf37973a3f4f95018961b8eac70 Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Tue, 24 Mar 2020 11:30:22 +0000 Subject: [PATCH 558/809] Sort the quadrature points for parallel safety --- tsfc/driver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 54795ff37e..4a031dde95 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -461,7 +461,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte if len(expression.ufl_shape) == 0: assert to_element.value_shape() == () for dual in to_element.dual_basis(): - quad_points = [*dual.pt_dict.keys()] + quad_points = [*sorted(dual.pt_dict.keys())] weights = [dual.pt_dict[pt][0][0] for pt in quad_points] quad_rule = QuadratureRule(PointSet(quad_points), weights) @@ -489,7 +489,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # reconstruct the functional, but with an index new_dual = copy.copy(dual) new_pt_dict = {} - for quad_pt in dual.pt_dict: + for quad_pt in sorted(dual.pt_dict): weights = [x[0] for x in dual.pt_dict[quad_pt]] new_weights_and_indices = [(weight, (component,)) for weight in weights] new_pt_dict[quad_pt] = new_weights_and_indices @@ -497,7 +497,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte dual_functionals.append(new_dual) for dual in dual_functionals: - quad_points = [*dual.pt_dict.keys()] + quad_points = [*sorted(dual.pt_dict.keys())] dual_expression = [] for comp, exp in enumerate(expression): From 99848ece79d6b3de237c3caca8c58d8748f63cd0 Mon Sep 17 00:00:00 2001 From: Fabian Laakmann Date: Fri, 27 Mar 2020 00:17:26 +0100 Subject: [PATCH 559/809] First attempt to implement interpolation into tensors. Tensor CG and Regge works, but there is a problem with broadcasting semantics for Vector BDM --- tsfc/driver.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 4a031dde95..b315c685cd 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -519,7 +519,69 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Case 3: tensors elif len(expression.ufl_shape) == 2: - raise NotImplementedError + # check we have a [scalar]^d or vector element + assert len(to_element.value_shape()) in [0, 1, 2] + + # if we have a native vector element, just use those dual functionals + if len(to_element.value_shape()) == 2: + dual_functionals = to_element.dual_basis() + # otherwise, implement broadcasting semantics + elif len(to_element.value_shape()) == 1: + orig_functionals = to_element.dual_basis() + dual_functionals = [] + + for dual in orig_functionals: + for comp1 in range(expression.ufl_shape[0]): + for comp2 in range(expression.ufl_shape[1]): + # reconstruct the functional, but with an index + new_dual = copy.copy(dual) + new_pt_dict = {} + for quad_pt in dual.pt_dict: + pt_dict_el = [x for x in dual.pt_dict[quad_pt]] + new_weights_and_indices = [(pt_dict_el[comp1][0], (comp1, comp2))] + new_pt_dict[quad_pt] = new_weights_and_indices + new_dual.pt_dict = new_pt_dict + dual_functionals.append(new_dual) + + elif len(to_element.value_shape()) == 0: + orig_functionals = to_element.dual_basis() + dual_functionals = [] + + for dual in orig_functionals: + for comp1 in range(expression.ufl_shape[0]): + for comp2 in range(expression.ufl_shape[1]): + # reconstruct the functional, but with an index + new_dual = copy.copy(dual) + new_pt_dict = {} + for quad_pt in dual.pt_dict: + weights = [x[0] for x in dual.pt_dict[quad_pt]] + new_weights_and_indices = [(weight, (comp1, comp2)) for weight in weights] + new_pt_dict[quad_pt] = new_weights_and_indices + new_dual.pt_dict = new_pt_dict + dual_functionals.append(new_dual) + + for dual in dual_functionals: + quad_points = [*dual.pt_dict.keys()] + dual_expression = [] + + for comp1 in range(expression.ufl_shape[0]): + for comp2 in range(expression.ufl_shape[1]): + exp = expression[comp1, comp2] + quad_points_comp = [q for q in quad_points if dual.pt_dict[q][0][1] == () or (comp1, comp2) in [w[1] for w in dual.pt_dict[q]]] + weights_comp = [] + for q in quad_points_comp: + weights_comp.extend([w[0] for w in dual.pt_dict[q] if dual.pt_dict[q][0][1] == () or (comp1, comp2) == w[1] ]) + if len(quad_points_comp) > 0: + quad_rule = QuadratureRule(PointSet(quad_points_comp), weights_comp) + config = kernel_cfg.copy() + config.update(quadrature_rule=quad_rule) + expressions = [gem.index_sum(e, quad_rule.point_set.indices) + for e in fem.compile_ufl(ufl.classes.QuadratureWeight(exp.ufl_domain())*exp, **config)] + dual_expression.extend(expressions) + + i = gem.Index() + dual_expr = gem.index_sum(gem.ListTensor(dual_expression)[i], (i, )) + dual_expressions.append(dual_expr) else: raise ValueError("Only know how to interpolate expressions in 1-3 dimensions") From 45e8287c81cca79015061350e190966d04d993ed Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Fri, 27 Mar 2020 15:42:12 +0000 Subject: [PATCH 560/809] Get vector -> tensor broadcasting working --- tsfc/driver.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b315c685cd..46874e8e4e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -522,7 +522,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # check we have a [scalar]^d or vector element assert len(to_element.value_shape()) in [0, 1, 2] - # if we have a native vector element, just use those dual functionals + # if we have a native tensor element, just use those dual functionals if len(to_element.value_shape()) == 2: dual_functionals = to_element.dual_basis() # otherwise, implement broadcasting semantics @@ -532,16 +532,16 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte for dual in orig_functionals: for comp1 in range(expression.ufl_shape[0]): - for comp2 in range(expression.ufl_shape[1]): - # reconstruct the functional, but with an index - new_dual = copy.copy(dual) - new_pt_dict = {} - for quad_pt in dual.pt_dict: - pt_dict_el = [x for x in dual.pt_dict[quad_pt]] - new_weights_and_indices = [(pt_dict_el[comp1][0], (comp1, comp2))] - new_pt_dict[quad_pt] = new_weights_and_indices - new_dual.pt_dict = new_pt_dict - dual_functionals.append(new_dual) + new_dual = copy.copy(dual) + new_pt_dict = {} + for quad_pt in sorted(dual.pt_dict): + old_weights_and_indices = dual.pt_dict[quad_pt] + new_weights_and_indices = [] + for (weight, index) in old_weights_and_indices: + new_weights_and_indices.append((weight, (comp1, index[0]))) + new_pt_dict[quad_pt] = new_weights_and_indices + new_dual.pt_dict = new_pt_dict + dual_functionals.append(new_dual) elif len(to_element.value_shape()) == 0: orig_functionals = to_element.dual_basis() @@ -553,7 +553,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # reconstruct the functional, but with an index new_dual = copy.copy(dual) new_pt_dict = {} - for quad_pt in dual.pt_dict: + for quad_pt in sorted(dual.pt_dict): weights = [x[0] for x in dual.pt_dict[quad_pt]] new_weights_and_indices = [(weight, (comp1, comp2)) for weight in weights] new_pt_dict[quad_pt] = new_weights_and_indices From 0b26519e131bf728a283055d5b8900fd2053930c Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Fri, 27 Mar 2020 15:45:16 +0000 Subject: [PATCH 561/809] Implement double covariant and double contravariant Piola --- tsfc/driver.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 46874e8e4e..6455bbecbf 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -408,6 +408,15 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte K = JacobianInverse(mesh) detJ = JacobianDeterminant(mesh) expression = detJ * K * expression + elif mapping == "double covariant piola": + mesh = expression.ufl_domain() + J = Jacobian(mesh) + expression = J.T * expression * J + elif mapping == "double contravariant piola": + mesh = expression.ufl_domain() + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + expression = (detJ)**2 * K * expression * K.T else: raise NotImplementedError("Don't know how to handle mapping type " + mapping) From 56c25c4de07a446a74690f6e787e1bc930082cf5 Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Fri, 27 Mar 2020 16:00:05 +0000 Subject: [PATCH 562/809] Missing parallel safety --- tsfc/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 6455bbecbf..bdc3fce001 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -570,7 +570,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte dual_functionals.append(new_dual) for dual in dual_functionals: - quad_points = [*dual.pt_dict.keys()] + quad_points = [*sorted(dual.pt_dict.keys())] dual_expression = [] for comp1 in range(expression.ufl_shape[0]): From d58fc7ff6d00d94e338ed1d3b1fbfcbd7a546afb Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Sun, 29 Mar 2020 16:15:15 +0100 Subject: [PATCH 563/809] This causes the firedrake interpolation test to pass. --- tsfc/driver.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index bdc3fce001..261a7caf0d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -16,6 +16,7 @@ from ufl.classes import Form, GeometricQuantity from ufl.log import GREEN from ufl.utils.sequences import max_degree +from ufl.tensors import as_tensor import gem import gem.impero_utils as impero_utils @@ -399,6 +400,15 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte if mapping == "affine": pass # do nothing + elif mapping == "covariant piola" and len(expression.ufl_shape) == 2: + mesh = expression.ufl_domain() + J = Jacobian(mesh) + expression = as_tensor([J.T * expression[i, :] for i in range(expression.ufl_shape[0])]) + elif mapping == "contravariant piola" and len(expression.ufl_shape) == 2: + mesh = expression.ufl_domain() + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + expression = as_tensor([detJ * K * expression[i, :] for i in range(expression.ufl_shape[0])]) elif mapping == "covariant piola": mesh = expression.ufl_domain() J = Jacobian(mesh) From 5ed4faa6282eb798f288a5b10577a7077b6806c6 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 30 Mar 2020 11:19:17 +0100 Subject: [PATCH 564/809] interpolation: Factor out apply_mapping --- tsfc/driver.py | 77 +++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 261a7caf0d..adc13be067 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -366,8 +366,44 @@ def compile_expression_at_points(expression, points, coordinates, interface=None return builder.construct_kernel(return_arg, impero_c, parameters["precision"], {point_index: 'p'}) +def apply_mapping(expression, to_element): + # Find out which mapping to apply + try: + mapping, = set(to_element.mapping()) + except ValueError: + raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") + + mesh = expression.ufl_domain() + rank = len(expression.ufl_shape) + if mapping == "affine": + return expression + elif mapping == "covariant piola": + J = Jacobian(mesh) + *i, j, k = ufl.indices(len(expression.ufl_shape) + 1) + expression = ufl.classes.Indexed(expression, ufl.classes.MultiIndex((*i, k))) + return as_tensor(J.T[j, k] * expression, (*i, j)) + elif mapping == "contravariant piola": + mesh = expression.ufl_domain() + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + *i, j, k = ufl.indices(len(expression.ufl_shape) + 1) + expression = ufl.classes.Indexed(expression, ufl.classes.MultiIndex((*i, k))) + return as_tensor(detJ * K[j, k] * expression, (*i, j)) + elif mapping == "double covariant piola" and rank == 2: + mesh = expression.ufl_domain() + J = Jacobian(mesh) + return J.T * expression * J + elif mapping == "double contravariant piola" and rank == 2: + mesh = expression.ufl_domain() + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + return (detJ)**2 * K * expression * K.T + else: + raise NotImplementedError("Don't know how to handle mapping type %s for expression of rank %d" % (mapping, rank)) + + def compile_expression_dual_evaluation(expression, to_element, coordinates, interface=None, - parameters=None, coffee=True): + parameters=None, coffee=True): """Compiles a UFL expression to be evaluated against specified dual functionals. Useful for interpolating UFL expressions into e.g. N1curl spaces. @@ -392,44 +428,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Determine whether in complex mode complex_mode = is_complex(parameters["scalar_type"]) - # Find out which mapping to apply - try: - mapping, = set(to_element.mapping()) - except ValueError: - raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") - - if mapping == "affine": - pass # do nothing - elif mapping == "covariant piola" and len(expression.ufl_shape) == 2: - mesh = expression.ufl_domain() - J = Jacobian(mesh) - expression = as_tensor([J.T * expression[i, :] for i in range(expression.ufl_shape[0])]) - elif mapping == "contravariant piola" and len(expression.ufl_shape) == 2: - mesh = expression.ufl_domain() - K = JacobianInverse(mesh) - detJ = JacobianDeterminant(mesh) - expression = as_tensor([detJ * K * expression[i, :] for i in range(expression.ufl_shape[0])]) - elif mapping == "covariant piola": - mesh = expression.ufl_domain() - J = Jacobian(mesh) - expression = J.T * expression - elif mapping == "contravariant piola": - mesh = expression.ufl_domain() - K = JacobianInverse(mesh) - detJ = JacobianDeterminant(mesh) - expression = detJ * K * expression - elif mapping == "double covariant piola": - mesh = expression.ufl_domain() - J = Jacobian(mesh) - expression = J.T * expression * J - elif mapping == "double contravariant piola": - mesh = expression.ufl_domain() - K = JacobianInverse(mesh) - detJ = JacobianDeterminant(mesh) - expression = (detJ)**2 * K * expression * K.T - else: - raise NotImplementedError("Don't know how to handle mapping type " + mapping) - + expression = apply_mapping(expression, to_element) # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, complex_mode=complex_mode) From a818e75261370f83f77802d11734fa3049bde07e Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 30 Mar 2020 12:24:58 +0100 Subject: [PATCH 565/809] interpolation: Refactor broadcasting of scalar subelements --- tsfc/driver.py | 102 ++++++++++++++++++------------------------------- 1 file changed, 37 insertions(+), 65 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index adc13be067..b880549277 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -429,6 +429,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte complex_mode = is_complex(parameters["scalar_type"]) expression = apply_mapping(expression, to_element) + expression = ufl.classes.QuadratureWeight(expression.ufl_domain())*expression # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, complex_mode=complex_mode) @@ -466,54 +467,41 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Translate to GEM kernel_cfg = dict(interface=builder, - ufl_cell=coordinates.ufl_domain().ufl_cell(), - precision=parameters["precision"], - integration_dim=coordinates.ufl_domain().ufl_cell().topological_dimension(), - argument_multiindices=argument_multiindices) + ufl_cell=coordinates.ufl_domain().ufl_cell(), + precision=parameters["precision"], + integration_dim=coordinates.ufl_domain().ufl_cell().topological_dimension(), + argument_multiindices=argument_multiindices, + index_cache={}) - dual_expressions = [] + if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()): + raise NotImplementedError("Can only interpolate onto dual basis functionals without derivative evaluation, sorry!") # We need to do different things based on the shape of the expression, unfortunately - - # Case 1: scalars - if len(expression.ufl_shape) == 0: - assert to_element.value_shape() == () + if to_element.value_shape() == (): + # scalar sub-element + qpoints = [] + qweights = [] + # Everything is just a point evaluation. for dual in to_element.dual_basis(): - quad_points = [*sorted(dual.pt_dict.keys())] - weights = [dual.pt_dict[pt][0][0] for pt in quad_points] - quad_rule = QuadratureRule(PointSet(quad_points), weights) - - config = kernel_cfg.copy() - config.update(quadrature_rule=quad_rule) - expressions = [gem.index_sum(e, quad_rule.point_set.indices) - for e in fem.compile_ufl(expression, **config)] - dual_expressions.append(expressions) - + ptdict = dual.get_point_dict() + qpoint, = ptdict.keys() + (qweight, component), = ptdict[qpoint] + assert component == () + qpoints.append(qpoint) + qweights.append(qweight) + quad_rule = QuadratureRule(PointSet(qpoints), qweights) + config = kernel_cfg.copy() + config.update(quadrature_rule=quad_rule) + expr, = fem.compile_ufl(expression, **config) + shape_indices = tuple(gem.Index() for _ in expr.shape) + expr = gem.Indexed(expr, shape_indices) + dual_expressions = gem.ComponentTensor(expr, quad_rule.point_set.indices + shape_indices) # Case 2: vectors elif len(expression.ufl_shape) == 1: - - # check we have a [scalar]^d or vector element - assert len(to_element.value_shape()) in [0, 1] - - # if we have a native vector element, just use those dual functionals - if len(to_element.value_shape()) == 1: - dual_functionals = to_element.dual_basis() - # otherwise, implement broadcasting semantics - else: - orig_functionals = to_element.dual_basis() - dual_functionals = [] - for dual in orig_functionals: - for component in range(len(expression)): - # reconstruct the functional, but with an index - new_dual = copy.copy(dual) - new_pt_dict = {} - for quad_pt in sorted(dual.pt_dict): - weights = [x[0] for x in dual.pt_dict[quad_pt]] - new_weights_and_indices = [(weight, (component,)) for weight in weights] - new_pt_dict[quad_pt] = new_weights_and_indices - new_dual.pt_dict = new_pt_dict - dual_functionals.append(new_dual) - + # check we have a vector element + assert to_element.value_shape() == 1 + dual_functionals = to_element.dual_basis() + dual_expressions = [] for dual in dual_functionals: quad_points = [*sorted(dual.pt_dict.keys())] dual_expression = [] @@ -528,18 +516,20 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule) expressions = [gem.index_sum(e, quad_rule.point_set.indices) - for e in fem.compile_ufl(ufl.classes.QuadratureWeight(exp.ufl_domain())*exp, **config)] + for e in fem.compile_ufl(exp, **config)] dual_expression.extend(expressions) i = gem.Index() dual_expr = gem.index_sum(gem.ListTensor(dual_expression)[i], (i, )) dual_expressions.append(dual_expr) + dual_expressions = gem.ListTensor(dual_expressions) # Case 3: tensors elif len(expression.ufl_shape) == 2: # check we have a [scalar]^d or vector element - assert len(to_element.value_shape()) in [0, 1, 2] + assert len(to_element.value_shape()) in [1, 2] + dual_expressions = [] # if we have a native tensor element, just use those dual functionals if len(to_element.value_shape()) == 2: dual_functionals = to_element.dual_basis() @@ -560,24 +550,6 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte new_pt_dict[quad_pt] = new_weights_and_indices new_dual.pt_dict = new_pt_dict dual_functionals.append(new_dual) - - elif len(to_element.value_shape()) == 0: - orig_functionals = to_element.dual_basis() - dual_functionals = [] - - for dual in orig_functionals: - for comp1 in range(expression.ufl_shape[0]): - for comp2 in range(expression.ufl_shape[1]): - # reconstruct the functional, but with an index - new_dual = copy.copy(dual) - new_pt_dict = {} - for quad_pt in sorted(dual.pt_dict): - weights = [x[0] for x in dual.pt_dict[quad_pt]] - new_weights_and_indices = [(weight, (comp1, comp2)) for weight in weights] - new_pt_dict[quad_pt] = new_weights_and_indices - new_dual.pt_dict = new_pt_dict - dual_functionals.append(new_dual) - for dual in dual_functionals: quad_points = [*sorted(dual.pt_dict.keys())] dual_expression = [] @@ -594,18 +566,18 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule) expressions = [gem.index_sum(e, quad_rule.point_set.indices) - for e in fem.compile_ufl(ufl.classes.QuadratureWeight(exp.ufl_domain())*exp, **config)] + for e in fem.compile_ufl(exp, **config)] dual_expression.extend(expressions) i = gem.Index() dual_expr = gem.index_sum(gem.ListTensor(dual_expression)[i], (i, )) dual_expressions.append(dual_expr) + dual_expressions = gem.ListTensor(dual_expressions) else: raise ValueError("Only know how to interpolate expressions in 1-3 dimensions") basis_indices = (gem.Index(), ) - ir = gem.ListTensor(dual_expressions) - ir = gem.partial_indexed(ir, basis_indices) + ir = gem.partial_indexed(dual_expressions, basis_indices) # Deal with non-scalar expressions value_shape = ir.shape From 95f74f2214c5c64beec34e1acf1dae38238d4862 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 31 Mar 2020 15:03:54 +0100 Subject: [PATCH 566/809] interpolation: Simplify dual evaluation routine Remove a bunch of replicated code and manual unrolling in Python. We still special-case for point evaluation functionals since we can generate nicer code right now by doing so. --- tsfc/driver.py | 169 ++++++++++++++++--------------------------------- 1 file changed, 56 insertions(+), 113 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b880549277..f0365f9cad 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -3,11 +3,10 @@ import string import time import sys -import copy from functools import reduce from itertools import chain -from numpy import asarray +from numpy import asarray, allclose import ufl from ufl.algorithms import extract_arguments, extract_coefficients @@ -24,7 +23,7 @@ from FIAT.reference_element import TensorProductCell from finat.point_set import PointSet -from finat.quadrature import AbstractQuadratureRule, make_quadrature, QuadratureRule +from finat.quadrature import AbstractQuadratureRule, make_quadrature from tsfc import fem, ufl_utils from tsfc.fiatinterface import as_fiat_cell @@ -403,13 +402,13 @@ def apply_mapping(expression, to_element): def compile_expression_dual_evaluation(expression, to_element, coordinates, interface=None, - parameters=None, coffee=True): - """Compiles a UFL expression to be evaluated against specified - dual functionals. Useful for interpolating UFL expressions into - e.g. N1curl spaces. + parameters=None, coffee=False): + """Compile a UFL expression to be evaluated against a compile-time known reference element's dual basis. + + Useful for interpolating UFL expressions into e.g. N1curl spaces. :arg expression: UFL expression - :arg to_element: FiniteElement for the target space + :arg to_element: A FIAT FiniteElement for the target space :arg coordinates: the coordinate function :arg interface: backend module for the kernel interface :arg parameters: parameters object @@ -417,6 +416,8 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte """ import coffee.base as ast import loopy as lp + if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()): + raise NotImplementedError("Can only interpolate onto dual basis functionals without derivative evaluation, sorry!") if parameters is None: parameters = default_parameters() @@ -429,7 +430,6 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte complex_mode = is_complex(parameters["scalar_type"]) expression = apply_mapping(expression, to_element) - expression = ufl.classes.QuadratureWeight(expression.ufl_domain())*expression # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, complex_mode=complex_mode) @@ -469,124 +469,65 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte kernel_cfg = dict(interface=builder, ufl_cell=coordinates.ufl_domain().ufl_cell(), precision=parameters["precision"], - integration_dim=coordinates.ufl_domain().ufl_cell().topological_dimension(), argument_multiindices=argument_multiindices, index_cache={}) - if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()): - raise NotImplementedError("Can only interpolate onto dual basis functionals without derivative evaluation, sorry!") - - # We need to do different things based on the shape of the expression, unfortunately - if to_element.value_shape() == (): - # scalar sub-element + if len(to_element.value_shape()) == 0: + # This is an optimisation for point-evaluation nodes which + # should go away once FInAT offers the interface properly qpoints = [] - qweights = [] # Everything is just a point evaluation. for dual in to_element.dual_basis(): ptdict = dual.get_point_dict() qpoint, = ptdict.keys() (qweight, component), = ptdict[qpoint] + assert allclose(qweight, 1.0) assert component == () qpoints.append(qpoint) - qweights.append(qweight) - quad_rule = QuadratureRule(PointSet(qpoints), qweights) + point_set = PointSet(qpoints) config = kernel_cfg.copy() - config.update(quadrature_rule=quad_rule) - expr, = fem.compile_ufl(expression, **config) + config.update(point_set=point_set) + expr, = fem.compile_ufl(expression, **config, point_sum=False) shape_indices = tuple(gem.Index() for _ in expr.shape) - expr = gem.Indexed(expr, shape_indices) - dual_expressions = gem.ComponentTensor(expr, quad_rule.point_set.indices + shape_indices) - # Case 2: vectors - elif len(expression.ufl_shape) == 1: - # check we have a vector element - assert to_element.value_shape() == 1 + basis_indices = point_set.indices + ir = gem.Indexed(expr, shape_indices) + else: + # This is general code but is more unrolled than necssary. dual_functionals = to_element.dual_basis() - dual_expressions = [] + dual_expressions = [] # one for each functional + broadcast_shape = len(expression.ufl_shape) - len(to_element.value_shape()) + shape_indices = tuple(gem.Index() for _ in expression.ufl_shape[:broadcast_shape]) + expr_cache = {} # Sharing of evaluation of the expression at points for dual in dual_functionals: - quad_points = [*sorted(dual.pt_dict.keys())] - dual_expression = [] - - for comp, exp in enumerate(expression): - quad_points_comp = [q for q in quad_points if dual.pt_dict[q][0][1] == () or comp in [w[1][0] for w in dual.pt_dict[q]]] - weights_comp = [] - for q in quad_points_comp: - weights_comp.extend([w[0] for w in dual.pt_dict[q] if dual.pt_dict[q][0][1] == () or comp == w[1][0] ]) - if len(quad_points_comp) > 0: - quad_rule = QuadratureRule(PointSet(quad_points_comp), weights_comp) - config = kernel_cfg.copy() - config.update(quadrature_rule=quad_rule) - expressions = [gem.index_sum(e, quad_rule.point_set.indices) - for e in fem.compile_ufl(exp, **config)] - dual_expression.extend(expressions) - - i = gem.Index() - dual_expr = gem.index_sum(gem.ListTensor(dual_expression)[i], (i, )) - dual_expressions.append(dual_expr) - dual_expressions = gem.ListTensor(dual_expressions) - - # Case 3: tensors - elif len(expression.ufl_shape) == 2: - # check we have a [scalar]^d or vector element - assert len(to_element.value_shape()) in [1, 2] - - dual_expressions = [] - # if we have a native tensor element, just use those dual functionals - if len(to_element.value_shape()) == 2: - dual_functionals = to_element.dual_basis() - # otherwise, implement broadcasting semantics - elif len(to_element.value_shape()) == 1: - orig_functionals = to_element.dual_basis() - dual_functionals = [] - - for dual in orig_functionals: - for comp1 in range(expression.ufl_shape[0]): - new_dual = copy.copy(dual) - new_pt_dict = {} - for quad_pt in sorted(dual.pt_dict): - old_weights_and_indices = dual.pt_dict[quad_pt] - new_weights_and_indices = [] - for (weight, index) in old_weights_and_indices: - new_weights_and_indices.append((weight, (comp1, index[0]))) - new_pt_dict[quad_pt] = new_weights_and_indices - new_dual.pt_dict = new_pt_dict - dual_functionals.append(new_dual) - for dual in dual_functionals: - quad_points = [*sorted(dual.pt_dict.keys())] - dual_expression = [] - - for comp1 in range(expression.ufl_shape[0]): - for comp2 in range(expression.ufl_shape[1]): - exp = expression[comp1, comp2] - quad_points_comp = [q for q in quad_points if dual.pt_dict[q][0][1] == () or (comp1, comp2) in [w[1] for w in dual.pt_dict[q]]] - weights_comp = [] - for q in quad_points_comp: - weights_comp.extend([w[0] for w in dual.pt_dict[q] if dual.pt_dict[q][0][1] == () or (comp1, comp2) == w[1] ]) - if len(quad_points_comp) > 0: - quad_rule = QuadratureRule(PointSet(quad_points_comp), weights_comp) - config = kernel_cfg.copy() - config.update(quadrature_rule=quad_rule) - expressions = [gem.index_sum(e, quad_rule.point_set.indices) - for e in fem.compile_ufl(exp, **config)] - dual_expression.extend(expressions) - - i = gem.Index() - dual_expr = gem.index_sum(gem.ListTensor(dual_expression)[i], (i, )) - dual_expressions.append(dual_expr) - dual_expressions = gem.ListTensor(dual_expressions) - else: - raise ValueError("Only know how to interpolate expressions in 1-3 dimensions") - - basis_indices = (gem.Index(), ) - ir = gem.partial_indexed(dual_expressions, basis_indices) - - # Deal with non-scalar expressions - value_shape = ir.shape - tensor_indices = tuple(gem.Index() for s in value_shape) - if value_shape: - ir = gem.Indexed(ir, tensor_indices) + pts = tuple(sorted(dual.get_point_dict().keys())) + try: + expr, point_set = expr_cache[pts] + except KeyError: + point_set = PointSet(pts) + config = kernel_cfg.copy() + config.update(point_set=point_set) + expr, = fem.compile_ufl(expression, **config, point_sum=False) + expr = gem.partial_indexed(expr, shape_indices) + expr_cache[pts] = expr, point_set + weights = collections.defaultdict(list) + for p in pts: + for (w, cmp) in dual.get_point_dict()[p]: + weights[cmp].append(w) + qexprs = gem.Zero() + for cmp in sorted(weights): + qweights = gem.Literal(weights[cmp]) + qexpr = gem.Indexed(expr, cmp) + qexpr = gem.index_sum(gem.Indexed(qweights, point_set.indices)*qexpr, + point_set.indices) + qexprs = gem.Sum(qexprs, qexpr) + assert qexprs.shape == () + assert set(qexprs.free_indices) == set(chain(shape_indices, *argument_multiindices)) + dual_expressions.append(qexprs) + basis_indices = (gem.Index(), ) + ir = gem.Indexed(gem.ListTensor(dual_expressions), basis_indices) # Build kernel body - return_indices = basis_indices + tensor_indices + tuple(chain(*argument_multiindices)) + return_indices = basis_indices + shape_indices + tuple(chain(*argument_multiindices)) return_shape = tuple(i.extent for i in return_indices) return_var = gem.Variable('A', return_shape) if coffee: @@ -595,14 +536,16 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte return_arg = lp.GlobalArg("A", dtype=parameters["scalar_type"], shape=return_shape) return_expr = gem.Indexed(return_var, return_indices) + + # TODO: one should apply some GEM optimisations as in assembly, + # but we don't for now. ir, = impero_utils.preprocess_gem([ir]) impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) - point_index, = basis_indices - + index_names = dict((idx, "p%d" % i) for (i, idx) in enumerate(basis_indices)) # Handle kernel interface requirements builder.register_requirements([ir]) # Build kernel tuple - return builder.construct_kernel(return_arg, impero_c, parameters["precision"], {point_index: 'p'}) + return builder.construct_kernel(return_arg, impero_c, parameters["precision"], index_names) def lower_integral_type(fiat_cell, integral_type): From 2edf527f7e55803f66c263a12adafc124a9bdf3a Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 31 Mar 2020 17:20:50 +0100 Subject: [PATCH 567/809] interpolation: Remove compile_expression_at_points --- tsfc/__init__.py | 2 +- tsfc/driver.py | 96 ------------------------------------------------ 2 files changed, 1 insertion(+), 97 deletions(-) diff --git a/tsfc/__init__.py b/tsfc/__init__.py index cffa0a716a..f9075c71a6 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -1,4 +1,4 @@ -from tsfc.driver import compile_form, compile_expression_at_points, compile_expression_dual_evaluation # noqa: F401 +from tsfc.driver import compile_form, compile_expression_dual_evaluation # noqa: F401 from tsfc.parameters import default_parameters # noqa: F401 try: diff --git a/tsfc/driver.py b/tsfc/driver.py index f0365f9cad..31879cb708 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -269,102 +269,6 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, impero_c, parameters["precision"], index_names, quad_rule) -def compile_expression_at_points(expression, points, coordinates, interface=None, - parameters=None, coffee=True): - """Compiles a UFL expression to be evaluated at compile-time known - reference points. Useful for interpolating UFL expressions onto - function spaces with only point evaluation nodes. - - :arg expression: UFL expression - :arg points: reference coordinates of the evaluation points - :arg coordinates: the coordinate function - :arg interface: backend module for the kernel interface - :arg parameters: parameters object - :arg coffee: compile coffee kernel instead of loopy kernel - """ - import coffee.base as ast - import loopy as lp - - if parameters is None: - parameters = default_parameters() - else: - _ = default_parameters() - _.update(parameters) - parameters = _ - - # Determine whether in complex mode - complex_mode = is_complex(parameters["scalar_type"]) - - # Apply UFL preprocessing - expression = ufl_utils.preprocess_expression(expression, - complex_mode=complex_mode) - - # Initialise kernel builder - if interface is None: - if coffee: - import tsfc.kernel_interface.firedrake as firedrake_interface_coffee - interface = firedrake_interface_coffee.ExpressionKernelBuilder - else: - # Delayed import, loopy is a runtime dependency - import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy - interface = firedrake_interface_loopy.ExpressionKernelBuilder - - builder = interface(parameters["scalar_type"]) - arguments = extract_arguments(expression) - argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() - for arg in arguments) - - # Replace coordinates (if any) - domain = expression.ufl_domain() - if domain: - assert coordinates.ufl_domain() == domain - builder.domain_coordinate[domain] = coordinates - builder.set_cell_sizes(domain) - - # Collect required coefficients - coefficients = extract_coefficients(expression) - if has_type(expression, GeometricQuantity) or any(fem.needs_coordinate_mapping(c.ufl_element()) for c in coefficients): - coefficients = [coordinates] + coefficients - builder.set_coefficients(coefficients) - - # Split mixed coefficients - expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) - - # Translate to GEM - point_set = PointSet(points) - config = dict(interface=builder, - ufl_cell=coordinates.ufl_domain().ufl_cell(), - precision=parameters["precision"], - point_set=point_set, - argument_multiindices=argument_multiindices) - ir, = fem.compile_ufl(expression, point_sum=False, **config) - - # Deal with non-scalar expressions - value_shape = ir.shape - tensor_indices = tuple(gem.Index() for s in value_shape) - if value_shape: - ir = gem.Indexed(ir, tensor_indices) - - # Build kernel body - return_indices = point_set.indices + tensor_indices + tuple(chain(*argument_multiindices)) - return_shape = tuple(i.extent for i in return_indices) - return_var = gem.Variable('A', return_shape) - if coffee: - return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) - else: - return_arg = lp.GlobalArg("A", dtype=parameters["scalar_type"], shape=return_shape) - - return_expr = gem.Indexed(return_var, return_indices) - ir, = impero_utils.preprocess_gem([ir]) - impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) - point_index, = point_set.indices - - # Handle kernel interface requirements - builder.register_requirements([ir]) - # Build kernel tuple - return builder.construct_kernel(return_arg, impero_c, parameters["precision"], {point_index: 'p'}) - - def apply_mapping(expression, to_element): # Find out which mapping to apply try: From 9af627d2c7999400c8d14493945bb24937029ae3 Mon Sep 17 00:00:00 2001 From: "Patrick E. Farrell" Date: Tue, 7 Apr 2020 20:08:54 +0100 Subject: [PATCH 568/809] Check for point evaluation directly value_shape() == () is not sufficient to ensure that every functional is a PointEvaluation. --- tsfc/driver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 31879cb708..da23b06a75 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -320,6 +320,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte """ import coffee.base as ast import loopy as lp + from FIAT.functional import PointEvaluation if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()): raise NotImplementedError("Can only interpolate onto dual basis functionals without derivative evaluation, sorry!") @@ -376,7 +377,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte argument_multiindices=argument_multiindices, index_cache={}) - if len(to_element.value_shape()) == 0: + if all(isinstance(dual, PointEvaluation) for dual in to_element.dual_basis()): # This is an optimisation for point-evaluation nodes which # should go away once FInAT offers the interface properly qpoints = [] @@ -397,12 +398,11 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte ir = gem.Indexed(expr, shape_indices) else: # This is general code but is more unrolled than necssary. - dual_functionals = to_element.dual_basis() dual_expressions = [] # one for each functional broadcast_shape = len(expression.ufl_shape) - len(to_element.value_shape()) shape_indices = tuple(gem.Index() for _ in expression.ufl_shape[:broadcast_shape]) expr_cache = {} # Sharing of evaluation of the expression at points - for dual in dual_functionals: + for dual in to_element.dual_basis(): pts = tuple(sorted(dual.get_point_dict().keys())) try: expr, point_set = expr_cache[pts] From b2477c265520a07a2fb62b0c014ea848a50742f4 Mon Sep 17 00:00:00 2001 From: Justincrum Date: Tue, 14 Apr 2020 11:27:05 -0700 Subject: [PATCH 569/809] Plumbing for SminusDiv.py --- tsfc/fiatinterface.py | 1 + tsfc/finatinterface.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 909ece4290..993bb1bf52 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -63,6 +63,7 @@ "DPC": FIAT.DPC, "S": FIAT.Serendipity, "SminusF": FIAT.TrimmedSerendipityFace, + "SminusDiv": FIAT.TrimmedSerendipityDiv, "SminusE": FIAT.TrimmedSerendipityEdge, "DPC L2": FIAT.DPC, "Discontinuous Lagrange L2": FIAT.DiscontinuousLagrange, diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index a84fe6623c..b4c0e74926 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -68,6 +68,7 @@ "DPC": finat.DPC, "S": finat.Serendipity, "SminusF": finat.TrimmedSerendipityFace, + "SminusDiv": finat.TrimmedSerendipityDiv, "SminusE": finat.TrimmedSerendipityEdge, "DPC L2": finat.DPC, "Discontinuous Lagrange L2": finat.DiscontinuousLagrange, From 316f0efc839a79939c1a82f98dc12221e66e4e4d Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Tue, 14 Apr 2020 19:37:17 +0100 Subject: [PATCH 570/809] Document apply_mapping --- tsfc/driver.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index da23b06a75..3de7517846 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -270,6 +270,50 @@ def name_multiindex(multiindex, name): def apply_mapping(expression, to_element): + """ + This applies the appropriate transformation to the + given expression for interpolation to a specific + element, according to the manner in which it maps + from the reference cell. + + The following is borrowed from the UFC documentation: + + Let g be a field defined on a physical domain T with physical + coordinates x. Let T_0 be a reference domain with coordinates + X. Assume that F: T_0 -> T such that + + x = F(X) + + Let J be the Jacobian of F, i.e J = dx/dX and let K denote the + inverse of the Jacobian K = J^{-1}. Then we (currently) have the + following four types of mappings: + + 'affine' mapping for g: + + G(X) = g(x) + + For vector fields g: + + 'contravariant piola' mapping for g: + + G(X) = det(J) K g(x) i.e G_i(X) = det(J) K_ij g_j(x) + + 'covariant piola' mapping for g: + + G(X) = J^T g(x) i.e G_i(X) = J^T_ij g(x) = J_ji g_j(x) + + 'double covariant piola' mapping for g: + + G(X) = J^T g(x) J i.e. G_il(X) = J_ji g_jk(x) J_kl + + 'double contravariant piola' mapping for g: + + G(X) = det(J)^2 K g(x) K^T i.e. G_il(X)=(detJ)^2 K_ij g_jk K_lk + + If 'contravariant piola' or 'covariant piola' are applied to a + matrix-valued function, the appropriate mappings are applied row-by-row. + """ + # Find out which mapping to apply try: mapping, = set(to_element.mapping()) From 696488365775733ccf619fe2aab1b01c083a0b5b Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Tue, 21 Apr 2020 16:20:46 +0100 Subject: [PATCH 571/809] Move apply_mapping to ufl_utils, as per Miklos' suggestion --- tsfc/driver.py | 93 +++++------------------------------------------ tsfc/ufl_utils.py | 78 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 84 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 3de7517846..9bbe06718d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -11,16 +11,15 @@ import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type -from ufl.geometry import Jacobian, JacobianDeterminant, JacobianInverse from ufl.classes import Form, GeometricQuantity from ufl.log import GREEN from ufl.utils.sequences import max_degree -from ufl.tensors import as_tensor import gem import gem.impero_utils as impero_utils from FIAT.reference_element import TensorProductCell +from FIAT.functional import PointEvaluation from finat.point_set import PointSet from finat.quadrature import AbstractQuadratureRule, make_quadrature @@ -29,6 +28,7 @@ from tsfc.fiatinterface import as_fiat_cell from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex +from tsfc.ufl_utils import apply_mapping # To handle big forms. The various transformations might need a deeper stack sys.setrecursionlimit(3000) @@ -269,86 +269,6 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, impero_c, parameters["precision"], index_names, quad_rule) -def apply_mapping(expression, to_element): - """ - This applies the appropriate transformation to the - given expression for interpolation to a specific - element, according to the manner in which it maps - from the reference cell. - - The following is borrowed from the UFC documentation: - - Let g be a field defined on a physical domain T with physical - coordinates x. Let T_0 be a reference domain with coordinates - X. Assume that F: T_0 -> T such that - - x = F(X) - - Let J be the Jacobian of F, i.e J = dx/dX and let K denote the - inverse of the Jacobian K = J^{-1}. Then we (currently) have the - following four types of mappings: - - 'affine' mapping for g: - - G(X) = g(x) - - For vector fields g: - - 'contravariant piola' mapping for g: - - G(X) = det(J) K g(x) i.e G_i(X) = det(J) K_ij g_j(x) - - 'covariant piola' mapping for g: - - G(X) = J^T g(x) i.e G_i(X) = J^T_ij g(x) = J_ji g_j(x) - - 'double covariant piola' mapping for g: - - G(X) = J^T g(x) J i.e. G_il(X) = J_ji g_jk(x) J_kl - - 'double contravariant piola' mapping for g: - - G(X) = det(J)^2 K g(x) K^T i.e. G_il(X)=(detJ)^2 K_ij g_jk K_lk - - If 'contravariant piola' or 'covariant piola' are applied to a - matrix-valued function, the appropriate mappings are applied row-by-row. - """ - - # Find out which mapping to apply - try: - mapping, = set(to_element.mapping()) - except ValueError: - raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") - - mesh = expression.ufl_domain() - rank = len(expression.ufl_shape) - if mapping == "affine": - return expression - elif mapping == "covariant piola": - J = Jacobian(mesh) - *i, j, k = ufl.indices(len(expression.ufl_shape) + 1) - expression = ufl.classes.Indexed(expression, ufl.classes.MultiIndex((*i, k))) - return as_tensor(J.T[j, k] * expression, (*i, j)) - elif mapping == "contravariant piola": - mesh = expression.ufl_domain() - K = JacobianInverse(mesh) - detJ = JacobianDeterminant(mesh) - *i, j, k = ufl.indices(len(expression.ufl_shape) + 1) - expression = ufl.classes.Indexed(expression, ufl.classes.MultiIndex((*i, k))) - return as_tensor(detJ * K[j, k] * expression, (*i, j)) - elif mapping == "double covariant piola" and rank == 2: - mesh = expression.ufl_domain() - J = Jacobian(mesh) - return J.T * expression * J - elif mapping == "double contravariant piola" and rank == 2: - mesh = expression.ufl_domain() - K = JacobianInverse(mesh) - detJ = JacobianDeterminant(mesh) - return (detJ)**2 * K * expression * K.T - else: - raise NotImplementedError("Don't know how to handle mapping type %s for expression of rank %d" % (mapping, rank)) - - def compile_expression_dual_evaluation(expression, to_element, coordinates, interface=None, parameters=None, coffee=False): """Compile a UFL expression to be evaluated against a compile-time known reference element's dual basis. @@ -364,7 +284,6 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte """ import coffee.base as ast import loopy as lp - from FIAT.functional import PointEvaluation if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()): raise NotImplementedError("Can only interpolate onto dual basis functionals without derivative evaluation, sorry!") @@ -378,7 +297,13 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Determine whether in complex mode complex_mode = is_complex(parameters["scalar_type"]) - expression = apply_mapping(expression, to_element) + # Find out which mapping to apply + try: + mapping, = set(to_element.mapping()) + except ValueError: + raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") + expression = apply_mapping(expression, mapping) + # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, complex_mode=complex_mode) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 888ac02821..b39b7a9018 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -18,6 +18,7 @@ from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction from ufl.geometry import QuadratureWeight +from ufl.geometry import Jacobian, JacobianDeterminant, JacobianInverse from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, ComponentTensor, Expr, FloatValue, Division, MixedElement, MultiIndex, Product, @@ -330,3 +331,80 @@ def simplify_abs(expression): purpose is to "neutralise" CellOrientation nodes that are surrounded by absolute values and thus not at all necessary.""" return MemoizerArg(_simplify_abs)(expression, False) + + +def apply_mapping(expression, mapping): + """ + This applies the appropriate transformation to the + given expression for interpolation to a specific + element, according to the manner in which it maps + from the reference cell. + + The following is borrowed from the UFC documentation: + + Let g be a field defined on a physical domain T with physical + coordinates x. Let T_0 be a reference domain with coordinates + X. Assume that F: T_0 -> T such that + + x = F(X) + + Let J be the Jacobian of F, i.e J = dx/dX and let K denote the + inverse of the Jacobian K = J^{-1}. Then we (currently) have the + following four types of mappings: + + 'affine' mapping for g: + + G(X) = g(x) + + For vector fields g: + + 'contravariant piola' mapping for g: + + G(X) = det(J) K g(x) i.e G_i(X) = det(J) K_ij g_j(x) + + 'covariant piola' mapping for g: + + G(X) = J^T g(x) i.e G_i(X) = J^T_ij g(x) = J_ji g_j(x) + + 'double covariant piola' mapping for g: + + G(X) = J^T g(x) J i.e. G_il(X) = J_ji g_jk(x) J_kl + + 'double contravariant piola' mapping for g: + + G(X) = det(J)^2 K g(x) K^T i.e. G_il(X)=(detJ)^2 K_ij g_jk K_lk + + If 'contravariant piola' or 'covariant piola' are applied to a + matrix-valued function, the appropriate mappings are applied row-by-row. + + :arg expression: UFL expression + :arg mapping: a string indicating the mapping to apply + """ + + mesh = expression.ufl_domain() + rank = len(expression.ufl_shape) + if mapping == "affine": + return expression + elif mapping == "covariant piola": + J = Jacobian(mesh) + *i, j, k = ufl.indices(len(expression.ufl_shape) + 1) + expression = ufl.classes.Indexed(expression, ufl.classes.MultiIndex((*i, k))) + return as_tensor(J.T[j, k] * expression, (*i, j)) + elif mapping == "contravariant piola": + mesh = expression.ufl_domain() + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + *i, j, k = ufl.indices(len(expression.ufl_shape) + 1) + expression = ufl.classes.Indexed(expression, ufl.classes.MultiIndex((*i, k))) + return as_tensor(detJ * K[j, k] * expression, (*i, j)) + elif mapping == "double covariant piola" and rank == 2: + mesh = expression.ufl_domain() + J = Jacobian(mesh) + return J.T * expression * J + elif mapping == "double contravariant piola" and rank == 2: + mesh = expression.ufl_domain() + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + return (detJ)**2 * K * expression * K.T + else: + raise NotImplementedError("Don't know how to handle mapping type %s for expression of rank %d" % (mapping, rank)) From a153fbeb859216e8610edfa37127a314b65b292f Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 21 Apr 2020 17:10:11 +0100 Subject: [PATCH 572/809] No need for fully-qualified class names --- tsfc/ufl_utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index b39b7a9018..4c2da3a662 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -21,7 +21,7 @@ from ufl.geometry import Jacobian, JacobianDeterminant, JacobianInverse from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, ComponentTensor, Expr, FloatValue, Division, - MixedElement, MultiIndex, Product, + Indexed, MixedElement, MultiIndex, Product, ScalarValue, Sqrt, Zero, CellVolume, FacetArea) from gem.node import MemoizerArg @@ -387,15 +387,15 @@ def apply_mapping(expression, mapping): return expression elif mapping == "covariant piola": J = Jacobian(mesh) - *i, j, k = ufl.indices(len(expression.ufl_shape) + 1) - expression = ufl.classes.Indexed(expression, ufl.classes.MultiIndex((*i, k))) + *i, j, k = indices(len(expression.ufl_shape) + 1) + expression = Indexed(expression, MultiIndex((*i, k))) return as_tensor(J.T[j, k] * expression, (*i, j)) elif mapping == "contravariant piola": mesh = expression.ufl_domain() K = JacobianInverse(mesh) detJ = JacobianDeterminant(mesh) - *i, j, k = ufl.indices(len(expression.ufl_shape) + 1) - expression = ufl.classes.Indexed(expression, ufl.classes.MultiIndex((*i, k))) + *i, j, k = indices(len(expression.ufl_shape) + 1) + expression = Indexed(expression, MultiIndex((*i, k))) return as_tensor(detJ * K[j, k] * expression, (*i, j)) elif mapping == "double covariant piola" and rank == 2: mesh = expression.ufl_domain() From ae8fe0e637764e0228ff0c5f17c7fce9f78b7c36 Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Tue, 28 Apr 2020 15:38:07 +0100 Subject: [PATCH 573/809] Fix tsfc bug #208. Enables interpolate(QuadratureWeight(mesh), FunctionSpace(..., QuadratureElement(...))). --- tsfc/driver.py | 9 ++++++++- tsfc/fiatinterface.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 9bbe06718d..7b5e3ca6b2 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -18,11 +18,12 @@ import gem import gem.impero_utils as impero_utils +import FIAT from FIAT.reference_element import TensorProductCell from FIAT.functional import PointEvaluation from finat.point_set import PointSet -from finat.quadrature import AbstractQuadratureRule, make_quadrature +from finat.quadrature import AbstractQuadratureRule, make_quadrature, QuadratureRule from tsfc import fem, ufl_utils from tsfc.fiatinterface import as_fiat_cell @@ -361,6 +362,12 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte point_set = PointSet(qpoints) config = kernel_cfg.copy() config.update(point_set=point_set) + + # Fix for interpolating QuadratureWeight into a QuadratureElement + if isinstance(to_element, FIAT.QuadratureElement): + quad_rule = QuadratureRule(point_set, to_element._weights) + config["quadrature_rule"] = quad_rule + expr, = fem.compile_ufl(expression, **config, point_sum=False) shape_indices = tuple(gem.Index() for _ in expr.shape) basis_indices = point_set.indices diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index c35171f475..e485d6bb35 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -112,7 +112,7 @@ def convert_finiteelement(element, vector_is_mixed): raise ValueError("Quadrature scheme and degree must be specified!") quad_rule = FIAT.create_quadrature(cell, degree, scheme) - return FIAT.QuadratureElement(cell, quad_rule.get_points()) + return FIAT.QuadratureElement(cell, quad_rule.get_points(), weights=quad_rule.get_weights()) lmbda = supported_elements[element.family()] if lmbda is None: if element.cell().cellname() == "quadrilateral": From 2a063c40c0db2af9f56485407ea06fc8bfe1cc6d Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Tue, 28 Apr 2020 19:08:54 +0100 Subject: [PATCH 574/809] Better comment --- tsfc/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7b5e3ca6b2..66f6e51f79 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -363,7 +363,8 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte config = kernel_cfg.copy() config.update(point_set=point_set) - # Fix for interpolating QuadratureWeight into a QuadratureElement + # Allow interpolation onto QuadratureElements to refer to the quadrature + # rule they represent if isinstance(to_element, FIAT.QuadratureElement): quad_rule = QuadratureRule(point_set, to_element._weights) config["quadrature_rule"] = quad_rule From 14f18d5e41fa717929013d6bbf20af3dc48fa2ff Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Wed, 29 Apr 2020 11:27:42 +0100 Subject: [PATCH 575/809] Assert that the point sets match up --- tsfc/driver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 66f6e51f79..4d3793a85b 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -366,6 +366,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Allow interpolation onto QuadratureElements to refer to the quadrature # rule they represent if isinstance(to_element, FIAT.QuadratureElement): + assert all(qpoints == to_element._points) quad_rule = QuadratureRule(point_set, to_element._weights) config["quadrature_rule"] = quad_rule From 801a3b2ee8db3ce881bf0c3b77bfe57bee421f46 Mon Sep 17 00:00:00 2001 From: Sophia Vorderwuelbecke Date: Thu, 30 Apr 2020 15:02:11 +0100 Subject: [PATCH 576/809] kernel: Adapt to new generate_coffee API --- tsfc/kernel_interface/firedrake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 7906e39c4e..a7321edfcc 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -157,7 +157,7 @@ def construct_kernel(self, return_arg, impero_c, precision, index_names): args.append(self.cell_sizes_arg) args.extend(self.kernel_args) - body = generate_coffee(impero_c, index_names, precision) + body = generate_coffee(impero_c, index_names, precision, self.scalar_type) for name_, shape in self.tabulations: args.append(coffee.Decl(self.scalar_type, coffee.Symbol( From e58b61832ae43c9349f318b5e5ba2faa886085e6 Mon Sep 17 00:00:00 2001 From: Sophia Vorderwuelbecke Date: Thu, 30 Apr 2020 15:03:58 +0100 Subject: [PATCH 577/809] loopy: Code generation to loopy for shaped GEM nodes Particularly ComponentTensor, Inverse, and Solve. ComponentTensor generation maps straightforwardly onto loopy's "vector-valued" calling conventions. For Inverse and Solve we emit opaque callables that the downstream consumer must ensure are provided. --- tsfc/loopy.py | 110 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 90 insertions(+), 20 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index 2df1e379c6..baa3b5975b 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -14,11 +14,14 @@ import loopy as lp import pymbolic.primitives as p +from loopy.symbolic import SubArrayRef from pytools import UniqueNameGenerator from tsfc.parameters import is_complex +from contextlib import contextmanager + class LoopyContext(object): def __init__(self): @@ -28,7 +31,7 @@ def __init__(self): self.gem_to_pymbolic = {} # gem node -> pymbolic variable self.name_gen = UniqueNameGenerator() - def pym_multiindex(self, multiindex): + def fetch_multiindex(self, multiindex): indices = [] for index in multiindex: if isinstance(index, gem.Index): @@ -40,27 +43,71 @@ def pym_multiindex(self, multiindex): indices.append(index) return tuple(indices) + # Generate index from gem multiindex + def gem_to_pym_multiindex(self, multiindex): + indices = [] + for index in multiindex: + assert index.extent + if not index.name: + name = self.name_gen(self.index_names[index]) + else: + name = index.name + self.index_extent[name] = index.extent + indices.append(p.Variable(name)) + return tuple(indices) + + # Generate index from shape + def pymbolic_multiindex(self, shape): + indices = [] + for extent in shape: + name = self.name_gen(self.index_names[extent]) + self.index_extent[name] = extent + indices.append(p.Variable(name)) + return tuple(indices) + + # Generate pym variable from gem + def pymbolic_variable_and_destruct(self, node): + pym = self.pymbolic_variable(node) + if isinstance(pym, p.Subscript): + return pym.aggregate, pym.index_tuple + else: + return pym, () + + # Generate pym variable or subscript def pymbolic_variable(self, node): + pym = self._gem_to_pym_var(node) + if node in self.indices: + indices = self.fetch_multiindex(self.indices[node]) + if indices: + return p.Subscript(pym, indices) + return pym + + def _gem_to_pym_var(self, node): try: pym = self.gem_to_pymbolic[node] except KeyError: name = self.name_gen(node.name) pym = p.Variable(name) self.gem_to_pymbolic[node] = pym - if node in self.indices: - indices = self.pym_multiindex(self.indices[node]) - if indices: - return p.Subscript(pym, indices) - else: - return pym - else: - return pym + return pym def active_inames(self): # Return all active indices return frozenset([i.name for i in self.active_indices.values()]) +@contextmanager +def active_indices(mapping, ctx): + """Push active indices onto context. + :arg mapping: dict mapping gem indices to pymbolic index expressions + :arg ctx: code generation context. + :returns: new code generation context.""" + ctx.active_indices.update(mapping) + yield ctx + for key in mapping: + ctx.active_indices.pop(key) + + def generate(impero_c, args, precision, scalar_type, kernel_name="loopy_kernel", index_names=[]): """Generates loopy code. @@ -141,13 +188,9 @@ def statement_for(tree, ctx): extent = tree.index.extent assert extent idx = ctx.name_gen(ctx.index_names[tree.index]) - ctx.active_indices[tree.index] = p.Variable(idx) ctx.index_extent[idx] = extent - - statements = statement(tree.children[0], ctx) - - ctx.active_indices.pop(tree.index) - return statements + with active_indices({tree.index: p.Variable(idx)}, ctx) as ctx_active: + return statement(tree.children[0], ctx_active) @statement.register(imp.Initialise) @@ -181,15 +224,42 @@ def statement_evaluate(leaf, ctx): expr = leaf.expression if isinstance(expr, gem.ListTensor): ops = [] - var = ctx.pymbolic_variable(expr) - index = () - if isinstance(var, p.Subscript): - var, index = var.aggregate, var.index_tuple + var, index = ctx.pymbolic_variable_and_destruct(expr) for multiindex, value in numpy.ndenumerate(expr.array): ops.append(lp.Assignment(p.Subscript(var, index + multiindex), expression(value, ctx), within_inames=ctx.active_inames())) return ops elif isinstance(expr, gem.Constant): return [] + elif isinstance(expr, gem.ComponentTensor): + idx = ctx.gem_to_pym_multiindex(expr.multiindex) + var, sub_idx = ctx.pymbolic_variable_and_destruct(expr) + lhs = p.Subscript(var, idx + sub_idx) + with active_indices(dict(zip(expr.multiindex, idx)), ctx) as ctx_active: + return [lp.Assignment(lhs, expression(expr.children[0], ctx_active), within_inames=ctx_active.active_inames())] + elif isinstance(expr, gem.Inverse): + idx = ctx.pymbolic_multiindex(expr.shape) + var = ctx.pymbolic_variable(expr) + lhs = (SubArrayRef(idx, p.Subscript(var, idx)),) + + idx_reads = ctx.pymbolic_multiindex(expr.children[0].shape) + var_reads = ctx.pymbolic_variable(expr.children[0]) + reads = (SubArrayRef(idx_reads, p.Subscript(var_reads, idx_reads)),) + rhs = p.Call(p.Variable("inv"), reads) + + return [lp.CallInstruction(lhs, rhs, within_inames=ctx.active_inames())] + elif isinstance(expr, gem.Solve): + idx = ctx.pymbolic_multiindex(expr.shape) + var = ctx.pymbolic_variable(expr) + lhs = (SubArrayRef(idx, p.Subscript(var, idx)),) + + reads = [] + for child in expr.children: + idx_reads = ctx.pymbolic_multiindex(child.shape) + var_reads = ctx.pymbolic_variable(child) + reads.append(SubArrayRef(idx_reads, p.Subscript(var_reads, idx_reads))) + rhs = p.Call(p.Variable("solve"), tuple(reads)) + + return [lp.CallInstruction(lhs, rhs, within_inames=ctx.active_inames())] else: return [lp.Assignment(ctx.pymbolic_variable(expr), expression(expr, ctx, top=True), within_inames=ctx.active_inames())] @@ -344,7 +414,7 @@ def _expression_variable(expr, ctx): @_expression.register(gem.Indexed) def _expression_indexed(expr, ctx): - rank = ctx.pym_multiindex(expr.multiindex) + rank = ctx.fetch_multiindex(expr.multiindex) var = expression(expr.children[0], ctx) if isinstance(var, p.Subscript): rank = var.index + rank From b50b8bea2af69ddfb1efe3a9b7e334ae821d7fba Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Tue, 5 May 2020 10:42:26 +0100 Subject: [PATCH 578/809] Type-cast everything to numpy arrays in assertion --- tsfc/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 4d3793a85b..c7f4f9c807 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -366,7 +366,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Allow interpolation onto QuadratureElements to refer to the quadrature # rule they represent if isinstance(to_element, FIAT.QuadratureElement): - assert all(qpoints == to_element._points) + assert (asarray(qpoints) == asarray(to_element._points)).all() quad_rule = QuadratureRule(point_set, to_element._weights) config["quadrature_rule"] = quad_rule From 8b863e545d62ff5396364dcfbe164532fda069c7 Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Tue, 5 May 2020 10:57:42 +0100 Subject: [PATCH 579/809] Use numpy.allclose --- tsfc/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index c7f4f9c807..239dc17c2e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -366,7 +366,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Allow interpolation onto QuadratureElements to refer to the quadrature # rule they represent if isinstance(to_element, FIAT.QuadratureElement): - assert (asarray(qpoints) == asarray(to_element._points)).all() + assert allclose(asarray(qpoints), asarray(to_element._points)) quad_rule = QuadratureRule(point_set, to_element._weights) config["quadrature_rule"] = quad_rule From 673b47f57f5bd5b8ef07e74d0db72210f1a1ade1 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 30 Apr 2020 16:58:07 +0100 Subject: [PATCH 580/809] loopy: Correct rounding in complex mode --- tsfc/loopy.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index baa3b5975b..a130c743d6 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -2,8 +2,6 @@ This is the final stage of code generation in TSFC.""" -from math import isnan - import numpy from functools import singledispatch from collections import defaultdict, OrderedDict @@ -399,10 +397,10 @@ def _expression_conditional(expr, ctx): def _expression_scalar(expr, parameters): assert not expr.shape v = expr.value - if isnan(v): + if numpy.isnan(v): return p.Variable("NAN") - r = round(v, 1) - if r and abs(v - r) < parameters.epsilon: + r = numpy.round(v, 1) + if r and numpy.abs(v - r) < parameters.epsilon: return r return v From 8a3e3f866e080ce042a05891b453a91ca0b2ebb9 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 30 Apr 2020 16:59:31 +0100 Subject: [PATCH 581/809] parameters: scalar_type is a dtype --- tsfc/driver.py | 6 +++++- tsfc/parameters.py | 10 +++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 239dc17c2e..2f31f2453c 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -98,6 +98,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co # Delayed import, loopy is a runtime dependency import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy interface = firedrake_interface_loopy.KernelBuilder + if coffee: + scalar_type = parameters["scalar_type_c"] + else: + scalar_type = parameters["scalar_type"] # Remove these here, they're handled below. if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: @@ -123,7 +127,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co domain_numbering = form_data.original_form.domain_numbering() builder = interface(integral_type, integral_data.subdomain_id, domain_numbering[integral_data.domain], - parameters["scalar_type"], + scalar_type, diagonal=diagonal) argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() for arg in arguments) diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 8cd98be42b..dbd65a824d 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -14,8 +14,11 @@ # that makes compilation time much shorter. "unroll_indexsum": 3, - # Scalar type (C typename string) - "scalar_type": "double", + # Scalar type numpy dtype + "scalar_type": numpy.dtype(numpy.float64), + + # So that tests pass (needs to match scalar_type) + "scalar_type_c": "double", # Precision of float printing (number of digits) "precision": numpy.finfo(numpy.dtype("double")).precision, @@ -28,4 +31,5 @@ def default_parameters(): def is_complex(scalar_type): """Decides complex mode based on scalar type.""" - return scalar_type and 'complex' in scalar_type + return scalar_type and (isinstance(scalar_type, numpy.dtype) and scalar_type.kind == 'c') \ + or (isinstance(scalar_type, str) and "complex" in scalar_type) From dd0e42373aea73f5145a08ca42fdb2cfd4a8d4a4 Mon Sep 17 00:00:00 2001 From: David Ham Date: Thu, 30 Apr 2020 17:00:29 +0100 Subject: [PATCH 582/809] loopy: Correct math function name mappings --- tsfc/loopy.py | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index a130c743d6..05bde41b16 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -21,6 +21,37 @@ from contextlib import contextmanager +# Table of handled math functions in real and complex modes +# Note that loopy handles addition of type prefixes and suffixes itself. +math_table = { + 'sqrt': ('sqrt', 'sqrt'), + 'abs': ('abs', 'abs'), + 'cos': ('cos', 'cos'), + 'sin': ('sin', 'sin'), + 'tan': ('tan', 'tan'), + 'acos': ('acos', 'acos'), + 'asin': ('asin', 'asin'), + 'atan': ('atan', 'atan'), + 'cosh': ('cosh', 'cosh'), + 'sinh': ('sinh', 'sinh'), + 'tanh': ('tanh', 'tanh'), + 'acosh': ('acosh', 'acosh'), + 'asinh': ('asinh', 'asinh'), + 'atanh': ('atanh', 'atanh'), + 'power': ('pow', 'pow'), + 'exp': ('exp', 'exp'), + 'ln': ('log', 'log'), + 'real': (None, 'real'), + 'imag': (None, 'imag'), + 'conj': (None, 'conj'), + 'erf': ('erf', None), + 'atan_2': ('atan2', None), + 'atan2': ('atan2', None), + 'min_value': ('min', None), + 'max_value': ('max', None) +} + + class LoopyContext(object): def __init__(self): self.indices = {} # indices for declarations and referencing values, from ImperoC @@ -309,11 +340,6 @@ def _expression_power(expr, ctx): @_expression.register(gem.MathFunction) def _expression_mathfunction(expr, ctx): - from tsfc.coffee import math_table - - math_table = math_table.copy() - math_table['abs'] = ('abs', 'cabs') - complex_mode = int(is_complex(ctx.scalar_type)) # Bessel functions @@ -352,7 +378,8 @@ def _expression_mathfunction(expr, ctx): # Other math functions name = math_table[expr.name][complex_mode] if name is None: - raise RuntimeError("{} not supported in complex mode".format(expr.name)) + raise RuntimeError("{} not supported in {} mode".format(expr.name, + ("real", "complex")[complex_mode])) return p.Variable(name)(*[expression(c, ctx) for c in expr.children]) From 8f62181526e02e9b22d65653d812f6830164cd58 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 30 Apr 2020 17:03:40 +0100 Subject: [PATCH 583/809] gem: Teach refactoriser about conj/real/imag Fixes #166. --- tsfc/driver.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2f31f2453c..d03165cc50 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -50,14 +50,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr assert isinstance(form, Form) # Determine whether in complex mode: - # complex nodes would break the refactoriser. complex_mode = parameters and is_complex(parameters.get("scalar_type")) - if complex_mode: - logger.warning("Disabling whole expression optimisations" - " in GEM for supporting complex mode.") - parameters = parameters.copy() - parameters["mode"] = 'vanilla' - fd = ufl_utils.compute_form_data(form, complex_mode=complex_mode) logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) From 70920bb9d20199281a6bca78562c8f7e91045a01 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 30 Apr 2020 17:04:24 +0100 Subject: [PATCH 584/809] loopy: Type propagation for expressions Enables correct temporary variable dtype to be applied during code generation. Fixes #171. --- tsfc/loopy.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index 05bde41b16..ca6a53568c 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -3,10 +3,11 @@ This is the final stage of code generation in TSFC.""" import numpy -from functools import singledispatch +from functools import singledispatch, partial from collections import defaultdict, OrderedDict from gem import gem, impero as imp +from gem.node import Memoizer import islpy as isl import loopy as lp @@ -52,6 +53,85 @@ } +maxtype = partial(numpy.find_common_type, []) + + +@singledispatch +def _assign_dtype(expression, self): + return maxtype(map(self, expression.children)) + + +@_assign_dtype.register(gem.Terminal) +def _assign_dtype_terminal(expression, self): + return self.scalar_type + + +@_assign_dtype.register(gem.Zero) +@_assign_dtype.register(gem.Identity) +@_assign_dtype.register(gem.Delta) +def _assign_dtype_real(expression, self): + return self.real_type + + +@_assign_dtype.register(gem.Literal) +def _assign_dtype_identity(expression, self): + return expression.array.dtype + + +@_assign_dtype.register(gem.Power) +def _assign_dtype_power(expression, self): + # Conservative + return self.scalar_type + + +@_assign_dtype.register(gem.MathFunction) +def _assign_dtype_mathfunction(expression, self): + if expression.name in {"abs", "real", "imag"}: + return self.real_type + elif expression.name == "sqrt": + return self.scalar_type + else: + return maxtype(map(self, expression.children)) + + +@_assign_dtype.register(gem.MinValue) +@_assign_dtype.register(gem.MaxValue) +def _assign_dtype_minmax(expression, self): + # UFL did correctness checking + return self.real_type + + +@_assign_dtype.register(gem.Conditional) +def _assign_dtype_conditional(expression, self): + return maxtype(map(self, expression.children[1:])) + + +@_assign_dtype.register(gem.Comparison) +@_assign_dtype.register(gem.LogicalNot) +@_assign_dtype.register(gem.LogicalAnd) +@_assign_dtype.register(gem.LogicalOr) +def _assign_dtype_logical(expression, self): + return numpy.int8 + + +def assign_dtypes(expressions, scalar_type): + """Assign numpy data types to expressions. + + Used for declaring temporaries when converting from Impero to lower level code. + + :arg expressions: List of GEM expressions. + :arg scalar_type: Default scalar type. + + :returns: list of tuples (expression, dtype).""" + mapper = Memoizer(_assign_dtype) + mapper.scalar_type = scalar_type + if scalar_type.kind == "c": + mapper.real_type = numpy.finfo(scalar_type).dtype + else: + mapper.real_type = scalar_type + return [(e, mapper(e)) for e in expressions] + + class LoopyContext(object): def __init__(self): self.indices = {} # indices for declarations and referencing values, from ImperoC @@ -157,13 +237,13 @@ def generate(impero_c, args, precision, scalar_type, kernel_name="loopy_kernel", # Create arguments data = list(args) - for i, temp in enumerate(impero_c.temporaries): + for i, (temp, dtype) in enumerate(assign_dtypes(impero_c.temporaries, scalar_type)): name = "t%d" % i if isinstance(temp, gem.Constant): - data.append(lp.TemporaryVariable(name, shape=temp.shape, dtype=temp.array.dtype, initializer=temp.array, address_space=lp.AddressSpace.LOCAL, read_only=True)) + data.append(lp.TemporaryVariable(name, shape=temp.shape, dtype=dtype, initializer=temp.array, address_space=lp.AddressSpace.LOCAL, read_only=True)) else: shape = tuple([i.extent for i in ctx.indices[temp]]) + temp.shape - data.append(lp.TemporaryVariable(name, shape=shape, dtype=numpy.float64, initializer=None, address_space=lp.AddressSpace.LOCAL, read_only=False)) + data.append(lp.TemporaryVariable(name, shape=shape, dtype=dtype, initializer=None, address_space=lp.AddressSpace.LOCAL, read_only=False)) ctx.gem_to_pymbolic[temp] = p.Variable(name) # Create instructions From bdc2de9f38f19b956f86f2e9be8022337013ed0c Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 7 May 2020 23:30:39 +0100 Subject: [PATCH 585/809] compile_ufl: correct abs-simplification for sqrt in complex mode The assumption that the sqrt of a value is positive (and therefore doesn't require wrapping in abs) is no longer true when values can be complex. --- tsfc/driver.py | 6 ++++-- tsfc/fem.py | 26 +++++++++++++------------- tsfc/ufl_utils.py | 13 +++++++++---- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index d03165cc50..cc98895dd3 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -155,7 +155,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co integration_dim=integration_dim, entity_ids=entity_ids, argument_multiindices=argument_multiindices, - index_cache=index_cache) + index_cache=index_cache, + complex_mode=is_complex(parameters.get("scalar_type"))) mode_irs = collections.OrderedDict() for integral in integral_data.integrals: @@ -342,7 +343,8 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte ufl_cell=coordinates.ufl_domain().ufl_cell(), precision=parameters["precision"], argument_multiindices=argument_multiindices, - index_cache={}) + index_cache={}, + complex_mode=complex_mode) if all(isinstance(dual, PointEvaluation) for dual in to_element.dual_basis()): # This is an optimisation for point-evaluation nodes which diff --git a/tsfc/fem.py b/tsfc/fem.py index 5c226384d0..1726c46882 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -56,7 +56,8 @@ class ContextBase(ProxyKernelInterface): 'precision', 'argument_multiindices', 'facetarea', - 'index_cache') + 'index_cache', + 'complex_mode') def __init__(self, interface, **kwargs): ProxyKernelInterface.__init__(self, interface) @@ -130,7 +131,7 @@ def __init__(self, mt, interface): @property def config(self): config = {name: getattr(self.interface, name) - for name in ["ufl_cell", "precision", "index_cache"]} + for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} config["interface"] = self.interface return config @@ -143,11 +144,10 @@ def jacobian_at(self, point): expr = PositiveRestricted(expr) elif self.mt.restriction == '-': expr = NegativeRestricted(expr) - expr = preprocess_expression(expr) - config = {"point_set": PointSingleton(point)} config.update(self.config) context = PointSetContext(**config) + expr = preprocess_expression(expr, complex_mode=context.complex_mode) return map_expr_dag(context.translator, expr) def reference_normals(self): @@ -175,10 +175,10 @@ def physical_edge_lengths(self): expr = NegativeRestricted(expr) expr = ufl.as_vector([ufl.sqrt(ufl.dot(expr[i, :], expr[i, :])) for i in range(3)]) - expr = preprocess_expression(expr) config = {"point_set": PointSingleton([1/3, 1/3])} config.update(self.config) context = PointSetContext(**config) + expr = preprocess_expression(expr, complex_mode=context.complex_mode) return map_expr_dag(context.translator, expr) @@ -273,7 +273,7 @@ def cell_avg(self, o): integrand, degree, argument_multiindices = entity_avg(integrand / CellVolume(domain), measure, self.context.argument_multiindices) config = {name: getattr(self.context, name) - for name in ["ufl_cell", "precision", "index_cache"]} + for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} config.update(quadrature_degree=degree, interface=self.context, argument_multiindices=argument_multiindices) expr, = compile_ufl(integrand, point_sum=True, **config) @@ -290,7 +290,7 @@ def facet_avg(self, o): config = {name: getattr(self.context, name) for name in ["ufl_cell", "precision", "index_cache", "integration_dim", "entity_ids", - "integral_type"]} + "integral_type", "complex_mode"]} config.update(quadrature_degree=degree, interface=self.context, argument_multiindices=argument_multiindices) expr, = compile_ufl(integrand, point_sum=True, **config) @@ -423,7 +423,7 @@ def translate_spatialcoordinate(terminal, mt, ctx): # Replace terminal with a Coefficient terminal = ctx.coordinate(terminal.ufl_domain()) # Get back to reference space - terminal = preprocess_expression(terminal) + terminal = preprocess_expression(terminal, complex_mode=ctx.complex_mode) # Rebuild modified terminal expr = construct_modified_terminal(mt, terminal) # Translate replaced UFL snippet @@ -451,7 +451,7 @@ def translate_cellvolume(terminal, mt, ctx): interface = CellVolumeKernelInterface(ctx, mt.restriction) config = {name: getattr(ctx, name) - for name in ["ufl_cell", "precision", "index_cache"]} + for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} config.update(interface=interface, quadrature_degree=degree) expr, = compile_ufl(integrand, point_sum=True, **config) return expr @@ -465,7 +465,7 @@ def translate_facetarea(terminal, mt, ctx): config = {name: getattr(ctx, name) for name in ["ufl_cell", "integration_dim", - "entity_ids", "precision", "index_cache"]} + "entity_ids", "precision", "index_cache", "complex_mode"]} config.update(interface=ctx, quadrature_degree=degree) expr, = compile_ufl(integrand, point_sum=True, **config) return expr @@ -479,7 +479,7 @@ def translate_cellorigin(terminal, mt, ctx): point_set = PointSingleton((0.0,) * domain.topological_dimension()) config = {name: getattr(ctx, name) - for name in ["ufl_cell", "precision", "index_cache"]} + for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} config.update(interface=ctx, point_set=point_set) context = PointSetContext(**config) return context.translator(expression) @@ -492,7 +492,7 @@ def translate_cell_vertices(terminal, mt, ctx): ps = PointSet(numpy.array(ctx.fiat_cell.get_vertices())) config = {name: getattr(ctx, name) - for name in ["ufl_cell", "precision", "index_cache"]} + for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} config.update(interface=ctx, point_set=ps) context = PointSetContext(**config) expr = context.translator(ufl_expr) @@ -633,7 +633,7 @@ def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): context = PointSetContext(**kwargs) # Abs-simplification - expression = simplify_abs(expression) + expression = simplify_abs(expression, context.complex_mode) if interior_facet: expressions = [] for rs in itertools.product(("+", "-"), repeat=len(context.argument_multiindices)): diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 4c2da3a662..35c87bb7d5 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -273,8 +273,11 @@ def _simplify_abs_expr(o, self, in_abs): @_simplify_abs.register(Sqrt) def _simplify_abs_sqrt(o, self, in_abs): - # Square root is always non-negative - return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) + result = ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) + if self.complex_mode and in_abs: + return Abs(result) + else: + return result @_simplify_abs.register(ScalarValue) @@ -326,11 +329,13 @@ def _simplify_abs_abs(o, self, in_abs): return self(o.ufl_operands[0], True) -def simplify_abs(expression): +def simplify_abs(expression, complex_mode): """Simplify absolute values in a UFL expression. Its primary purpose is to "neutralise" CellOrientation nodes that are surrounded by absolute values and thus not at all necessary.""" - return MemoizerArg(_simplify_abs)(expression, False) + mapper = MemoizerArg(_simplify_abs) + mapper.complex_mode = complex_mode + return mapper(expression, False) def apply_mapping(expression, mapping): From fb472932f9f606170c4fce888952eed7f02861e4 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Sat, 9 May 2020 19:02:33 +0100 Subject: [PATCH 586/809] loopy: Refactor mathfunction mapping Remove need for math_table since loopy does name mapping and the only names that need translating are Bessel functions and ln. Additionally, don't forbid mathfunctions that only operator on real values in complex mode (since their argument may be real!). --- tsfc/loopy.py | 93 ++++++++++++++------------------------------------- 1 file changed, 26 insertions(+), 67 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index ca6a53568c..e04ba2ba24 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -22,37 +22,6 @@ from contextlib import contextmanager -# Table of handled math functions in real and complex modes -# Note that loopy handles addition of type prefixes and suffixes itself. -math_table = { - 'sqrt': ('sqrt', 'sqrt'), - 'abs': ('abs', 'abs'), - 'cos': ('cos', 'cos'), - 'sin': ('sin', 'sin'), - 'tan': ('tan', 'tan'), - 'acos': ('acos', 'acos'), - 'asin': ('asin', 'asin'), - 'atan': ('atan', 'atan'), - 'cosh': ('cosh', 'cosh'), - 'sinh': ('sinh', 'sinh'), - 'tanh': ('tanh', 'tanh'), - 'acosh': ('acosh', 'acosh'), - 'asinh': ('asinh', 'asinh'), - 'atanh': ('atanh', 'atanh'), - 'power': ('pow', 'pow'), - 'exp': ('exp', 'exp'), - 'ln': ('log', 'log'), - 'real': (None, 'real'), - 'imag': (None, 'imag'), - 'conj': (None, 'conj'), - 'erf': ('erf', None), - 'atan_2': ('atan2', None), - 'atan2': ('atan2', None), - 'min_value': ('min', None), - 'max_value': ('max', None) -} - - maxtype = partial(numpy.find_common_type, []) @@ -419,49 +388,39 @@ def _expression_power(expr, ctx): @_expression.register(gem.MathFunction) def _expression_mathfunction(expr, ctx): - - complex_mode = int(is_complex(ctx.scalar_type)) - - # Bessel functions if expr.name.startswith('cyl_bessel_'): - if complex_mode: - msg = "Bessel functions for complex numbers: missing implementation" - raise NotImplementedError(msg) + # Bessel functions + if is_complex(ctx.scalar_type): + raise NotImplementedError("Bessel functions for complex numbers: " + "missing implementation") nu, arg = expr.children - nu_thunk = lambda: expression(nu, ctx) - arg_loopy = expression(arg, ctx) - if expr.name == 'cyl_bessel_j': - if nu == gem.Zero(): - return p.Variable("j0")(arg_loopy) - elif nu == gem.one: - return p.Variable("j1")(arg_loopy) - else: - return p.Variable("jn")(nu_thunk(), arg_loopy) - if expr.name == 'cyl_bessel_y': - if nu == gem.Zero(): - return p.Variable("y0")(arg_loopy) - elif nu == gem.one: - return p.Variable("y1")(arg_loopy) - else: - return p.Variable("yn")(nu_thunk(), arg_loopy) - + nu_ = expression(nu, ctx) + arg_ = expression(arg, ctx) # Modified Bessel functions (C++ only) # # These mappings work for FEniCS only, and fail with Firedrake # since no Boost available. - if expr.name in ['cyl_bessel_i', 'cyl_bessel_k']: + if expr.name in {'cyl_bessel_i', 'cyl_bessel_k'}: name = 'boost::math::' + expr.name - return p.Variable(name)(nu_thunk(), arg_loopy) - - assert False, "Unknown Bessel function: {}".format(expr.name) - - # Other math functions - name = math_table[expr.name][complex_mode] - if name is None: - raise RuntimeError("{} not supported in {} mode".format(expr.name, - ("real", "complex")[complex_mode])) - - return p.Variable(name)(*[expression(c, ctx) for c in expr.children]) + return p.Variable(name)(nu_, arg_) + else: + # cyl_bessel_{jy} -> {jy} + name = expr.name[-1:] + if nu == gem.Zero(): + return p.Variable(f"{name}0")(arg_) + elif nu == gem.one: + return p.Variable(f"{name}1")(arg_) + else: + return p.Variable(f"{name}n")(nu_, arg_) + else: + if expr.name == "ln": + name = "log" + else: + name = expr.name + # Not all mathfunctions apply to complex numbers, but this + # will be picked up in loopy. This way we allow erf(real(...)) + # in complex mode (say). + return p.Variable(name)(*(expression(c, ctx) for c in expr.children)) @_expression.register(gem.MinValue) From 8dbcfbefafaf0d40ce795265e3a63573b8ee37c9 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 7 May 2020 23:18:04 +0100 Subject: [PATCH 587/809] loopy: Provide option to generate assignment for impero.Return nodes For pointwise kernels we will just use assignment in Return rather than incrementing. --- tsfc/loopy.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index e04ba2ba24..8473c98066 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -186,7 +186,8 @@ def active_indices(mapping, ctx): ctx.active_indices.pop(key) -def generate(impero_c, args, precision, scalar_type, kernel_name="loopy_kernel", index_names=[]): +def generate(impero_c, args, precision, scalar_type, kernel_name="loopy_kernel", index_names=[], + return_increments=True): """Generates loopy code. :arg impero_c: ImperoC tuple with Impero AST and other data @@ -195,6 +196,7 @@ def generate(impero_c, args, precision, scalar_type, kernel_name="loopy_kernel", :arg scalar_type: type of scalars as C typename string :arg kernel_name: function name of the kernel :arg index_names: pre-assigned index names + :arg return_increments: Does codegen for Return nodes increment the lvalue, or assign? :returns: loopy kernel """ ctx = LoopyContext() @@ -203,6 +205,7 @@ def generate(impero_c, args, precision, scalar_type, kernel_name="loopy_kernel", ctx.precision = precision ctx.scalar_type = scalar_type ctx.epsilon = 10.0 ** (-precision) + ctx.return_increments = return_increments # Create arguments data = list(args) @@ -286,7 +289,9 @@ def statement_accumulate(leaf, ctx): @statement.register(imp.Return) def statement_return(leaf, ctx): lhs = expression(leaf.variable, ctx) - rhs = lhs + expression(leaf.expression, ctx) + rhs = expression(leaf.expression, ctx) + if ctx.return_increments: + rhs = lhs + rhs return [lp.Assignment(lhs, rhs, within_inames=ctx.active_inames())] From 26fa3953aabde64d65389e7af45196e65961e98d Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 19 May 2020 11:30:27 +0100 Subject: [PATCH 588/809] codegen: Inherit precision from scalar_type Simplifies interface in a bunch of places. --- tsfc/coffee.py | 8 +++--- tsfc/driver.py | 10 +++----- tsfc/fem.py | 32 ++++++++++++------------ tsfc/kernel_interface/firedrake.py | 9 +++---- tsfc/kernel_interface/firedrake_loopy.py | 11 +++----- tsfc/kernel_interface/ufc.py | 5 ++-- tsfc/loopy.py | 14 +++++------ tsfc/parameters.py | 3 --- 8 files changed, 40 insertions(+), 52 deletions(-) diff --git a/tsfc/coffee.py b/tsfc/coffee.py index 0629f062e3..294d2626e2 100644 --- a/tsfc/coffee.py +++ b/tsfc/coffee.py @@ -20,20 +20,20 @@ class Bunch(object): pass -def generate(impero_c, index_names, precision, scalar_type): +def generate(impero_c, index_names, scalar_type): """Generates COFFEE code. :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names - :arg precision: floating-point precision for printing :arg scalar_type: type of scalars as C typename string :returns: COFFEE function body """ params = Bunch() params.declare = impero_c.declare params.indices = impero_c.indices - params.precision = precision - params.epsilon = 10.0 * eval("1e-%d" % precision) + finfo = numpy.finfo(scalar_type) + params.precision = finfo.precision + params.epsilon = finfo.resolution params.scalar_type = scalar_type params.names = {} diff --git a/tsfc/driver.py b/tsfc/driver.py index cc98895dd3..bdc663137d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -151,12 +151,11 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co kernel_cfg = dict(interface=builder, ufl_cell=cell, integral_type=integral_type, - precision=parameters["precision"], integration_dim=integration_dim, entity_ids=entity_ids, argument_multiindices=argument_multiindices, index_cache=index_cache, - complex_mode=is_complex(parameters.get("scalar_type"))) + scalar_type=parameters["scalar_type"]) mode_irs = collections.OrderedDict() for integral in integral_data.integrals: @@ -265,7 +264,7 @@ def name_multiindex(multiindex, name): for multiindex, name in zip(argument_multiindices, ['j', 'k']): name_multiindex(multiindex, name) - return builder.construct_kernel(kernel_name, impero_c, parameters["precision"], index_names, quad_rule) + return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) def compile_expression_dual_evaluation(expression, to_element, coordinates, interface=None, @@ -341,10 +340,9 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Translate to GEM kernel_cfg = dict(interface=builder, ufl_cell=coordinates.ufl_domain().ufl_cell(), - precision=parameters["precision"], argument_multiindices=argument_multiindices, index_cache={}, - complex_mode=complex_mode) + scalar_type=parameters["scalar_type"]) if all(isinstance(dual, PointEvaluation) for dual in to_element.dual_basis()): # This is an optimisation for point-evaluation nodes which @@ -426,7 +424,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte # Handle kernel interface requirements builder.register_requirements([ir]) # Build kernel tuple - return builder.construct_kernel(return_arg, impero_c, parameters["precision"], index_names) + return builder.construct_kernel(return_arg, impero_c, index_names) def lower_integral_type(fiat_cell, integral_type): diff --git a/tsfc/fem.py b/tsfc/fem.py index 1726c46882..f70df95449 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -39,7 +39,7 @@ from tsfc.kernel_interface import ProxyKernelInterface from tsfc.modified_terminals import (analyse_modified_terminal, construct_modified_terminal) -from tsfc.parameters import PARAMETERS +from tsfc.parameters import is_complex from tsfc.ufl_utils import (ModifiedTerminalMixin, PickRestriction, entity_avg, one_times, simplify_abs, preprocess_expression) @@ -53,11 +53,10 @@ class ContextBase(ProxyKernelInterface): 'integral_type', 'integration_dim', 'entity_ids', - 'precision', 'argument_multiindices', 'facetarea', 'index_cache', - 'complex_mode') + 'scalar_type') def __init__(self, interface, **kwargs): ProxyKernelInterface.__init__(self, interface) @@ -77,12 +76,13 @@ def integration_dim(self): entity_ids = [0] - precision = PARAMETERS["precision"] - @cached_property def epsilon(self): - # Rounding tolerance mimicking FFC - return 10.0 * eval("1e-%d" % self.precision) + return numpy.finfo(self.scalar_type).resolution + + @cached_property + def complex_mode(self): + return is_complex(self.scalar_type) def entity_selector(self, callback, restriction): """Selects code for the correct entity at run-time. Callback @@ -131,7 +131,7 @@ def __init__(self, mt, interface): @property def config(self): config = {name: getattr(self.interface, name) - for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} + for name in ["ufl_cell", "index_cache", "scalar_type"]} config["interface"] = self.interface return config @@ -273,7 +273,7 @@ def cell_avg(self, o): integrand, degree, argument_multiindices = entity_avg(integrand / CellVolume(domain), measure, self.context.argument_multiindices) config = {name: getattr(self.context, name) - for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} + for name in ["ufl_cell", "index_cache", "scalar_type"]} config.update(quadrature_degree=degree, interface=self.context, argument_multiindices=argument_multiindices) expr, = compile_ufl(integrand, point_sum=True, **config) @@ -288,9 +288,9 @@ def facet_avg(self, o): integrand, degree, argument_multiindices = entity_avg(integrand / FacetArea(domain), measure, self.context.argument_multiindices) config = {name: getattr(self.context, name) - for name in ["ufl_cell", "precision", "index_cache", + for name in ["ufl_cell", "index_cache", "scalar_type", "integration_dim", "entity_ids", - "integral_type", "complex_mode"]} + "integral_type"]} config.update(quadrature_degree=degree, interface=self.context, argument_multiindices=argument_multiindices) expr, = compile_ufl(integrand, point_sum=True, **config) @@ -451,7 +451,7 @@ def translate_cellvolume(terminal, mt, ctx): interface = CellVolumeKernelInterface(ctx, mt.restriction) config = {name: getattr(ctx, name) - for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} + for name in ["ufl_cell", "index_cache", "scalar_type"]} config.update(interface=interface, quadrature_degree=degree) expr, = compile_ufl(integrand, point_sum=True, **config) return expr @@ -464,8 +464,8 @@ def translate_facetarea(terminal, mt, ctx): integrand, degree = one_times(ufl.Measure(ctx.integral_type, domain=domain)) config = {name: getattr(ctx, name) - for name in ["ufl_cell", "integration_dim", - "entity_ids", "precision", "index_cache", "complex_mode"]} + for name in ["ufl_cell", "integration_dim", "scalar_type", + "entity_ids", "index_cache"]} config.update(interface=ctx, quadrature_degree=degree) expr, = compile_ufl(integrand, point_sum=True, **config) return expr @@ -479,7 +479,7 @@ def translate_cellorigin(terminal, mt, ctx): point_set = PointSingleton((0.0,) * domain.topological_dimension()) config = {name: getattr(ctx, name) - for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} + for name in ["ufl_cell", "index_cache", "scalar_type"]} config.update(interface=ctx, point_set=point_set) context = PointSetContext(**config) return context.translator(expression) @@ -492,7 +492,7 @@ def translate_cell_vertices(terminal, mt, ctx): ps = PointSet(numpy.array(ctx.fiat_cell.get_vertices())) config = {name: getattr(ctx, name) - for name in ["ufl_cell", "precision", "index_cache", "complex_mode"]} + for name in ["ufl_cell", "index_cache", "scalar_type"]} config.update(interface=ctx, point_set=ps) context = PointSetContext(**config) expr = context.translator(ufl_expr) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index a7321edfcc..378e9e746c 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -143,7 +143,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) - def construct_kernel(self, return_arg, impero_c, precision, index_names): + def construct_kernel(self, return_arg, impero_c, index_names): """Constructs an :class:`ExpressionKernel`. :arg return_arg: COFFEE argument for the return value @@ -157,7 +157,7 @@ def construct_kernel(self, return_arg, impero_c, precision, index_names): args.append(self.cell_sizes_arg) args.extend(self.kernel_args) - body = generate_coffee(impero_c, index_names, precision, self.scalar_type) + body = generate_coffee(impero_c, index_names, self.scalar_type) for name_, shape in self.tabulations: args.append(coffee.Decl(self.scalar_type, coffee.Symbol( @@ -260,7 +260,7 @@ def register_requirements(self, ir): knl = self.kernel knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) - def construct_kernel(self, name, impero_c, precision, index_names, quadrature_rule): + def construct_kernel(self, name, impero_c, index_names, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -268,13 +268,12 @@ def construct_kernel(self, name, impero_c, precision, index_names, quadrature_ru :arg name: function name :arg impero_c: ImperoC tuple with Impero AST and other data - :arg precision: floating-point precision for printing :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object """ - body = generate_coffee(impero_c, index_names, precision, self.scalar_type) + body = generate_coffee(impero_c, index_names, self.scalar_type) args = [self.local_tensor, self.coordinates_arg] if self.kernel.oriented: diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 4eb86afe68..6186dccb35 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -146,12 +146,11 @@ def register_requirements(self, ir): provided by the kernel interface.""" self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) - def construct_kernel(self, return_arg, impero_c, precision, index_names): + def construct_kernel(self, return_arg, impero_c, index_names): """Constructs an :class:`ExpressionKernel`. :arg return_arg: loopy.GlobalArg for the return value :arg impero_c: gem.ImperoC object that represents the kernel - :arg precision: floating point precision for code generation :arg index_names: pre-assigned index names :returns: :class:`ExpressionKernel` object """ @@ -164,7 +163,7 @@ def construct_kernel(self, return_arg, impero_c, precision, index_names): for name_, shape in self.tabulations: args.append(lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape)) - loopy_kernel = generate_loopy(impero_c, args, precision, self.scalar_type, + loopy_kernel = generate_loopy(impero_c, args, self.scalar_type, "expression_kernel", index_names) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, self.coefficients, self.tabulations) @@ -263,7 +262,7 @@ def register_requirements(self, ir): knl = self.kernel knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) - def construct_kernel(self, name, impero_c, precision, index_names, quadrature_rule): + def construct_kernel(self, name, impero_c, index_names, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -271,7 +270,6 @@ def construct_kernel(self, name, impero_c, precision, index_names, quadrature_ru :arg name: function name :arg impero_c: ImperoC tuple with Impero AST and other data - :arg precision: floating-point precision for printing :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object @@ -292,8 +290,7 @@ def construct_kernel(self, name, impero_c, precision, index_names, quadrature_ru args.append(lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape)) self.kernel.quadrature_rule = quadrature_rule - self.kernel.ast = generate_loopy(impero_c, args, precision, - self.scalar_type, name, index_names) + self.kernel.ast = generate_loopy(impero_c, args, self.scalar_type, name, index_names) return self.kernel def construct_empty_kernel(self, name): diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 6335e92c61..305e098899 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -104,7 +104,7 @@ def set_coefficients(self, integral_data, form_data): expression = prepare_coefficient(coefficient, n, name, self.interior_facet) self.coefficient_map[coefficient] = expression - def construct_kernel(self, name, impero_c, precision, index_names, quadrature_rule=None): + def construct_kernel(self, name, impero_c, index_names, quadrature_rule=None): """Construct a fully built kernel function. This function contains the logic for building the argument @@ -112,13 +112,12 @@ def construct_kernel(self, name, impero_c, precision, index_names, quadrature_ru :arg name: function name :arg impero_c: ImperoC tuple with Impero AST and other data - :arg precision: floating-point precision for printing :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule (not used, stubbed out for Themis integration) :returns: a COFFEE function definition object """ from tsfc.coffee import generate as generate_coffee - body = generate_coffee(impero_c, index_names, precision, scalar_type=self.scalar_type) + body = generate_coffee(impero_c, index_names, scalar_type=self.scalar_type) return self._construct_kernel_from_body(name, body) def _construct_kernel_from_body(self, name, body, quadrature_rule): diff --git a/tsfc/loopy.py b/tsfc/loopy.py index 8473c98066..c5fe5ee023 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -186,13 +186,12 @@ def active_indices(mapping, ctx): ctx.active_indices.pop(key) -def generate(impero_c, args, precision, scalar_type, kernel_name="loopy_kernel", index_names=[], +def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_names=[], return_increments=True): """Generates loopy code. :arg impero_c: ImperoC tuple with Impero AST and other data :arg args: list of loopy.GlobalArgs - :arg precision: floating-point precision for printing :arg scalar_type: type of scalars as C typename string :arg kernel_name: function name of the kernel :arg index_names: pre-assigned index names @@ -202,9 +201,8 @@ def generate(impero_c, args, precision, scalar_type, kernel_name="loopy_kernel", ctx = LoopyContext() ctx.indices = impero_c.indices ctx.index_names = defaultdict(lambda: "i", index_names) - ctx.precision = precision + ctx.epsilon = numpy.finfo(scalar_type).resolution ctx.scalar_type = scalar_type - ctx.epsilon = 10.0 ** (-precision) ctx.return_increments = return_increments # Create arguments @@ -362,12 +360,12 @@ def expression(expr, ctx, top=False): @singledispatch -def _expression(expr, parameters): +def _expression(expr, ctx): raise AssertionError("cannot generate expression from %s" % type(expr)) @_expression.register(gem.Failure) -def _expression_failure(expr, parameters): +def _expression_failure(expr, ctx): raise expr.exception @@ -465,13 +463,13 @@ def _expression_conditional(expr, ctx): @_expression.register(gem.Constant) -def _expression_scalar(expr, parameters): +def _expression_scalar(expr, ctx): assert not expr.shape v = expr.value if numpy.isnan(v): return p.Variable("NAN") r = numpy.round(v, 1) - if r and numpy.abs(v - r) < parameters.epsilon: + if r and numpy.abs(v - r) < ctx.epsilon: return r return v diff --git a/tsfc/parameters.py b/tsfc/parameters.py index dbd65a824d..640fcfcb56 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -19,9 +19,6 @@ # So that tests pass (needs to match scalar_type) "scalar_type_c": "double", - - # Precision of float printing (number of digits) - "precision": numpy.finfo(numpy.dtype("double")).precision, } From c803b3a4789492724f88ebcea230d9a2dc69d96f Mon Sep 17 00:00:00 2001 From: Sophia Vorderwuelbecke Date: Wed, 13 May 2020 20:52:03 +0100 Subject: [PATCH 589/809] Make create domains accessible from outside for reusage in slate. --- tsfc/loopy.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index c5fe5ee023..0dfc0b2443 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -220,13 +220,7 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name instructions = statement(impero_c.tree, ctx) # Create domains - domains = [] - for idx, extent in ctx.index_extent.items(): - inames = isl.make_zero_and_vars([idx]) - domains.append(((inames[0].le_set(inames[idx])) & (inames[idx].lt_set(inames[0] + extent)))) - - if not domains: - domains = [isl.BasicSet("[] -> {[]}")] + domains = create_domains(ctx.index_extent.items()) # Create loopy kernel knl = lp.make_function(domains, instructions, data, name=kernel_name, target=lp.CTarget(), @@ -244,6 +238,22 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name return knl +def create_domains(indices): + """ Create ISL domains from indices + + :arg indices: iterable of (index_name, extent) pairs + :returns: A list of ISL sets representing the iteration domain of the indices.""" + + domains = [] + for idx, extent in indices: + inames = isl.make_zero_and_vars([idx]) + domains.append(((inames[0].le_set(inames[idx])) & (inames[idx].lt_set(inames[0] + extent)))) + + if not domains: + domains = [isl.BasicSet("[] -> {[]}")] + return domains + + @singledispatch def statement(tree, ctx): """Translates an Impero (sub)tree into a loopy instructions corresponding From 14290c4cf27eeaf0312dea4316a21c9bac0750d9 Mon Sep 17 00:00:00 2001 From: Reuben Hill Date: Wed, 6 May 2020 10:12:51 +0100 Subject: [PATCH 590/809] Stop builder.set_cell_sizes doing anything with vertex cells --- tsfc/kernel_interface/firedrake.py | 15 +++++++++++---- tsfc/kernel_interface/firedrake_loopy.py | 15 +++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 378e9e746c..5c5c3ee590 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -98,11 +98,18 @@ def set_cell_sizes(self, domain): physically mapped elements (Argyris, Bell, etc...). We need a measure of the mesh size around each vertex (hence this lives in P1). + + Should the domain have topological dimension 0 this does + nothing. """ - f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) - self.cell_sizes_arg = funarg - self._cell_sizes = expression + if domain.ufl_cell().topological_dimension() > 0: + # Can't create P1 since only P0 is a valid finite element if + # topological_dimension is 0 and the concept of "cell size" + # is not useful for a vertex. + f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) + funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) + self.cell_sizes_arg = funarg + self._cell_sizes = expression def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 6186dccb35..ee5cafa41d 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -101,11 +101,18 @@ def set_cell_sizes(self, domain): physically mapped elements (Argyris, Bell, etc...). We need a measure of the mesh size around each vertex (hence this lives in P1). + + Should the domain have topological dimension 0 this does + nothing. """ - f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) - self.cell_sizes_arg = funarg - self._cell_sizes = expression + if domain.ufl_cell().topological_dimension() > 0: + # Can't create P1 since only P0 is a valid finite element if + # topological_dimension is 0 and the concept of "cell size" + # is not useful for a vertex. + f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) + funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) + self.cell_sizes_arg = funarg + self._cell_sizes = expression def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given From 61a1e0b677eaa71ccf5a23228e92be475c9bd289 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Fri, 26 Jun 2020 12:24:06 -0500 Subject: [PATCH 591/809] Rename element --- tsfc/finatinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index a8cd845781..96209f595f 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -71,7 +71,7 @@ "Discontinuous Lagrange L2": finat.DiscontinuousLagrange, "Gauss-Legendre L2": finat.GaussLegendre, "DQ L2": None, - "Sphys": finat.DirectSerendipity, + "Sdirect": finat.DirectSerendipity, } """A :class:`.dict` mapping UFL element family names to their FInAT-equivalent constructors. If the value is ``None``, the UFL From abf93ad1264f8732b7cd4b692925d48ef4211d83 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Fri, 3 Jul 2020 09:52:03 -0500 Subject: [PATCH 592/809] Clean up merge rubbish --- tsfc/fem.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 4a21d48390..8a8c90ec45 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -147,12 +147,7 @@ def jacobian_at(self, point): expr = PositiveRestricted(expr) elif self.mt.restriction == '-': expr = NegativeRestricted(expr) -# <<<<<<< HEAD -# expr = preprocess_expression(expr) -# config = {"point_set": ps} -# ======= config = {"point_set": PointSingleton(point)} -# >>>>>>> origin/master config.update(self.config) context = PointSetContext(**config) expr = preprocess_expression(expr, complex_mode=context.complex_mode) From b3fcc28dc4afd44490eb695b8e2c35abcfa345be Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Mon, 13 Jul 2020 16:56:52 +0100 Subject: [PATCH 593/809] Add optional domain argument to expression compilation It might be that we want to compile an expression with no domain, so provide a default one in the case that apply_mapping needs one to construct geometric quantities. --- tsfc/driver.py | 6 ++++-- tsfc/ufl_utils.py | 9 +++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index bdc663137d..0fd79b4ed8 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -267,7 +267,8 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) -def compile_expression_dual_evaluation(expression, to_element, coordinates, interface=None, +def compile_expression_dual_evaluation(expression, to_element, coordinates, *, + domain=None, interface=None, parameters=None, coffee=False): """Compile a UFL expression to be evaluated against a compile-time known reference element's dual basis. @@ -276,6 +277,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte :arg expression: UFL expression :arg to_element: A FIAT FiniteElement for the target space :arg coordinates: the coordinate function + :arg domain: optional UFL domain the expression is defined on (useful when expression contains no domain). :arg interface: backend module for the kernel interface :arg parameters: parameters object :arg coffee: compile coffee kernel instead of loopy kernel @@ -300,7 +302,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, inte mapping, = set(to_element.mapping()) except ValueError: raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") - expression = apply_mapping(expression, mapping) + expression = apply_mapping(expression, mapping, domain) # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 35c87bb7d5..634b9d64c0 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -338,7 +338,7 @@ def simplify_abs(expression, complex_mode): return mapper(expression, False) -def apply_mapping(expression, mapping): +def apply_mapping(expression, mapping, domain): """ This applies the appropriate transformation to the given expression for interpolation to a specific @@ -387,6 +387,10 @@ def apply_mapping(expression, mapping): """ mesh = expression.ufl_domain() + if mesh is None: + mesh = domain + if domain is not None and mesh != domain: + raise NotImplementedError("Multiple domains not supported") rank = len(expression.ufl_shape) if mapping == "affine": return expression @@ -396,18 +400,15 @@ def apply_mapping(expression, mapping): expression = Indexed(expression, MultiIndex((*i, k))) return as_tensor(J.T[j, k] * expression, (*i, j)) elif mapping == "contravariant piola": - mesh = expression.ufl_domain() K = JacobianInverse(mesh) detJ = JacobianDeterminant(mesh) *i, j, k = indices(len(expression.ufl_shape) + 1) expression = Indexed(expression, MultiIndex((*i, k))) return as_tensor(detJ * K[j, k] * expression, (*i, j)) elif mapping == "double covariant piola" and rank == 2: - mesh = expression.ufl_domain() J = Jacobian(mesh) return J.T * expression * J elif mapping == "double contravariant piola" and rank == 2: - mesh = expression.ufl_domain() K = JacobianInverse(mesh) detJ = JacobianDeterminant(mesh) return (detJ)**2 * K * expression * K.T From 364c5ffd82d1d01696b92fb5b21f02cd1356b527 Mon Sep 17 00:00:00 2001 From: FabianL1908 Date: Thu, 7 May 2020 19:37:25 +0200 Subject: [PATCH 594/809] Pass variant when translating RT/BDM/Nedelec elements --- tsfc/fiatinterface.py | 5 ++++- tsfc/finatinterface.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index e485d6bb35..3007e166e1 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -137,7 +137,10 @@ def convert_finiteelement(element, vector_is_mixed): lmbda = FIAT.GaussLobattoLegendre else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: + elif element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", + "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)"}: + lmbda = partial(lmbda, variant=element.variant()) + elif element.family() in {"Discontinuous Lagrange", "Discontinuous Lagrange L2"}: if kind == 'equispaced': lmbda = FIAT.DiscontinuousLagrange elif kind == 'spectral' and element.cell().cellname() == 'interval': diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 84c9fddf04..5ee97b109c 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -21,7 +21,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with FFC. If not, see . -from functools import singledispatch +from functools import singledispatch, partial import weakref import finat @@ -143,6 +143,9 @@ def convert_finiteelement(element, **kwargs): return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction), deps else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) + elif element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", + "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)"}: + lmbda = partial(lmbda, variant=element.variant()) elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: if kind == 'equispaced': lmbda = finat.DiscontinuousLagrange From 3497954376769297f4e35095c4da6c0bb887c3f8 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 24 Jul 2020 16:51:53 +0100 Subject: [PATCH 595/809] Use finat RestrictedElement --- tsfc/finatinterface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 5ee97b109c..39734b0497 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -244,8 +244,8 @@ def convert_hcurlelement(element, **kwargs): @convert.register(ufl.RestrictedElement) def convert_restrictedelement(element, **kwargs): - # Fall back on FIAT - return fiat_compat(element), set() + finat_elem, deps = _create_element(element._element, **kwargs) + return finat.RestrictedElement(finat_elem, element.restriction_domain()), deps @convert.register(ufl.NodalEnrichedElement) From 2c54e07e648e649a927116e9213537318ad51d6d Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Fri, 10 Jul 2020 14:21:14 -0500 Subject: [PATCH 596/809] Fix a type check in CoordinateMapping causing a few tests to fail. --- tsfc/fem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 8a8c90ec45..b9b856f834 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -23,7 +23,7 @@ from FIAT.reference_element import make_affine_mapping -from FIAT import ufc_simplex +from FIAT.reference_element import UFCSimplex import gem from gem.node import traversal @@ -154,13 +154,13 @@ def jacobian_at(self, point): return map_expr_dag(context.translator, expr) def reference_normals(self): - if not (isinstance(self.interface.fiat_cell, ufc_simplex) and + if not (isinstance(self.interface.fiat_cell, UFCSimplex) and self.interface.fiat_cell.get_spatial_dimension() == 2): raise NotImplementedError("Only works for triangles for now") return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) def physical_tangents(self): - if not (isinstance(self.interface.fiat_cell, ufc_simplex) and + if not (isinstance(self.interface.fiat_cell, UFCSimplex) and self.interface.fiat_cell.get_spatial_dimension() == 2): raise NotImplementedError("Only works for triangles for now") From dc162ec201251d038a36aad3a1e6c99145ae9b25 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Fri, 21 Aug 2020 12:25:12 -0500 Subject: [PATCH 597/809] keep up with UFL renaming --- tsfc/finatinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 96209f595f..2a686eab86 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -71,7 +71,7 @@ "Discontinuous Lagrange L2": finat.DiscontinuousLagrange, "Gauss-Legendre L2": finat.GaussLegendre, "DQ L2": None, - "Sdirect": finat.DirectSerendipity, + "Direct Serendipity": finat.DirectSerendipity, } """A :class:`.dict` mapping UFL element family names to their FInAT-equivalent constructors. If the value is ``None``, the UFL From d508a158141728af8a59e86873ba056263d7201f Mon Sep 17 00:00:00 2001 From: Justincrum Date: Mon, 24 Aug 2020 12:42:01 -0700 Subject: [PATCH 598/809] Fixing an update for the init file. --- tsfc/__init__.py | 3 +- tsfc/driver.py | 138 ++++++++++++++++++++++++++++++++++------------ tsfc/ufl_utils.py | 94 +++++++++++++++++++++++++++++-- 3 files changed, 193 insertions(+), 42 deletions(-) diff --git a/tsfc/__init__.py b/tsfc/__init__.py index e69abac5a8..967457bdd9 100644 --- a/tsfc/__init__.py +++ b/tsfc/__init__.py @@ -1,4 +1,5 @@ -from tsfc.driver import compile_form, compile_expression_at_points # noqa: F401 +#from tsfc.driver import compile_form, compile_expression_at_points # noqa: F401 +from tsfc.driver import compile_form, compile_expression_dual_evaluation #noqa F401 from tsfc.parameters import default_parameters # noqa: F401 try: diff --git a/tsfc/driver.py b/tsfc/driver.py index 7297d19c08..0fd79b4ed8 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -6,7 +6,7 @@ from functools import reduce from itertools import chain -from numpy import asarray +from numpy import asarray, allclose import ufl from ufl.algorithms import extract_arguments, extract_coefficients @@ -18,15 +18,18 @@ import gem import gem.impero_utils as impero_utils +import FIAT from FIAT.reference_element import TensorProductCell +from FIAT.functional import PointEvaluation from finat.point_set import PointSet -from finat.quadrature import AbstractQuadratureRule, make_quadrature +from finat.quadrature import AbstractQuadratureRule, make_quadrature, QuadratureRule from tsfc import fem, ufl_utils from tsfc.fiatinterface import as_fiat_cell from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex +from tsfc.ufl_utils import apply_mapping # To handle big forms. The various transformations might need a deeper stack sys.setrecursionlimit(3000) @@ -47,14 +50,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr assert isinstance(form, Form) # Determine whether in complex mode: - # complex nodes would break the refactoriser. complex_mode = parameters and is_complex(parameters.get("scalar_type")) - if complex_mode: - logger.warning("Disabling whole expression optimisations" - " in GEM for supporting complex mode.") - parameters = parameters.copy() - parameters["mode"] = 'vanilla' - fd = ufl_utils.compute_form_data(form, complex_mode=complex_mode) logger.info(GREEN % "compute_form_data finished in %g seconds.", time.time() - cpu_time) @@ -95,6 +91,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co # Delayed import, loopy is a runtime dependency import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy interface = firedrake_interface_loopy.KernelBuilder + if coffee: + scalar_type = parameters["scalar_type_c"] + else: + scalar_type = parameters["scalar_type"] # Remove these here, they're handled below. if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: @@ -120,7 +120,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co domain_numbering = form_data.original_form.domain_numbering() builder = interface(integral_type, integral_data.subdomain_id, domain_numbering[integral_data.domain], - parameters["scalar_type"], + scalar_type, diagonal=diagonal) argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() for arg in arguments) @@ -151,11 +151,11 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co kernel_cfg = dict(interface=builder, ufl_cell=cell, integral_type=integral_type, - precision=parameters["precision"], integration_dim=integration_dim, entity_ids=entity_ids, argument_multiindices=argument_multiindices, - index_cache=index_cache) + index_cache=index_cache, + scalar_type=parameters["scalar_type"]) mode_irs = collections.OrderedDict() for integral in integral_data.integrals: @@ -264,24 +264,28 @@ def name_multiindex(multiindex, name): for multiindex, name in zip(argument_multiindices, ['j', 'k']): name_multiindex(multiindex, name) - return builder.construct_kernel(kernel_name, impero_c, parameters["precision"], index_names, quad_rule) + return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) -def compile_expression_at_points(expression, points, coordinates, interface=None, - parameters=None, coffee=True): - """Compiles a UFL expression to be evaluated at compile-time known - reference points. Useful for interpolating UFL expressions onto - function spaces with only point evaluation nodes. +def compile_expression_dual_evaluation(expression, to_element, coordinates, *, + domain=None, interface=None, + parameters=None, coffee=False): + """Compile a UFL expression to be evaluated against a compile-time known reference element's dual basis. + + Useful for interpolating UFL expressions into e.g. N1curl spaces. :arg expression: UFL expression - :arg points: reference coordinates of the evaluation points + :arg to_element: A FIAT FiniteElement for the target space :arg coordinates: the coordinate function + :arg domain: optional UFL domain the expression is defined on (useful when expression contains no domain). :arg interface: backend module for the kernel interface :arg parameters: parameters object :arg coffee: compile coffee kernel instead of loopy kernel """ import coffee.base as ast import loopy as lp + if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()): + raise NotImplementedError("Can only interpolate onto dual basis functionals without derivative evaluation, sorry!") if parameters is None: parameters = default_parameters() @@ -293,6 +297,13 @@ def compile_expression_at_points(expression, points, coordinates, interface=None # Determine whether in complex mode complex_mode = is_complex(parameters["scalar_type"]) + # Find out which mapping to apply + try: + mapping, = set(to_element.mapping()) + except ValueError: + raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") + expression = apply_mapping(expression, mapping, domain) + # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, complex_mode=complex_mode) @@ -329,22 +340,75 @@ def compile_expression_at_points(expression, points, coordinates, interface=None expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) # Translate to GEM - point_set = PointSet(points) - config = dict(interface=builder, - ufl_cell=coordinates.ufl_domain().ufl_cell(), - precision=parameters["precision"], - point_set=point_set, - argument_multiindices=argument_multiindices) - ir, = fem.compile_ufl(expression, point_sum=False, **config) - - # Deal with non-scalar expressions - value_shape = ir.shape - tensor_indices = tuple(gem.Index() for s in value_shape) - if value_shape: - ir = gem.Indexed(ir, tensor_indices) + kernel_cfg = dict(interface=builder, + ufl_cell=coordinates.ufl_domain().ufl_cell(), + argument_multiindices=argument_multiindices, + index_cache={}, + scalar_type=parameters["scalar_type"]) + + if all(isinstance(dual, PointEvaluation) for dual in to_element.dual_basis()): + # This is an optimisation for point-evaluation nodes which + # should go away once FInAT offers the interface properly + qpoints = [] + # Everything is just a point evaluation. + for dual in to_element.dual_basis(): + ptdict = dual.get_point_dict() + qpoint, = ptdict.keys() + (qweight, component), = ptdict[qpoint] + assert allclose(qweight, 1.0) + assert component == () + qpoints.append(qpoint) + point_set = PointSet(qpoints) + config = kernel_cfg.copy() + config.update(point_set=point_set) + + # Allow interpolation onto QuadratureElements to refer to the quadrature + # rule they represent + if isinstance(to_element, FIAT.QuadratureElement): + assert allclose(asarray(qpoints), asarray(to_element._points)) + quad_rule = QuadratureRule(point_set, to_element._weights) + config["quadrature_rule"] = quad_rule + + expr, = fem.compile_ufl(expression, **config, point_sum=False) + shape_indices = tuple(gem.Index() for _ in expr.shape) + basis_indices = point_set.indices + ir = gem.Indexed(expr, shape_indices) + else: + # This is general code but is more unrolled than necssary. + dual_expressions = [] # one for each functional + broadcast_shape = len(expression.ufl_shape) - len(to_element.value_shape()) + shape_indices = tuple(gem.Index() for _ in expression.ufl_shape[:broadcast_shape]) + expr_cache = {} # Sharing of evaluation of the expression at points + for dual in to_element.dual_basis(): + pts = tuple(sorted(dual.get_point_dict().keys())) + try: + expr, point_set = expr_cache[pts] + except KeyError: + point_set = PointSet(pts) + config = kernel_cfg.copy() + config.update(point_set=point_set) + expr, = fem.compile_ufl(expression, **config, point_sum=False) + expr = gem.partial_indexed(expr, shape_indices) + expr_cache[pts] = expr, point_set + weights = collections.defaultdict(list) + for p in pts: + for (w, cmp) in dual.get_point_dict()[p]: + weights[cmp].append(w) + qexprs = gem.Zero() + for cmp in sorted(weights): + qweights = gem.Literal(weights[cmp]) + qexpr = gem.Indexed(expr, cmp) + qexpr = gem.index_sum(gem.Indexed(qweights, point_set.indices)*qexpr, + point_set.indices) + qexprs = gem.Sum(qexprs, qexpr) + assert qexprs.shape == () + assert set(qexprs.free_indices) == set(chain(shape_indices, *argument_multiindices)) + dual_expressions.append(qexprs) + basis_indices = (gem.Index(), ) + ir = gem.Indexed(gem.ListTensor(dual_expressions), basis_indices) # Build kernel body - return_indices = point_set.indices + tensor_indices + tuple(chain(*argument_multiindices)) + return_indices = basis_indices + shape_indices + tuple(chain(*argument_multiindices)) return_shape = tuple(i.extent for i in return_indices) return_var = gem.Variable('A', return_shape) if coffee: @@ -353,14 +417,16 @@ def compile_expression_at_points(expression, points, coordinates, interface=None return_arg = lp.GlobalArg("A", dtype=parameters["scalar_type"], shape=return_shape) return_expr = gem.Indexed(return_var, return_indices) + + # TODO: one should apply some GEM optimisations as in assembly, + # but we don't for now. ir, = impero_utils.preprocess_gem([ir]) impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) - point_index, = point_set.indices - + index_names = dict((idx, "p%d" % i) for (i, idx) in enumerate(basis_indices)) # Handle kernel interface requirements builder.register_requirements([ir]) # Build kernel tuple - return builder.construct_kernel(return_arg, impero_c, parameters["precision"], {point_index: 'p'}) + return builder.construct_kernel(return_arg, impero_c, index_names) def lower_integral_type(fiat_cell, integral_type): diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 888ac02821..634b9d64c0 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -18,9 +18,10 @@ from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction from ufl.geometry import QuadratureWeight +from ufl.geometry import Jacobian, JacobianDeterminant, JacobianInverse from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, ComponentTensor, Expr, FloatValue, Division, - MixedElement, MultiIndex, Product, + Indexed, MixedElement, MultiIndex, Product, ScalarValue, Sqrt, Zero, CellVolume, FacetArea) from gem.node import MemoizerArg @@ -272,8 +273,11 @@ def _simplify_abs_expr(o, self, in_abs): @_simplify_abs.register(Sqrt) def _simplify_abs_sqrt(o, self, in_abs): - # Square root is always non-negative - return ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) + result = ufl_reuse_if_untouched(o, self(o.ufl_operands[0], False)) + if self.complex_mode and in_abs: + return Abs(result) + else: + return result @_simplify_abs.register(ScalarValue) @@ -325,8 +329,88 @@ def _simplify_abs_abs(o, self, in_abs): return self(o.ufl_operands[0], True) -def simplify_abs(expression): +def simplify_abs(expression, complex_mode): """Simplify absolute values in a UFL expression. Its primary purpose is to "neutralise" CellOrientation nodes that are surrounded by absolute values and thus not at all necessary.""" - return MemoizerArg(_simplify_abs)(expression, False) + mapper = MemoizerArg(_simplify_abs) + mapper.complex_mode = complex_mode + return mapper(expression, False) + + +def apply_mapping(expression, mapping, domain): + """ + This applies the appropriate transformation to the + given expression for interpolation to a specific + element, according to the manner in which it maps + from the reference cell. + + The following is borrowed from the UFC documentation: + + Let g be a field defined on a physical domain T with physical + coordinates x. Let T_0 be a reference domain with coordinates + X. Assume that F: T_0 -> T such that + + x = F(X) + + Let J be the Jacobian of F, i.e J = dx/dX and let K denote the + inverse of the Jacobian K = J^{-1}. Then we (currently) have the + following four types of mappings: + + 'affine' mapping for g: + + G(X) = g(x) + + For vector fields g: + + 'contravariant piola' mapping for g: + + G(X) = det(J) K g(x) i.e G_i(X) = det(J) K_ij g_j(x) + + 'covariant piola' mapping for g: + + G(X) = J^T g(x) i.e G_i(X) = J^T_ij g(x) = J_ji g_j(x) + + 'double covariant piola' mapping for g: + + G(X) = J^T g(x) J i.e. G_il(X) = J_ji g_jk(x) J_kl + + 'double contravariant piola' mapping for g: + + G(X) = det(J)^2 K g(x) K^T i.e. G_il(X)=(detJ)^2 K_ij g_jk K_lk + + If 'contravariant piola' or 'covariant piola' are applied to a + matrix-valued function, the appropriate mappings are applied row-by-row. + + :arg expression: UFL expression + :arg mapping: a string indicating the mapping to apply + """ + + mesh = expression.ufl_domain() + if mesh is None: + mesh = domain + if domain is not None and mesh != domain: + raise NotImplementedError("Multiple domains not supported") + rank = len(expression.ufl_shape) + if mapping == "affine": + return expression + elif mapping == "covariant piola": + J = Jacobian(mesh) + *i, j, k = indices(len(expression.ufl_shape) + 1) + expression = Indexed(expression, MultiIndex((*i, k))) + return as_tensor(J.T[j, k] * expression, (*i, j)) + elif mapping == "contravariant piola": + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + *i, j, k = indices(len(expression.ufl_shape) + 1) + expression = Indexed(expression, MultiIndex((*i, k))) + return as_tensor(detJ * K[j, k] * expression, (*i, j)) + elif mapping == "double covariant piola" and rank == 2: + J = Jacobian(mesh) + return J.T * expression * J + elif mapping == "double contravariant piola" and rank == 2: + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + return (detJ)**2 * K * expression * K.T + else: + raise NotImplementedError("Don't know how to handle mapping type %s for expression of rank %d" % (mapping, rank)) From bb10f121e412559ea91c5987cd7a27ecb59bfdf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Sat, 22 Aug 2020 18:13:36 +0200 Subject: [PATCH 599/809] Make FInAT interface not depend on FIAT interface --- tsfc/fiatinterface.py | 11 ++--------- tsfc/finatinterface.py | 16 +++++++++++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 3007e166e1..9d24de1522 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -29,6 +29,8 @@ import ufl +from tsfc.finatinterface import as_fiat_cell + __all__ = ("create_element", "supported_elements", "as_fiat_cell") @@ -73,15 +75,6 @@ have a direct FIAT equivalent.""" -def as_fiat_cell(cell): - """Convert a ufl cell to a FIAT cell. - - :arg cell: the :class:`ufl.Cell` to convert.""" - if not isinstance(cell, ufl.AbstractCell): - raise ValueError("Expecting a UFL Cell") - return FIAT.ufc_cell(cell) - - @singledispatch def convert(element, vector_is_mixed): """Handler for converting UFL elements to FIAT elements. diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 8146458530..1801f87b81 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -24,12 +24,10 @@ from functools import singledispatch, partial import weakref +import FIAT import finat - import ufl -from tsfc.fiatinterface import as_fiat_cell - __all__ = ("create_element", "supported_elements", "as_fiat_cell") @@ -79,12 +77,20 @@ have a direct FInAT equivalent.""" +def as_fiat_cell(cell): + """Convert a ufl cell to a FIAT cell. + + :arg cell: the :class:`ufl.Cell` to convert.""" + if not isinstance(cell, ufl.AbstractCell): + raise ValueError("Expecting a UFL Cell") + return FIAT.ufc_cell(cell) + + def fiat_compat(element): - from tsfc.fiatinterface import create_element from finat.fiat_elements import FiatElement assert element.cell().is_simplex() - return FiatElement(create_element(element)) + return FiatElement(create_element(element).fiat_equivalent) @singledispatch From 3b9ceba8274fd7aa605dda13f4d5f69e2c27ffb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Sat, 22 Aug 2020 18:33:46 +0200 Subject: [PATCH 600/809] Reduce FIAT interface to compatibility functions --- tsfc/fiatinterface.py | 170 ++---------------------------------------- 1 file changed, 6 insertions(+), 164 deletions(-) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 9d24de1522..b7c7f6a166 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -21,15 +21,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with FFC. If not, see . -from functools import singledispatch, partial -import weakref - import FIAT -from FIAT.tensor_product import FlattenedDimensions -import ufl +from finat.tensorfiniteelement import TensorFiniteElement from tsfc.finatinterface import as_fiat_cell +from tsfc.finatinterface import create_element as create_finat_element __all__ = ("create_element", "supported_elements", "as_fiat_cell") @@ -75,148 +72,6 @@ have a direct FIAT equivalent.""" -@singledispatch -def convert(element, vector_is_mixed): - """Handler for converting UFL elements to FIAT elements. - - :arg element: The UFL element to convert. - :arg vector_is_mixed: Should Vector and Tensor elements be treated - as Mixed? If ``False``, then just look at the sub-element. - - Do not use this function directly, instead call - :func:`create_element`.""" - if element.family() in supported_elements: - raise ValueError("Element %s supported, but no handler provided" % element) - raise ValueError("Unsupported element type %s" % type(element)) - - -# Base finite elements first -@convert.register(ufl.FiniteElement) -def convert_finiteelement(element, vector_is_mixed): - if element.family() == "Real": - # Real element is just DG0 - cell = element.cell() - return create_element(ufl.FiniteElement("DG", cell, 0), vector_is_mixed) - cell = as_fiat_cell(element.cell()) - if element.family() == "Quadrature": - degree = element.degree() - scheme = element.quadrature_scheme() - if degree is None or scheme is None: - raise ValueError("Quadrature scheme and degree must be specified!") - - quad_rule = FIAT.create_quadrature(cell, degree, scheme) - return FIAT.QuadratureElement(cell, quad_rule.get_points(), weights=quad_rule.get_weights()) - lmbda = supported_elements[element.family()] - if lmbda is None: - if element.cell().cellname() == "quadrilateral": - # Handle quadrilateral short names like RTCF and RTCE. - element = element.reconstruct(cell=quadrilateral_tpc) - elif element.cell().cellname() == "hexahedron": - # Handle hexahedron short names like NCF and NCE. - element = element.reconstruct(cell=hexahedron_tpc) - else: - raise ValueError("%s is supported, but handled incorrectly" % - element.family()) - return FlattenedDimensions(create_element(element, vector_is_mixed)) - - kind = element.variant() - if kind is None: - kind = 'spectral' if element.cell().cellname() == 'interval' else 'equispaced' # default variant - - if element.family() == "Lagrange": - if kind == 'equispaced': - lmbda = FIAT.Lagrange - elif kind == 'spectral' and element.cell().cellname() == 'interval': - lmbda = FIAT.GaussLobattoLegendre - else: - raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", - "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)"}: - lmbda = partial(lmbda, variant=element.variant()) - elif element.family() in {"Discontinuous Lagrange", "Discontinuous Lagrange L2"}: - if kind == 'equispaced': - lmbda = FIAT.DiscontinuousLagrange - elif kind == 'spectral' and element.cell().cellname() == 'interval': - lmbda = FIAT.GaussLegendre - else: - raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - return lmbda(cell, element.degree()) - - -# Element modifiers -@convert.register(ufl.RestrictedElement) -def convert_restrictedelement(element, vector_is_mixed): - return FIAT.RestrictedElement(create_element(element.sub_element(), vector_is_mixed), - restriction_domain=element.restriction_domain()) - - -@convert.register(ufl.EnrichedElement) -def convert_enrichedelement(element, vector_is_mixed): - return FIAT.EnrichedElement(*(create_element(e, vector_is_mixed) - for e in element._elements)) - - -@convert.register(ufl.NodalEnrichedElement) -def convert_nodalenrichedelement(element, vector_is_mixed): - return FIAT.NodalEnrichedElement(*(create_element(e, vector_is_mixed) - for e in element._elements)) - - -@convert.register(ufl.BrokenElement) -def convert_brokenelement(element, vector_is_mixed): - return FIAT.DiscontinuousElement(create_element(element._element, vector_is_mixed)) - - -# Now for the TPE-specific stuff -@convert.register(ufl.TensorProductElement) -def convert_tensorproductelement(element, vector_is_mixed): - cell = element.cell() - if type(cell) is not ufl.TensorProductCell: - raise ValueError("TPE not on TPC?") - A, B = element.sub_elements() - return FIAT.TensorProductElement(create_element(A, vector_is_mixed), - create_element(B, vector_is_mixed)) - - -@convert.register(ufl.HDivElement) -def convert_hdivelement(element, vector_is_mixed): - return FIAT.Hdiv(create_element(element._element, vector_is_mixed)) - - -@convert.register(ufl.HCurlElement) -def convert_hcurlelement(element, vector_is_mixed): - return FIAT.Hcurl(create_element(element._element, vector_is_mixed)) - - -# Finally the MixedElement case -@convert.register(ufl.MixedElement) -def convert_mixedelement(element, vector_is_mixed): - # If we're just trying to get the scalar part of a vector element? - if not vector_is_mixed: - assert isinstance(element, (ufl.VectorElement, - ufl.TensorElement)) - return create_element(element.sub_elements()[0], vector_is_mixed) - - elements = [] - - def rec(eles): - for ele in eles: - if isinstance(ele, ufl.MixedElement): - rec(ele.sub_elements()) - else: - elements.append(ele) - - rec(element.sub_elements()) - fiat_elements = map(partial(create_element, vector_is_mixed=vector_is_mixed), - elements) - return FIAT.MixedElement(fiat_elements) - - -hexahedron_tpc = ufl.TensorProductCell(ufl.quadrilateral, ufl.interval) -quadrilateral_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) -_cache = weakref.WeakKeyDictionary() - - def create_element(element, vector_is_mixed=True): """Create a FIAT element (suitable for tabulating with) given a UFL element. @@ -227,20 +82,7 @@ def create_element(element, vector_is_mixed=True): useful if you want a FIAT element that tells you how many "nodes" the finite element has. """ - try: - cache = _cache[element] - except KeyError: - _cache[element] = {} - cache = _cache[element] - - try: - return cache[vector_is_mixed] - except KeyError: - pass - - if element.cell() is None: - raise ValueError("Don't know how to build element when cell is not given") - - fiat_element = convert(element, vector_is_mixed) - cache[vector_is_mixed] = fiat_element - return fiat_element + finat_elem = create_finat_element(element) + if isinstance(finat_elem, TensorFiniteElement) and not vector_is_mixed: + finat_elem = finat_elem.base_element + return finat_elem.fiat_equivalent From 766e2d4a277f5b033cb442123fdd2bdc491882bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Sat, 22 Aug 2020 23:31:15 +0200 Subject: [PATCH 601/809] Avoid use of fiat_compat with Bernstein elements --- tsfc/finatinterface.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 1801f87b81..26817d2bc9 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -34,6 +34,7 @@ supported_elements = { # These all map directly to FInAT elements + "Bernstein": finat.Bernstein, "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": finat.BrezziDouglasFortinMarini, "Bubble": finat.Bubble, @@ -117,8 +118,6 @@ def convert_finiteelement(element, **kwargs): raise ValueError("Quadrature scheme and degree must be specified!") return finat.QuadratureElement(cell, degree, scheme), set() - elif element.family() == "Bernstein": - return fiat_compat(element), set() lmbda = supported_elements[element.family()] if lmbda is None: if element.cell().cellname() == "quadrilateral": From c0714821297ab560c42c020305e040cbba2b2db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Sun, 23 Aug 2020 00:13:50 +0200 Subject: [PATCH 602/809] Avoid use of fiat_compat with nodal enriched elements Finally, remove fiat_compat function. --- tsfc/finatinterface.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 26817d2bc9..1db9375c7c 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -87,13 +87,6 @@ def as_fiat_cell(cell): return FIAT.ufc_cell(cell) -def fiat_compat(element): - from finat.fiat_elements import FiatElement - - assert element.cell().is_simplex() - return FiatElement(create_element(element).fiat_equivalent) - - @singledispatch def convert(element, **kwargs): """Handler for converting UFL elements to FInAT elements. @@ -193,6 +186,13 @@ def convert_enrichedelement(element, **kwargs): return finat.EnrichedElement(elements), set.union(*deps) +@convert.register(ufl.NodalEnrichedElement) +def convert_nodalenrichedelement(element, **kwargs): + elements, deps = zip(*[_create_element(elem, **kwargs) + for elem in element._elements]) + return finat.NodalEnrichedElement(elements), set.union(*deps) + + @convert.register(ufl.MixedElement) def convert_mixedelement(element, **kwargs): elements, deps = zip(*[_create_element(elem, **kwargs) @@ -254,11 +254,6 @@ def convert_restrictedelement(element, **kwargs): return finat.RestrictedElement(finat_elem, element.restriction_domain()), deps -@convert.register(ufl.NodalEnrichedElement) -def convert_nodalenrichedelement(element, **kwargs): - return fiat_compat(element), set() - - hexahedron_tpc = ufl.TensorProductCell(ufl.quadrilateral, ufl.interval) quadrilateral_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() From f72fe3916296a924f774b1302b02021fa52208a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Sun, 23 Aug 2020 00:39:55 +0200 Subject: [PATCH 603/809] Eliminate as_fiat_cell from tsfc.fiatinterface --- tsfc/driver.py | 2 +- tsfc/fiatinterface.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 0fd79b4ed8..7823536c49 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -26,7 +26,7 @@ from finat.quadrature import AbstractQuadratureRule, make_quadrature, QuadratureRule from tsfc import fem, ufl_utils -from tsfc.fiatinterface import as_fiat_cell +from tsfc.finatinterface import as_fiat_cell from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex from tsfc.ufl_utils import apply_mapping diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index b7c7f6a166..2c17eca5e1 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -25,11 +25,10 @@ from finat.tensorfiniteelement import TensorFiniteElement -from tsfc.finatinterface import as_fiat_cell from tsfc.finatinterface import create_element as create_finat_element -__all__ = ("create_element", "supported_elements", "as_fiat_cell") +__all__ = ("create_element", "supported_elements") supported_elements = { From 8ef4a0a625cdb29573e898bd17977efc616afd6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Sun, 23 Aug 2020 00:40:29 +0200 Subject: [PATCH 604/809] Inline fiatinterface.supported_elements in test case --- tests/test_create_fiat_element.py | 17 +++++++++++- tsfc/fiatinterface.py | 44 +------------------------------ 2 files changed, 17 insertions(+), 44 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 0271e8ba9c..360a61e2fe 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -4,7 +4,22 @@ from FIAT.discontinuous_lagrange import HigherOrderDiscontinuousLagrange as FIAT_DiscontinuousLagrange import ufl -from tsfc.fiatinterface import create_element, supported_elements +from tsfc.fiatinterface import create_element + + +supported_elements = { + # These all map directly to FIAT elements + "Brezzi-Douglas-Marini": FIAT.BrezziDouglasMarini, + "Brezzi-Douglas-Fortin-Marini": FIAT.BrezziDouglasFortinMarini, + "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, + "Lagrange": FIAT.Lagrange, + "Nedelec 1st kind H(curl)": FIAT.Nedelec, + "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, + "Raviart-Thomas": FIAT.RaviartThomas, + "Regge": FIAT.Regge, +} +"""A :class:`.dict` mapping UFL element family names to their +FIAT-equivalent constructors.""" @pytest.fixture(params=["BDM", diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 2c17eca5e1..11bb3d0aab 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -21,54 +21,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with FFC. If not, see . -import FIAT - from finat.tensorfiniteelement import TensorFiniteElement from tsfc.finatinterface import create_element as create_finat_element -__all__ = ("create_element", "supported_elements") - - -supported_elements = { - # These all map directly to FIAT elements - "Bernstein": FIAT.Bernstein, - "Brezzi-Douglas-Marini": FIAT.BrezziDouglasMarini, - "Brezzi-Douglas-Fortin-Marini": FIAT.BrezziDouglasFortinMarini, - "Bubble": FIAT.Bubble, - "FacetBubble": FIAT.FacetBubble, - "Crouzeix-Raviart": FIAT.CrouzeixRaviart, - "Discontinuous Lagrange": FIAT.DiscontinuousLagrange, - "Discontinuous Taylor": FIAT.DiscontinuousTaylor, - "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, - "Gauss-Lobatto-Legendre": FIAT.GaussLobattoLegendre, - "Gauss-Legendre": FIAT.GaussLegendre, - "Lagrange": FIAT.Lagrange, - "Nedelec 1st kind H(curl)": FIAT.Nedelec, - "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, - "Raviart-Thomas": FIAT.RaviartThomas, - "HDiv Trace": FIAT.HDivTrace, - "Regge": FIAT.Regge, - "Hellan-Herrmann-Johnson": FIAT.HellanHerrmannJohnson, - # These require special treatment below - "DQ": None, - "Q": None, - "RTCE": None, - "RTCF": None, - "NCE": None, - "NCF": None, - "DPC": FIAT.DPC, - "S": FIAT.Serendipity, - "DPC L2": FIAT.DPC, - "Discontinuous Lagrange L2": FIAT.DiscontinuousLagrange, - "Gauss-Legendre L2": FIAT.GaussLegendre, - "DQ L2": None, -} -"""A :class:`.dict` mapping UFL element family names to their -FIAT-equivalent constructors. If the value is ``None``, the UFL -element is supported, but must be handled specially because it doesn't -have a direct FIAT equivalent.""" +__all__ = ("create_element",) def create_element(element, vector_is_mixed=True): From adea7a0b6c66587b862664c9afdac4d1afa8979a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Sun, 23 Aug 2020 11:29:12 +0200 Subject: [PATCH 605/809] Kill tsfc/fiatinterface.py (finally!) Resolves #88. --- tests/test_create_fiat_element.py | 8 +++++- tsfc/fiatinterface.py | 45 ------------------------------- tsfc/finatinterface.py | 15 ++++++++++- 3 files changed, 21 insertions(+), 47 deletions(-) delete mode 100644 tsfc/fiatinterface.py diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 360a61e2fe..de07fb3fde 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -4,7 +4,7 @@ from FIAT.discontinuous_lagrange import HigherOrderDiscontinuousLagrange as FIAT_DiscontinuousLagrange import ufl -from tsfc.fiatinterface import create_element +from tsfc.finatinterface import create_element as _create_element supported_elements = { @@ -22,6 +22,12 @@ FIAT-equivalent constructors.""" +def create_element(ufl_element): + """Create a FIAT element given a UFL element.""" + finat_element = _create_element(ufl_element) + return finat_element.fiat_equivalent + + @pytest.fixture(params=["BDM", "BDFM", "DRT", diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py deleted file mode 100644 index 11bb3d0aab..0000000000 --- a/tsfc/fiatinterface.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file was modified from FFC -# (http://bitbucket.org/fenics-project/ffc), copyright notice -# reproduced below. -# -# Copyright (C) 2009-2013 Kristian B. Oelgaard and Anders Logg -# -# This file is part of FFC. -# -# FFC is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# FFC is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with FFC. If not, see . - -from finat.tensorfiniteelement import TensorFiniteElement - -from tsfc.finatinterface import create_element as create_finat_element - - -__all__ = ("create_element",) - - -def create_element(element, vector_is_mixed=True): - """Create a FIAT element (suitable for tabulating with) given a UFL element. - - :arg element: The UFL element to create a FIAT element from. - - :arg vector_is_mixed: indicate whether VectorElement (or - TensorElement) should be treated as a MixedElement. Maybe - useful if you want a FIAT element that tells you how many - "nodes" the finite element has. - """ - finat_elem = create_finat_element(element) - if isinstance(finat_elem, TensorFiniteElement) and not vector_is_mixed: - finat_elem = finat_elem.base_element - return finat_elem.fiat_equivalent diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 1db9375c7c..8b47012f2f 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -29,7 +29,8 @@ import ufl -__all__ = ("create_element", "supported_elements", "as_fiat_cell") +__all__ = ("as_fiat_cell", "create_base_element", + "create_element", "supported_elements") supported_elements = { @@ -305,3 +306,15 @@ def _create_element(ufl_element, **kwargs): # Forward result return finat_element, deps + + +def create_base_element(ufl_element, **kwargs): + """Create a "scalar" base FInAT element given a UFL element. + + Takes a UFL element and an unspecified set of parameter options, + and returns the converted element. + """ + finat_element = create_element(ufl_element, **kwargs) + if isinstance(finat_element, finat.TensorFiniteElement): + finat_element = finat_element.base_element + return finat_element From 1c4313148494e4ecf19953588e87f2df13bfdc56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Sun, 23 Aug 2020 13:47:06 +0200 Subject: [PATCH 606/809] Make compile_expression_dual_evaluation accept FInAT element instead --- tsfc/driver.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7823536c49..b8160a85b5 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -275,7 +275,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, *, Useful for interpolating UFL expressions into e.g. N1curl spaces. :arg expression: UFL expression - :arg to_element: A FIAT FiniteElement for the target space + :arg to_element: A FInAT element for the target space :arg coordinates: the coordinate function :arg domain: optional UFL domain the expression is defined on (useful when expression contains no domain). :arg interface: backend module for the kernel interface @@ -284,6 +284,11 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, *, """ import coffee.base as ast import loopy as lp + + # Just convert FInAT element to FIAT for now. + # Dual evaluation in FInAT will bring a thorough revision. + to_element = to_element.fiat_equivalent + if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()): raise NotImplementedError("Can only interpolate onto dual basis functionals without derivative evaluation, sorry!") From 49e333bd5288d02fa8026a2a7f03fe01292c875c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Homolya?= Date: Tue, 25 Aug 2020 11:28:23 +0200 Subject: [PATCH 607/809] Drop DRT test for FIAT interface --- tests/test_create_fiat_element.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index de07fb3fde..0924edb39c 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -11,7 +11,6 @@ # These all map directly to FIAT elements "Brezzi-Douglas-Marini": FIAT.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": FIAT.BrezziDouglasFortinMarini, - "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, "Lagrange": FIAT.Lagrange, "Nedelec 1st kind H(curl)": FIAT.Nedelec, "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, @@ -30,7 +29,6 @@ def create_element(ufl_element): @pytest.fixture(params=["BDM", "BDFM", - "DRT", "Lagrange", "N1curl", "N2curl", From 099b73af89004ce835a8dfc2dd4f35f6e60083cf Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 25 Aug 2020 22:29:51 +0100 Subject: [PATCH 608/809] More magic methods for gem Nodes Adds a __matmul__ method, and a componentwise helper so the existing sugar also works transparently for shaped objects. --- tests/test_syntax_sugar.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_syntax_sugar.py b/tests/test_syntax_sugar.py index a2615e84ed..56bbc4f4ac 100644 --- a/tests/test_syntax_sugar.py +++ b/tests/test_syntax_sugar.py @@ -21,8 +21,14 @@ def test_expressions(): assert xij + 1 == gem.Sum(xij, gem.Literal(1)) assert 1 + xij == gem.Sum(gem.Literal(1), xij) - with pytest.raises(AssertionError): - xij + y + assert (xij + y).shape == (4, ) + + assert (x @ y).shape == (3, ) + + assert x.T.shape == (4, 3) + + with pytest.raises(ValueError): + xij.T @ y with pytest.raises(ValueError): xij + "foo" From b42dccf34c5f7791a5200cf26dd87d3e8e24052a Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 26 Aug 2020 16:24:15 +0100 Subject: [PATCH 609/809] Faster type inference in loopy codegen We can just take the union type at the end, rather than every stage. This approximately halves the time in this pass. How long until we're doing Hindley-Milner? --- tsfc/loopy.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index 0dfc0b2443..a1f1ae89cf 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -3,7 +3,7 @@ This is the final stage of code generation in TSFC.""" import numpy -from functools import singledispatch, partial +from functools import singledispatch from collections import defaultdict, OrderedDict from gem import gem, impero as imp @@ -22,57 +22,54 @@ from contextlib import contextmanager -maxtype = partial(numpy.find_common_type, []) - - @singledispatch def _assign_dtype(expression, self): - return maxtype(map(self, expression.children)) + return set.union(*map(self, expression.children)) @_assign_dtype.register(gem.Terminal) def _assign_dtype_terminal(expression, self): - return self.scalar_type + return {self.scalar_type} @_assign_dtype.register(gem.Zero) @_assign_dtype.register(gem.Identity) @_assign_dtype.register(gem.Delta) def _assign_dtype_real(expression, self): - return self.real_type + return {self.real_type} @_assign_dtype.register(gem.Literal) def _assign_dtype_identity(expression, self): - return expression.array.dtype + return {expression.array.dtype} @_assign_dtype.register(gem.Power) def _assign_dtype_power(expression, self): # Conservative - return self.scalar_type + return {self.scalar_type} @_assign_dtype.register(gem.MathFunction) def _assign_dtype_mathfunction(expression, self): if expression.name in {"abs", "real", "imag"}: - return self.real_type + return {self.real_type} elif expression.name == "sqrt": - return self.scalar_type + return {self.scalar_type} else: - return maxtype(map(self, expression.children)) + return set.union(*map(self, expression.children)) @_assign_dtype.register(gem.MinValue) @_assign_dtype.register(gem.MaxValue) def _assign_dtype_minmax(expression, self): # UFL did correctness checking - return self.real_type + return {self.real_type} @_assign_dtype.register(gem.Conditional) def _assign_dtype_conditional(expression, self): - return maxtype(map(self, expression.children[1:])) + return set.union(*map(self, expression.children[1:])) @_assign_dtype.register(gem.Comparison) @@ -80,7 +77,7 @@ def _assign_dtype_conditional(expression, self): @_assign_dtype.register(gem.LogicalAnd) @_assign_dtype.register(gem.LogicalOr) def _assign_dtype_logical(expression, self): - return numpy.int8 + return {numpy.int8} def assign_dtypes(expressions, scalar_type): @@ -94,11 +91,8 @@ def assign_dtypes(expressions, scalar_type): :returns: list of tuples (expression, dtype).""" mapper = Memoizer(_assign_dtype) mapper.scalar_type = scalar_type - if scalar_type.kind == "c": - mapper.real_type = numpy.finfo(scalar_type).dtype - else: - mapper.real_type = scalar_type - return [(e, mapper(e)) for e in expressions] + mapper.real_type = numpy.finfo(scalar_type).dtype + return [(e, numpy.find_common_type(mapper(e), [])) for e in expressions] class LoopyContext(object): From d6c92b44f6bbd00c3cdafab100760e813af458cb Mon Sep 17 00:00:00 2001 From: Robert Kirby Date: Fri, 28 Aug 2020 10:32:06 -0500 Subject: [PATCH 610/809] Enable some zany H(div) elements (Mardal-Tai-Winther and Arnold-Winther) (#225) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrick Farrell Co-authored-by: Francis Aznaran Co-authored-by: Francis Aznaran Co-authored-by: Miklós Homolya --- tsfc/fem.py | 18 +++++++++++++++++- tsfc/finatinterface.py | 3 +++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index b9b856f834..2411a1dd8e 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -14,7 +14,7 @@ CellFacetJacobian, CellOrientation, CellOrigin, CellVertices, CellVolume, Coefficient, FacetArea, FacetCoordinate, - GeometricQuantity, Jacobian, + GeometricQuantity, Jacobian, JacobianDeterminant, NegativeRestricted, QuadratureWeight, PositiveRestricted, ReferenceCellVolume, ReferenceCellEdgeVectors, @@ -153,12 +153,28 @@ def jacobian_at(self, point): expr = preprocess_expression(expr, complex_mode=context.complex_mode) return map_expr_dag(context.translator, expr) + def detJ_at(self, point): + expr = JacobianDeterminant(self.mt.terminal.ufl_domain()) + if self.mt.restriction == '+': + expr = PositiveRestricted(expr) + elif self.mt.restriction == '-': + expr = NegativeRestricted(expr) + expr = preprocess_expression(expr) + + config = {"point_set": PointSingleton(point)} + config.update(self.config) + context = PointSetContext(**config) + return map_expr_dag(context.translator, expr) + def reference_normals(self): if not (isinstance(self.interface.fiat_cell, UFCSimplex) and self.interface.fiat_cell.get_spatial_dimension() == 2): raise NotImplementedError("Only works for triangles for now") return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) + def reference_edge_tangents(self): + return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_edge_tangent(i) for i in range(3)])) + def physical_tangents(self): if not (isinstance(self.interface.fiat_cell, UFCSimplex) and self.interface.fiat_cell.get_spatial_dimension() == 2): diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 8b47012f2f..841b68cfdf 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -48,8 +48,11 @@ "Gauss-Lobatto-Legendre": finat.GaussLobattoLegendre, "HDiv Trace": finat.HDivTrace, "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, + "Nonconforming Arnold-Winther": finat.ArnoldWintherNC, + "Conforming Arnold-Winther": finat.ArnoldWinther, "Hermite": finat.Hermite, "Argyris": finat.Argyris, + "Mardal-Tai-Winther": finat.MardalTaiWinther, "Morley": finat.Morley, "Bell": finat.Bell, "Lagrange": finat.Lagrange, From f3e2558532035bc24580fa4155f5442c11e17aa5 Mon Sep 17 00:00:00 2001 From: Justincrum Date: Tue, 8 Sep 2020 13:24:03 -0700 Subject: [PATCH 611/809] Changes to get SminusCurl implemented. --- tsfc/fiatinterface.py | 1 + tsfc/finatinterface.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py index 3935ffab73..b8b0b8d2f1 100644 --- a/tsfc/fiatinterface.py +++ b/tsfc/fiatinterface.py @@ -65,6 +65,7 @@ "SminusF": FIAT.TrimmedSerendipityFace, "SminusDiv": FIAT.TrimmedSerendipityDiv, "SminusE": FIAT.TrimmedSerendipityEdge, + "SminusCurl": FIAT.TrimmedSerendipityCurl, "DPC L2": FIAT.DPC, "Discontinuous Lagrange L2": FIAT.DiscontinuousLagrange, "Gauss-Legendre L2": FIAT.GaussLegendre, diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index facccf8a71..5429a1bcd6 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -70,6 +70,7 @@ "SminusF": finat.TrimmedSerendipityFace, "SminusDiv": finat.TrimmedSerendipityDiv, "SminusE": finat.TrimmedSerendipityEdge, + "SminusCurl": finat.TrimmedSerendipityCurl, "DPC L2": finat.DPC, "Discontinuous Lagrange L2": finat.DiscontinuousLagrange, "Gauss-Legendre L2": finat.GaussLegendre, From ef528e140a0ba29bc1ab63d16ba55afeadcc6bef Mon Sep 17 00:00:00 2001 From: Robert Kirby Date: Fri, 11 Sep 2020 12:23:08 -0500 Subject: [PATCH 612/809] Hook up KMV elements (#231) --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 841b68cfdf..7b1486d7db 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -51,6 +51,7 @@ "Nonconforming Arnold-Winther": finat.ArnoldWintherNC, "Conforming Arnold-Winther": finat.ArnoldWinther, "Hermite": finat.Hermite, + "Kong-Mulder-Veldhuizen": finat.KongMulderVeldhuizen, "Argyris": finat.Argyris, "Mardal-Tai-Winther": finat.MardalTaiWinther, "Morley": finat.Morley, From d5ea6e0b23d3c03e0d0dc5ef1b05cf6deb320c51 Mon Sep 17 00:00:00 2001 From: Justincrum Date: Fri, 11 Sep 2020 11:14:44 -0700 Subject: [PATCH 613/809] More fixes from over the summer. --- tsfc/finatinterface.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 5429a1bcd6..8932904c3b 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -31,7 +31,7 @@ from tsfc.fiatinterface import as_fiat_cell -__all__ = ("create_element", "supported_elements", "as_fiat_cell") +__all__ = ("create_element", "supported_elements", "as_fiat_cell", "create_base_element") supported_elements = { @@ -308,3 +308,14 @@ def _create_element(ufl_element, **kwargs): # Forward result return finat_element, deps + + +def create_base_element(ufl_element, **kwargs): + """Create a "scalar" base FInAT element given a UFL element. + Takes a UFL element and an unspecified set of parameter options, + and returns the converted element. + """ + finat_element = create_element(ufl_element, **kwargs) + if isinstance(finat_element, finat.TensorFiniteElement): + finat_element = finat_element.base_element + return finat_element From 2e3957075c53b88d10520c9df1ab970228001156 Mon Sep 17 00:00:00 2001 From: Reuben Hill Date: Fri, 9 Oct 2020 15:50:26 +0100 Subject: [PATCH 614/809] Remove coords argument in compile_expression_dual_evaluation First step towards dealing with #232. Use fake coordinates coefficient when required instead of data carrying .coordinates firedrake Function. --- tsfc/driver.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b8160a85b5..95ff9bd6c4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -267,7 +267,7 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) -def compile_expression_dual_evaluation(expression, to_element, coordinates, *, +def compile_expression_dual_evaluation(expression, to_element, *, domain=None, interface=None, parameters=None, coffee=False): """Compile a UFL expression to be evaluated against a compile-time known reference element's dual basis. @@ -276,8 +276,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, *, :arg expression: UFL expression :arg to_element: A FInAT element for the target space - :arg coordinates: the coordinate function - :arg domain: optional UFL domain the expression is defined on (useful when expression contains no domain). + :arg domain: optional UFL domain the expression is defined on (required when expression contains no domain). :arg interface: backend module for the kernel interface :arg parameters: parameters object :arg coffee: compile coffee kernel instead of loopy kernel @@ -328,17 +327,20 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, *, argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() for arg in arguments) - # Replace coordinates (if any) - domain = expression.ufl_domain() - if domain: - assert coordinates.ufl_domain() == domain - builder.domain_coordinate[domain] = coordinates - builder.set_cell_sizes(domain) + # Replace coordinates (if any) unless otherwise specified by kwarg + if domain is None: + domain = expression.ufl_domain() + assert domain is not None + + # Create a fake coordinate coefficient for a domain. + coords_coefficient = ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) + builder.domain_coordinate[domain] = coords_coefficient + builder.set_cell_sizes(domain) # Collect required coefficients coefficients = extract_coefficients(expression) if has_type(expression, GeometricQuantity) or any(fem.needs_coordinate_mapping(c.ufl_element()) for c in coefficients): - coefficients = [coordinates] + coefficients + coefficients = [coords_coefficient] + coefficients builder.set_coefficients(coefficients) # Split mixed coefficients @@ -346,7 +348,7 @@ def compile_expression_dual_evaluation(expression, to_element, coordinates, *, # Translate to GEM kernel_cfg = dict(interface=builder, - ufl_cell=coordinates.ufl_domain().ufl_cell(), + ufl_cell=domain.ufl_cell(), argument_multiindices=argument_multiindices, index_cache={}, scalar_type=parameters["scalar_type"]) From f35f7a8bcf6187566dc861f4c158945f96f1e183 Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Mon, 19 Oct 2020 17:42:07 +0100 Subject: [PATCH 615/809] test compile_expression_dual_evaluation with symbolics Tests for non-dependence of compile_expression_dual_evaluation on numerics by checking it works with purely symbolic expressions. --- tests/test_dual_evaluation.py | 57 +++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/test_dual_evaluation.py diff --git a/tests/test_dual_evaluation.py b/tests/test_dual_evaluation.py new file mode 100644 index 0000000000..da6bf7e6cf --- /dev/null +++ b/tests/test_dual_evaluation.py @@ -0,0 +1,57 @@ +import pytest +import ufl +from tsfc.finatinterface import create_element +from tsfc import compile_expression_dual_evaluation + + +def test_ufl_only_simple(): + mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) + v = ufl.Coefficient(V) + expr = ufl.inner(v, v) + W = V + to_element = create_element(W.ufl_element()) + ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + + +def test_ufl_only_spatialcoordinate(): + mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) + x, y = ufl.SpatialCoordinate(mesh) + expr = x*y - y**2 + x + W = V + to_element = create_element(W.ufl_element()) + ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + + +def test_ufl_only_from_contravariant_piola(): + mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) + v = ufl.Coefficient(V) + expr = ufl.inner(v, v) + W = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) + to_element = create_element(W.ufl_element()) + ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + + +def test_ufl_only_to_contravariant_piola(): + mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) + v = ufl.Coefficient(V) + expr = ufl.as_vector([v, v]) + W = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) + to_element = create_element(W.ufl_element()) + ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + + +def test_ufl_only_shape_mismatch(): + mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) + v = ufl.Coefficient(V) + expr = ufl.inner(v, v) + assert expr.ufl_shape == () + W = V + to_element = create_element(W.ufl_element()) + assert to_element.value_shape == (2,) + with pytest.raises(ValueError): + ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) From 95681dc4485664491d6ef8621fdeb84df12c4605 Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Wed, 18 Nov 2020 11:50:09 +0000 Subject: [PATCH 616/809] Make clear if using manufactured coordinates coefficient So that caller can replace if necessary --- tests/test_dual_evaluation.py | 14 +++++++++----- tsfc/driver.py | 13 +++++++------ tsfc/kernel_interface/firedrake_loopy.py | 9 ++++++--- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/tests/test_dual_evaluation.py b/tests/test_dual_evaluation.py index da6bf7e6cf..8cabba703b 100644 --- a/tests/test_dual_evaluation.py +++ b/tests/test_dual_evaluation.py @@ -11,7 +11,8 @@ def test_ufl_only_simple(): expr = ufl.inner(v, v) W = V to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + assert first_coeff_fake_coords is False def test_ufl_only_spatialcoordinate(): @@ -21,7 +22,8 @@ def test_ufl_only_spatialcoordinate(): expr = x*y - y**2 + x W = V to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + assert first_coeff_fake_coords is True def test_ufl_only_from_contravariant_piola(): @@ -31,7 +33,8 @@ def test_ufl_only_from_contravariant_piola(): expr = ufl.inner(v, v) W = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + assert first_coeff_fake_coords is True def test_ufl_only_to_contravariant_piola(): @@ -41,7 +44,8 @@ def test_ufl_only_to_contravariant_piola(): expr = ufl.as_vector([v, v]) W = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + assert first_coeff_fake_coords is True def test_ufl_only_shape_mismatch(): @@ -54,4 +58,4 @@ def test_ufl_only_shape_mismatch(): to_element = create_element(W.ufl_element()) assert to_element.value_shape == (2,) with pytest.raises(ValueError): - ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) diff --git a/tsfc/driver.py b/tsfc/driver.py index 95ff9bd6c4..d6db7120d4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -332,15 +332,16 @@ def compile_expression_dual_evaluation(expression, to_element, *, domain = expression.ufl_domain() assert domain is not None - # Create a fake coordinate coefficient for a domain. - coords_coefficient = ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) - builder.domain_coordinate[domain] = coords_coefficient - builder.set_cell_sizes(domain) - # Collect required coefficients + first_coefficient_fake_coords = False coefficients = extract_coefficients(expression) if has_type(expression, GeometricQuantity) or any(fem.needs_coordinate_mapping(c.ufl_element()) for c in coefficients): + # Create a fake coordinate coefficient for a domain. + coords_coefficient = ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) + builder.domain_coordinate[domain] = coords_coefficient + builder.set_cell_sizes(domain) coefficients = [coords_coefficient] + coefficients + first_coefficient_fake_coords = True builder.set_coefficients(coefficients) # Split mixed coefficients @@ -433,7 +434,7 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Handle kernel interface requirements builder.register_requirements([ir]) # Build kernel tuple - return builder.construct_kernel(return_arg, impero_c, index_names) + return builder.construct_kernel(return_arg, impero_c, index_names, first_coefficient_fake_coords) def lower_integral_type(fiat_cell, integral_type): diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index ee5cafa41d..eb90cf7235 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -17,7 +17,7 @@ # Expression kernel description type -ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', 'tabulations']) +ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', 'first_coefficient_fake_coords', 'tabulations']) def make_builder(*args, **kwargs): @@ -153,12 +153,14 @@ def register_requirements(self, ir): provided by the kernel interface.""" self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) - def construct_kernel(self, return_arg, impero_c, index_names): + def construct_kernel(self, return_arg, impero_c, index_names, first_coefficient_fake_coords): """Constructs an :class:`ExpressionKernel`. :arg return_arg: loopy.GlobalArg for the return value :arg impero_c: gem.ImperoC object that represents the kernel :arg index_names: pre-assigned index names + :arg first_coefficient_fake_coords: If true, the kernel's first + coefficient is a constructed UFL coordinate field :returns: :class:`ExpressionKernel` object """ args = [return_arg] @@ -173,7 +175,8 @@ def construct_kernel(self, return_arg, impero_c, index_names): loopy_kernel = generate_loopy(impero_c, args, self.scalar_type, "expression_kernel", index_names) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, - self.coefficients, self.tabulations) + self.coefficients, first_coefficient_fake_coords, + self.tabulations) class KernelBuilder(KernelBuilderBase): From ac65271496c473f2082311366a6d2ec334081188 Mon Sep 17 00:00:00 2001 From: "Reuben W. Nixon-Hill" Date: Thu, 18 Feb 2021 09:25:01 +0000 Subject: [PATCH 617/809] check free indices after calling fem.compile_ufl (#241) Add asserts that we get the free indices we expect after calling fem.compile_ufl. This is based on the understanding that GEM can optimise away unnecessary free indices early, e.g. when interpolating an expression which has no spatial variation (a ufl Constant). --- tsfc/driver.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index d6db7120d4..a24ba8cf12 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -378,6 +378,9 @@ def compile_expression_dual_evaluation(expression, to_element, *, config["quadrature_rule"] = quad_rule expr, = fem.compile_ufl(expression, **config, point_sum=False) + # In some cases point_set.indices may be dropped from expr, but nothing + # new should now appear + assert set(expr.free_indices) <= set(chain(point_set.indices, *argument_multiindices)) shape_indices = tuple(gem.Index() for _ in expr.shape) basis_indices = point_set.indices ir = gem.Indexed(expr, shape_indices) @@ -396,6 +399,9 @@ def compile_expression_dual_evaluation(expression, to_element, *, config = kernel_cfg.copy() config.update(point_set=point_set) expr, = fem.compile_ufl(expression, **config, point_sum=False) + # In some cases point_set.indices may be dropped from expr, but + # nothing new should now appear + assert set(expr.free_indices) <= set(chain(point_set.indices, *argument_multiindices)) expr = gem.partial_indexed(expr, shape_indices) expr_cache[pts] = expr, point_set weights = collections.defaultdict(list) From 9699d31c3aa31e9b1dc13a5018aef4cd82777d00 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 12 Feb 2021 17:32:44 +0000 Subject: [PATCH 618/809] Translate WithMapping element Just forward to wrapee --- tsfc/finatinterface.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 7b1486d7db..0e0b9e5ae2 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -253,6 +253,11 @@ def convert_hcurlelement(element, **kwargs): return finat.HCurlElement(finat_elem), deps +@convert.register(ufl.WithMapping) +def convert_withmapping(element, **kwargs): + return _create_element(element.wrapee, **kwargs) + + @convert.register(ufl.RestrictedElement) def convert_restrictedelement(element, **kwargs): finat_elem, deps = _create_element(element._element, **kwargs) From 1ef850c603abfc1a2fec15767bfafe1f69ed7b88 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Sat, 24 Apr 2021 16:59:44 +0100 Subject: [PATCH 619/809] fem: CoordinateMapping needs to push restrictions to terminals When preprocessing restricted expressions inside the CoordinateMapping callbacks we need to push restrictions to terminals so the later parts of the pipeline work. Fixes FInAT/FInAT#77. --- tsfc/fem.py | 20 +++++++++++++++----- tsfc/ufl_utils.py | 9 ++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 2411a1dd8e..7dd79f6dc3 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -129,6 +129,17 @@ def __init__(self, mt, interface): self.mt = mt self.interface = interface + def preprocess(self, expr, context): + """Preprocess a UFL expression for translation. + + :arg expr: A UFL expression + :arg context: The translation context. + :returns: A new UFL expression + """ + ifacet = self.interface.integral_type.startswith("interior_facet") + return preprocess_expression(expr, complex_mode=context.complex_mode, + do_apply_restrictions=ifacet) + @property def config(self): config = {name: getattr(self.interface, name) @@ -150,7 +161,7 @@ def jacobian_at(self, point): config = {"point_set": PointSingleton(point)} config.update(self.config) context = PointSetContext(**config) - expr = preprocess_expression(expr, complex_mode=context.complex_mode) + expr = self.preprocess(expr, context) return map_expr_dag(context.translator, expr) def detJ_at(self, point): @@ -159,11 +170,10 @@ def detJ_at(self, point): expr = PositiveRestricted(expr) elif self.mt.restriction == '-': expr = NegativeRestricted(expr) - expr = preprocess_expression(expr) - config = {"point_set": PointSingleton(point)} config.update(self.config) context = PointSetContext(**config) + expr = self.preprocess(expr, context) return map_expr_dag(context.translator, expr) def reference_normals(self): @@ -204,7 +214,7 @@ def physical_edge_lengths(self): config = {"point_set": PointSingleton([1/3, 1/3])} config.update(self.config) context = PointSetContext(**config) - expr = preprocess_expression(expr, complex_mode=context.complex_mode) + expr = self.preprocess(expr, context) return map_expr_dag(context.translator, expr) def physical_points(self, point_set, entity=None): @@ -220,13 +230,13 @@ def physical_points(self, point_set, entity=None): expr = PositiveRestricted(expr) elif self.mt.restriction == '-': expr = NegativeRestricted(expr) - expr = preprocess_expression(expr) config = {"point_set": point_set} config.update(self.config) if entity is not None: config.update({name: getattr(self.interface, name) for name in ["integration_dim", "entity_ids"]}) context = PointSetContext(**config) + expr = self.preprocess(expr, context) mapped = map_expr_dag(context.translator, expr) indices = tuple(gem.Index() for _ in mapped.shape) return gem.ComponentTensor(gem.Indexed(mapped, indices), point_set.indices + indices) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 634b9d64c0..0f571fe40f 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -13,6 +13,7 @@ from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering +from ufl.algorithms.apply_restrictions import apply_restrictions from ufl.algorithms.comparison_checker import do_comparison_check from ufl.algorithms.remove_complex_nodes import remove_complex_nodes from ufl.corealg.map_dag import map_expr_dag @@ -102,9 +103,13 @@ def entity_avg(integrand, measure, argument_multiindices): return integrand, degree, argument_multiindices -def preprocess_expression(expression, complex_mode=False): +def preprocess_expression(expression, complex_mode=False, + do_apply_restrictions=False): """Imitates the compute_form_data processing pipeline. + :arg complex_mode: Are we in complex UFL mode? + :arg do_apply_restrictions: Propogate restrictions to terminals? + Useful, for example, to preprocess non-scalar expressions, which are not and cannot be forms. """ @@ -121,6 +126,8 @@ def preprocess_expression(expression, complex_mode=False): expression = apply_derivatives(expression) if not complex_mode: expression = remove_complex_nodes(expression) + if do_apply_restrictions: + expression = apply_restrictions(expression) return expression From aaca43b739e446fc1426e5cc3a7dcacafb633b1d Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 4 May 2021 09:53:42 +0100 Subject: [PATCH 620/809] fem: Fix CoordinateMapping changes for interpolation No integral_type available in that case, so check that integration dimension matches the cell dimension. --- tsfc/fem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 7dd79f6dc3..63bcb91154 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -136,9 +136,10 @@ def preprocess(self, expr, context): :arg context: The translation context. :returns: A new UFL expression """ - ifacet = self.interface.integral_type.startswith("interior_facet") + is_facet = (self.interface.integration_dim != + self.interface.fiat_cell.get_dimension()) return preprocess_expression(expr, complex_mode=context.complex_mode, - do_apply_restrictions=ifacet) + do_apply_restrictions=is_facet) @property def config(self): From 1fa61b9108ce250ed10fae074aa43dfabbf45747 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 4 May 2021 16:47:08 +0100 Subject: [PATCH 621/809] fem: Really fix CoordinateMapping changes for interpolation Provide a cell integral_type for interpolation. This part needs bigger refactoring if we want interpolation onto faces (say). --- tsfc/driver.py | 3 +++ tsfc/fem.py | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index a24ba8cf12..d5d665cd9f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -350,6 +350,9 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Translate to GEM kernel_cfg = dict(interface=builder, ufl_cell=domain.ufl_cell(), + # FIXME: change if we ever implement + # interpolation on facets. + integral_type="cell", argument_multiindices=argument_multiindices, index_cache={}, scalar_type=parameters["scalar_type"]) diff --git a/tsfc/fem.py b/tsfc/fem.py index 63bcb91154..7dd79f6dc3 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -136,10 +136,9 @@ def preprocess(self, expr, context): :arg context: The translation context. :returns: A new UFL expression """ - is_facet = (self.interface.integration_dim != - self.interface.fiat_cell.get_dimension()) + ifacet = self.interface.integral_type.startswith("interior_facet") return preprocess_expression(expr, complex_mode=context.complex_mode, - do_apply_restrictions=is_facet) + do_apply_restrictions=ifacet) @property def config(self): From 4f282421ba6ed8f8d01c90fc8f7e08106657d3f4 Mon Sep 17 00:00:00 2001 From: "Reuben W. Nixon-Hill" Date: Fri, 14 May 2021 09:34:55 +0100 Subject: [PATCH 622/809] Loopy sprint (#244) * Don't help loopy scheduling with priorities The API for accessing instructions changed and keeping this optimisation has been deemed unnecessary. API Change details: A Program is a collection of LoopKernels now where you specify which kernel's instructions you intend to query: So instead of program.instructions[0] it would be program["kernel_name"].instructions[0] * Save names of kernels that tsfc generates * Fix failing test due to API change * Use make_function to build loopy kernel * translate gem.Inverse to "inverse", not "inv" in loopy * set lang_version when generating loopy kernel * COFFEE kernelbuilder sets name * Testing: set loopy to correct branch. * Testing: Fix egg information of loopy? * Jenkins * Drop package branches. * Update docstring Co-authored-by: Lawrence Mitchell Co-authored-by: Connor Ward Co-authored-by: Sophia Vorderwuelbecke --- tests/test_dual_evaluation.py | 10 +++++----- tsfc/kernel_interface/firedrake.py | 4 ++-- tsfc/kernel_interface/firedrake_loopy.py | 12 ++++++++---- tsfc/loopy.py | 11 +++-------- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/test_dual_evaluation.py b/tests/test_dual_evaluation.py index 8cabba703b..3791e0d7a4 100644 --- a/tests/test_dual_evaluation.py +++ b/tests/test_dual_evaluation.py @@ -11,7 +11,7 @@ def test_ufl_only_simple(): expr = ufl.inner(v, v) W = V to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) assert first_coeff_fake_coords is False @@ -22,7 +22,7 @@ def test_ufl_only_spatialcoordinate(): expr = x*y - y**2 + x W = V to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) assert first_coeff_fake_coords is True @@ -33,7 +33,7 @@ def test_ufl_only_from_contravariant_piola(): expr = ufl.inner(v, v) W = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) assert first_coeff_fake_coords is True @@ -44,7 +44,7 @@ def test_ufl_only_to_contravariant_piola(): expr = ufl.as_vector([v, v]) W = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) assert first_coeff_fake_coords is True @@ -58,4 +58,4 @@ def test_ufl_only_shape_mismatch(): to_element = create_element(W.ufl_element()) assert to_element.value_shape == (2,) with pytest.raises(ValueError): - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, _ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 5c5c3ee590..63ada6ad75 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -27,7 +27,7 @@ def make_builder(*args, **kwargs): class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", - "coefficient_numbers", "__weakref__") + "coefficient_numbers", "name", "__weakref__") """A compiled Kernel object. :kwarg ast: The COFFEE ast for the kernel. @@ -302,7 +302,7 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): name_, rank=shape), qualifiers=["const"])) self.kernel.quadrature_rule = quadrature_rule - + self.kernel.name = name self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) return self.kernel diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index eb90cf7235..1304978bbc 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -17,7 +17,8 @@ # Expression kernel description type -ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', 'first_coefficient_fake_coords', 'tabulations']) +ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', + 'first_coefficient_fake_coords', 'tabulations', 'name']) def make_builder(*args, **kwargs): @@ -27,7 +28,7 @@ def make_builder(*args, **kwargs): class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", - "coefficient_numbers", "__weakref__") + "coefficient_numbers", "name", "__weakref__") """A compiled Kernel object. :kwarg ast: The loopy kernel object. @@ -42,6 +43,7 @@ class Kernel(object): :kwarg quadrature_rule: The finat quadrature rule used to generate this kernel :kwarg tabulations: The runtime tabulations this kernel requires :kwarg needs_cell_sizes: Does the kernel require cell sizes. + :kwarg name: The name of this kernel. """ def __init__(self, ast=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, quadrature_rule=None, @@ -172,11 +174,12 @@ def construct_kernel(self, return_arg, impero_c, index_names, first_coefficient_ for name_, shape in self.tabulations: args.append(lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape)) + name = "expression_kernel" loopy_kernel = generate_loopy(impero_c, args, self.scalar_type, - "expression_kernel", index_names) + name, index_names) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, self.coefficients, first_coefficient_fake_coords, - self.tabulations) + self.tabulations, name) class KernelBuilder(KernelBuilderBase): @@ -301,6 +304,7 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): self.kernel.quadrature_rule = quadrature_rule self.kernel.ast = generate_loopy(impero_c, args, self.scalar_type, name, index_names) + self.kernel.name = name return self.kernel def construct_empty_kernel(self, name): diff --git a/tsfc/loopy.py b/tsfc/loopy.py index 0dfc0b2443..0df1ab8645 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -224,17 +224,12 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name # Create loopy kernel knl = lp.make_function(domains, instructions, data, name=kernel_name, target=lp.CTarget(), - seq_dependencies=True, silenced_warnings=["summing_if_branches_ops"]) + seq_dependencies=True, silenced_warnings=["summing_if_branches_ops"], + lang_version=(2018, 2)) # Prevent loopy interchange by loopy knl = lp.prioritize_loops(knl, ",".join(ctx.index_extent.keys())) - # Help loopy in scheduling by assigning priority to instructions - insn_new = [] - for i, insn in enumerate(knl.instructions): - insn_new.append(insn.copy(priority=len(knl.instructions) - i)) - knl = knl.copy(instructions=insn_new) - return knl @@ -335,7 +330,7 @@ def statement_evaluate(leaf, ctx): idx_reads = ctx.pymbolic_multiindex(expr.children[0].shape) var_reads = ctx.pymbolic_variable(expr.children[0]) reads = (SubArrayRef(idx_reads, p.Subscript(var_reads, idx_reads)),) - rhs = p.Call(p.Variable("inv"), reads) + rhs = p.Call(p.Variable("inverse"), reads) return [lp.CallInstruction(lhs, rhs, within_inames=ctx.active_inames())] elif isinstance(expr, gem.Solve): From e05d05a9664681cd8c455f3189dfb61f6555101c Mon Sep 17 00:00:00 2001 From: Reuben Hill Date: Fri, 21 Aug 2020 17:23:56 +0100 Subject: [PATCH 623/809] Allow GemPointContext setup in compile_ufl Switches which context to create depending on if the context kwargs are for a PointSetContext or GemPointContext. --- tsfc/fem.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 7dd79f6dc3..cf31286dd0 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -694,7 +694,13 @@ def take_singleton(xs): def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): - context = PointSetContext(**kwargs) + + # kwargs must be context kwargs for a PointSetContext or GemPointContext + point_expr = kwargs.get("point_expr") + if point_expr: + context = GemPointContext(**kwargs) + else: + context = PointSetContext(**kwargs) # Abs-simplification expression = simplify_abs(expression, context.complex_mode) From 50105b859698f0989cb5c099d8bb3f0c6e95a538 Mon Sep 17 00:00:00 2001 From: Reuben Hill Date: Fri, 21 Aug 2020 17:25:55 +0100 Subject: [PATCH 624/809] Allow runtime point set FInAT QuadratureElement interpolation Runtime tabulation gem.Variables (here used to create the FInAT QuadratureElement with FInAT UnknownPointSet in its quadrature rule) should be named with the prefix "rt_" to trigger their runtime tabulation. --- tsfc/driver.py | 85 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index d5d665cd9f..0f8768b0f7 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -6,7 +6,7 @@ from functools import reduce from itertools import chain -from numpy import asarray, allclose +from numpy import asarray, allclose, isnan import ufl from ufl.algorithms import extract_arguments, extract_coefficients @@ -22,8 +22,9 @@ from FIAT.reference_element import TensorProductCell from FIAT.functional import PointEvaluation -from finat.point_set import PointSet +from finat.point_set import PointSet, UnknownPointSet from finat.quadrature import AbstractQuadratureRule, make_quadrature, QuadratureRule +from finat.quadrature_element import QuadratureElement from tsfc import fem, ufl_utils from tsfc.finatinterface import as_fiat_cell @@ -286,7 +287,8 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Just convert FInAT element to FIAT for now. # Dual evaluation in FInAT will bring a thorough revision. - to_element = to_element.fiat_equivalent + finat_to_element = to_element + to_element = finat_to_element.fiat_equivalent if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()): raise NotImplementedError("Can only interpolate onto dual basis functionals without derivative evaluation, sorry!") @@ -357,28 +359,54 @@ def compile_expression_dual_evaluation(expression, to_element, *, index_cache={}, scalar_type=parameters["scalar_type"]) + # A FInAT QuadratureElement with a runtime tabulated UnknownPointSet + # point set is the target element on the reference cell for dual evaluation + # where the points are specified at runtime. This special casing will not + # be necessary when FInAT dual evaluation is done - the dual evaluation + # method of every FInAT element will create the necessary gem code. + from finat.tensorfiniteelement import TensorFiniteElement + runtime_quadrature_rule = ( + isinstance(finat_to_element, QuadratureElement) or + ( + isinstance(finat_to_element, TensorFiniteElement) and + isinstance(finat_to_element.base_element, QuadratureElement) + ) and + isinstance(finat_to_element._rule.point_set, UnknownPointSet) + ) + if all(isinstance(dual, PointEvaluation) for dual in to_element.dual_basis()): # This is an optimisation for point-evaluation nodes which # should go away once FInAT offers the interface properly - qpoints = [] - # Everything is just a point evaluation. - for dual in to_element.dual_basis(): - ptdict = dual.get_point_dict() - qpoint, = ptdict.keys() - (qweight, component), = ptdict[qpoint] - assert allclose(qweight, 1.0) - assert component == () - qpoints.append(qpoint) - point_set = PointSet(qpoints) config = kernel_cfg.copy() - config.update(point_set=point_set) - - # Allow interpolation onto QuadratureElements to refer to the quadrature - # rule they represent - if isinstance(to_element, FIAT.QuadratureElement): - assert allclose(asarray(qpoints), asarray(to_element._points)) - quad_rule = QuadratureRule(point_set, to_element._weights) - config["quadrature_rule"] = quad_rule + if runtime_quadrature_rule: + # Until FInAT dual evaluation is done, FIAT + # QuadratureElements with UnknownPointSet point sets + # advertise NaNs as their points for each node in the dual + # basis. This has to be manually replaced with the real + # UnknownPointSet point set used to create the + # QuadratureElement rule. + point_set = finat_to_element._rule.point_set + # config for fem.GemPointContext + config.update(point_indices=point_set.indices, point_expr=point_set.expression) + else: + qpoints = [] + # Everything is just a point evaluation. + for dual in to_element.dual_basis(): + ptdict = dual.get_point_dict() + qpoint, = ptdict.keys() + (qweight, component), = ptdict[qpoint] + assert allclose(qweight, 1.0) + assert component == () + qpoints.append(qpoint) + point_set = PointSet(qpoints) + config.update(point_set=point_set) + + # Allow interpolation onto QuadratureElements to refer to the quadrature + # rule they represent + if isinstance(to_element, FIAT.QuadratureElement): + assert allclose(asarray(qpoints), asarray(to_element._points)) + quad_rule = QuadratureRule(point_set, to_element._weights) + config["quadrature_rule"] = quad_rule expr, = fem.compile_ufl(expression, **config, point_sum=False) # In some cases point_set.indices may be dropped from expr, but nothing @@ -398,9 +426,20 @@ def compile_expression_dual_evaluation(expression, to_element, *, try: expr, point_set = expr_cache[pts] except KeyError: - point_set = PointSet(pts) config = kernel_cfg.copy() - config.update(point_set=point_set) + if runtime_quadrature_rule: + # Until FInAT dual evaluation is done, FIAT + # QuadratureElements with UnknownPointSet point sets + # advertise NaNs as their points for each node in the dual + # basis. This has to be manually replaced with the real + # UnknownPointSet point set used to create the + # QuadratureElement rule. + assert isnan(pts).all() + point_set = finat_to_element._rule.point_set + config.update(point_indices=point_set.indices, point_expr=point_set.expression) + else: + point_set = PointSet(pts) + config.update(point_set=point_set) expr, = fem.compile_ufl(expression, **config, point_sum=False) # In some cases point_set.indices may be dropped from expr, but # nothing new should now appear From d68cefbe125c887feeb11711d5b314b59c2497e6 Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Tue, 11 May 2021 16:14:07 +0100 Subject: [PATCH 625/809] Support change to finat.QuadratureElement API The FInAT QuadratureElement now takes a FIAT reference cell and FInAT AbstractQuadratureRule - the old API is kept via a make_quadrature_element function. --- tsfc/finatinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0e0b9e5ae2..f66cb81b6b 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -115,7 +115,7 @@ def convert_finiteelement(element, **kwargs): if degree is None or scheme is None: raise ValueError("Quadrature scheme and degree must be specified!") - return finat.QuadratureElement(cell, degree, scheme), set() + return finat.make_quadrature_element(cell, degree, scheme), set() lmbda = supported_elements[element.family()] if lmbda is None: if element.cell().cellname() == "quadrilateral": From 9f8cc27de746b14c2b641a5992afc9d557e67fe4 Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Thu, 20 May 2021 15:47:31 +0100 Subject: [PATCH 626/809] compile_ufl api: make context an argument Lift context out of compile_ufl so that it is an argument since it can be either a GemPointContext or PointSetContext. Also add docstring. --- tsfc/driver.py | 14 +++++++++----- tsfc/fem.py | 25 ++++++++++++++----------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 0f8768b0f7..b0e46af689 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -204,8 +204,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co config = kernel_cfg.copy() config.update(quadrature_rule=quad_rule) expressions = fem.compile_ufl(integrand, - interior_facet=interior_facet, - **config) + fem.PointSetContext(**config), + interior_facet=interior_facet) reps = mode.Integrals(expressions, quadrature_multiindex, argument_multiindices, params) for var, rep in zip(return_variables, reps): @@ -386,8 +386,8 @@ def compile_expression_dual_evaluation(expression, to_element, *, # UnknownPointSet point set used to create the # QuadratureElement rule. point_set = finat_to_element._rule.point_set - # config for fem.GemPointContext config.update(point_indices=point_set.indices, point_expr=point_set.expression) + context = fem.GemPointContext(**config) else: qpoints = [] # Everything is just a point evaluation. @@ -408,7 +408,9 @@ def compile_expression_dual_evaluation(expression, to_element, *, quad_rule = QuadratureRule(point_set, to_element._weights) config["quadrature_rule"] = quad_rule - expr, = fem.compile_ufl(expression, **config, point_sum=False) + context = fem.PointSetContext(**config) + + expr, = fem.compile_ufl(expression, context, point_sum=False) # In some cases point_set.indices may be dropped from expr, but nothing # new should now appear assert set(expr.free_indices) <= set(chain(point_set.indices, *argument_multiindices)) @@ -437,10 +439,12 @@ def compile_expression_dual_evaluation(expression, to_element, *, assert isnan(pts).all() point_set = finat_to_element._rule.point_set config.update(point_indices=point_set.indices, point_expr=point_set.expression) + context = fem.GemPointContext(**config) else: point_set = PointSet(pts) config.update(point_set=point_set) - expr, = fem.compile_ufl(expression, **config, point_sum=False) + context = fem.PointSetContext(**config) + expr, = fem.compile_ufl(expression, context, point_sum=False) # In some cases point_set.indices may be dropped from expr, but # nothing new should now appear assert set(expr.free_indices) <= set(chain(point_set.indices, *argument_multiindices)) diff --git a/tsfc/fem.py b/tsfc/fem.py index cf31286dd0..2c5c865c2d 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -340,7 +340,7 @@ def cell_avg(self, o): for name in ["ufl_cell", "index_cache", "scalar_type"]} config.update(quadrature_degree=degree, interface=self.context, argument_multiindices=argument_multiindices) - expr, = compile_ufl(integrand, point_sum=True, **config) + expr, = compile_ufl(integrand, PointSetContext(**config), point_sum=True) return expr def facet_avg(self, o): @@ -357,7 +357,7 @@ def facet_avg(self, o): "integral_type"]} config.update(quadrature_degree=degree, interface=self.context, argument_multiindices=argument_multiindices) - expr, = compile_ufl(integrand, point_sum=True, **config) + expr, = compile_ufl(integrand, PointSetContext(**config), point_sum=True) return expr def modified_terminal(self, o): @@ -517,7 +517,7 @@ def translate_cellvolume(terminal, mt, ctx): config = {name: getattr(ctx, name) for name in ["ufl_cell", "index_cache", "scalar_type"]} config.update(interface=interface, quadrature_degree=degree) - expr, = compile_ufl(integrand, point_sum=True, **config) + expr, = compile_ufl(integrand, PointSetContext(**config), point_sum=True) return expr @@ -531,7 +531,7 @@ def translate_facetarea(terminal, mt, ctx): for name in ["ufl_cell", "integration_dim", "scalar_type", "entity_ids", "index_cache"]} config.update(interface=ctx, quadrature_degree=degree) - expr, = compile_ufl(integrand, point_sum=True, **config) + expr, = compile_ufl(integrand, PointSetContext(**config), point_sum=True) return expr @@ -693,14 +693,17 @@ def take_singleton(xs): return result -def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): +def compile_ufl(expression, context, interior_facet=False, point_sum=False): + """Translate a UFL expression to GEM. - # kwargs must be context kwargs for a PointSetContext or GemPointContext - point_expr = kwargs.get("point_expr") - if point_expr: - context = GemPointContext(**kwargs) - else: - context = PointSetContext(**kwargs) + :arg expression: The UFL expression to compile. + :arg context: translation context - either a :class:`GemPointContext` + or :class:`PointSetContext` + :arg interior_facet: If ``true``, treat expression as an interior + facet integral (default ``False``) + :arg point_sum: If ``true``, return a `gem.IndexSum` of the final + gem expression along the ``context.point_indices`` (if present). + """ # Abs-simplification expression = simplify_abs(expression, context.complex_mode) From 114e30b117360d4f0b5a6f20ea21fe33bbd5a499 Mon Sep 17 00:00:00 2001 From: Andrew Whitmell Date: Wed, 19 May 2021 11:01:07 +0100 Subject: [PATCH 627/809] Add flop counting visitor Count the approximate number of flops required for a GEM expression by visiting the scheduled Impero loop tree. Record these flops in the kernels returned by TSFC so that we can inspect them later. --- tests/test_flop_count.py | 64 ++++++++++++++++++++++++ tests/test_sum_factorisation.py | 5 +- tsfc/driver.py | 4 +- tsfc/kernel_interface/firedrake.py | 13 +++-- tsfc/kernel_interface/firedrake_loopy.py | 13 +++-- 5 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 tests/test_flop_count.py diff --git a/tests/test_flop_count.py b/tests/test_flop_count.py new file mode 100644 index 0000000000..1925fbaf28 --- /dev/null +++ b/tests/test_flop_count.py @@ -0,0 +1,64 @@ +import pytest +import gem.gem as gem +from gem.flop_count import count_flops +from gem.impero_utils import preprocess_gem +from gem.impero_utils import compile_gem + + +def test_count_flops(expression): + expr, expected = expression + flops = count_flops(expr) + assert flops == expected + + +@pytest.fixture(params=("expr1", "expr2", "expr3", "expr4")) +def expression(request): + if request.param == "expr1": + expr = gem.Sum(gem.Product(gem.Variable("a", ()), gem.Literal(2)), + gem.Division(gem.Literal(3), gem.Variable("b", ()))) + C = gem.Variable("C", (1,)) + i, = gem.indices(1) + Ci = C[i] + expr, = preprocess_gem([expr]) + assignments = [(Ci, expr)] + expr = compile_gem(assignments, (i,)) + # C += a*2 + 3/b + expected = 1 + 3 + elif request.param == "expr2": + expr = gem.Comparison(">=", gem.MaxValue(gem.Literal(1), gem.Literal(2)), + gem.MinValue(gem.Literal(3), gem.Literal(1))) + C = gem.Variable("C", (1,)) + i, = gem.indices(1) + Ci = C[i] + expr, = preprocess_gem([expr]) + assignments = [(Ci, expr)] + expr = compile_gem(assignments, (i,)) + # C += max(1, 2) >= min(3, 1) + expected = 1 + 3 + elif request.param == "expr3": + expr = gem.Solve(gem.Identity(3), gem.Inverse(gem.Identity(3))) + C = gem.Variable("C", (3, 3)) + i, j = gem.indices(2) + Cij = C[i, j] + expr, = preprocess_gem([expr[i, j]]) + assignments = [(Cij, expr)] + expr = compile_gem(assignments, (i, j)) + # C += solve(Id(3x3), Id(3x3)^{-1}) + expected = 9 + 18 + 54 + 54 + elif request.param == "expr4": + A = gem.Variable("A", (10, 15)) + B = gem.Variable("B", (8, 10)) + i, j, k = gem.indices(3) + Aij = A[i, j] + Bki = B[k, i] + Cjk = gem.IndexSum(Aij * Bki, (i,)) + expr = Cjk + expr, = preprocess_gem([expr]) + assignments = [(gem.Variable("C", (15, 8))[j, k], expr)] + expr = compile_gem(assignments, (j, k)) + # Cjk += \sum_i Aij * Bki + expected = 2 * 10 * 8 * 15 + + else: + raise ValueError("Unexpected expression") + return expr, expected diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index 9e31b12501..cb6a992e0b 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -1,8 +1,6 @@ import numpy import pytest -from coffee.visitors import EstimateFlops - from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, TestFunction, TrialFunction, TensorProductCell, EnrichedElement, HCurlElement, HDivElement, @@ -68,7 +66,8 @@ def split_vector_laplace(cell, degree): def count_flops(form): kernel, = compile_form(form, parameters=dict(mode='spectral')) - return EstimateFlops().visit(kernel.ast) + flops = kernel.flop_count + return flops @pytest.mark.parametrize(('cell', 'order'), diff --git a/tsfc/driver.py b/tsfc/driver.py index b0e46af689..a5928b700e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -17,6 +17,7 @@ import gem import gem.impero_utils as impero_utils +from gem.flop_count import count_flops import FIAT from FIAT.reference_element import TensorProductCell @@ -240,6 +241,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co index_ordering = tuple(quadrature_indices) + split_argument_indices try: impero_c = impero_utils.compile_gem(assignments, index_ordering, remove_zeros=True) + flop_count = count_flops(impero_c) except impero_utils.NoopError: # No operations, construct empty kernel return builder.construct_empty_kernel(kernel_name) @@ -265,7 +267,7 @@ def name_multiindex(multiindex, name): for multiindex, name in zip(argument_multiindices, ['j', 'k']): name_multiindex(multiindex, name) - return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) + return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule, flop_count=flop_count) def compile_expression_dual_evaluation(expression, to_element, *, diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 63ada6ad75..df27466cd3 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -27,7 +27,8 @@ def make_builder(*args, **kwargs): class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", - "coefficient_numbers", "name", "__weakref__") + "coefficient_numbers", "name", "__weakref__", + "flop_count") """A compiled Kernel object. :kwarg ast: The COFFEE ast for the kernel. @@ -42,11 +43,13 @@ class Kernel(object): :kwarg quadrature_rule: The finat quadrature rule used to generate this kernel :kwarg tabulations: The runtime tabulations this kernel requires :kwarg needs_cell_sizes: Does the kernel require cell sizes. + :kwarg flop_count: Estimated total flops for this kernel. """ def __init__(self, ast=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, quadrature_rule=None, coefficient_numbers=(), - needs_cell_sizes=False): + needs_cell_sizes=False, + flop_count=0): # Defaults self.ast = ast self.integral_type = integral_type @@ -55,6 +58,7 @@ def __init__(self, ast=None, integral_type=None, oriented=False, self.subdomain_id = subdomain_id self.coefficient_numbers = coefficient_numbers self.needs_cell_sizes = needs_cell_sizes + self.flop_count = flop_count super(Kernel, self).__init__() @@ -267,7 +271,8 @@ def register_requirements(self, ir): knl = self.kernel knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) - def construct_kernel(self, name, impero_c, index_names, quadrature_rule): + def construct_kernel(self, name, impero_c, index_names, quadrature_rule, + flop_count=0): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -277,6 +282,7 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule + :arg flop_count: Estimated total flops for this kernel. :returns: :class:`Kernel` object """ @@ -304,6 +310,7 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): self.kernel.quadrature_rule = quadrature_rule self.kernel.name = name self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) + self.kernel.flop_count = flop_count return self.kernel def construct_empty_kernel(self, name): diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 1304978bbc..3c3e63c10b 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -28,7 +28,8 @@ def make_builder(*args, **kwargs): class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", - "coefficient_numbers", "name", "__weakref__") + "coefficient_numbers", "name", "flop_count", + "__weakref__") """A compiled Kernel object. :kwarg ast: The loopy kernel object. @@ -44,11 +45,13 @@ class Kernel(object): :kwarg tabulations: The runtime tabulations this kernel requires :kwarg needs_cell_sizes: Does the kernel require cell sizes. :kwarg name: The name of this kernel. + :kwarg flop_count: Estimated total flops for this kernel. """ def __init__(self, ast=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, quadrature_rule=None, coefficient_numbers=(), - needs_cell_sizes=False): + needs_cell_sizes=False, + flop_count=0): # Defaults self.ast = ast self.integral_type = integral_type @@ -57,6 +60,7 @@ def __init__(self, ast=None, integral_type=None, oriented=False, self.subdomain_id = subdomain_id self.coefficient_numbers = coefficient_numbers self.needs_cell_sizes = needs_cell_sizes + self.flop_count = flop_count super(Kernel, self).__init__() @@ -275,7 +279,8 @@ def register_requirements(self, ir): knl = self.kernel knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) - def construct_kernel(self, name, impero_c, index_names, quadrature_rule): + def construct_kernel(self, name, impero_c, index_names, quadrature_rule, + flop_count=0): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -285,6 +290,7 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule + :arg flop_count: Estimated total flops for this kernel. :returns: :class:`Kernel` object """ @@ -305,6 +311,7 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): self.kernel.quadrature_rule = quadrature_rule self.kernel.ast = generate_loopy(impero_c, args, self.scalar_type, name, index_names) self.kernel.name = name + self.kernel.flop_count = flop_count return self.kernel def construct_empty_kernel(self, name): From e299c959b4ab97237b6456b48d2a656eafd95e22 Mon Sep 17 00:00:00 2001 From: Andrew Whitmell Date: Wed, 23 Jun 2021 14:59:15 +0100 Subject: [PATCH 628/809] Test impero flop counting by comparing to loopy --- tests/test_impero_loopy_flop_counts.py | 66 ++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/test_impero_loopy_flop_counts.py diff --git a/tests/test_impero_loopy_flop_counts.py b/tests/test_impero_loopy_flop_counts.py new file mode 100644 index 0000000000..db393203f1 --- /dev/null +++ b/tests/test_impero_loopy_flop_counts.py @@ -0,0 +1,66 @@ +""" +Tests impero flop counts against loopy. +""" +import pytest +import numpy +import loopy +from tsfc import compile_form +from ufl import (FiniteElement, FunctionSpace, Mesh, TestFunction, + TrialFunction, VectorElement, dx, grad, inner, + interval, triangle, quadrilateral, + TensorProductCell) + + +def count_loopy_flops(kernel): + name = kernel.name + program = kernel.ast + program = program.with_kernel( + program[name].copy( + target=loopy.CTarget(), + silenced_warnings=["insn_count_subgroups_upper_bound", + "get_x_map_guessing_subgroup_size"]) + ) + op_map = loopy.get_op_map(program + .with_entrypoints(kernel.name), + numpy_types=None, + subgroup_size=1) + return op_map.filter_by(name=['add', 'sub', 'mul', 'div', + 'func:abs'], + dtype=[float]).eval_and_sum({}) + + +@pytest.fixture(params=[interval, triangle, quadrilateral, + TensorProductCell(triangle, interval)], + ids=lambda cell: cell.cellname()) +def cell(request): + return request.param + + +@pytest.fixture(params=[{"mode": "vanilla"}, + {"mode": "spectral"}], + ids=["vanilla", "spectral"]) +def parameters(request): + return request.param + + +def test_flop_count(cell, parameters): + mesh = Mesh(VectorElement("P", cell, 1)) + loopy_flops = [] + new_flops = [] + for k in range(1, 5): + V = FunctionSpace(mesh, FiniteElement("P", cell, k)) + u = TrialFunction(V) + v = TestFunction(V) + a = inner(u, v)*dx + inner(grad(u), grad(v))*dx + kernel, = compile_form(a, prefix="form", + parameters=parameters, + coffee=False) + # Record new flops here, and compare asymptotics and + # approximate order of magnitude. + new_flops.append(kernel.flop_count) + loopy_flops.append(count_loopy_flops(kernel)) + + new_flops = numpy.asarray(new_flops) + loopy_flops = numpy.asarray(loopy_flops) + + assert all(new_flops == loopy_flops) From 9ca4cdb1833d159b3cb169a3801712c1f7718c0d Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 9 Jul 2021 11:40:28 +0100 Subject: [PATCH 629/809] Correct shapes for Variables representing Real coefficients --- tsfc/kernel_interface/firedrake.py | 4 ++-- tsfc/kernel_interface/firedrake_loopy.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index df27466cd3..faed280314 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -358,8 +358,8 @@ def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): funarg = coffee.Decl(scalar_type, coffee.Symbol(name), pointers=[("restrict",)], qualifiers=["const"]) - - expression = gem.reshape(gem.Variable(name, (None,)), + value_size = coefficient.ufl_element().value_size() + expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) return funarg, expression diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 3c3e63c10b..1a7bda9cde 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -339,8 +339,9 @@ def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): if coefficient.ufl_element().family() == 'Real': # Constant - funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(coefficient.ufl_element().value_size(),)) - expression = gem.reshape(gem.Variable(name, (None,)), + value_size = coefficient.ufl_element().value_size() + funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(value_size,)) + expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) return funarg, expression From 4e6c2d2341e0d71980d8ced1cdee388dc279a27f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 17 Aug 2021 15:25:29 +0100 Subject: [PATCH 630/809] Refactor prepare_coefficients Need to be able to create separate kernel arguments for cell sizes and generic coefficients. --- tsfc/kernel_interface/firedrake_loopy.py | 26 ++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 1a7bda9cde..f15f164a5c 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -94,9 +94,9 @@ def _coefficient(self, coefficient, name): :arg name: coefficient name :returns: loopy argument for the coefficient """ - funarg, expression = prepare_coefficient(coefficient, name, self.scalar_type, interior_facet=self.interior_facet) + expression, shape = prepare_coefficient(coefficient, name, self.interior_facet) self.coefficient_map[coefficient] = expression - return funarg + return lp.GlobalArg(name, self.scalar_type, shape=(shape,)) def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -116,8 +116,9 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) - self.cell_sizes_arg = funarg + name = "cell_sizes" + expression, shape = prepare_coefficient(f, name, interior_facet=self.interior_facet) + self.cell_sizes_arg = lp.GlobalArg(name, self.scalar_type, shape=(shape,)) self._cell_sizes = expression def create_element(self, element, **kwargs): @@ -323,28 +324,25 @@ def construct_empty_kernel(self, name): return None -def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): +def prepare_coefficient(coefficient, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. :arg coefficient: UFL Coefficient :arg name: unique name to refer to the Coefficient in the kernel :arg interior_facet: interior facet integral? - :returns: (funarg, expression) - funarg - :class:`loopy.GlobalArg` function argument - expression - GEM expression referring to the Coefficient - values + :returns: (expression, shape) + expression - GEM expression referring to the Coefficient values + shape - TODO """ assert isinstance(interior_facet, bool) if coefficient.ufl_element().family() == 'Real': # Constant value_size = coefficient.ufl_element().value_size() - funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(value_size,)) expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) - - return funarg, expression + return expression, value_size finat_element = create_element(coefficient.ufl_element()) @@ -359,8 +357,7 @@ def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): minus = gem.view(varexp, slice(size, 2*size)) expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(size,)) - return funarg, expression + return expression, size def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): @@ -377,7 +374,6 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False expressions - GEM expressions referring to the argument tensor """ - assert isinstance(interior_facet, bool) if len(arguments) == 0: From cdec3fd2a5def13f6f06ff2140fdf62a5fdac15e Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 16 Aug 2021 16:16:34 +0100 Subject: [PATCH 631/809] WIP Track argument metadata --- tsfc/driver.py | 2 +- tsfc/kernel_interface/firedrake_loopy.py | 37 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index a5928b700e..e2dbe7897e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -471,7 +471,7 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Build kernel body return_indices = basis_indices + shape_indices + tuple(chain(*argument_multiindices)) - return_shape = tuple(i.extent for i in return_indices) + return_shape = tuple(i.extent for i in return_indices) # QUERY return_var = gem.Variable('A', return_shape) if coffee: return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index f15f164a5c..60364ac50f 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -1,3 +1,4 @@ +import enum import numpy from collections import namedtuple from itertools import chain, product @@ -25,6 +26,40 @@ def make_builder(*args, **kwargs): return partial(KernelBuilder, *args, **kwargs) +class Intent(enum.IntEnum): + IN = enum.auto() + OUT = enum.auto() + + +# QUERY +# The idea here is that we pass these back as kernel metadata and they can be used to +# generate PyOP2 GlobalKernelArgs. +# We likely need to have multiple classes defined (e.g. ReturnKernelArg, TwoFormKernelArg, ...) +class KernelArg: + """Class encapsulating information about kernel arguments.""" + + intent = NotImplemented + + def __init__(self, loopy_arg): + """Initialise a kernel argument. + + :arg loopy_arg: The corresponding loopy argument. + """ + self.loopy_arg = loopy_arg + + @property + def dtype(self): + return self.loopy_arg.dtype + + @property + def shape(self): + self.loopy_arg.shape + + +class CoefficientKernelArg(KernelArg): + intent = Intent.IN + + class Kernel(object): __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", @@ -54,6 +89,8 @@ def __init__(self, ast=None, integral_type=None, oriented=False, flop_count=0): # Defaults self.ast = ast + # QUERY + # self.arguments = arguments self.integral_type = integral_type self.oriented = oriented self.domain_number = domain_number From 9c89988a8fe8f01f42c6563fe5a908ff6c262b44 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 17 Aug 2021 16:26:58 +0100 Subject: [PATCH 632/809] WIP Tests now passing --- tsfc/driver.py | 3 +- tsfc/kernel_interface/firedrake_loopy.py | 125 +++++++++++++++++------ 2 files changed, 93 insertions(+), 35 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index e2dbe7897e..7be9c07f9b 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -476,7 +476,8 @@ def compile_expression_dual_evaluation(expression, to_element, *, if coffee: return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) else: - return_arg = lp.GlobalArg("A", dtype=parameters["scalar_type"], shape=return_shape) + from tsfc.kernel_interface.firedrake_loopy import LocalTensorKernelArg + return_arg = LocalTensorKernelArg(dtype=parameters["scalar_type"], shape=return_shape) return_expr = gem.Indexed(return_var, return_indices) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 60364ac50f..fbc665c205 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -1,3 +1,4 @@ +import abc import enum import numpy from collections import namedtuple @@ -19,7 +20,7 @@ # Expression kernel description type ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', - 'first_coefficient_fake_coords', 'tabulations', 'name']) + 'first_coefficient_fake_coords', 'tabulations', 'name', 'arguments']) def make_builder(*args, **kwargs): @@ -35,33 +36,86 @@ class Intent(enum.IntEnum): # The idea here is that we pass these back as kernel metadata and they can be used to # generate PyOP2 GlobalKernelArgs. # We likely need to have multiple classes defined (e.g. ReturnKernelArg, TwoFormKernelArg, ...) -class KernelArg: +class KernelArg(abc.ABC): """Class encapsulating information about kernel arguments.""" - intent = NotImplemented + name: str + intent: Intent + dtype: numpy.dtype + shape: tuple - def __init__(self, loopy_arg): - """Initialise a kernel argument. + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - :arg loopy_arg: The corresponding loopy argument. - """ - self.loopy_arg = loopy_arg - @property - def dtype(self): - return self.loopy_arg.dtype +class CoefficientKernelArg(KernelArg): + intent = Intent.IN - @property - def shape(self): - self.loopy_arg.shape + def __init__(self, name, dtype, shape): + self.name = name + self.dtype = dtype + self.shape = shape -class CoefficientKernelArg(KernelArg): +class CellOrientationsKernelArg(KernelArg): + intent = Intent.IN + name = "cell_orientations" + dtype = numpy.int32 + + def __init__(self, shape): + self.shape = shape + + +class CellSizesKernelArg(KernelArg): + + intent = Intent.IN + name = "cell_sizes" + + def __init__(self, dtype, shape): + self.dtype = dtype + self.shape = shape + + +class ExteriorFacetKernelArg(KernelArg): + + intent = Intent.IN + name = "facet" + shape = (1,) + dtype = numpy.uint32 + + +class InteriorFacetKernelArg(KernelArg): + + intent = Intent.IN + name = "facet" + shape = (2,) + dtype = numpy.uint32 + + +class TabulationKernelArg(KernelArg): + + intent = Intent.IN + + def __init__(self, name, dtype, shape): + self.name = name + self.dtype = dtype + self.shape = shape + + +class LocalTensorKernelArg(KernelArg): + + intent = Intent.OUT + name = "A" + + def __init__(self, dtype, shape): + self.dtype = dtype + self.shape = shape class Kernel(object): - __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", "coefficient_numbers", "name", "flop_count", "__weakref__") @@ -82,15 +136,14 @@ class Kernel(object): :kwarg name: The name of this kernel. :kwarg flop_count: Estimated total flops for this kernel. """ - def __init__(self, ast=None, integral_type=None, oriented=False, + def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, quadrature_rule=None, coefficient_numbers=(), needs_cell_sizes=False, flop_count=0): # Defaults self.ast = ast - # QUERY - # self.arguments = arguments + self.arguments = arguments self.integral_type = integral_type self.oriented = oriented self.domain_number = domain_number @@ -121,7 +174,7 @@ def __init__(self, scalar_type, interior_facet=False): shape = (1,) cell_orientations = gem.Variable("cell_orientations", shape) self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) - self.cell_orientations_loopy_arg = lp.GlobalArg("cell_orientations", dtype=numpy.int32, shape=shape) + self.cell_orientations_loopy_arg = CellOrientationsKernelArg(shape) def _coefficient(self, coefficient, name): """Prepare a coefficient. Adds glue code for the coefficient @@ -133,7 +186,7 @@ def _coefficient(self, coefficient, name): """ expression, shape = prepare_coefficient(coefficient, name, self.interior_facet) self.coefficient_map[coefficient] = expression - return lp.GlobalArg(name, self.scalar_type, shape=(shape,)) + return CoefficientKernelArg(name, self.scalar_type, shape) def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -153,9 +206,8 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - name = "cell_sizes" - expression, shape = prepare_coefficient(f, name, interior_facet=self.interior_facet) - self.cell_sizes_arg = lp.GlobalArg(name, self.scalar_type, shape=(shape,)) + expression, shape = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) + self.cell_sizes_arg = CellSizesKernelArg(self.scalar_type, shape) self._cell_sizes = expression def create_element(self, element, **kwargs): @@ -214,14 +266,16 @@ def construct_kernel(self, return_arg, impero_c, index_names, first_coefficient_ args.append(self.cell_sizes_arg) args.extend(self.kernel_args) for name_, shape in self.tabulations: - args.append(lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape)) + args.append(TabulationKernelArg(name_, self.scalar_type, shape)) + + loopy_args = [arg.loopy_arg for arg in args] name = "expression_kernel" - loopy_kernel = generate_loopy(impero_c, args, self.scalar_type, + loopy_kernel = generate_loopy(impero_c, loopy_args, self.scalar_type, name, index_names) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, self.coefficients, first_coefficient_fake_coords, - self.tabulations, name) + self.tabulations, name, args) class KernelBuilder(KernelBuilderBase): @@ -339,15 +393,18 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule, args.append(self.cell_sizes_arg) args.extend(self.coefficient_args) if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: - args.append(lp.GlobalArg("facet", dtype=numpy.uint32, shape=(1,))) + args.append(ExteriorFacetKernelArg()) elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: - args.append(lp.GlobalArg("facet", dtype=numpy.uint32, shape=(2,))) + args.append(InteriorFacetKernelArg()) for name_, shape in self.kernel.tabulations: - args.append(lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape)) + args.append(TabulationKernelArg(name_, self.scalar_type, shape)) + + loopy_args = [arg.loopy_arg for arg in args] + self.kernel.arguments = args self.kernel.quadrature_rule = quadrature_rule - self.kernel.ast = generate_loopy(impero_c, args, self.scalar_type, name, index_names) + self.kernel.ast = generate_loopy(impero_c, loopy_args, self.scalar_type, name, index_names) self.kernel.name = name self.kernel.flop_count = flop_count return self.kernel @@ -394,7 +451,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): minus = gem.view(varexp, slice(size, 2*size)) expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - return expression, size + return expression, (size,) def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): @@ -415,7 +472,7 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False if len(arguments) == 0: # No arguments - funarg = lp.GlobalArg("A", dtype=scalar_type, shape=(1,)) + funarg = LocalTensorKernelArg(scalar_type, (1,)) expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) return funarg, [expression] @@ -449,7 +506,7 @@ def expression(restricted): c_shape = tuple(u_shape) slicez = [[slice(s) for s in u_shape]] - funarg = lp.GlobalArg("A", dtype=scalar_type, shape=c_shape) + funarg = LocalTensorKernelArg(scalar_type, c_shape) varexp = gem.Variable("A", c_shape) expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] return funarg, prune(expressions) From 80a406746191123ee4291a896c86159e59dcdb80 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 17 Aug 2021 16:28:47 +0100 Subject: [PATCH 633/809] Remove old-style class declaration --- tsfc/kernel_interface/firedrake_loopy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index fbc665c205..cc5264e763 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -114,7 +114,7 @@ def __init__(self, dtype, shape): self.shape = shape -class Kernel(object): +class Kernel: __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", "coefficient_numbers", "name", "flop_count", From 3543bd725a5b71af7f6dc5b6c03f32a2c45da686 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 18 Aug 2021 11:52:56 +0100 Subject: [PATCH 634/809] finatinterface: Handle Real space on quads/hexes --- tsfc/finatinterface.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index f66cb81b6b..537b56d2d6 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -117,6 +117,9 @@ def convert_finiteelement(element, **kwargs): return finat.make_quadrature_element(cell, degree, scheme), set() lmbda = supported_elements[element.family()] + if element.family() == "Real" and element.cell().cellname() in {"quadrilateral", "hexahedron"}: + lmbda = None + element = ufl.FiniteElement("DQ", element.cell(), 0) if lmbda is None: if element.cell().cellname() == "quadrilateral": # Handle quadrilateral short names like RTCF and RTCE. From f1915a917e688df16bc1e295b96bf609278eacf9 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 18 Aug 2021 12:21:06 +0100 Subject: [PATCH 635/809] compile_expression: Remove coffee option This doesn't actually work with Firedrake anyway. --- tests/test_dual_evaluation.py | 10 +++++----- tsfc/driver.py | 19 +++++-------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/tests/test_dual_evaluation.py b/tests/test_dual_evaluation.py index 3791e0d7a4..b075c56037 100644 --- a/tests/test_dual_evaluation.py +++ b/tests/test_dual_evaluation.py @@ -11,7 +11,7 @@ def test_ufl_only_simple(): expr = ufl.inner(v, v) W = V to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) assert first_coeff_fake_coords is False @@ -22,7 +22,7 @@ def test_ufl_only_spatialcoordinate(): expr = x*y - y**2 + x W = V to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) assert first_coeff_fake_coords is True @@ -33,7 +33,7 @@ def test_ufl_only_from_contravariant_piola(): expr = ufl.inner(v, v) W = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) assert first_coeff_fake_coords is True @@ -44,7 +44,7 @@ def test_ufl_only_to_contravariant_piola(): expr = ufl.as_vector([v, v]) W = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) assert first_coeff_fake_coords is True @@ -58,4 +58,4 @@ def test_ufl_only_shape_mismatch(): to_element = create_element(W.ufl_element()) assert to_element.value_shape == (2,) with pytest.raises(ValueError): - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element, coffee=False) + ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) diff --git a/tsfc/driver.py b/tsfc/driver.py index a5928b700e..fe76c30295 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -272,7 +272,7 @@ def name_multiindex(multiindex, name): def compile_expression_dual_evaluation(expression, to_element, *, domain=None, interface=None, - parameters=None, coffee=False): + parameters=None): """Compile a UFL expression to be evaluated against a compile-time known reference element's dual basis. Useful for interpolating UFL expressions into e.g. N1curl spaces. @@ -282,9 +282,8 @@ def compile_expression_dual_evaluation(expression, to_element, *, :arg domain: optional UFL domain the expression is defined on (required when expression contains no domain). :arg interface: backend module for the kernel interface :arg parameters: parameters object - :arg coffee: compile coffee kernel instead of loopy kernel + :returns: Loopy-based ExpressionKernel object. """ - import coffee.base as ast import loopy as lp # Just convert FInAT element to FIAT for now. @@ -318,13 +317,8 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Initialise kernel builder if interface is None: - if coffee: - import tsfc.kernel_interface.firedrake as firedrake_interface_coffee - interface = firedrake_interface_coffee.ExpressionKernelBuilder - else: - # Delayed import, loopy is a runtime dependency - import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy - interface = firedrake_interface_loopy.ExpressionKernelBuilder + # Delayed import, loopy is a runtime dependency + from tsfc.kernel_interface.firedrake_loopy import ExpressionKernelBuilder as interface builder = interface(parameters["scalar_type"]) arguments = extract_arguments(expression) @@ -473,10 +467,7 @@ def compile_expression_dual_evaluation(expression, to_element, *, return_indices = basis_indices + shape_indices + tuple(chain(*argument_multiindices)) return_shape = tuple(i.extent for i in return_indices) return_var = gem.Variable('A', return_shape) - if coffee: - return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) - else: - return_arg = lp.GlobalArg("A", dtype=parameters["scalar_type"], shape=return_shape) + return_arg = lp.GlobalArg("A", dtype=parameters["scalar_type"], shape=return_shape) return_expr = gem.Indexed(return_var, return_indices) From 8e88be388ef3bf3c2a03006b99e82172ea7d454c Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 18 Aug 2021 12:22:15 +0100 Subject: [PATCH 636/809] kernels: move responsibility for flop counting to builder Since we pass in the impero_c to construct the kernel, we can just count flops there. --- tsfc/driver.py | 4 +--- tsfc/kernel_interface/firedrake.py | 8 +++----- tsfc/kernel_interface/firedrake_loopy.py | 14 +++++++------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index fe76c30295..46d7582084 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -17,7 +17,6 @@ import gem import gem.impero_utils as impero_utils -from gem.flop_count import count_flops import FIAT from FIAT.reference_element import TensorProductCell @@ -241,7 +240,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co index_ordering = tuple(quadrature_indices) + split_argument_indices try: impero_c = impero_utils.compile_gem(assignments, index_ordering, remove_zeros=True) - flop_count = count_flops(impero_c) except impero_utils.NoopError: # No operations, construct empty kernel return builder.construct_empty_kernel(kernel_name) @@ -267,7 +265,7 @@ def name_multiindex(multiindex, name): for multiindex, name in zip(argument_multiindices, ['j', 'k']): name_multiindex(multiindex, name) - return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule, flop_count=flop_count) + return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) def compile_expression_dual_evaluation(expression, to_element, *, diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index faed280314..320ece67f5 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -8,6 +8,7 @@ import coffee.base as coffee import gem +from gem.flop_count import count_flops from gem.node import traversal from gem.optimise import remove_componenttensors as prune @@ -271,8 +272,7 @@ def register_requirements(self, ir): knl = self.kernel knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) - def construct_kernel(self, name, impero_c, index_names, quadrature_rule, - flop_count=0): + def construct_kernel(self, name, impero_c, index_names, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -282,8 +282,6 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule, :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule - :arg flop_count: Estimated total flops for this kernel. - :returns: :class:`Kernel` object """ body = generate_coffee(impero_c, index_names, self.scalar_type) @@ -310,7 +308,7 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule, self.kernel.quadrature_rule = quadrature_rule self.kernel.name = name self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) - self.kernel.flop_count = flop_count + self.kernel.flop_count = count_flops(impero_c) return self.kernel def construct_empty_kernel(self, name): diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 1a7bda9cde..e9ee0ae077 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -6,6 +6,7 @@ from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import gem +from gem.flop_count import count_flops from gem.optimise import remove_componenttensors as prune import loopy as lp @@ -17,8 +18,9 @@ # Expression kernel description type -ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', - 'first_coefficient_fake_coords', 'tabulations', 'name']) +ExpressionKernel = namedtuple('ExpressionKernel', + ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', + 'first_coefficient_fake_coords', 'tabulations', 'name', 'flop_count']) def make_builder(*args, **kwargs): @@ -183,7 +185,7 @@ def construct_kernel(self, return_arg, impero_c, index_names, first_coefficient_ name, index_names) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, self.coefficients, first_coefficient_fake_coords, - self.tabulations, name) + self.tabulations, name, count_flops(impero_c)) class KernelBuilder(KernelBuilderBase): @@ -279,8 +281,7 @@ def register_requirements(self, ir): knl = self.kernel knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) - def construct_kernel(self, name, impero_c, index_names, quadrature_rule, - flop_count=0): + def construct_kernel(self, name, impero_c, index_names, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -290,7 +291,6 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule, :arg impero_c: ImperoC tuple with Impero AST and other data :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule - :arg flop_count: Estimated total flops for this kernel. :returns: :class:`Kernel` object """ @@ -311,7 +311,7 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule, self.kernel.quadrature_rule = quadrature_rule self.kernel.ast = generate_loopy(impero_c, args, self.scalar_type, name, index_names) self.kernel.name = name - self.kernel.flop_count = flop_count + self.kernel.flop_count = count_flops(impero_c) return self.kernel def construct_empty_kernel(self, name): From 4bfaba48a8c1c71481c4402b81a71b2eb2902672 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 18 Aug 2021 12:30:07 +0100 Subject: [PATCH 637/809] compile_expression: Remove loopy-specific return arg construction Push responsibility for construction of the return argument inside the kernel onto the builder. --- tsfc/driver.py | 7 ++----- tsfc/kernel_interface/firedrake_loopy.py | 9 +++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 46d7582084..c1f9b51093 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -282,8 +282,6 @@ def compile_expression_dual_evaluation(expression, to_element, *, :arg parameters: parameters object :returns: Loopy-based ExpressionKernel object. """ - import loopy as lp - # Just convert FInAT element to FIAT for now. # Dual evaluation in FInAT will bring a thorough revision. finat_to_element = to_element @@ -465,8 +463,6 @@ def compile_expression_dual_evaluation(expression, to_element, *, return_indices = basis_indices + shape_indices + tuple(chain(*argument_multiindices)) return_shape = tuple(i.extent for i in return_indices) return_var = gem.Variable('A', return_shape) - return_arg = lp.GlobalArg("A", dtype=parameters["scalar_type"], shape=return_shape) - return_expr = gem.Indexed(return_var, return_indices) # TODO: one should apply some GEM optimisations as in assembly, @@ -476,8 +472,9 @@ def compile_expression_dual_evaluation(expression, to_element, *, index_names = dict((idx, "p%d" % i) for (i, idx) in enumerate(basis_indices)) # Handle kernel interface requirements builder.register_requirements([ir]) + builder.set_output(return_var) # Build kernel tuple - return builder.construct_kernel(return_arg, impero_c, index_names, first_coefficient_fake_coords) + return builder.construct_kernel(impero_c, index_names, first_coefficient_fake_coords) def lower_integral_type(fiat_cell, integral_type): diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index e9ee0ae077..338bcbe7bc 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -161,7 +161,12 @@ def register_requirements(self, ir): provided by the kernel interface.""" self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) - def construct_kernel(self, return_arg, impero_c, index_names, first_coefficient_fake_coords): + def set_output(self, o): + """Produce the kernel return argument""" + self.return_arg = lp.GlobalArg(o.name, dtype=self.scalar_type, + shape=o.shape) + + def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords): """Constructs an :class:`ExpressionKernel`. :arg return_arg: loopy.GlobalArg for the return value @@ -171,7 +176,7 @@ def construct_kernel(self, return_arg, impero_c, index_names, first_coefficient_ coefficient is a constructed UFL coordinate field :returns: :class:`ExpressionKernel` object """ - args = [return_arg] + args = [self.return_arg] if self.oriented: args.append(self.cell_orientations_loopy_arg) if self.cell_sizes: From 30ef3968705437545cae8650df779a08337dfe06 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 18 Aug 2021 12:19:33 +0100 Subject: [PATCH 638/809] coffee: Remove expression kernel builder The Firedrake code requires a loopy kernel anyway, so cull dead code. --- tsfc/kernel_interface/firedrake.py | 62 ------------------------------ 1 file changed, 62 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 320ece67f5..5eb15d2180 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -1,5 +1,4 @@ import numpy -from collections import namedtuple from itertools import chain, product from functools import partial @@ -17,10 +16,6 @@ from tsfc.coffee import generate as generate_coffee -# Expression kernel description type -ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', 'tabulations']) - - def make_builder(*args, **kwargs): return partial(KernelBuilder, *args, **kwargs) @@ -122,63 +117,6 @@ def create_element(self, element, **kwargs): return create_element(element, **kwargs) -class ExpressionKernelBuilder(KernelBuilderBase): - """Builds expression kernels for UFL interpolation in Firedrake.""" - - def __init__(self, scalar_type): - super(ExpressionKernelBuilder, self).__init__(scalar_type) - self.oriented = False - self.cell_sizes = False - - def set_coefficients(self, coefficients): - """Prepare the coefficients of the expression. - - :arg coefficients: UFL coefficients from Firedrake - """ - self.coefficients = [] # Firedrake coefficients for calling the kernel - self.coefficient_split = {} - self.kernel_args = [] - - for i, coefficient in enumerate(coefficients): - if type(coefficient.ufl_element()) == ufl_MixedElement: - subcoeffs = coefficient.split() # Firedrake-specific - self.coefficients.extend(subcoeffs) - self.coefficient_split[coefficient] = subcoeffs - self.kernel_args += [self._coefficient(subcoeff, "w_%d_%d" % (i, j)) - for j, subcoeff in enumerate(subcoeffs)] - else: - self.coefficients.append(coefficient) - self.kernel_args.append(self._coefficient(coefficient, "w_%d" % (i,))) - - def register_requirements(self, ir): - """Inspect what is referenced by the IR that needs to be - provided by the kernel interface.""" - self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) - - def construct_kernel(self, return_arg, impero_c, index_names): - """Constructs an :class:`ExpressionKernel`. - - :arg return_arg: COFFEE argument for the return value - :arg body: function body (:class:`coffee.Block` node) - :returns: :class:`ExpressionKernel` object - """ - args = [return_arg] - if self.oriented: - args.append(cell_orientations_coffee_arg) - if self.cell_sizes: - args.append(self.cell_sizes_arg) - args.extend(self.kernel_args) - - body = generate_coffee(impero_c, index_names, self.scalar_type) - - for name_, shape in self.tabulations: - args.append(coffee.Decl(self.scalar_type, coffee.Symbol( - name_, rank=shape), qualifiers=["const"])) - - kernel_code = super(ExpressionKernelBuilder, self).construct_kernel("expression_kernel", args, body) - return ExpressionKernel(kernel_code, self.oriented, self.cell_sizes, self.coefficients, self.tabulations) - - class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" From 45bd741004c2b6408c5e6439569c4ce3b29df786 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 18 Aug 2021 13:43:19 +0100 Subject: [PATCH 639/809] Add CoordinatesKernelArg class --- tsfc/kernel_interface/firedrake_loopy.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index cc5264e763..d8def85186 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -49,6 +49,16 @@ def loopy_arg(self): return lp.GlobalArg(self.name, self.dtype, shape=self.shape) +class CoordinatesKernelArg(KernelArg): + + intent = Intent.IN + name = "coords" + + def __init__(self, dtype, shape): + self.dtype = dtype + self.shape = shape + + class CoefficientKernelArg(KernelArg): intent = Intent.IN @@ -328,7 +338,11 @@ def set_coordinates(self, domain): # Create a fake coordinate coefficient for a domain. f = Coefficient(FunctionSpace(domain, domain.ufl_coordinate_element())) self.domain_coordinate[domain] = f - self.coordinates_arg = self._coefficient(f, "coords") + # TODO Copy-pasted from _coefficient - needs refactor + # self.coordinates_arg = self._coefficient(f, "coords") + expression, shape = prepare_coefficient(f, "coords", self.interior_facet) + self.coefficient_map[f] = expression + self.coordinates_arg = CoordinatesKernelArg(self.scalar_type, shape) def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. From 11bc2057762543ab1ff7dfee55fe6a3359d6407b Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 18 Aug 2021 17:36:28 +0100 Subject: [PATCH 640/809] Track unflattened shape information --- tsfc/kernel_interface/firedrake_loopy.py | 124 +++++++++++++++-------- 1 file changed, 79 insertions(+), 45 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index d8def85186..c17db8f5f3 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -32,96 +32,126 @@ class Intent(enum.IntEnum): OUT = enum.auto() -# QUERY -# The idea here is that we pass these back as kernel metadata and they can be used to -# generate PyOP2 GlobalKernelArgs. -# We likely need to have multiple classes defined (e.g. ReturnKernelArg, TwoFormKernelArg, ...) class KernelArg(abc.ABC): """Class encapsulating information about kernel arguments.""" name: str - intent: Intent - dtype: numpy.dtype shape: tuple + dtype: numpy.dtype + intent: Intent + interior_facet: bool + + def __init__(self, name=None, shape=None, dtype=None, + intent=None, interior_facet=None): + if name is not None: + self.name = name + if shape is not None: + self.shape = shape + if dtype is not None: + self.dtype = dtype + if intent is not None: + self.intent = intent + if interior_facet is not None: + self.interior_facet = interior_facet + + @property + def loopy_shape(self): + lp_shape = numpy.prod(self.shape, dtype=int) + return (lp_shape,) if not self.interior_facet else (2*lp_shape,) @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) + return lp.GlobalArg(self.name, self.dtype, shape=self.loopy_shape) class CoordinatesKernelArg(KernelArg): - intent = Intent.IN name = "coords" + intent = Intent.IN - def __init__(self, dtype, shape): - self.dtype = dtype - self.shape = shape + def __init__(self, shape, dtype, interior_facet=False): + super().__init__(shape=shape, dtype=dtype, interior_facet=interior_facet) class CoefficientKernelArg(KernelArg): + intent = Intent.IN - def __init__(self, name, dtype, shape): - self.name = name - self.dtype = dtype - self.shape = shape + def __init__(self, name, shape, dtype, interior_facet=False): + super().__init__(name=name, shape=shape, dtype=dtype, + interior_facet=interior_facet) class CellOrientationsKernelArg(KernelArg): - intent = Intent.IN name = "cell_orientations" + shape = (1,) + intent = Intent.IN dtype = numpy.int32 - def __init__(self, shape): - self.shape = shape + def __init__(self, interior_facet=False): + super().__init__(self, interior_facet=interior_facet) class CellSizesKernelArg(KernelArg): - intent = Intent.IN name = "cell_sizes" + intent = Intent.IN - def __init__(self, dtype, shape): - self.dtype = dtype - self.shape = shape + def __init__(self, shape, dtype, interior_facet=False): + super().__init__(shape=shape, dtype=dtype, interior_facet=interior_facet) -class ExteriorFacetKernelArg(KernelArg): +class FacetKernelArg(KernelArg): - intent = Intent.IN name = "facet" shape = (1,) + intent = Intent.IN dtype = numpy.uint32 + def __init__(self, interior_facet): + super().__init__(interior_facet=interior_facet) -class InteriorFacetKernelArg(KernelArg): - intent = Intent.IN - name = "facet" - shape = (2,) - dtype = numpy.uint32 +def ExteriorFacetKernelArg(): + return FacetKernelArg(interior_facet=False) + + +def InteriorFacetKernelArg(): + return FacetKernelArg(interior_facet=True) class TabulationKernelArg(KernelArg): intent = Intent.IN - def __init__(self, name, dtype, shape): - self.name = name - self.dtype = dtype - self.shape = shape + def __init__(self, name, shape, dtype, interior_facet=False): + super().__init__( + name=name, + shape=shape, + dtype=dtype, + interior_facet=interior_facet + ) class LocalTensorKernelArg(KernelArg): - intent = Intent.OUT name = "A" + intent = Intent.OUT + + def __init__(self, shape, dtype, interior_facet=False): + super().__init__( + shape=shape, + dtype=dtype, + interior_facet=interior_facet + ) + + @property + def loopy_shape(self): + # The outer dimension of self.shape corresponds to the form arguments. + lp_shape = numpy.array([numpy.prod(s, dtype=int) for s in self.shape]) + return tuple(lp_shape) if not self.interior_facet else tuple(2*lp_shape) - def __init__(self, dtype, shape): - self.dtype = dtype - self.shape = shape class Kernel: @@ -184,7 +214,9 @@ def __init__(self, scalar_type, interior_facet=False): shape = (1,) cell_orientations = gem.Variable("cell_orientations", shape) self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) - self.cell_orientations_loopy_arg = CellOrientationsKernelArg(shape) + self.cell_orientations_loopy_arg = CellOrientationsKernelArg( + interior_facet=self.interior_facet + ) def _coefficient(self, coefficient, name): """Prepare a coefficient. Adds glue code for the coefficient @@ -196,7 +228,7 @@ def _coefficient(self, coefficient, name): """ expression, shape = prepare_coefficient(coefficient, name, self.interior_facet) self.coefficient_map[coefficient] = expression - return CoefficientKernelArg(name, self.scalar_type, shape) + return CoefficientKernelArg(name, shape, self.scalar_type, self.interior_facet) def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -217,7 +249,7 @@ def set_cell_sizes(self, domain): # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) expression, shape = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) - self.cell_sizes_arg = CellSizesKernelArg(self.scalar_type, shape) + self.cell_sizes_arg = CellSizesKernelArg(self.scalar_type, shape, self.interior_facet) self._cell_sizes = expression def create_element(self, element, **kwargs): @@ -342,7 +374,9 @@ def set_coordinates(self, domain): # self.coordinates_arg = self._coefficient(f, "coords") expression, shape = prepare_coefficient(f, "coords", self.interior_facet) self.coefficient_map[f] = expression - self.coordinates_arg = CoordinatesKernelArg(self.scalar_type, shape) + self.coordinates_arg = CoordinatesKernelArg( + shape, self.scalar_type, interior_facet=self.interior_facet + ) def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -412,7 +446,7 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule, args.append(InteriorFacetKernelArg()) for name_, shape in self.kernel.tabulations: - args.append(TabulationKernelArg(name_, self.scalar_type, shape)) + args.append(TabulationKernelArg(name_, shape, self.scalar_type)) loopy_args = [arg.loopy_arg for arg in args] self.kernel.arguments = args @@ -465,7 +499,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): minus = gem.view(varexp, slice(size, 2*size)) expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - return expression, (size,) + return expression, shape def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): @@ -486,7 +520,7 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False if len(arguments) == 0: # No arguments - funarg = LocalTensorKernelArg(scalar_type, (1,)) + funarg = LocalTensorKernelArg((1,), scalar_type) expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) return funarg, [expression] @@ -520,7 +554,7 @@ def expression(restricted): c_shape = tuple(u_shape) slicez = [[slice(s) for s in u_shape]] - funarg = LocalTensorKernelArg(scalar_type, c_shape) + funarg = LocalTensorKernelArg(shapes, scalar_type, interior_facet=interior_facet) varexp = gem.Variable("A", c_shape) expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] return funarg, prune(expressions) From 897adba91b8c203ac982bd966e76b50a87ec1677 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 18 Aug 2021 17:43:36 +0100 Subject: [PATCH 641/809] Make {Interior,Exterior}FacetKernelArg into classes again --- tsfc/kernel_interface/firedrake_loopy.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index c17db8f5f3..33fd674540 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -102,23 +102,28 @@ def __init__(self, shape, dtype, interior_facet=False): super().__init__(shape=shape, dtype=dtype, interior_facet=interior_facet) -class FacetKernelArg(KernelArg): +class ExteriorFacetKernelArg(KernelArg): name = "facet" shape = (1,) intent = Intent.IN dtype = numpy.uint32 - def __init__(self, interior_facet): - super().__init__(interior_facet=interior_facet) + @property + def loopy_shape(self): + return self.shape -def ExteriorFacetKernelArg(): - return FacetKernelArg(interior_facet=False) +class InteriorFacetKernelArg(KernelArg): + name = "facet" + shape = (2,) + intent = Intent.IN + dtype = numpy.uint32 -def InteriorFacetKernelArg(): - return FacetKernelArg(interior_facet=True) + @property + def loopy_shape(self): + return self.shape class TabulationKernelArg(KernelArg): From ab408a9c3406c61cc380f293d8c9a7d8572b226c Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 19 Aug 2021 14:24:13 +0100 Subject: [PATCH 642/809] Add ConstantKernelArg --- tsfc/driver.py | 3 +- tsfc/kernel_interface/firedrake_loopy.py | 48 +++++++++++++++++++----- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7be9c07f9b..abf9616fa7 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -477,7 +477,8 @@ def compile_expression_dual_evaluation(expression, to_element, *, return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) else: from tsfc.kernel_interface.firedrake_loopy import LocalTensorKernelArg - return_arg = LocalTensorKernelArg(dtype=parameters["scalar_type"], shape=return_shape) + return_arg = LocalTensorKernelArg(return_shape, len(return_shape), + parameters["scalar_type"]) return_expr = gem.Indexed(return_var, return_indices) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 33fd674540..1091b92268 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -37,16 +37,19 @@ class KernelArg(abc.ABC): name: str shape: tuple + rank: int dtype: numpy.dtype intent: Intent interior_facet: bool - def __init__(self, name=None, shape=None, dtype=None, + def __init__(self, name=None, shape=None, rank=None, dtype=None, intent=None, interior_facet=None): if name is not None: self.name = name if shape is not None: self.shape = shape + if rank is not None: + self.rank = rank if dtype is not None: self.dtype = dtype if intent is not None: @@ -67,14 +70,29 @@ def loopy_arg(self): class CoordinatesKernelArg(KernelArg): name = "coords" + rank = 1 intent = Intent.IN def __init__(self, shape, dtype, interior_facet=False): super().__init__(shape=shape, dtype=dtype, interior_facet=interior_facet) +class ConstantKernelArg(KernelArg): + + rank = 0 + intent = Intent.IN + + def __init__(self, name, shape, dtype): + super().__init__(name=name, shape=shape, dtype=dtype) + + @property + def loopy_shape(self): + return self.shape + + class CoefficientKernelArg(KernelArg): + rank = 1 intent = Intent.IN def __init__(self, name, shape, dtype, interior_facet=False): @@ -85,6 +103,7 @@ def __init__(self, name, shape, dtype, interior_facet=False): class CellOrientationsKernelArg(KernelArg): name = "cell_orientations" + rank = 0 shape = (1,) intent = Intent.IN dtype = numpy.int32 @@ -96,6 +115,7 @@ def __init__(self, interior_facet=False): class CellSizesKernelArg(KernelArg): name = "cell_sizes" + rank = 1 intent = Intent.IN def __init__(self, shape, dtype, interior_facet=False): @@ -106,6 +126,7 @@ class ExteriorFacetKernelArg(KernelArg): name = "facet" shape = (1,) + rank = 0 intent = Intent.IN dtype = numpy.uint32 @@ -118,6 +139,7 @@ class InteriorFacetKernelArg(KernelArg): name = "facet" shape = (2,) + rank = 0 intent = Intent.IN dtype = numpy.uint32 @@ -128,6 +150,7 @@ def loopy_shape(self): class TabulationKernelArg(KernelArg): + rank = 1 intent = Intent.IN def __init__(self, name, shape, dtype, interior_facet=False): @@ -144,9 +167,10 @@ class LocalTensorKernelArg(KernelArg): name = "A" intent = Intent.OUT - def __init__(self, shape, dtype, interior_facet=False): + def __init__(self, shape, rank, dtype, interior_facet=False): super().__init__( shape=shape, + rank=rank, dtype=dtype, interior_facet=interior_facet ) @@ -231,9 +255,12 @@ def _coefficient(self, coefficient, name): :arg name: coefficient name :returns: loopy argument for the coefficient """ - expression, shape = prepare_coefficient(coefficient, name, self.interior_facet) + expression, shape, is_constant = prepare_coefficient(coefficient, name, self.interior_facet) self.coefficient_map[coefficient] = expression - return CoefficientKernelArg(name, shape, self.scalar_type, self.interior_facet) + if is_constant: + return ConstantKernelArg(name, shape, self.scalar_type) + else: + return CoefficientKernelArg(name, shape, self.scalar_type, self.interior_facet) def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -253,7 +280,7 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - expression, shape = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) + expression, shape, _ = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) self.cell_sizes_arg = CellSizesKernelArg(self.scalar_type, shape, self.interior_facet) self._cell_sizes = expression @@ -377,7 +404,7 @@ def set_coordinates(self, domain): self.domain_coordinate[domain] = f # TODO Copy-pasted from _coefficient - needs refactor # self.coordinates_arg = self._coefficient(f, "coords") - expression, shape = prepare_coefficient(f, "coords", self.interior_facet) + expression, shape, _ = prepare_coefficient(f, "coords", self.interior_facet) self.coefficient_map[f] = expression self.coordinates_arg = CoordinatesKernelArg( shape, self.scalar_type, interior_facet=self.interior_facet @@ -471,6 +498,7 @@ def construct_empty_kernel(self, name): return None +# TODO Returning is_constant is nasty. Refactor. def prepare_coefficient(coefficient, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. @@ -489,7 +517,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): value_size = coefficient.ufl_element().value_size() expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) - return expression, value_size + return expression, (value_size,), True finat_element = create_element(coefficient.ufl_element()) @@ -504,7 +532,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): minus = gem.view(varexp, slice(size, 2*size)) expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - return expression, shape + return expression, shape, False def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): @@ -525,7 +553,7 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False if len(arguments) == 0: # No arguments - funarg = LocalTensorKernelArg((1,), scalar_type) + funarg = LocalTensorKernelArg((1,), 0, scalar_type) expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) return funarg, [expression] @@ -559,7 +587,7 @@ def expression(restricted): c_shape = tuple(u_shape) slicez = [[slice(s) for s in u_shape]] - funarg = LocalTensorKernelArg(shapes, scalar_type, interior_facet=interior_facet) + funarg = LocalTensorKernelArg(shapes, len(shapes), scalar_type, interior_facet=interior_facet) varexp = gem.Variable("A", c_shape) expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] return funarg, prune(expressions) From 4e76a30c609abde52e11ff6e62701daebac0187a Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 23 Aug 2021 15:16:07 +0100 Subject: [PATCH 643/809] Fix constructor bug and add safeguard --- tsfc/kernel_interface/firedrake_loopy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 1091b92268..37059fbe0b 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -42,7 +42,7 @@ class KernelArg(abc.ABC): intent: Intent interior_facet: bool - def __init__(self, name=None, shape=None, rank=None, dtype=None, + def __init__(self, *, name=None, shape=None, rank=None, dtype=None, intent=None, interior_facet=None): if name is not None: self.name = name @@ -109,7 +109,7 @@ class CellOrientationsKernelArg(KernelArg): dtype = numpy.int32 def __init__(self, interior_facet=False): - super().__init__(self, interior_facet=interior_facet) + super().__init__(interior_facet=interior_facet) class CellSizesKernelArg(KernelArg): From 7fdf0eebfbf93f482768cb57547ed7ffabc011d2 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 23 Aug 2021 15:46:31 +0100 Subject: [PATCH 644/809] Modify return_shape to work with interpolation --- tsfc/driver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index abf9616fa7..158c2eb915 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -477,7 +477,10 @@ def compile_expression_dual_evaluation(expression, to_element, *, return_arg = ast.Decl(parameters["scalar_type"], ast.Symbol('A', rank=return_shape)) else: from tsfc.kernel_interface.firedrake_loopy import LocalTensorKernelArg - return_arg = LocalTensorKernelArg(return_shape, len(return_shape), + # TODO Explain/understand more + # If arguments has length 1 then we assemble into a matrix, else to a function + assert len(arguments) in {0, 1} + return_arg = LocalTensorKernelArg((return_shape,), len(arguments)+1, parameters["scalar_type"]) return_expr = gem.Indexed(return_var, return_indices) From f7fa93610a5917fbd114790a5f42b973e038dd99 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 24 Aug 2021 16:50:39 +0100 Subject: [PATCH 645/809] Nasty hack for interpolation shapes --- tsfc/driver.py | 2 +- tsfc/kernel_interface/firedrake_loopy.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 158c2eb915..a73f5433cd 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -481,7 +481,7 @@ def compile_expression_dual_evaluation(expression, to_element, *, # If arguments has length 1 then we assemble into a matrix, else to a function assert len(arguments) in {0, 1} return_arg = LocalTensorKernelArg((return_shape,), len(arguments)+1, - parameters["scalar_type"]) + parameters["scalar_type"], shape_fixed=True) return_expr = gem.Indexed(return_var, return_indices) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 37059fbe0b..fca28569bf 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -167,19 +167,23 @@ class LocalTensorKernelArg(KernelArg): name = "A" intent = Intent.OUT - def __init__(self, shape, rank, dtype, interior_facet=False): + def __init__(self, shape, rank, dtype, interior_facet=False, shape_fixed=False): super().__init__( shape=shape, rank=rank, dtype=dtype, interior_facet=interior_facet ) + self.shape_fixed = shape_fixed # debug param because interpolating doesn't work @property def loopy_shape(self): - # The outer dimension of self.shape corresponds to the form arguments. - lp_shape = numpy.array([numpy.prod(s, dtype=int) for s in self.shape]) - return tuple(lp_shape) if not self.interior_facet else tuple(2*lp_shape) + if self.shape_fixed: + return self.shape[0] + else: + # The outer dimension of self.shape corresponds to the form arguments. + lp_shape = numpy.array([numpy.prod(s, dtype=int) for s in self.shape]) + return tuple(lp_shape) if not self.interior_facet else tuple(2*lp_shape) From 2404c96c865be4436103431341cae99aac27ea84 Mon Sep 17 00:00:00 2001 From: Matthew Kan Date: Fri, 5 Jun 2020 12:40:51 +0100 Subject: [PATCH 646/809] c_e_d_e: Move dual evaluation to FInAT A new "UFLtoGEMCallback" representing the function to dual evaluate is added. This takes the processed UFL expression and, when called, performs the necessary translation to gem. Also add some helpful documentation to partial_indexed. --- tsfc/driver.py | 192 ++++++++++++++++++------------------------------- 1 file changed, 70 insertions(+), 122 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index c1f9b51093..1f418c8b34 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -6,25 +6,22 @@ from functools import reduce from itertools import chain -from numpy import asarray, allclose, isnan +from numpy import asarray import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type from ufl.classes import Form, GeometricQuantity +from ufl.differentiation import ReferenceGrad from ufl.log import GREEN from ufl.utils.sequences import max_degree import gem import gem.impero_utils as impero_utils -import FIAT from FIAT.reference_element import TensorProductCell -from FIAT.functional import PointEvaluation -from finat.point_set import PointSet, UnknownPointSet -from finat.quadrature import AbstractQuadratureRule, make_quadrature, QuadratureRule -from finat.quadrature_element import QuadratureElement +from finat.quadrature import AbstractQuadratureRule, make_quadrature from tsfc import fem, ufl_utils from tsfc.finatinterface import as_fiat_cell @@ -282,14 +279,6 @@ def compile_expression_dual_evaluation(expression, to_element, *, :arg parameters: parameters object :returns: Loopy-based ExpressionKernel object. """ - # Just convert FInAT element to FIAT for now. - # Dual evaluation in FInAT will bring a thorough revision. - finat_to_element = to_element - to_element = finat_to_element.fiat_equivalent - - if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()): - raise NotImplementedError("Can only interpolate onto dual basis functionals without derivative evaluation, sorry!") - if parameters is None: parameters = default_parameters() else: @@ -302,7 +291,7 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Find out which mapping to apply try: - mapping, = set(to_element.mapping()) + mapping, = set((to_element.mapping,)) except ValueError: raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") expression = apply_mapping(expression, mapping, domain) @@ -351,113 +340,12 @@ def compile_expression_dual_evaluation(expression, to_element, *, index_cache={}, scalar_type=parameters["scalar_type"]) - # A FInAT QuadratureElement with a runtime tabulated UnknownPointSet - # point set is the target element on the reference cell for dual evaluation - # where the points are specified at runtime. This special casing will not - # be necessary when FInAT dual evaluation is done - the dual evaluation - # method of every FInAT element will create the necessary gem code. - from finat.tensorfiniteelement import TensorFiniteElement - runtime_quadrature_rule = ( - isinstance(finat_to_element, QuadratureElement) or - ( - isinstance(finat_to_element, TensorFiniteElement) and - isinstance(finat_to_element.base_element, QuadratureElement) - ) and - isinstance(finat_to_element._rule.point_set, UnknownPointSet) - ) - - if all(isinstance(dual, PointEvaluation) for dual in to_element.dual_basis()): - # This is an optimisation for point-evaluation nodes which - # should go away once FInAT offers the interface properly - config = kernel_cfg.copy() - if runtime_quadrature_rule: - # Until FInAT dual evaluation is done, FIAT - # QuadratureElements with UnknownPointSet point sets - # advertise NaNs as their points for each node in the dual - # basis. This has to be manually replaced with the real - # UnknownPointSet point set used to create the - # QuadratureElement rule. - point_set = finat_to_element._rule.point_set - config.update(point_indices=point_set.indices, point_expr=point_set.expression) - context = fem.GemPointContext(**config) - else: - qpoints = [] - # Everything is just a point evaluation. - for dual in to_element.dual_basis(): - ptdict = dual.get_point_dict() - qpoint, = ptdict.keys() - (qweight, component), = ptdict[qpoint] - assert allclose(qweight, 1.0) - assert component == () - qpoints.append(qpoint) - point_set = PointSet(qpoints) - config.update(point_set=point_set) - - # Allow interpolation onto QuadratureElements to refer to the quadrature - # rule they represent - if isinstance(to_element, FIAT.QuadratureElement): - assert allclose(asarray(qpoints), asarray(to_element._points)) - quad_rule = QuadratureRule(point_set, to_element._weights) - config["quadrature_rule"] = quad_rule - - context = fem.PointSetContext(**config) - - expr, = fem.compile_ufl(expression, context, point_sum=False) - # In some cases point_set.indices may be dropped from expr, but nothing - # new should now appear - assert set(expr.free_indices) <= set(chain(point_set.indices, *argument_multiindices)) - shape_indices = tuple(gem.Index() for _ in expr.shape) - basis_indices = point_set.indices - ir = gem.Indexed(expr, shape_indices) - else: - # This is general code but is more unrolled than necssary. - dual_expressions = [] # one for each functional - broadcast_shape = len(expression.ufl_shape) - len(to_element.value_shape()) - shape_indices = tuple(gem.Index() for _ in expression.ufl_shape[:broadcast_shape]) - expr_cache = {} # Sharing of evaluation of the expression at points - for dual in to_element.dual_basis(): - pts = tuple(sorted(dual.get_point_dict().keys())) - try: - expr, point_set = expr_cache[pts] - except KeyError: - config = kernel_cfg.copy() - if runtime_quadrature_rule: - # Until FInAT dual evaluation is done, FIAT - # QuadratureElements with UnknownPointSet point sets - # advertise NaNs as their points for each node in the dual - # basis. This has to be manually replaced with the real - # UnknownPointSet point set used to create the - # QuadratureElement rule. - assert isnan(pts).all() - point_set = finat_to_element._rule.point_set - config.update(point_indices=point_set.indices, point_expr=point_set.expression) - context = fem.GemPointContext(**config) - else: - point_set = PointSet(pts) - config.update(point_set=point_set) - context = fem.PointSetContext(**config) - expr, = fem.compile_ufl(expression, context, point_sum=False) - # In some cases point_set.indices may be dropped from expr, but - # nothing new should now appear - assert set(expr.free_indices) <= set(chain(point_set.indices, *argument_multiindices)) - expr = gem.partial_indexed(expr, shape_indices) - expr_cache[pts] = expr, point_set - weights = collections.defaultdict(list) - for p in pts: - for (w, cmp) in dual.get_point_dict()[p]: - weights[cmp].append(w) - qexprs = gem.Zero() - for cmp in sorted(weights): - qweights = gem.Literal(weights[cmp]) - qexpr = gem.Indexed(expr, cmp) - qexpr = gem.index_sum(gem.Indexed(qweights, point_set.indices)*qexpr, - point_set.indices) - qexprs = gem.Sum(qexprs, qexpr) - assert qexprs.shape == () - assert set(qexprs.free_indices) == set(chain(shape_indices, *argument_multiindices)) - dual_expressions.append(qexprs) - basis_indices = (gem.Index(), ) - ir = gem.Indexed(gem.ListTensor(dual_expressions), basis_indices) + # Use dual evaluation method to get gem tensor with (num_nodes, ) shape. + ir_shape = to_element.dual_evaluation(UFLtoGEMCallback(expression, kernel_cfg, complex_mode)) + broadcast_shape = len(expression.ufl_shape) - len(to_element.value_shape) + shape_indices = gem.indices(broadcast_shape) + basis_indices = tuple(gem.Index(extent=ex) for ex in ir_shape.shape) + ir = gem.Indexed(ir_shape, basis_indices) # Build kernel body return_indices = basis_indices + shape_indices + tuple(chain(*argument_multiindices)) @@ -477,6 +365,66 @@ def compile_expression_dual_evaluation(expression, to_element, *, return builder.construct_kernel(impero_c, index_names, first_coefficient_fake_coords) +class UFLtoGEMCallback(object): + """A callable defined given an UFL expression, returns a GEM expression + of the UFL expression evaluated at points. Defined as class to fix + for ReferenceGrad of constants having no topological dimension. + + :arg expression: The UFL expression. + :arg kernel_cfg: Kernel config for fem.compile_ufl. + :arg complex_mode: Determine whether in complex mode. + """ + def __init__(self, expression, kernel_cfg, complex_mode): + # UFL expression to turn into GEM + self.expression = expression + + # Geometric dimension or topological dimension + # For differentiating UFL Zero + # Fix for Zero expressions with no dimension + # TODO: Fix for topolgical dimension different from geometric + try: + self.dimension = expression.ufl_domain().topological_dimension() + except AttributeError: + self.dimension = 0 + + # Kernel config for fem.compile_ufl + self.kernel_cfg = kernel_cfg + + # Determine whether in complex mode + self.complex_mode = complex_mode + + def __call__(self, point_set, derivative=0): + '''Wrapper function for converting UFL `expression` into GEM expression. + + :param point_set: FInAT PointSet + :param derivative: Maximum order of differentiation + ''' + config = self.kernel_cfg.copy() + config.update(point_set=point_set) + + # Using expression directly changes scope + dexpression = self.expression + for _ in range(derivative): + # Hack since UFL derivative of ConstantValue has no dimension + if isinstance(dexpression, ufl.constantvalue.Zero): + shape = dexpression.ufl_shape + free_indices = dexpression.ufl_free_indices + index_dimension = self.expression.ufl_index_dimensions + dexpression = ufl.constantvalue.Zero( + shape + (self.dimension,), free_indices, index_dimension) + else: + dexpression = ReferenceGrad(dexpression) + dexpression = ufl_utils.preprocess_expression(dexpression, complex_mode=self.complex_mode) + # TODO: will need to be fem.GemPointContext in certain cases + gem_expr, = fem.compile_ufl(dexpression, fem.PointSetContext(**config), point_sum=False) + # In some cases point_set.indices may be dropped from expr, but nothing + # new should now appear + argument_multiindices = config["argument_multiindices"] + assert set(gem_expr.free_indices) <= set(chain(point_set.indices, *argument_multiindices)) + + return gem_expr + + def lower_integral_type(fiat_cell, integral_type): """Lower integral type into the dimension of the integration subentity and a list of entity numbers for that dimension. From 26942fda9bd091347ea4ef1b821adb3225fbadbd Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Fri, 18 Jun 2021 11:22:23 +0100 Subject: [PATCH 647/809] c_e_d_e: UFLtoGEMCallback -> DualEvaluationCallable This rename/refactor makes the purpose of DualEvaluationCallable much clearer. Also removes the untested symbolic derivative stuff since Kirby posits that you can do this with a quadrature rule (like for moments). This is not supported by FInAT's dual_evaluation anyway. --- tsfc/driver.py | 112 +++++++++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 1f418c8b34..315403c48e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -12,7 +12,6 @@ from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type from ufl.classes import Form, GeometricQuantity -from ufl.differentiation import ReferenceGrad from ufl.log import GREEN from ufl.utils.sequences import max_degree @@ -21,6 +20,7 @@ from FIAT.reference_element import TensorProductCell +import finat from finat.quadrature import AbstractQuadratureRule, make_quadrature from tsfc import fem, ufl_utils @@ -330,7 +330,7 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Split mixed coefficients expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) - # Translate to GEM + # Create callable for translation to GEM kernel_cfg = dict(interface=builder, ufl_cell=domain.ufl_cell(), # FIXME: change if we ever implement @@ -339,9 +339,10 @@ def compile_expression_dual_evaluation(expression, to_element, *, argument_multiindices=argument_multiindices, index_cache={}, scalar_type=parameters["scalar_type"]) + fn = DualEvaluationCallable(expression, kernel_cfg) - # Use dual evaluation method to get gem tensor with (num_nodes, ) shape. - ir_shape = to_element.dual_evaluation(UFLtoGEMCallback(expression, kernel_cfg, complex_mode)) + # Get gem tensor with (num_nodes, ) shape. + ir_shape = to_element.dual_evaluation(fn) broadcast_shape = len(expression.ufl_shape) - len(to_element.value_shape) shape_indices = gem.indices(broadcast_shape) basis_indices = tuple(gem.Index(extent=ex) for ex in ir_shape.shape) @@ -365,62 +366,65 @@ def compile_expression_dual_evaluation(expression, to_element, *, return builder.construct_kernel(impero_c, index_names, first_coefficient_fake_coords) -class UFLtoGEMCallback(object): - """A callable defined given an UFL expression, returns a GEM expression - of the UFL expression evaluated at points. Defined as class to fix - for ReferenceGrad of constants having no topological dimension. - - :arg expression: The UFL expression. - :arg kernel_cfg: Kernel config for fem.compile_ufl. - :arg complex_mode: Determine whether in complex mode. +class DualEvaluationCallable(object): """ - def __init__(self, expression, kernel_cfg, complex_mode): - # UFL expression to turn into GEM - self.expression = expression + Callable representing a function to dual evaluate. - # Geometric dimension or topological dimension - # For differentiating UFL Zero - # Fix for Zero expressions with no dimension - # TODO: Fix for topolgical dimension different from geometric - try: - self.dimension = expression.ufl_domain().topological_dimension() - except AttributeError: - self.dimension = 0 + When called, this takes in a + :class:`finat.point_set.AbstractPointSet` and returns a GEM + expression for evaluation of the function at those points. + + :param expression: UFL expression for the function to dual evaluate. + :param kernel_cfg: A kernel configuration for creation of a + :class:`GemPointContext` or a :class:`PointSetContext` - # Kernel config for fem.compile_ufl + Not intended for use outside of + :func:`compile_expression_dual_evaluation`. + """ + def __init__(self, expression, kernel_cfg): + self.expression = expression self.kernel_cfg = kernel_cfg + # TODO: Deal with case when expression can be split into subexpressions + # for dual evaluation on FInAT tensor product elements. If so add a + # ``self.factors`` property which can be tested for existence of in + # FInAT's tensor product element dual basis method. + + def __call__(self, ps): + """The function to dual evaluate. + + :param ps: The :class:`finat.point_set.AbstractPointSet` for + evaluating at + :returns: a gem expression representing the evaluation of the + input UFL expression at the given point set ``ps``. + For point set points with some shape ``(*value_shape)`` + (i.e. ``()`` for scalar points ``(x)`` for vector points + ``(x, y)`` for tensor points etc) then the gem expression + has shape ``(*value_shape)`` and free indices corresponding + to the input :class:`finat.point_set.AbstractPointSet`'s + free indices alongside any input UFL expression free + indices. + """ + + if not isinstance(ps, finat.point_set.AbstractPointSet): + raise ValueError("Callable argument not a point set!") + + # Avoid modifying saved kernel config + kernel_cfg = self.kernel_cfg.copy() + + if isinstance(ps.expression, gem.Literal): + # Compile time known points + kernel_cfg.update(point_set=ps) + translation_context = fem.PointSetContext(**kernel_cfg) + else: + # Run time known points + kernel_cfg.update(point_indices=ps.indices, point_expr=ps.expression) + translation_context = fem.GemPointContext(**kernel_cfg) - # Determine whether in complex mode - self.complex_mode = complex_mode - - def __call__(self, point_set, derivative=0): - '''Wrapper function for converting UFL `expression` into GEM expression. - - :param point_set: FInAT PointSet - :param derivative: Maximum order of differentiation - ''' - config = self.kernel_cfg.copy() - config.update(point_set=point_set) - - # Using expression directly changes scope - dexpression = self.expression - for _ in range(derivative): - # Hack since UFL derivative of ConstantValue has no dimension - if isinstance(dexpression, ufl.constantvalue.Zero): - shape = dexpression.ufl_shape - free_indices = dexpression.ufl_free_indices - index_dimension = self.expression.ufl_index_dimensions - dexpression = ufl.constantvalue.Zero( - shape + (self.dimension,), free_indices, index_dimension) - else: - dexpression = ReferenceGrad(dexpression) - dexpression = ufl_utils.preprocess_expression(dexpression, complex_mode=self.complex_mode) - # TODO: will need to be fem.GemPointContext in certain cases - gem_expr, = fem.compile_ufl(dexpression, fem.PointSetContext(**config), point_sum=False) - # In some cases point_set.indices may be dropped from expr, but nothing + gem_expr, = fem.compile_ufl(self.expression, translation_context, point_sum=False) + # In some cases ps.indices may be dropped from expr, but nothing # new should now appear - argument_multiindices = config["argument_multiindices"] - assert set(gem_expr.free_indices) <= set(chain(point_set.indices, *argument_multiindices)) + argument_multiindices = kernel_cfg["argument_multiindices"] + assert set(gem_expr.free_indices) <= set(chain(ps.indices, *argument_multiindices)) return gem_expr From ea56fd1e778bb580ee173f1fcc45e2d0a4fbde96 Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Wed, 14 Jul 2021 11:34:19 +0100 Subject: [PATCH 648/809] c_e_d_e: Remove unnecessary tensor finite element optimisation This fiddling for expression shape not matching element value shape is already taken care of in the FInAT dual_evaluation method of a tensor finite element and would now be incorrect. We also no longer give c_e_d_e a base element in the tensor case. --- tsfc/driver.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 315403c48e..603b6cdd3f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -343,13 +343,11 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Get gem tensor with (num_nodes, ) shape. ir_shape = to_element.dual_evaluation(fn) - broadcast_shape = len(expression.ufl_shape) - len(to_element.value_shape) - shape_indices = gem.indices(broadcast_shape) basis_indices = tuple(gem.Index(extent=ex) for ex in ir_shape.shape) ir = gem.Indexed(ir_shape, basis_indices) # Build kernel body - return_indices = basis_indices + shape_indices + tuple(chain(*argument_multiindices)) + return_indices = basis_indices + tuple(chain(*argument_multiindices)) return_shape = tuple(i.extent for i in return_indices) return_var = gem.Variable('A', return_shape) return_expr = gem.Indexed(return_var, return_indices) From 0ae6bad6db521367cde6b29a58b38d83a0783b38 Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Thu, 22 Jul 2021 16:39:02 +0100 Subject: [PATCH 649/809] c_e_d_e: adjust to new FInAT dual_evaluation API FInAT element.dual_evaluation now returns (gem_expression, basis_indices) --- tsfc/driver.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 603b6cdd3f..15e07c8465 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -341,10 +341,8 @@ def compile_expression_dual_evaluation(expression, to_element, *, scalar_type=parameters["scalar_type"]) fn = DualEvaluationCallable(expression, kernel_cfg) - # Get gem tensor with (num_nodes, ) shape. - ir_shape = to_element.dual_evaluation(fn) - basis_indices = tuple(gem.Index(extent=ex) for ex in ir_shape.shape) - ir = gem.Indexed(ir_shape, basis_indices) + # Get gem.Indexed tensor and basis indices + ir, basis_indices = to_element.dual_evaluation(fn) # Build kernel body return_indices = basis_indices + tuple(chain(*argument_multiindices)) From 17ef560854694fbff80ad08e7435a46358a0b32d Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Tue, 27 Jul 2021 17:08:59 +0100 Subject: [PATCH 650/809] c_e_d_e: Fix translation context dispatch --- tsfc/driver.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 15e07c8465..7199188822 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -407,14 +407,14 @@ def __call__(self, ps): # Avoid modifying saved kernel config kernel_cfg = self.kernel_cfg.copy() - if isinstance(ps.expression, gem.Literal): - # Compile time known points - kernel_cfg.update(point_set=ps) - translation_context = fem.PointSetContext(**kernel_cfg) - else: + if isinstance(ps, finat.point_set.UnknownPointSet): # Run time known points kernel_cfg.update(point_indices=ps.indices, point_expr=ps.expression) translation_context = fem.GemPointContext(**kernel_cfg) + else: + # Compile time known points + kernel_cfg.update(point_set=ps) + translation_context = fem.PointSetContext(**kernel_cfg) gem_expr, = fem.compile_ufl(self.expression, translation_context, point_sum=False) # In some cases ps.indices may be dropped from expr, but nothing From bdf838cb1d81bff53a25e9ad761d87e3a2b55cb3 Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Thu, 19 Aug 2021 16:47:21 +0100 Subject: [PATCH 651/809] c_e_d_e: Remove .factors in DualEvaluationCallable gem.optimise.contraction now takes care of sum factorisation inside FInAT so there is no need to tell FInAT about factors. Also not yet implemented --- tsfc/driver.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 7199188822..ab3b27e51a 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -380,10 +380,6 @@ class DualEvaluationCallable(object): def __init__(self, expression, kernel_cfg): self.expression = expression self.kernel_cfg = kernel_cfg - # TODO: Deal with case when expression can be split into subexpressions - # for dual evaluation on FInAT tensor product elements. If so add a - # ``self.factors`` property which can be tested for existence of in - # FInAT's tensor product element dual basis method. def __call__(self, ps): """The function to dual evaluate. From 839da10037b673d50d9d25d9262ea473e897be9b Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Fri, 20 Aug 2021 16:04:32 +0100 Subject: [PATCH 652/809] c_e_d_e: Rename ir -> evaluation for clarity --- tsfc/driver.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index ab3b27e51a..b9a7b1b10d 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -341,8 +341,9 @@ def compile_expression_dual_evaluation(expression, to_element, *, scalar_type=parameters["scalar_type"]) fn = DualEvaluationCallable(expression, kernel_cfg) - # Get gem.Indexed tensor and basis indices - ir, basis_indices = to_element.dual_evaluation(fn) + # Get the gem expression for dual evaluation and corresponding basis + # indices needed for compilation of the expression + evaluation, basis_indices = to_element.dual_evaluation(fn) # Build kernel body return_indices = basis_indices + tuple(chain(*argument_multiindices)) @@ -352,11 +353,11 @@ def compile_expression_dual_evaluation(expression, to_element, *, # TODO: one should apply some GEM optimisations as in assembly, # but we don't for now. - ir, = impero_utils.preprocess_gem([ir]) - impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices) + evaluation, = impero_utils.preprocess_gem([evaluation]) + impero_c = impero_utils.compile_gem([(return_expr, evaluation)], return_indices) index_names = dict((idx, "p%d" % i) for (i, idx) in enumerate(basis_indices)) # Handle kernel interface requirements - builder.register_requirements([ir]) + builder.register_requirements([evaluation]) builder.set_output(return_var) # Build kernel tuple return builder.construct_kernel(impero_c, index_names, first_coefficient_fake_coords) From 9074dad192a61b4e64d57af77ce0324a4072cfc7 Mon Sep 17 00:00:00 2001 From: Reuben Nixon-Hill Date: Mon, 23 Aug 2021 17:24:53 +0100 Subject: [PATCH 653/809] c_e_d_e: restore QuadratureElement interpolation FInAT has also been updated --- tsfc/driver.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b9a7b1b10d..79d341efc4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -330,7 +330,7 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Split mixed coefficients expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) - # Create callable for translation to GEM + # Set up kernel config for translation of UFL expression to gem kernel_cfg = dict(interface=builder, ufl_cell=domain.ufl_cell(), # FIXME: change if we ever implement @@ -339,6 +339,13 @@ def compile_expression_dual_evaluation(expression, to_element, *, argument_multiindices=argument_multiindices, index_cache={}, scalar_type=parameters["scalar_type"]) + + # Allow interpolation onto QuadratureElements to refer to the quadrature + # rule they represent + if isinstance(to_element, finat.QuadratureElement): + kernel_cfg["quadrature_rule"] = to_element._rule + + # Create callable for translation of UFL expression to gem fn = DualEvaluationCallable(expression, kernel_cfg) # Get the gem expression for dual evaluation and corresponding basis @@ -407,6 +414,8 @@ def __call__(self, ps): if isinstance(ps, finat.point_set.UnknownPointSet): # Run time known points kernel_cfg.update(point_indices=ps.indices, point_expr=ps.expression) + # GemPointContext's aren't allowed to have quadrature rules + kernel_cfg.pop("quadrature_rule", None) translation_context = fem.GemPointContext(**kernel_cfg) else: # Compile time known points From 293c97ea3ba2157e164c48638d42e06738cc8a6c Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 25 Aug 2021 13:10:35 +0100 Subject: [PATCH 654/809] tests: Check that interpolation sum factorises for simple cases On tensor product cells, interpolation of Q/DQ elements should sum factorise to give an operation count that is O(p^{d+1}). Check this works. Additionally check that interpolation of Vector/TensorElement costs only the product of the value_shape more flops than the equivalent scalar element. --- tests/test_interpolation_factorisation.py | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/test_interpolation_factorisation.py diff --git a/tests/test_interpolation_factorisation.py b/tests/test_interpolation_factorisation.py new file mode 100644 index 0000000000..39a67aab22 --- /dev/null +++ b/tests/test_interpolation_factorisation.py @@ -0,0 +1,64 @@ +from functools import partial +import numpy +import pytest + +from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, + TensorElement, Coefficient, + interval, quadrilateral, hexahedron) + +from tsfc.driver import compile_expression_dual_evaluation +from tsfc.finatinterface import create_element + + +@pytest.fixture(params=[interval, quadrilateral, hexahedron], + ids=lambda x: x.cellname()) +def mesh(request): + return Mesh(VectorElement("P", request.param, 1)) + + +@pytest.fixture(params=[FiniteElement, VectorElement, TensorElement], + ids=lambda x: x.__name__) +def element(request, mesh): + if mesh.ufl_cell() == interval: + family = "DP" + else: + family = "DQ" + return partial(request.param, family, mesh.ufl_cell()) + + +def flop_count(mesh, source, target): + Vtarget = FunctionSpace(mesh, target) + Vsource = FunctionSpace(mesh, source) + to_element = create_element(Vtarget.ufl_element()) + expr = Coefficient(Vsource) + kernel = compile_expression_dual_evaluation(expr, to_element) + return kernel.flop_count + + +def test_sum_factorisation(mesh, element): + # Interpolation between sum factorisable elements should cost + # O(p^{d+1}) + degrees = numpy.asarray([2**n - 1 for n in range(2, 9)]) + flops = [] + for lo, hi in zip(degrees - 1, degrees): + flops.append(flop_count(mesh, element(int(lo)), element(int(hi)))) + flops = numpy.asarray(flops) + rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees)) + assert (rates < (mesh.topological_dimension()+1)).all() + + +def test_sum_factorisation_scalar_tensor(mesh, element): + # Interpolation into tensor elements should cost value_shape + # more than the equivalent scalar element. + degree = 2**7 - 1 + source = element(degree - 1) + target = element(degree) + tensor_flops = flop_count(mesh, source, target) + expect = numpy.prod(target.value_shape()) + if isinstance(target, FiniteElement): + scalar_flops = tensor_flops + else: + target = target.sub_elements()[0] + source = source.sub_elements()[0] + scalar_flops = flop_count(mesh, source, target) + assert numpy.allclose(tensor_flops / scalar_flops, expect, rtol=1e-2) From 3b72357e371c968b960b52b1e2f0b822986f25c6 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 21 Sep 2021 09:05:38 +0100 Subject: [PATCH 655/809] Some more firedrake interpolation tests passing This shape stuff is super nasty. The tuple nesting seems quite random and inconsistent between interp and assembly. --- tsfc/kernel_interface/firedrake_loopy.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 658de66b6f..9887ec0b8d 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -168,23 +168,18 @@ class LocalTensorKernelArg(KernelArg): name = "A" intent = Intent.OUT - def __init__(self, shape, rank, dtype, interior_facet=False, shape_fixed=False): + def __init__(self, shape, rank, dtype, interior_facet=False): super().__init__( shape=shape, rank=rank, dtype=dtype, interior_facet=interior_facet ) - self.shape_fixed = shape_fixed # debug param because interpolating doesn't work @property def loopy_shape(self): - if self.shape_fixed: - return self.shape[0] - else: - # The outer dimension of self.shape corresponds to the form arguments. - lp_shape = numpy.array([numpy.prod(s, dtype=int) for s in self.shape]) - return tuple(lp_shape) if not self.interior_facet else tuple(2*lp_shape) + lp_shape = numpy.array([numpy.prod(s, dtype=int) for s in self.shape]) + return tuple(lp_shape) if not self.interior_facet else tuple(2*lp_shape) @@ -330,8 +325,16 @@ def register_requirements(self, ir): def set_output(self, o): """Produce the kernel return argument""" - self.return_arg = LocalTensorKernelArg((o.shape,), 1, # TODO - dtype=self.scalar_type, shape_fixed=True) + # Since dual evaluation always returns scalar values, we know that the length of + # o.shape matches the rank of the tensor. + if len(o.shape) == 1: + self.return_arg = LocalTensorKernelArg((o.shape,), 1, + dtype=self.scalar_type) + else: + assert len(o.shape) == 2 + # TODO clean this up + self.return_arg = LocalTensorKernelArg(((o.shape[0],), (o.shape[1],)), 2, + dtype=self.scalar_type) def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords): """Constructs an :class:`ExpressionKernel`. From 914be9774140f88ead016d05be9cd60d8d9a151f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 21 Sep 2021 13:05:48 +0100 Subject: [PATCH 656/809] Add new KernelArg types for different rank outputs --- tsfc/driver.py | 17 ++- tsfc/kernel_interface/firedrake_loopy.py | 178 ++++++++++++++++------- 2 files changed, 142 insertions(+), 53 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 79d341efc4..4a31963a96 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -352,11 +352,20 @@ def compile_expression_dual_evaluation(expression, to_element, *, # indices needed for compilation of the expression evaluation, basis_indices = to_element.dual_evaluation(fn) + from tsfc.kernel_interface.firedrake_loopy import LocalVectorKernelArg, LocalMatrixKernelArg + # Build kernel body return_indices = basis_indices + tuple(chain(*argument_multiindices)) - return_shape = tuple(i.extent for i in return_indices) - return_var = gem.Variable('A', return_shape) - return_expr = gem.Indexed(return_var, return_indices) + if len(argument_multiindices) == 0: + shape = tuple([idx.extent for idx in basis_indices]) + return_arg = LocalVectorKernelArg(shape, builder.scalar_type) + elif len(argument_multiindices) == 1: + rshape = tuple([idx.extent for idx in basis_indices]) + cshape = tuple([idx.extent for idx in chain(*argument_multiindices)]) + return_arg = LocalMatrixKernelArg(rshape, cshape, builder.scalar_type) + else: + raise AssertionError + return_expr, = return_arg.make_gem_exprs([return_indices]) # TODO: one should apply some GEM optimisations as in assembly, # but we don't for now. @@ -365,7 +374,7 @@ def compile_expression_dual_evaluation(expression, to_element, *, index_names = dict((idx, "p%d" % i) for (i, idx) in enumerate(basis_indices)) # Handle kernel interface requirements builder.register_requirements([evaluation]) - builder.set_output(return_var) + builder.set_output(return_arg) # Build kernel tuple return builder.construct_kernel(impero_c, index_names, first_coefficient_fake_coords) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 9887ec0b8d..852fd11a42 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -168,19 +168,124 @@ class LocalTensorKernelArg(KernelArg): name = "A" intent = Intent.OUT - def __init__(self, shape, rank, dtype, interior_facet=False): - super().__init__( - shape=shape, - rank=rank, - dtype=dtype, - interior_facet=interior_facet +class LocalScalarKernelArg(LocalTensorKernelArg): + + rank = 0 + shape = (1,) + + def __init__(self, dtype): + self.dtype = dtype + + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) + + def make_gem_exprs(self, multiindices): + assert len(multiindices) == 0 + return [gem.Indexed(gem.Variable(self.name, self.shape), (0,))] + + +class LocalVectorKernelArg(LocalTensorKernelArg): + + rank = 1 + + def __init__(self, shape, dtype, interior_facet=False, diagonal=False): + assert type(shape) == tuple + self.shape = shape + self.dtype = dtype + self.interior_facet = interior_facet + self.diagonal = diagonal + + @property + def u_shape(self): + return numpy.array([numpy.prod(self.shape, dtype=int)]) + + @property + def c_shape(self): + if self.interior_facet: + return tuple(2*self.u_shape) + else: + return tuple(self.u_shape) + + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape) + + def make_gem_exprs(self, multiindices): + if self.diagonal: + multiindices = multiindices[:1] + + if self.interior_facet: + slicez = [ + [slice(r*s, (r + 1)*s) for r, s in zip(restrictions, self.u_shape)] + for restrictions in product((0, 1), repeat=self.rank) + ] + else: + slicez = [[slice(s) for s in self.u_shape]] + + var = gem.Variable(self.name, self.c_shape) + exprs = [self._make_expression(gem.view(var, *slices), multiindices) for slices in slicez] + return prune(exprs) + + + # TODO More descriptive name + def _make_expression(self, restricted, multiindices): + return gem.Indexed(gem.reshape(restricted, self.shape), + tuple(chain(*multiindices))) + + + + +class LocalMatrixKernelArg(LocalTensorKernelArg): + + rank = 2 + + def __init__(self, rshape, cshape, dtype, interior_facet=False): + assert type(rshape) == tuple and type(cshape) == tuple + self.rshape = rshape + self.cshape = cshape + self.dtype = dtype + self.interior_facet = interior_facet + + @property + def shape(self): + return self.rshape, self.cshape + + @property + def u_shape(self): + return numpy.array( + [numpy.prod(self.rshape, dtype=int), numpy.prod(self.cshape, dtype=int)] ) @property - def loopy_shape(self): - lp_shape = numpy.array([numpy.prod(s, dtype=int) for s in self.shape]) - return tuple(lp_shape) if not self.interior_facet else tuple(2*lp_shape) + def c_shape(self): + if self.interior_facet: + return tuple(2*self.u_shape) + else: + return tuple(self.u_shape) + + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape) + + def make_gem_exprs(self, multiindices): + if self.interior_facet: + slicez = [ + [slice(r*s, (r + 1)*s) for r, s in zip(restrictions, self.u_shape)] + for restrictions in product((0, 1), repeat=self.rank) + ] + else: + slicez = [[slice(s) for s in self.u_shape]] + var = gem.Variable(self.name, self.c_shape) + exprs = [self._make_expression(gem.view(var, *slices), multiindices) for slices in slicez] + return prune(exprs) + + + # TODO More descriptive name + def _make_expression(self, restricted, multiindices): + return gem.Indexed(gem.reshape(restricted, self.rshape, self.cshape), + tuple(chain(*multiindices))) class Kernel: @@ -323,18 +428,9 @@ def register_requirements(self, ir): provided by the kernel interface.""" self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) - def set_output(self, o): + def set_output(self, kernel_arg): """Produce the kernel return argument""" - # Since dual evaluation always returns scalar values, we know that the length of - # o.shape matches the rank of the tensor. - if len(o.shape) == 1: - self.return_arg = LocalTensorKernelArg((o.shape,), 1, - dtype=self.scalar_type) - else: - assert len(o.shape) == 2 - # TODO clean this up - self.return_arg = LocalTensorKernelArg(((o.shape[0],), (o.shape[1],)), 2, - dtype=self.scalar_type) + self.return_arg = kernel_arg def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords): """Constructs an :class:`ExpressionKernel`. @@ -402,10 +498,11 @@ def set_arguments(self, arguments, multiindices): :arg multiindices: GEM argument multiindices :returns: GEM expression representing the return variable """ - self.local_tensor, expressions = prepare_arguments( - arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet, + kernel_arg = prepare_arguments( + arguments, self.scalar_type, interior_facet=self.interior_facet, diagonal=self.diagonal) - return expressions + self.local_tensor = kernel_arg + return kernel_arg.make_gem_exprs(multiindices) def set_coordinates(self, domain): """Prepare the coordinate field. @@ -546,13 +643,12 @@ def prepare_coefficient(coefficient, name, interior_facet=False): return expression, shape, False -def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): +def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. :arg arguments: UFL Arguments - :arg multiindices: Argument multiindices :arg interior_facet: interior facet integral? :arg diagonal: Are we assembling the diagonal of a rank-2 element tensor? :returns: (funarg, expression) @@ -563,14 +659,9 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False assert isinstance(interior_facet, bool) if len(arguments) == 0: - # No arguments - funarg = LocalTensorKernelArg((1,), 0, scalar_type) - expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) - - return funarg, [expression] + return LocalScalarKernelArg(scalar_type) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - shapes = tuple(element.index_shape for element in elements) if diagonal: if len(arguments) != 2: @@ -581,24 +672,13 @@ def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") elements = (element, ) - shapes = tuple(element.index_shape for element in elements) - multiindices = multiindices[:1] - def expression(restricted): - return gem.Indexed(gem.reshape(restricted, *shapes), - tuple(chain(*multiindices))) - u_shape = numpy.array([numpy.prod(shape, dtype=int) for shape in shapes]) - if interior_facet: - c_shape = tuple(2 * u_shape) - slicez = [[slice(r * s, (r + 1) * s) - for r, s in zip(restrictions, u_shape)] - for restrictions in product((0, 1), repeat=len(arguments))] + if len(arguments) == 1 or diagonal: + element, = elements # elements must contain only one item + return LocalVectorKernelArg(element.index_shape, scalar_type, interior_facet=interior_facet, diagonal=diagonal) + elif len(arguments) == 2: + relem, celem = elements + return LocalMatrixKernelArg(relem.index_shape, celem.index_shape, scalar_type, interior_facet=interior_facet) else: - c_shape = tuple(u_shape) - slicez = [[slice(s) for s in u_shape]] - - funarg = LocalTensorKernelArg(shapes, len(shapes), scalar_type, interior_facet=interior_facet) - varexp = gem.Variable("A", c_shape) - expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] - return funarg, prune(expressions) + raise AssertionError From 8c2b3629711cf66504b890f310c9600eb3ff575d Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 22 Sep 2021 11:00:54 +0100 Subject: [PATCH 657/809] Split basis_shape and node_shape - Firedrake assembly working --- tsfc/driver.py | 15 +++++- tsfc/kernel_interface/firedrake_loopy.py | 67 ++++++++++++++++++++---- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 4a31963a96..28d104bb39 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -357,9 +357,16 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Build kernel body return_indices = basis_indices + tuple(chain(*argument_multiindices)) if len(argument_multiindices) == 0: - shape = tuple([idx.extent for idx in basis_indices]) - return_arg = LocalVectorKernelArg(shape, builder.scalar_type) + if isinstance(to_element, finat.TensorFiniteElement): + assert isinstance(to_element._shape, tuple) + basis_shape = _get_shape_from_indices(basis_indices[:-len(to_element._shape)]) + node_shape = to_element._shape + else: + basis_shape = _get_shape_from_indices(basis_indices) + node_shape = () + return_arg = LocalVectorKernelArg(basis_shape, builder.scalar_type, node_shape=node_shape) elif len(argument_multiindices) == 1: + raise NotImplementedError rshape = tuple([idx.extent for idx in basis_indices]) cshape = tuple([idx.extent for idx in chain(*argument_multiindices)]) return_arg = LocalMatrixKernelArg(rshape, cshape, builder.scalar_type) @@ -506,3 +513,7 @@ def pick_mode(mode): else: raise ValueError("Unknown mode: {}".format(mode)) return m + + +def _get_shape_from_indices(indices): + return tuple([idx.extent for idx in indices]) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 852fd11a42..a3ee6ea30e 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -5,6 +5,7 @@ from itertools import chain, product from functools import partial +import finat from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import gem @@ -189,13 +190,21 @@ class LocalVectorKernelArg(LocalTensorKernelArg): rank = 1 - def __init__(self, shape, dtype, interior_facet=False, diagonal=False): - assert type(shape) == tuple - self.shape = shape + def __init__( + self, basis_shape, dtype, *, node_shape=(), interior_facet=False, diagonal=False + ): + assert type(basis_shape) == tuple + + self.basis_shape = basis_shape self.dtype = dtype + self.node_shape = node_shape self.interior_facet = interior_facet self.diagonal = diagonal + @property + def shape(self): + return self.basis_shape + self.node_shape + @property def u_shape(self): return numpy.array([numpy.prod(self.shape, dtype=int)]) @@ -211,6 +220,7 @@ def c_shape(self): def loopy_arg(self): return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape) + # TODO Function please def make_gem_exprs(self, multiindices): if self.diagonal: multiindices = multiindices[:1] @@ -240,13 +250,24 @@ class LocalMatrixKernelArg(LocalTensorKernelArg): rank = 2 - def __init__(self, rshape, cshape, dtype, interior_facet=False): - assert type(rshape) == tuple and type(cshape) == tuple - self.rshape = rshape - self.cshape = cshape + def __init__(self, rbasis_shape, cbasis_shape, dtype, *, rnode_shape=(), cnode_shape=(), interior_facet=False): + assert type(rbasis_shape) == tuple and type(cbasis_shape) == tuple + + self.rbasis_shape = rbasis_shape + self.cbasis_shape = cbasis_shape self.dtype = dtype + self.rnode_shape = rnode_shape + self.cnode_shape = cnode_shape self.interior_facet = interior_facet + @property + def rshape(self): + return self.rbasis_shape + self.rnode_shape + + @property + def cshape(self): + return self.cbasis_shape + self.cnode_shape + @property def shape(self): return self.rshape, self.cshape @@ -676,9 +697,37 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal if len(arguments) == 1 or diagonal: element, = elements # elements must contain only one item - return LocalVectorKernelArg(element.index_shape, scalar_type, interior_facet=interior_facet, diagonal=diagonal) + if isinstance(element, finat.TensorFiniteElement): + assert type(element._shape) == tuple + basis_shape = element.index_shape[:-len(element._shape)] + node_shape = element._shape + else: + basis_shape = element.index_shape + node_shape = () + return LocalVectorKernelArg(basis_shape, scalar_type, node_shape=node_shape, interior_facet=interior_facet, diagonal=diagonal) elif len(arguments) == 2: + # TODO Refactor! relem, celem = elements - return LocalMatrixKernelArg(relem.index_shape, celem.index_shape, scalar_type, interior_facet=interior_facet) + if isinstance(relem, finat.TensorFiniteElement): + assert type(relem._shape) == tuple + rbasis_shape = relem.index_shape[:-len(relem._shape)] + rnode_shape = relem._shape + else: + rbasis_shape = relem.index_shape + rnode_shape = () + + if isinstance(celem, finat.TensorFiniteElement): + assert type(celem._shape) == tuple + cbasis_shape = celem.index_shape[:-len(celem._shape)] + cnode_shape = celem._shape + else: + cbasis_shape = celem.index_shape + cnode_shape = () + return LocalMatrixKernelArg( + rbasis_shape, cbasis_shape, scalar_type, + rnode_shape=rnode_shape, + cnode_shape=cnode_shape, + interior_facet=interior_facet + ) else: raise AssertionError From f296925fc6695ec103d1d1023674cb15c3aa9068 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 22 Sep 2021 11:51:59 +0100 Subject: [PATCH 658/809] Add tensor element checks - now onto coeffs --- tsfc/driver.py | 35 ++++++++++++++++++++---- tsfc/kernel_interface/firedrake_loopy.py | 1 + 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 28d104bb39..85702dad2f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -307,8 +307,9 @@ def compile_expression_dual_evaluation(expression, to_element, *, builder = interface(parameters["scalar_type"]) arguments = extract_arguments(expression) - argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() - for arg in arguments) + assert len(arguments) in {0, 1} # sanity check - could it ever be more? + argument_elements = tuple(builder.create_element(arg.ufl_element()) for arg in arguments) + argument_multiindices = tuple(elem.get_indices() for elem in argument_elements) # Replace coordinates (if any) unless otherwise specified by kwarg if domain is None: @@ -366,10 +367,32 @@ def compile_expression_dual_evaluation(expression, to_element, *, node_shape = () return_arg = LocalVectorKernelArg(basis_shape, builder.scalar_type, node_shape=node_shape) elif len(argument_multiindices) == 1: - raise NotImplementedError - rshape = tuple([idx.extent for idx in basis_indices]) - cshape = tuple([idx.extent for idx in chain(*argument_multiindices)]) - return_arg = LocalMatrixKernelArg(rshape, cshape, builder.scalar_type) + # rshape first + if isinstance(to_element, finat.TensorFiniteElement): + assert isinstance(to_element._shape, tuple) + rbasis_shape = _get_shape_from_indices(basis_indices[:-len(to_element._shape)]) + rnode_shape = to_element._shape + else: + rbasis_shape = _get_shape_from_indices(basis_indices) + rnode_shape = () + + # now cshape + # both should only have one item in them + arg_elem, = argument_elements + arg_multiindices, = argument_multiindices + if isinstance(arg_elem, finat.TensorFiniteElement): + assert isinstance(arg_elem._shape, tuple) + cbasis_shape = _get_shape_from_indices(arg_multiindices[:-len(arg_elem._shape)]) + cnode_shape = arg_elem._shape + else: + cbasis_shape = _get_shape_from_indices(arg_multiindices) + cnode_shape = () + + return_arg = LocalMatrixKernelArg( + rbasis_shape, cbasis_shape, builder.scalar_type, + rnode_shape=rnode_shape, + cnode_shape=cnode_shape + ) else: raise AssertionError return_expr, = return_arg.make_gem_exprs([return_indices]) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index a3ee6ea30e..61b8fda80c 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -661,6 +661,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): minus = gem.view(varexp, slice(size, 2*size)) expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 + breakpoint() return expression, shape, False From 5b47a7af21dc5881e29c1a53fa498cf4d11aea47 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 22 Sep 2021 13:38:36 +0100 Subject: [PATCH 659/809] Add basis_shape to more objects --- tsfc/kernel_interface/firedrake_loopy.py | 107 +++++++++++++++++------ 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 61b8fda80c..7a62f6105c 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -75,8 +75,15 @@ class CoordinatesKernelArg(KernelArg): rank = 1 intent = Intent.IN - def __init__(self, shape, dtype, interior_facet=False): - super().__init__(shape=shape, dtype=dtype, interior_facet=interior_facet) + def __init__(self, basis_shape, node_shape, dtype, interior_facet=False): + self.basis_shape = basis_shape + self.node_shape = node_shape + self.dtype = dtype + self.interior_facet = interior_facet + + @property + def shape(self): + return self.basis_shape + self.node_shape class ConstantKernelArg(KernelArg): @@ -97,16 +104,40 @@ class CoefficientKernelArg(KernelArg): rank = 1 intent = Intent.IN - def __init__(self, name, shape, dtype, interior_facet=False): - super().__init__(name=name, shape=shape, dtype=dtype, - interior_facet=interior_facet) + def __init__(self, name, basis_shape, dtype, *, node_shape=(), interior_facet=False): + self.name = name + self.basis_shape = basis_shape + self.dtype = dtype + self.node_shape = node_shape + self.interior_facet = interior_facet + + @property + def shape(self): + return self.basis_shape + self.node_shape + + @property + def u_shape(self): + return numpy.array([numpy.prod(self.shape, dtype=int)]) + + @property + def c_shape(self): + if self.interior_facet: + return tuple(2*self.u_shape) + else: + return tuple(self.u_shape) + + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape) class CellOrientationsKernelArg(KernelArg): name = "cell_orientations" - rank = 0 + rank = 1 shape = (1,) + basis_shape = (1,) + node_shape = () intent = Intent.IN dtype = numpy.int32 @@ -120,15 +151,19 @@ class CellSizesKernelArg(KernelArg): rank = 1 intent = Intent.IN - def __init__(self, shape, dtype, interior_facet=False): - super().__init__(shape=shape, dtype=dtype, interior_facet=interior_facet) + def __init__(self, basis_shape, node_shape, dtype, interior_facet=False): + self.basis_shape = basis_shape + self.node_shape = node_shape + super().__init__(dtype=dtype, interior_facet=interior_facet) class ExteriorFacetKernelArg(KernelArg): name = "facet" shape = (1,) - rank = 0 + basis_shape = (1,) + node_shape = () + rank = 1 intent = Intent.IN dtype = numpy.uint32 @@ -141,7 +176,9 @@ class InteriorFacetKernelArg(KernelArg): name = "facet" shape = (2,) - rank = 0 + basis_shape = (2,) # this is a guess + node_shape = () + rank = 1 intent = Intent.IN dtype = numpy.uint32 @@ -381,12 +418,9 @@ def _coefficient(self, coefficient, name): :arg name: coefficient name :returns: loopy argument for the coefficient """ - expression, shape, is_constant = prepare_coefficient(coefficient, name, self.interior_facet) + kernel_arg, expression = prepare_coefficient(coefficient, name, self.scalar_type, self.interior_facet) self.coefficient_map[coefficient] = expression - if is_constant: - return ConstantKernelArg(name, shape, self.scalar_type) - else: - return CoefficientKernelArg(name, shape, self.scalar_type, self.interior_facet) + return kernel_arg def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -406,8 +440,8 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - expression, shape, _ = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) - self.cell_sizes_arg = CellSizesKernelArg(self.scalar_type, shape, self.interior_facet) + kernel_arg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) + self.cell_sizes_arg = kernel_arg self._cell_sizes = expression def create_element(self, element, **kwargs): @@ -535,11 +569,9 @@ def set_coordinates(self, domain): self.domain_coordinate[domain] = f # TODO Copy-pasted from _coefficient - needs refactor # self.coordinates_arg = self._coefficient(f, "coords") - expression, shape, _ = prepare_coefficient(f, "coords", self.interior_facet) + kernel_arg, expression = prepare_coefficient(f, "coords", self.scalar_type, self.interior_facet) self.coefficient_map[f] = expression - self.coordinates_arg = CoordinatesKernelArg( - shape, self.scalar_type, interior_facet=self.interior_facet - ) + self.coordinates_arg = kernel_arg def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -628,7 +660,7 @@ def construct_empty_kernel(self, name): # TODO Returning is_constant is nasty. Refactor. -def prepare_coefficient(coefficient, name, interior_facet=False): +def prepare_coefficient(coefficient, name, dtype, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. @@ -639,14 +671,15 @@ def prepare_coefficient(coefficient, name, interior_facet=False): expression - GEM expression referring to the Coefficient values shape - TODO """ + # TODO Return expression and kernel arg... assert isinstance(interior_facet, bool) if coefficient.ufl_element().family() == 'Real': - # Constant value_size = coefficient.ufl_element().value_size() + kernel_arg = ConstantKernelArg(name, (value_size,), dtype) expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) - return expression, (value_size,), True + return kernel_arg, expression finat_element = create_element(coefficient.ufl_element()) @@ -661,8 +694,30 @@ def prepare_coefficient(coefficient, name, interior_facet=False): minus = gem.view(varexp, slice(size, 2*size)) expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - breakpoint() - return expression, shape, False + + if isinstance(finat_element, finat.TensorFiniteElement): + basis_shape = finat_element.index_shape[:-len(finat_element._shape)] + node_shape = finat_element._shape + else: + basis_shape = finat_element.index_shape + node_shape = () + + # This is truly disgusting, clean up ASAP + if name == "cell_sizes": + kernel_arg = CellSizesKernelArg(dtype, basis_shape, node_shape, interior_facet) + elif name == "coords": + kernel_arg = CoordinatesKernelArg( + basis_shape, node_shape, dtype, interior_facet=interior_facet + ) + else: + kernel_arg = CoefficientKernelArg( + name, + basis_shape, + dtype, + node_shape=node_shape, + interior_facet=interior_facet + ) + return kernel_arg, expression def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=False): From 8a0b4e655b562b11d69b9f8925963a7f11d3a693 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 22 Sep 2021 14:49:39 +0100 Subject: [PATCH 660/809] Add is_output option to kernel args --- tsfc/kernel_interface/firedrake_loopy.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 7a62f6105c..ed43ab7830 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -206,6 +206,7 @@ class LocalTensorKernelArg(KernelArg): name = "A" intent = Intent.OUT + class LocalScalarKernelArg(LocalTensorKernelArg): rank = 0 @@ -216,7 +217,7 @@ def __init__(self, dtype): @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) + return lp.GlobalArg(self.name, self.dtype, shape=self.shape, is_output=True) def make_gem_exprs(self, multiindices): assert len(multiindices) == 0 @@ -255,7 +256,7 @@ def c_shape(self): @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape) + return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape, is_output=True) # TODO Function please def make_gem_exprs(self, multiindices): @@ -324,7 +325,7 @@ def c_shape(self): @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape) + return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape, is_output=True) def make_gem_exprs(self, multiindices): if self.interior_facet: From a8f6f7618ae4da32469bfef14f05d1ef359e3616 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 23 Sep 2021 11:18:32 +0100 Subject: [PATCH 661/809] Add split_shape method as part of refactor --- tsfc/finatinterface.py | 15 +++++++++ tsfc/kernel_interface/firedrake_loopy.py | 42 +++++++----------------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 537b56d2d6..0cb3c0bbe5 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -330,3 +330,18 @@ def create_base_element(ufl_element, **kwargs): if isinstance(finat_element, finat.TensorFiniteElement): finat_element = finat_element.base_element return finat_element + + +def split_shape(finat_element): + """Split a FInAT element's index_shape into its 'basis' and 'node' shapes where the + former describes the number and layout of nodes and the latter describes the local + shape at each node. + """ + # Only tensor elements have node-local shape + if isinstance(finat_element, finat.TensorFiniteElement): + basis_shape = finat_element.index_shape[:-len(finat_element._shape)] + node_shape = finat_element._shape + else: + basis_shape = finat_element.index_shape + node_shape = () + return basis_shape, node_shape diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index ed43ab7830..1e31152f1c 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -14,7 +14,7 @@ import loopy as lp -from tsfc.finatinterface import create_element +from tsfc.finatinterface import create_element, split_shape from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase from tsfc.kernel_interface.firedrake import check_requirements from tsfc.loopy import generate as generate_loopy @@ -229,12 +229,14 @@ class LocalVectorKernelArg(LocalTensorKernelArg): rank = 1 def __init__( - self, basis_shape, dtype, *, node_shape=(), interior_facet=False, diagonal=False + self, basis_shape, dtype, *, name="A", node_shape=(), interior_facet=False, diagonal=False ): assert type(basis_shape) == tuple self.basis_shape = basis_shape self.dtype = dtype + + self.name = name self.node_shape = node_shape self.interior_facet = interior_facet self.diagonal = diagonal @@ -288,12 +290,14 @@ class LocalMatrixKernelArg(LocalTensorKernelArg): rank = 2 - def __init__(self, rbasis_shape, cbasis_shape, dtype, *, rnode_shape=(), cnode_shape=(), interior_facet=False): + def __init__(self, rbasis_shape, cbasis_shape, dtype, *, name="A", rnode_shape=(), cnode_shape=(), interior_facet=False): assert type(rbasis_shape) == tuple and type(cbasis_shape) == tuple self.rbasis_shape = rbasis_shape self.cbasis_shape = cbasis_shape self.dtype = dtype + + self.name = name self.rnode_shape = rnode_shape self.cnode_shape = cnode_shape self.interior_facet = interior_facet @@ -696,12 +700,7 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False): expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - if isinstance(finat_element, finat.TensorFiniteElement): - basis_shape = finat_element.index_shape[:-len(finat_element._shape)] - node_shape = finat_element._shape - else: - basis_shape = finat_element.index_shape - node_shape = () + basis_shape, node_shape = split_shape(finat_element) # This is truly disgusting, clean up ASAP if name == "cell_sizes": @@ -754,32 +753,13 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal if len(arguments) == 1 or diagonal: element, = elements # elements must contain only one item - if isinstance(element, finat.TensorFiniteElement): - assert type(element._shape) == tuple - basis_shape = element.index_shape[:-len(element._shape)] - node_shape = element._shape - else: - basis_shape = element.index_shape - node_shape = () + basis_shape, node_shape = split_shape(element) return LocalVectorKernelArg(basis_shape, scalar_type, node_shape=node_shape, interior_facet=interior_facet, diagonal=diagonal) elif len(arguments) == 2: # TODO Refactor! relem, celem = elements - if isinstance(relem, finat.TensorFiniteElement): - assert type(relem._shape) == tuple - rbasis_shape = relem.index_shape[:-len(relem._shape)] - rnode_shape = relem._shape - else: - rbasis_shape = relem.index_shape - rnode_shape = () - - if isinstance(celem, finat.TensorFiniteElement): - assert type(celem._shape) == tuple - cbasis_shape = celem.index_shape[:-len(celem._shape)] - cnode_shape = celem._shape - else: - cbasis_shape = celem.index_shape - cnode_shape = () + rbasis_shape, rnode_shape = split_shape(relem) + cbasis_shape, cnode_shape = split_shape(celem) return LocalMatrixKernelArg( rbasis_shape, cbasis_shape, scalar_type, rnode_shape=rnode_shape, From 000949b492df278dfa7d5292dc82362f3b9e9a9f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 23 Sep 2021 14:59:36 +0100 Subject: [PATCH 662/809] Handle UFL real family case for argument --- tsfc/driver.py | 24 +++++++++++++++++------- tsfc/kernel_interface/firedrake_loopy.py | 7 +++++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 85702dad2f..cc0abd55d9 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -376,22 +376,32 @@ def compile_expression_dual_evaluation(expression, to_element, *, rbasis_shape = _get_shape_from_indices(basis_indices) rnode_shape = () + breakpoint() # now cshape # both should only have one item in them arg_elem, = argument_elements arg_multiindices, = argument_multiindices - if isinstance(arg_elem, finat.TensorFiniteElement): - assert isinstance(arg_elem._shape, tuple) - cbasis_shape = _get_shape_from_indices(arg_multiindices[:-len(arg_elem._shape)]) - cnode_shape = arg_elem._shape - else: - cbasis_shape = _get_shape_from_indices(arg_multiindices) + if arguments[0].ufl_element().family() == "Real": + cis_real = True + # these are guesses + cbasis_shape = (1,) cnode_shape = () + else: + if isinstance(arg_elem, finat.TensorFiniteElement): + assert isinstance(arg_elem._shape, tuple) + cbasis_shape = _get_shape_from_indices(arg_multiindices[:-len(arg_elem._shape)]) + cnode_shape = arg_elem._shape + else: + cbasis_shape = _get_shape_from_indices(arg_multiindices) + cnode_shape = () + cis_real = False return_arg = LocalMatrixKernelArg( rbasis_shape, cbasis_shape, builder.scalar_type, rnode_shape=rnode_shape, - cnode_shape=cnode_shape + cnode_shape=cnode_shape, + rreal=False, # I dont know how to find the UFL family from to_element, not sure if I need to + creal=cis_real ) else: raise AssertionError diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 1e31152f1c..5d48ecb3b2 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -290,7 +290,7 @@ class LocalMatrixKernelArg(LocalTensorKernelArg): rank = 2 - def __init__(self, rbasis_shape, cbasis_shape, dtype, *, name="A", rnode_shape=(), cnode_shape=(), interior_facet=False): + def __init__(self, rbasis_shape, cbasis_shape, dtype, *, name="A", rnode_shape=(), cnode_shape=(), interior_facet=False, rreal=False, creal=False): assert type(rbasis_shape) == tuple and type(cbasis_shape) == tuple self.rbasis_shape = rbasis_shape @@ -302,6 +302,10 @@ def __init__(self, rbasis_shape, cbasis_shape, dtype, *, name="A", rnode_shape=( self.cnode_shape = cnode_shape self.interior_facet = interior_facet + # hack + self.rreal = rreal + self.creal = creal + @property def rshape(self): return self.rbasis_shape + self.rnode_shape @@ -756,7 +760,6 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal basis_shape, node_shape = split_shape(element) return LocalVectorKernelArg(basis_shape, scalar_type, node_shape=node_shape, interior_facet=interior_facet, diagonal=diagonal) elif len(arguments) == 2: - # TODO Refactor! relem, celem = elements rbasis_shape, rnode_shape = split_shape(relem) cbasis_shape, cnode_shape = split_shape(celem) From 970e64ef8c7aaf2dcf56cc11b14418de3ab999e5 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 30 Sep 2021 18:35:16 +0100 Subject: [PATCH 663/809] Remove breakpoint --- tsfc/driver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index cc0abd55d9..5c281101a5 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -376,7 +376,6 @@ def compile_expression_dual_evaluation(expression, to_element, *, rbasis_shape = _get_shape_from_indices(basis_indices) rnode_shape = () - breakpoint() # now cshape # both should only have one item in them arg_elem, = argument_elements From 986f95cf29bee06d10e4fa8641e4092f907da429 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 6 Oct 2021 13:08:08 +0100 Subject: [PATCH 664/809] Made some updates --- tsfc/driver.py | 65 +------- tsfc/finatinterface.py | 15 -- tsfc/kernel_interface/firedrake_loopy.py | 188 +++++++++++------------ 3 files changed, 102 insertions(+), 166 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 5c281101a5..a1fa251dfb 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -279,6 +279,8 @@ def compile_expression_dual_evaluation(expression, to_element, *, :arg parameters: parameters object :returns: Loopy-based ExpressionKernel object. """ + from tsfc.kernel_interface.firedrake_loopy import DualEvaluationOutputKernelArg + if parameters is None: parameters = default_parameters() else: @@ -307,9 +309,8 @@ def compile_expression_dual_evaluation(expression, to_element, *, builder = interface(parameters["scalar_type"]) arguments = extract_arguments(expression) - assert len(arguments) in {0, 1} # sanity check - could it ever be more? - argument_elements = tuple(builder.create_element(arg.ufl_element()) for arg in arguments) - argument_multiindices = tuple(elem.get_indices() for elem in argument_elements) + argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() + for arg in arguments) # Replace coordinates (if any) unless otherwise specified by kwarg if domain is None: @@ -353,58 +354,12 @@ def compile_expression_dual_evaluation(expression, to_element, *, # indices needed for compilation of the expression evaluation, basis_indices = to_element.dual_evaluation(fn) - from tsfc.kernel_interface.firedrake_loopy import LocalVectorKernelArg, LocalMatrixKernelArg - # Build kernel body return_indices = basis_indices + tuple(chain(*argument_multiindices)) - if len(argument_multiindices) == 0: - if isinstance(to_element, finat.TensorFiniteElement): - assert isinstance(to_element._shape, tuple) - basis_shape = _get_shape_from_indices(basis_indices[:-len(to_element._shape)]) - node_shape = to_element._shape - else: - basis_shape = _get_shape_from_indices(basis_indices) - node_shape = () - return_arg = LocalVectorKernelArg(basis_shape, builder.scalar_type, node_shape=node_shape) - elif len(argument_multiindices) == 1: - # rshape first - if isinstance(to_element, finat.TensorFiniteElement): - assert isinstance(to_element._shape, tuple) - rbasis_shape = _get_shape_from_indices(basis_indices[:-len(to_element._shape)]) - rnode_shape = to_element._shape - else: - rbasis_shape = _get_shape_from_indices(basis_indices) - rnode_shape = () - - # now cshape - # both should only have one item in them - arg_elem, = argument_elements - arg_multiindices, = argument_multiindices - if arguments[0].ufl_element().family() == "Real": - cis_real = True - # these are guesses - cbasis_shape = (1,) - cnode_shape = () - else: - if isinstance(arg_elem, finat.TensorFiniteElement): - assert isinstance(arg_elem._shape, tuple) - cbasis_shape = _get_shape_from_indices(arg_multiindices[:-len(arg_elem._shape)]) - cnode_shape = arg_elem._shape - else: - cbasis_shape = _get_shape_from_indices(arg_multiindices) - cnode_shape = () - cis_real = False - - return_arg = LocalMatrixKernelArg( - rbasis_shape, cbasis_shape, builder.scalar_type, - rnode_shape=rnode_shape, - cnode_shape=cnode_shape, - rreal=False, # I dont know how to find the UFL family from to_element, not sure if I need to - creal=cis_real - ) - else: - raise AssertionError - return_expr, = return_arg.make_gem_exprs([return_indices]) + return_shape = tuple(i.extent for i in return_indices) + return_var = gem.Variable('A', return_shape) + return_arg = DualEvaluationOutputKernelArg(return_shape, builder.scalar_type) + return_expr = gem.Indexed(return_var, return_indices) # TODO: one should apply some GEM optimisations as in assembly, # but we don't for now. @@ -545,7 +500,3 @@ def pick_mode(mode): else: raise ValueError("Unknown mode: {}".format(mode)) return m - - -def _get_shape_from_indices(indices): - return tuple([idx.extent for idx in indices]) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0cb3c0bbe5..537b56d2d6 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -330,18 +330,3 @@ def create_base_element(ufl_element, **kwargs): if isinstance(finat_element, finat.TensorFiniteElement): finat_element = finat_element.base_element return finat_element - - -def split_shape(finat_element): - """Split a FInAT element's index_shape into its 'basis' and 'node' shapes where the - former describes the number and layout of nodes and the latter describes the local - shape at each node. - """ - # Only tensor elements have node-local shape - if isinstance(finat_element, finat.TensorFiniteElement): - basis_shape = finat_element.index_shape[:-len(finat_element._shape)] - node_shape = finat_element._shape - else: - basis_shape = finat_element.index_shape - node_shape = () - return basis_shape, node_shape diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 5d48ecb3b2..3ba10a59de 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -14,7 +14,7 @@ import loopy as lp -from tsfc.finatinterface import create_element, split_shape +from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase from tsfc.kernel_interface.firedrake import check_requirements from tsfc.loopy import generate as generate_loopy @@ -37,36 +37,51 @@ class Intent(enum.IntEnum): class KernelArg(abc.ABC): """Class encapsulating information about kernel arguments.""" - name: str - shape: tuple - rank: int - dtype: numpy.dtype - intent: Intent - interior_facet: bool - - def __init__(self, *, name=None, shape=None, rank=None, dtype=None, - intent=None, interior_facet=None): - if name is not None: - self.name = name - if shape is not None: - self.shape = shape - if rank is not None: - self.rank = rank - if dtype is not None: - self.dtype = dtype - if intent is not None: - self.intent = intent - if interior_facet is not None: - self.interior_facet = interior_facet + # name: str + # shape: tuple + # rank: int + # dtype: numpy.dtype + # intent: Intent + # interior_facet: bool + + # def __init__(self, *, name=None, shape=None, rank=None, dtype=None, + # intent=None, interior_facet=None): + # if name is not None: + # self.name = name + # if shape is not None: + # self.shape = shape + # if rank is not None: + # self.rank = rank + # if dtype is not None: + # self.dtype = dtype + # if intent is not None: + # self.intent = intent + # if interior_facet is not None: + # self.interior_facet = interior_facet + + # @property + # def loopy_shape(self): + # lp_shape = numpy.prod(self.shape, dtype=int) + # return (lp_shape,) if not self.interior_facet else (2*lp_shape,) + + @abc.abstractproperty + def loopy_arg(self): + ... + # return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - @property - def loopy_shape(self): - lp_shape = numpy.prod(self.shape, dtype=int) - return (lp_shape,) if not self.interior_facet else (2*lp_shape,) + +class DualEvaluationOutputKernelArg: + + name = "A" + intent = Intent.OUT + + def __init__(self, shape, dtype): + self.shape = shape + self.dtype = dtype @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.loopy_shape) + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) class CoordinatesKernelArg(KernelArg): @@ -75,15 +90,16 @@ class CoordinatesKernelArg(KernelArg): rank = 1 intent = Intent.IN - def __init__(self, basis_shape, node_shape, dtype, interior_facet=False): - self.basis_shape = basis_shape - self.node_shape = node_shape + def __init__(self, finat_element, dtype, interior_facet=False): + self.finat_element = finat_element self.dtype = dtype self.interior_facet = interior_facet @property - def shape(self): - return self.basis_shape + self.node_shape + def loopy_arg(self): + lp_shape = numpy.prod(self.finat_element.index_shape, dtype=int) + lp_shape = (lp_shape,) if not self.interior_facet else (2*lp_shape,) + return lp.GlobalArg(self.name, self.dtype, shape=lp_shape) class ConstantKernelArg(KernelArg): @@ -104,16 +120,15 @@ class CoefficientKernelArg(KernelArg): rank = 1 intent = Intent.IN - def __init__(self, name, basis_shape, dtype, *, node_shape=(), interior_facet=False): + def __init__(self, name, finat_element, dtype, *, interior_facet=False): self.name = name - self.basis_shape = basis_shape + self.finat_element = finat_element self.dtype = dtype - self.node_shape = node_shape self.interior_facet = interior_facet @property def shape(self): - return self.basis_shape + self.node_shape + return self.finat_element.index_shape @property def u_shape(self): @@ -134,57 +149,57 @@ def loopy_arg(self): class CellOrientationsKernelArg(KernelArg): name = "cell_orientations" - rank = 1 - shape = (1,) - basis_shape = (1,) - node_shape = () intent = Intent.IN dtype = numpy.int32 def __init__(self, interior_facet=False): - super().__init__(interior_facet=interior_facet) + self.interior_facet = interior_facet + + @property + def loopy_arg(self): + raise NotImplementedError + # return lp.GlobalArg(self.name, self.dtype, shape= class CellSizesKernelArg(KernelArg): name = "cell_sizes" - rank = 1 intent = Intent.IN - def __init__(self, basis_shape, node_shape, dtype, interior_facet=False): - self.basis_shape = basis_shape - self.node_shape = node_shape - super().__init__(dtype=dtype, interior_facet=interior_facet) + def __init__(self, finat_element, dtype, interior_facet=False): + self.finat_element = finat_element + self.dtype = dtype + + @property + def loopy_arg(self): + raise NotImplementedError + # return lp.GlobalArg(self.name, self.dtype, shape= class ExteriorFacetKernelArg(KernelArg): name = "facet" shape = (1,) - basis_shape = (1,) - node_shape = () rank = 1 intent = Intent.IN dtype = numpy.uint32 @property - def loopy_shape(self): - return self.shape + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) class InteriorFacetKernelArg(KernelArg): name = "facet" shape = (2,) - basis_shape = (2,) # this is a guess - node_shape = () rank = 1 intent = Intent.IN dtype = numpy.uint32 @property - def loopy_shape(self): - return self.shape + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) class TabulationKernelArg(KernelArg): @@ -193,12 +208,14 @@ class TabulationKernelArg(KernelArg): intent = Intent.IN def __init__(self, name, shape, dtype, interior_facet=False): - super().__init__( - name=name, - shape=shape, - dtype=dtype, - interior_facet=interior_facet - ) + self.name = name + self.shape = shape + self.dtype = dtype + self.interior_facet = interior_facet + + @property + def loopy_arg(self): + raise NotImplementedError class LocalTensorKernelArg(KernelArg): @@ -229,21 +246,18 @@ class LocalVectorKernelArg(LocalTensorKernelArg): rank = 1 def __init__( - self, basis_shape, dtype, *, name="A", node_shape=(), interior_facet=False, diagonal=False + self, finat_element, dtype, *, name="A", interior_facet=False, diagonal=False ): - assert type(basis_shape) == tuple - - self.basis_shape = basis_shape + self.finat_element = finat_element self.dtype = dtype self.name = name - self.node_shape = node_shape self.interior_facet = interior_facet self.diagonal = diagonal @property def shape(self): - return self.basis_shape + self.node_shape + return self.finat_element.index_shape @property def u_shape(self): @@ -290,29 +304,21 @@ class LocalMatrixKernelArg(LocalTensorKernelArg): rank = 2 - def __init__(self, rbasis_shape, cbasis_shape, dtype, *, name="A", rnode_shape=(), cnode_shape=(), interior_facet=False, rreal=False, creal=False): - assert type(rbasis_shape) == tuple and type(cbasis_shape) == tuple - - self.rbasis_shape = rbasis_shape - self.cbasis_shape = cbasis_shape + def __init__(self, relem, celem, dtype, *, name="A", interior_facet=False): + self.relem = relem + self.celem = celem self.dtype = dtype self.name = name - self.rnode_shape = rnode_shape - self.cnode_shape = cnode_shape self.interior_facet = interior_facet - # hack - self.rreal = rreal - self.creal = creal - @property def rshape(self): - return self.rbasis_shape + self.rnode_shape + return self.relem.index_shape @property def cshape(self): - return self.cbasis_shape + self.cnode_shape + return self.celem.index_shape @property def shape(self): @@ -704,21 +710,20 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False): expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - basis_shape, node_shape = split_shape(finat_element) + # basis_shape, node_shape = split_shape(finat_element) # This is truly disgusting, clean up ASAP if name == "cell_sizes": - kernel_arg = CellSizesKernelArg(dtype, basis_shape, node_shape, interior_facet) + kernel_arg = CellSizesKernelArg(finat_element, dtype, interior_facet) elif name == "coords": kernel_arg = CoordinatesKernelArg( - basis_shape, node_shape, dtype, interior_facet=interior_facet + finat_element, dtype, interior_facet=interior_facet ) else: kernel_arg = CoefficientKernelArg( name, - basis_shape, + finat_element, dtype, - node_shape=node_shape, interior_facet=interior_facet ) return kernel_arg, expression @@ -752,21 +757,16 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal except ValueError: raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") - elements = (element, ) + elements = (element,) if len(arguments) == 1 or diagonal: - element, = elements # elements must contain only one item - basis_shape, node_shape = split_shape(element) - return LocalVectorKernelArg(basis_shape, scalar_type, node_shape=node_shape, interior_facet=interior_facet, diagonal=diagonal) + finat_element, = elements + return LocalVectorKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal) elif len(arguments) == 2: - relem, celem = elements - rbasis_shape, rnode_shape = split_shape(relem) - cbasis_shape, cnode_shape = split_shape(celem) + rfinat_element, cfinat_element = elements return LocalMatrixKernelArg( - rbasis_shape, cbasis_shape, scalar_type, - rnode_shape=rnode_shape, - cnode_shape=cnode_shape, + rfinat_element, cfinat_element, scalar_type, interior_facet=interior_facet ) else: From dc9b62fc0ab759977e92a4ab20d8539269d3cc07 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 6 Oct 2021 17:44:32 +0100 Subject: [PATCH 665/809] WIP --- tsfc/kernel_interface/firedrake_loopy.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 3ba10a59de..5648acb3da 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -108,11 +108,14 @@ class ConstantKernelArg(KernelArg): intent = Intent.IN def __init__(self, name, shape, dtype): - super().__init__(name=name, shape=shape, dtype=dtype) + self.name = name + self.shape = shape + self.dtype = dtype @property - def loopy_shape(self): - return self.shape + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) + class CoefficientKernelArg(KernelArg): @@ -149,16 +152,21 @@ def loopy_arg(self): class CellOrientationsKernelArg(KernelArg): name = "cell_orientations" + rank = 1 intent = Intent.IN dtype = numpy.int32 def __init__(self, interior_facet=False): self.interior_facet = interior_facet + @property + def shape(self): + return (2,) if self.interior_facet else (1,) + + @property def loopy_arg(self): - raise NotImplementedError - # return lp.GlobalArg(self.name, self.dtype, shape= + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) class CellSizesKernelArg(KernelArg): From 0b4f3c5f7c11dab80edab7520a6d8bfac831658c Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 27 Aug 2021 12:47:31 +0100 Subject: [PATCH 666/809] finatinterface: Handle tensor elements with shaped sub element --- tsfc/finatinterface.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 537b56d2d6..ced2398140 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -209,20 +209,13 @@ def convert_mixedelement(element, **kwargs): @convert.register(ufl.VectorElement) -def convert_vectorelement(element, **kwargs): - scalar_elem, deps = _create_element(element.sub_elements()[0], **kwargs) - shape = (element.num_sub_elements(),) - shape_innermost = kwargs["shape_innermost"] - return (finat.TensorFiniteElement(scalar_elem, shape, not shape_innermost), - deps | {"shape_innermost"}) - - @convert.register(ufl.TensorElement) def convert_tensorelement(element, **kwargs): - scalar_elem, deps = _create_element(element.sub_elements()[0], **kwargs) + inner_elem, deps = _create_element(element.sub_elements()[0], **kwargs) shape = element.reference_value_shape() + shape = shape[:len(shape) - len(inner_elem.value_shape)] shape_innermost = kwargs["shape_innermost"] - return (finat.TensorFiniteElement(scalar_elem, shape, not shape_innermost), + return (finat.TensorFiniteElement(inner_elem, shape, not shape_innermost), deps | {"shape_innermost"}) From 0c948a9c07b434cebaf359ec9942336ef53da8ce Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 27 Aug 2021 15:31:20 +0100 Subject: [PATCH 667/809] interpolation: New API We need access to the UFL element to be able to handle TensorElements with symmetry, or L2 Piola mapped elements. The FInAT element in these cases does not provide us enough information to construct the inverse of the pullback. --- tests/test_dual_evaluation.py | 18 +++++++++--------- tests/test_interpolation_factorisation.py | 4 ++-- tsfc/driver.py | 12 ++++++------ tsfc/ufl_utils.py | 13 ++++++++----- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/tests/test_dual_evaluation.py b/tests/test_dual_evaluation.py index b075c56037..3e7eabf4ca 100644 --- a/tests/test_dual_evaluation.py +++ b/tests/test_dual_evaluation.py @@ -11,8 +11,8 @@ def test_ufl_only_simple(): expr = ufl.inner(v, v) W = V to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) - assert first_coeff_fake_coords is False + kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) + assert kernel.first_coefficient_fake_coords is False def test_ufl_only_spatialcoordinate(): @@ -22,8 +22,8 @@ def test_ufl_only_spatialcoordinate(): expr = x*y - y**2 + x W = V to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) - assert first_coeff_fake_coords is True + kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) + assert kernel.first_coefficient_fake_coords is True def test_ufl_only_from_contravariant_piola(): @@ -33,8 +33,8 @@ def test_ufl_only_from_contravariant_piola(): expr = ufl.inner(v, v) W = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) - assert first_coeff_fake_coords is True + kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) + assert kernel.first_coefficient_fake_coords is True def test_ufl_only_to_contravariant_piola(): @@ -44,8 +44,8 @@ def test_ufl_only_to_contravariant_piola(): expr = ufl.as_vector([v, v]) W = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) to_element = create_element(W.ufl_element()) - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) - assert first_coeff_fake_coords is True + kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) + assert kernel.first_coefficient_fake_coords is True def test_ufl_only_shape_mismatch(): @@ -58,4 +58,4 @@ def test_ufl_only_shape_mismatch(): to_element = create_element(W.ufl_element()) assert to_element.value_shape == (2,) with pytest.raises(ValueError): - ast, oriented, needs_cell_sizes, coefficients, first_coeff_fake_coords, *_ = compile_expression_dual_evaluation(expr, to_element) + compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) diff --git a/tests/test_interpolation_factorisation.py b/tests/test_interpolation_factorisation.py index 39a67aab22..b588b34d07 100644 --- a/tests/test_interpolation_factorisation.py +++ b/tests/test_interpolation_factorisation.py @@ -6,7 +6,7 @@ TensorElement, Coefficient, interval, quadrilateral, hexahedron) -from tsfc.driver import compile_expression_dual_evaluation +from tsfc import compile_expression_dual_evaluation from tsfc.finatinterface import create_element @@ -31,7 +31,7 @@ def flop_count(mesh, source, target): Vsource = FunctionSpace(mesh, source) to_element = create_element(Vtarget.ufl_element()) expr = Coefficient(Vsource) - kernel = compile_expression_dual_evaluation(expr, to_element) + kernel = compile_expression_dual_evaluation(expr, to_element, Vtarget.ufl_element()) return kernel.flop_count diff --git a/tsfc/driver.py b/tsfc/driver.py index 79d341efc4..21db6d9a07 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -5,6 +5,7 @@ import sys from functools import reduce from itertools import chain +from finat.physically_mapped import DirectlyDefinedElement, PhysicallyMappedElement from numpy import asarray @@ -265,7 +266,7 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) -def compile_expression_dual_evaluation(expression, to_element, *, +def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, domain=None, interface=None, parameters=None): """Compile a UFL expression to be evaluated against a compile-time known reference element's dual basis. @@ -274,6 +275,7 @@ def compile_expression_dual_evaluation(expression, to_element, *, :arg expression: UFL expression :arg to_element: A FInAT element for the target space + :arg ufl_element: The UFL element of the target space. :arg domain: optional UFL domain the expression is defined on (required when expression contains no domain). :arg interface: backend module for the kernel interface :arg parameters: parameters object @@ -289,12 +291,10 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Determine whether in complex mode complex_mode = is_complex(parameters["scalar_type"]) - # Find out which mapping to apply - try: - mapping, = set((to_element.mapping,)) - except ValueError: + if isinstance(to_element, (PhysicallyMappedElement, DirectlyDefinedElement)): raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") - expression = apply_mapping(expression, mapping, domain) + # Map into reference space + expression = apply_mapping(expression, ufl_element, domain) # Apply UFL preprocessing expression = ufl_utils.preprocess_expression(expression, diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 0f571fe40f..70676cc4a4 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -345,13 +345,16 @@ def simplify_abs(expression, complex_mode): return mapper(expression, False) -def apply_mapping(expression, mapping, domain): - """ - This applies the appropriate transformation to the +def apply_mapping(expression, element, domain): + """Apply the appropriate transformation to the given expression for interpolation to a specific element, according to the manner in which it maps from the reference cell. + :arg expression: The expression to map + :arg element: The UFL element of the target space + :arg domain: domain to consider. + The following is borrowed from the UFC documentation: Let g be a field defined on a physical domain T with physical @@ -392,14 +395,14 @@ def apply_mapping(expression, mapping, domain): :arg expression: UFL expression :arg mapping: a string indicating the mapping to apply """ - mesh = expression.ufl_domain() if mesh is None: mesh = domain if domain is not None and mesh != domain: raise NotImplementedError("Multiple domains not supported") rank = len(expression.ufl_shape) - if mapping == "affine": + mapping = element.mapping().lower() + if mapping == "identity": return expression elif mapping == "covariant piola": J = Jacobian(mesh) From 29c69382db4a64a3914dc9efc719f2288fad3b14 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 27 Aug 2021 15:50:23 +0100 Subject: [PATCH 668/809] utils: Generalise apply_mapping to handle arbitrary rank expressions Double cov- or contra-variant Piola mappings can be handled like their single counterparts by apply the relevant mapping "componentwise". --- tsfc/ufl_utils.py | 49 +++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 70676cc4a4..8b8cf20e0f 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -22,7 +22,7 @@ from ufl.geometry import Jacobian, JacobianDeterminant, JacobianInverse from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, ComponentTensor, Expr, FloatValue, Division, - Indexed, MixedElement, MultiIndex, Product, + MixedElement, MultiIndex, Product, ScalarValue, Sqrt, Zero, CellVolume, FacetArea) from gem.node import MemoizerArg @@ -351,9 +351,16 @@ def apply_mapping(expression, element, domain): element, according to the manner in which it maps from the reference cell. - :arg expression: The expression to map - :arg element: The UFL element of the target space - :arg domain: domain to consider. + :arg expression: An expression in physical space + :arg element: The element we're going to interpolate into, whose + value_shape must match the shape of the expression, and will + advertise the pullback to apply. + :arg domain: Optional domain to provide in case expression does + not contain a domain (used for constructing geometric quantities). + :returns: A new UFL expression with shape element.reference_value_shape() + :raises NotImplementedError: If we don't know how to apply the + inverse of the pullback. + :raises ValueError: If we get shape mismatches. The following is borrowed from the UFC documentation: @@ -389,11 +396,14 @@ def apply_mapping(expression, element, domain): G(X) = det(J)^2 K g(x) K^T i.e. G_il(X)=(detJ)^2 K_ij g_jk K_lk - If 'contravariant piola' or 'covariant piola' are applied to a - matrix-valued function, the appropriate mappings are applied row-by-row. + If 'contravariant piola' or 'covariant piola' (or their double + variants) are applied to a matrix-valued function, the appropriate + mappings are applied row-by-row. :arg expression: UFL expression :arg mapping: a string indicating the mapping to apply + :returns: an appropriately mapped UFL expression + :raises NotImplementedError: For unhandled mappings """ mesh = expression.ufl_domain() if mesh is None: @@ -406,21 +416,28 @@ def apply_mapping(expression, element, domain): return expression elif mapping == "covariant piola": J = Jacobian(mesh) - *i, j, k = indices(len(expression.ufl_shape) + 1) - expression = Indexed(expression, MultiIndex((*i, k))) - return as_tensor(J.T[j, k] * expression, (*i, j)) + *k, i, j = indices(len(expression.ufl_shape) + 1) + kj = (*k, j) + return as_tensor(J[j, i] * expression[kj], (*k, i)) + elif mapping == "l2 piola": + detJ = JacobianDeterminant(mesh) + return expression * detJ elif mapping == "contravariant piola": K = JacobianInverse(mesh) detJ = JacobianDeterminant(mesh) - *i, j, k = indices(len(expression.ufl_shape) + 1) - expression = Indexed(expression, MultiIndex((*i, k))) - return as_tensor(detJ * K[j, k] * expression, (*i, j)) - elif mapping == "double covariant piola" and rank == 2: + *k, i, j = indices(len(expression.ufl_shape) + 1) + kj = (*k, j) + return as_tensor(detJ * K[i, j] * expression[kj], (*k, i)) + elif mapping == "double covariant piola": J = Jacobian(mesh) - return J.T * expression * J - elif mapping == "double contravariant piola" and rank == 2: + *k, i, j, m, n = indices(len(expression.ufl_shape) + 2) + kmn = (*k, m, n) + return as_tensor(J[m, i] * expression[kmn] * J[n, j], (*k, i, j)) + elif mapping == "double contravariant piola": K = JacobianInverse(mesh) detJ = JacobianDeterminant(mesh) - return (detJ)**2 * K * expression * K.T + *k, i, j, m, n = indices(len(expression.ufl_shape) + 2) + kmn = (*k, m, n) + return as_tensor(detJ**2 * K[i, m] * expression[kmn] * K[j, n], (*k, i, j)) else: raise NotImplementedError("Don't know how to handle mapping type %s for expression of rank %d" % (mapping, rank)) From 5c8a294d1d948b3801f63953b64f313facd55a55 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 27 Aug 2021 16:03:06 +0100 Subject: [PATCH 669/809] utils: Implement pullback inverse for symmetries Add better documentation and error checking for application of pullback inverse, and handle tensor elements with symmetries. --- tsfc/ufl_utils.py | 61 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 8b8cf20e0f..aefa552410 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -346,10 +346,7 @@ def simplify_abs(expression, complex_mode): def apply_mapping(expression, element, domain): - """Apply the appropriate transformation to the - given expression for interpolation to a specific - element, according to the manner in which it maps - from the reference cell. + """Apply the inverse of the pullback for element to an expression. :arg expression: An expression in physical space :arg element: The element we're going to interpolate into, whose @@ -374,7 +371,7 @@ def apply_mapping(expression, element, domain): inverse of the Jacobian K = J^{-1}. Then we (currently) have the following four types of mappings: - 'affine' mapping for g: + 'identity' mapping for g: G(X) = g(x) @@ -399,45 +396,73 @@ def apply_mapping(expression, element, domain): If 'contravariant piola' or 'covariant piola' (or their double variants) are applied to a matrix-valued function, the appropriate mappings are applied row-by-row. - - :arg expression: UFL expression - :arg mapping: a string indicating the mapping to apply - :returns: an appropriately mapped UFL expression - :raises NotImplementedError: For unhandled mappings """ mesh = expression.ufl_domain() if mesh is None: mesh = domain if domain is not None and mesh != domain: raise NotImplementedError("Multiple domains not supported") - rank = len(expression.ufl_shape) + if expression.ufl_shape != element.value_shape(): + raise ValueError(f"Mismatching shapes, got {expression.ufl_shape}, expected {element.value_shape()}") mapping = element.mapping().lower() if mapping == "identity": - return expression + rexpression = expression elif mapping == "covariant piola": J = Jacobian(mesh) *k, i, j = indices(len(expression.ufl_shape) + 1) kj = (*k, j) - return as_tensor(J[j, i] * expression[kj], (*k, i)) + rexpression = as_tensor(J[j, i] * expression[kj], (*k, i)) elif mapping == "l2 piola": detJ = JacobianDeterminant(mesh) - return expression * detJ + rexpression = expression * detJ elif mapping == "contravariant piola": K = JacobianInverse(mesh) detJ = JacobianDeterminant(mesh) *k, i, j = indices(len(expression.ufl_shape) + 1) kj = (*k, j) - return as_tensor(detJ * K[i, j] * expression[kj], (*k, i)) + rexpression = as_tensor(detJ * K[i, j] * expression[kj], (*k, i)) elif mapping == "double covariant piola": J = Jacobian(mesh) *k, i, j, m, n = indices(len(expression.ufl_shape) + 2) kmn = (*k, m, n) - return as_tensor(J[m, i] * expression[kmn] * J[n, j], (*k, i, j)) + rexpression = as_tensor(J[m, i] * expression[kmn] * J[n, j], (*k, i, j)) elif mapping == "double contravariant piola": K = JacobianInverse(mesh) detJ = JacobianDeterminant(mesh) *k, i, j, m, n = indices(len(expression.ufl_shape) + 2) kmn = (*k, m, n) - return as_tensor(detJ**2 * K[i, m] * expression[kmn] * K[j, n], (*k, i, j)) + rexpression = as_tensor(detJ**2 * K[i, m] * expression[kmn] * K[j, n], (*k, i, j)) + elif mapping == "symmetries": + # This tells us how to get from the pieces of the reference + # space expression to the physical space one. + # We're going to apply the inverse of the physical to + # reference space mapping. + fcm = element.flattened_sub_element_mapping() + sub_elem = element.sub_elements()[0] + shape = expression.ufl_shape + flat = ufl.as_vector([expression[i] for i in numpy.ndindex(shape)]) + vs = sub_elem.value_shape() + rvs = sub_elem.reference_value_shape() + seen = set() + rpieces = [] + gm = int(numpy.prod(vs, dtype=int)) + for gi, ri in enumerate(fcm): + # For each unique piece in reference space + if ri in seen: + continue + seen.add(ri) + # Get the physical space piece + piece = [flat[gm*gi + j] for j in range(gm)] + piece = as_tensor(numpy.asarray(piece).reshape(vs)) + # get into reference space + piece = apply_mapping(piece, sub_elem, mesh) + assert piece.ufl_shape == rvs + # Concatenate with the other pieces + rpieces.extend([piece[idx] for idx in numpy.ndindex(rvs)]) + # And reshape + rexpression = as_tensor(numpy.asarray(rpieces).reshape(element.reference_value_shape())) else: - raise NotImplementedError("Don't know how to handle mapping type %s for expression of rank %d" % (mapping, rank)) + raise NotImplementedError(f"Don't know how to handle mapping type {mapping} for expression of rank {element.value_shape()}") + if rexpression.ufl_shape != element.reference_value_shape(): + raise ValueError(f"Mismatching reference shapes, got {rexpression.ufl_shape} expected {element.reference_value_shape()}") + return rexpression From fd3b3370a8ca133db1d85d2aa22867ea65e910bb Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 14 Oct 2021 22:53:53 +0100 Subject: [PATCH 670/809] All tests passing following arg refactor --- tsfc/driver.py | 13 +- tsfc/kernel_args.py | 525 +++++++++++++++++++++++ tsfc/kernel_interface/firedrake_loopy.py | 371 +--------------- 3 files changed, 547 insertions(+), 362 deletions(-) create mode 100644 tsfc/kernel_args.py diff --git a/tsfc/driver.py b/tsfc/driver.py index a1fa251dfb..4d1afda63c 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -23,7 +23,7 @@ import finat from finat.quadrature import AbstractQuadratureRule, make_quadrature -from tsfc import fem, ufl_utils +from tsfc import fem, kernel_args, ufl_utils from tsfc.finatinterface import as_fiat_cell from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex @@ -279,8 +279,6 @@ def compile_expression_dual_evaluation(expression, to_element, *, :arg parameters: parameters object :returns: Loopy-based ExpressionKernel object. """ - from tsfc.kernel_interface.firedrake_loopy import DualEvaluationOutputKernelArg - if parameters is None: parameters = default_parameters() else: @@ -358,7 +356,14 @@ def compile_expression_dual_evaluation(expression, to_element, *, return_indices = basis_indices + tuple(chain(*argument_multiindices)) return_shape = tuple(i.extent for i in return_indices) return_var = gem.Variable('A', return_shape) - return_arg = DualEvaluationOutputKernelArg(return_shape, builder.scalar_type) + + if len(argument_multiindices) == 0: + return_arg = kernel_args.DualEvalVectorOutputKernelArg(return_shape, builder.scalar_type) + elif len(argument_multiindices) == 1: + return_arg = kernel_args.DualEvalMatrixOutputKernelArg(return_shape[0], return_shape[1], builder.scalar_type) + else: + raise AssertionError + return_expr = gem.Indexed(return_var, return_indices) # TODO: one should apply some GEM optimisations as in assembly, diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py new file mode 100644 index 0000000000..d7a424ab9a --- /dev/null +++ b/tsfc/kernel_args.py @@ -0,0 +1,525 @@ +import abc +import enum +import itertools + +import finat +import gem +from gem.optimise import remove_componenttensors as prune +import loopy as lp +import numpy as np + + +class Intent(enum.IntEnum): + IN = enum.auto() + OUT = enum.auto() + + +class KernelArg(abc.ABC): + """Class encapsulating information about kernel arguments.""" + + @abc.abstractproperty + def name(self): + ... + + @abc.abstractproperty + def dtype(self): + ... + + @abc.abstractproperty + def intent(self): + ... + + @abc.abstractproperty + def loopy_arg(self): + ... + + +class RankZeroKernelArg(KernelArg, abc.ABC): + + @abc.abstractproperty + def shape(self): + """The shape of the per-node tensor. + + For example, a scalar-valued element will have shape == (1,) whilst a 3-vector + would have shape (3,). + """ + ... + + +class RankOneKernelArg(KernelArg, abc.ABC): + + @abc.abstractproperty + def shape(self): + ... + + @abc.abstractproperty + def node_shape(self): + ... + + +class RankTwoKernelArg(KernelArg, abc.ABC): + + @abc.abstractproperty + def rshape(self): + ... + + @abc.abstractproperty + def cshape(self): + ... + + @abc.abstractproperty + def rnode_shape(self): + ... + + @abc.abstractproperty + def cnode_shape(self): + ... + + +class DualEvalVectorOutputKernelArg(RankOneKernelArg): + + def __init__(self, node_shape, dtype): + self._node_shape = node_shape + self._dtype = dtype + + @property + def name(self): + return "A" + + @property + def dtype(self): + return self._dtype + + @property + def intent(self): + return Intent.OUT + + @property + def shape(self): + return (1,) + + @property + def node_shape(self): + return self._node_shape + + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.node_shape) + + +class DualEvalMatrixOutputKernelArg(RankTwoKernelArg): + + def __init__(self, rnode_shape, cnode_shape, dtype): + self._rnode_shape = rnode_shape + self._cnode_shape = cnode_shape + self._dtype = dtype + + @property + def name(self): + return "A" + + @property + def dtype(self): + return self._dtype + + @property + def intent(self): + return Intent.OUT + + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=(self.rnode_shape, self.cnode_shape)) + + @property + def rshape(self): + return (1,) + + @property + def cshape(self): + return (1,) + + @property + def rnode_shape(self): + return self._rnode_shape + + @property + def cnode_shape(self): + return self._cnode_shape + + +class CoordinatesKernelArg(RankOneKernelArg): + + def __init__(self, elem, dtype, interior_facet=False): + self._elem = _ElementHandler(elem) + self._dtype = dtype + self._interior_facet = interior_facet + + @property + def name(self): + return "coords" + + @property + def dtype(self): + return self._dtype + + @property + def intent(self): + return Intent.IN + + @property + def shape(self): + return self._elem.tensor_shape + + @property + def node_shape(self): + shape = self._elem.node_shape + return 2*shape if self._interior_facet else shape + + @property + def loopy_arg(self): + shape = np.prod([self.node_shape, *self.shape], dtype=int) + return lp.GlobalArg(self.name, self.dtype, shape=shape) + + +class ConstantKernelArg(RankZeroKernelArg): + + def __init__(self, name, shape, dtype): + self._name = name + self._shape = shape + self._dtype = dtype + + @property + def name(self): + return self._name + + @property + def shape(self): + return self._shape + + @property + def dtype(self): + return self._dtype + + @property + def intent(self): + return Intent.IN + + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) + + +class CoefficientKernelArg(RankOneKernelArg): + + def __init__(self, name, elem, dtype, *, interior_facet=False): + self._name = name + self._elem = _ElementHandler(elem) + self._dtype = dtype + self._interior_facet = interior_facet + + @property + def name(self): + return self._name + + @property + def dtype(self): + return self._dtype + + @property + def intent(self): + return Intent.IN + + @property + def shape(self): + return self._elem.tensor_shape + + @property + def node_shape(self): + shape = self._elem.node_shape + return 2*shape if self._interior_facet else shape + + @property + def loopy_arg(self): + shape = np.prod([self.node_shape, *self.shape], dtype=int) + return lp.GlobalArg(self.name, self.dtype, shape=shape) + + +class CellOrientationsKernelArg(RankOneKernelArg): + + def __init__(self, interior_facet=False): + self._interior_facet = interior_facet + + @property + def name(self): + return "cell_orientations" + + @property + def dtype(self): + return np.int32 + + @property + def intent(self): + return Intent.IN + + @property + def loopy_arg(self): + shape = np.prod([self.node_shape, *self.shape], dtype=int) + return lp.GlobalArg(self.name, self.dtype, shape=shape) + + @property + def shape(self): + return (2,) if self._interior_facet else (1,) + + @property + def node_shape(self): + return (1,) + + +class CellSizesKernelArg(RankOneKernelArg): + + def __init__(self, elem, dtype, *, interior_facet=False): + self._elem = elem + self._dtype = dtype + self._interior_facet = interior_facet + + @property + def name(self): + return "cell_sizes" + + @property + def dtype(self): + return self._dtype + + @property + def intent(self): + return Intent.IN + + @property + def shape(self): + if _is_tensor_element(self._elem): + return self._elem._shape + else: + return (1,) + + @property + def node_shape(self): + if _is_tensor_element(self._elem): + shape = self._elem.index_shape[:-len(self.shape)] + else: + shape = self._elem.index_shape + shape = np.prod(shape, dtype=int) + return 2*shape if self._interior_facet else shape + + @property + def loopy_arg(self): + shape = np.prod([self.node_shape, *self.shape], dtype=int) + return lp.GlobalArg(self.name, self.dtype, shape=shape) + + +class FacetKernelArg(RankOneKernelArg): + + def __init__(self, interior_facet=False): + self._interior_facet = interior_facet + + @property + def name(self): + return "facet" + + @property + def shape(self): + return (2,) if self._interior_facet else (1,) + + @property + def dtype(self): + return np.uint32 + + @property + def intent(self): + return Intent.IN + + @property + def node_shape(self): + return (1,) + + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) + + +# class TabulationKernelArg(KernelArg): + +# rank = 1 +# intent = Intent.IN + +# def __init__(self, name, shape, dtype, interior_facet=False): +# self.name = name +# self.shape = shape +# self.dtype = dtype +# self.interior_facet = interior_facet + +# @property +# def loopy_arg(self): +# raise NotImplementedError + +class OutputKernelArg(KernelArg, abc.ABC): + + name = "A" + intent = Intent.OUT + + +class ScalarOutputKernelArg(RankZeroKernelArg, OutputKernelArg): + + def __init__(self, dtype): + self._dtype = dtype + + @property + def dtype(self): + return self._dtype + + @property + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.shape, is_output=True) + + @property + def shape(self): + return (1,) + + def make_gem_exprs(self, multiindices): + assert len(multiindices) == 0 + return [gem.Indexed(gem.Variable(self.name, self.shape), (0,))] + + +class VectorOutputKernelArg(RankOneKernelArg, OutputKernelArg): + + def __init__( + self, elem, dtype, *, interior_facet=False, diagonal=False + ): + self._elem = _ElementHandler(elem) + self._dtype = dtype + + self._interior_facet = interior_facet + self._diagonal = diagonal + + @property + def dtype(self): + return self._dtype + + @property + def shape(self): + return self._elem.tensor_shape + + @property + def node_shape(self): + shape = self._elem.node_shape + return 2*shape if self._interior_facet else shape + + @property + def loopy_arg(self): + shape = np.prod([self.node_shape, *self.shape], dtype=int) + return lp.GlobalArg(self.name, self.dtype, shape=shape) + + # TODO Function please + def make_gem_exprs(self, multiindices): + u_shape = np.array([np.prod(self._elem._elem.index_shape, dtype=int)]) + c_shape = tuple(2*u_shape) if self._interior_facet else tuple(u_shape) + + if self._diagonal: + multiindices = multiindices[:1] + + if self._interior_facet: + slicez = [ + [slice(r*s, (r + 1)*s) for r, s in zip(restrictions, u_shape)] + for restrictions in [(0,), (1,)] + ] + else: + slicez = [[slice(s) for s in u_shape]] + + var = gem.Variable(self.name, c_shape) + exprs = [self._make_expression(gem.view(var, *slices), multiindices) for slices in slicez] + return prune(exprs) + + # TODO More descriptive name + def _make_expression(self, restricted, multiindices): + return gem.Indexed(gem.reshape(restricted, self._elem._elem.index_shape), + tuple(itertools.chain(*multiindices))) + + +class MatrixOutputKernelArg(RankTwoKernelArg, OutputKernelArg): + + def __init__(self, relem, celem, dtype, *, interior_facet=False): + self._relem = _ElementHandler(relem) + self._celem = _ElementHandler(celem) + self._dtype = dtype + self._interior_facet = interior_facet + + @property + def dtype(self): + return self._dtype + + @property + def loopy_arg(self): + rshape = np.prod([self.rnode_shape, *self.cshape], dtype=int) + cshape = np.prod([self.rnode_shape, *self.cshape], dtype=int) + return lp.GlobalArg(self.name, self.dtype, shape=(rshape, cshape)) + + @property + def rshape(self): + return self._relem.tensor_shape + + @property + def cshape(self): + return self._celem.tensor_shape + + @property + def rnode_shape(self): + shape = self._relem.node_shape + return 2*shape if self._interior_facet else shape + + @property + def cnode_shape(self): + shape = self._celem.node_shape + return 2*shape if self._interior_facet else shape + + def make_gem_exprs(self, multiindices): + u_shape = np.array([np.prod(elem._elem.index_shape, dtype=int) + for elem in [self._relem, self._celem]]) + c_shape = tuple(2*u_shape) if self._interior_facet else tuple(u_shape) + + if self._interior_facet: + slicez = [ + [slice(r*s, (r + 1)*s) for r, s in zip(restrictions, u_shape)] + for restrictions in itertools.product((0, 1), repeat=2) + ] + else: + slicez = [[slice(s) for s in u_shape]] + + var = gem.Variable(self.name, c_shape) + exprs = [self._make_expression(gem.view(var, *slices), multiindices) for slices in slicez] + return prune(exprs) + + # TODO More descriptive name + def _make_expression(self, restricted, multiindices): + return gem.Indexed(gem.reshape(restricted, self._relem._elem.index_shape, self._celem._elem.index_shape), + tuple(itertools.chain(*multiindices))) + + +class _ElementHandler: + + def __init__(self, elem): + self._elem = elem + + @property + def node_shape(self): + if self._is_tensor_element: + shape = self._elem.index_shape[:-len(self.tensor_shape)] + else: + shape = self._elem.index_shape + return np.prod(shape, dtype=int) + + @property + def tensor_shape(self): + return self._elem._shape if self._is_tensor_element else (1,) + + @property + def _is_tensor_element(self): + return isinstance(self._elem, finat.TensorFiniteElement) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 5648acb3da..d03dfb59ae 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -14,6 +14,7 @@ import loopy as lp +from tsfc import kernel_args from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase from tsfc.kernel_interface.firedrake import check_requirements @@ -27,348 +28,6 @@ def make_builder(*args, **kwargs): return partial(KernelBuilder, *args, **kwargs) - - -class Intent(enum.IntEnum): - IN = enum.auto() - OUT = enum.auto() - - -class KernelArg(abc.ABC): - """Class encapsulating information about kernel arguments.""" - - # name: str - # shape: tuple - # rank: int - # dtype: numpy.dtype - # intent: Intent - # interior_facet: bool - - # def __init__(self, *, name=None, shape=None, rank=None, dtype=None, - # intent=None, interior_facet=None): - # if name is not None: - # self.name = name - # if shape is not None: - # self.shape = shape - # if rank is not None: - # self.rank = rank - # if dtype is not None: - # self.dtype = dtype - # if intent is not None: - # self.intent = intent - # if interior_facet is not None: - # self.interior_facet = interior_facet - - # @property - # def loopy_shape(self): - # lp_shape = numpy.prod(self.shape, dtype=int) - # return (lp_shape,) if not self.interior_facet else (2*lp_shape,) - - @abc.abstractproperty - def loopy_arg(self): - ... - # return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - - -class DualEvaluationOutputKernelArg: - - name = "A" - intent = Intent.OUT - - def __init__(self, shape, dtype): - self.shape = shape - self.dtype = dtype - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - - -class CoordinatesKernelArg(KernelArg): - - name = "coords" - rank = 1 - intent = Intent.IN - - def __init__(self, finat_element, dtype, interior_facet=False): - self.finat_element = finat_element - self.dtype = dtype - self.interior_facet = interior_facet - - @property - def loopy_arg(self): - lp_shape = numpy.prod(self.finat_element.index_shape, dtype=int) - lp_shape = (lp_shape,) if not self.interior_facet else (2*lp_shape,) - return lp.GlobalArg(self.name, self.dtype, shape=lp_shape) - - -class ConstantKernelArg(KernelArg): - - rank = 0 - intent = Intent.IN - - def __init__(self, name, shape, dtype): - self.name = name - self.shape = shape - self.dtype = dtype - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - - - -class CoefficientKernelArg(KernelArg): - - rank = 1 - intent = Intent.IN - - def __init__(self, name, finat_element, dtype, *, interior_facet=False): - self.name = name - self.finat_element = finat_element - self.dtype = dtype - self.interior_facet = interior_facet - - @property - def shape(self): - return self.finat_element.index_shape - - @property - def u_shape(self): - return numpy.array([numpy.prod(self.shape, dtype=int)]) - - @property - def c_shape(self): - if self.interior_facet: - return tuple(2*self.u_shape) - else: - return tuple(self.u_shape) - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape) - - -class CellOrientationsKernelArg(KernelArg): - - name = "cell_orientations" - rank = 1 - intent = Intent.IN - dtype = numpy.int32 - - def __init__(self, interior_facet=False): - self.interior_facet = interior_facet - - @property - def shape(self): - return (2,) if self.interior_facet else (1,) - - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - - -class CellSizesKernelArg(KernelArg): - - name = "cell_sizes" - intent = Intent.IN - - def __init__(self, finat_element, dtype, interior_facet=False): - self.finat_element = finat_element - self.dtype = dtype - - @property - def loopy_arg(self): - raise NotImplementedError - # return lp.GlobalArg(self.name, self.dtype, shape= - - -class ExteriorFacetKernelArg(KernelArg): - - name = "facet" - shape = (1,) - rank = 1 - intent = Intent.IN - dtype = numpy.uint32 - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - - -class InteriorFacetKernelArg(KernelArg): - - name = "facet" - shape = (2,) - rank = 1 - intent = Intent.IN - dtype = numpy.uint32 - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - - -class TabulationKernelArg(KernelArg): - - rank = 1 - intent = Intent.IN - - def __init__(self, name, shape, dtype, interior_facet=False): - self.name = name - self.shape = shape - self.dtype = dtype - self.interior_facet = interior_facet - - @property - def loopy_arg(self): - raise NotImplementedError - - -class LocalTensorKernelArg(KernelArg): - - name = "A" - intent = Intent.OUT - - -class LocalScalarKernelArg(LocalTensorKernelArg): - - rank = 0 - shape = (1,) - - def __init__(self, dtype): - self.dtype = dtype - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape, is_output=True) - - def make_gem_exprs(self, multiindices): - assert len(multiindices) == 0 - return [gem.Indexed(gem.Variable(self.name, self.shape), (0,))] - - -class LocalVectorKernelArg(LocalTensorKernelArg): - - rank = 1 - - def __init__( - self, finat_element, dtype, *, name="A", interior_facet=False, diagonal=False - ): - self.finat_element = finat_element - self.dtype = dtype - - self.name = name - self.interior_facet = interior_facet - self.diagonal = diagonal - - @property - def shape(self): - return self.finat_element.index_shape - - @property - def u_shape(self): - return numpy.array([numpy.prod(self.shape, dtype=int)]) - - @property - def c_shape(self): - if self.interior_facet: - return tuple(2*self.u_shape) - else: - return tuple(self.u_shape) - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape, is_output=True) - - # TODO Function please - def make_gem_exprs(self, multiindices): - if self.diagonal: - multiindices = multiindices[:1] - - if self.interior_facet: - slicez = [ - [slice(r*s, (r + 1)*s) for r, s in zip(restrictions, self.u_shape)] - for restrictions in product((0, 1), repeat=self.rank) - ] - else: - slicez = [[slice(s) for s in self.u_shape]] - - var = gem.Variable(self.name, self.c_shape) - exprs = [self._make_expression(gem.view(var, *slices), multiindices) for slices in slicez] - return prune(exprs) - - - # TODO More descriptive name - def _make_expression(self, restricted, multiindices): - return gem.Indexed(gem.reshape(restricted, self.shape), - tuple(chain(*multiindices))) - - - - -class LocalMatrixKernelArg(LocalTensorKernelArg): - - rank = 2 - - def __init__(self, relem, celem, dtype, *, name="A", interior_facet=False): - self.relem = relem - self.celem = celem - self.dtype = dtype - - self.name = name - self.interior_facet = interior_facet - - @property - def rshape(self): - return self.relem.index_shape - - @property - def cshape(self): - return self.celem.index_shape - - @property - def shape(self): - return self.rshape, self.cshape - - @property - def u_shape(self): - return numpy.array( - [numpy.prod(self.rshape, dtype=int), numpy.prod(self.cshape, dtype=int)] - ) - - @property - def c_shape(self): - if self.interior_facet: - return tuple(2*self.u_shape) - else: - return tuple(self.u_shape) - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape, is_output=True) - - def make_gem_exprs(self, multiindices): - if self.interior_facet: - slicez = [ - [slice(r*s, (r + 1)*s) for r, s in zip(restrictions, self.u_shape)] - for restrictions in product((0, 1), repeat=self.rank) - ] - else: - slicez = [[slice(s) for s in self.u_shape]] - - var = gem.Variable(self.name, self.c_shape) - exprs = [self._make_expression(gem.view(var, *slices), multiindices) for slices in slicez] - return prune(exprs) - - - # TODO More descriptive name - def _make_expression(self, restricted, multiindices): - return gem.Indexed(gem.reshape(restricted, self.rshape, self.cshape), - tuple(chain(*multiindices))) - - class Kernel: __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", @@ -429,7 +88,7 @@ def __init__(self, scalar_type, interior_facet=False): shape = (1,) cell_orientations = gem.Variable("cell_orientations", shape) self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) - self.cell_orientations_loopy_arg = CellOrientationsKernelArg( + self.cell_orientations_loopy_arg = kernel_args.CellOrientationsKernelArg( interior_facet=self.interior_facet ) @@ -527,7 +186,7 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords) args.append(self.cell_sizes_arg) args.extend(self.kernel_args) for name_, shape in self.tabulations: - args.append(TabulationKernelArg(name_, self.scalar_type, shape)) + args.append(kernel_args.TabulationKernelArg(name_, self.scalar_type, shape)) loopy_args = [arg.loopy_arg for arg in args] @@ -657,12 +316,12 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): args.append(self.cell_sizes_arg) args.extend(self.coefficient_args) if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: - args.append(ExteriorFacetKernelArg()) + args.append(kernel_args.FacetKernelArg(interior_facet=False)) elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: - args.append(InteriorFacetKernelArg()) + args.append(kernel_args.FacetKernelArg(interior_facet=True)) for name_, shape in self.kernel.tabulations: - args.append(TabulationKernelArg(name_, shape, self.scalar_type)) + args.append(kernel_args.TabulationKernelArg(name_, shape, self.scalar_type)) loopy_args = [arg.loopy_arg for arg in args] self.kernel.arguments = args @@ -699,7 +358,7 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False): if coefficient.ufl_element().family() == 'Real': value_size = coefficient.ufl_element().value_size() - kernel_arg = ConstantKernelArg(name, (value_size,), dtype) + kernel_arg = kernel_args.ConstantKernelArg(name, (value_size,), dtype) expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) return kernel_arg, expression @@ -718,17 +377,13 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False): expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - # basis_shape, node_shape = split_shape(finat_element) - # This is truly disgusting, clean up ASAP if name == "cell_sizes": - kernel_arg = CellSizesKernelArg(finat_element, dtype, interior_facet) + kernel_arg = kernel_args.CellSizesKernelArg(finat_element, dtype, interior_facet=interior_facet) elif name == "coords": - kernel_arg = CoordinatesKernelArg( - finat_element, dtype, interior_facet=interior_facet - ) + kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, dtype, interior_facet=interior_facet) else: - kernel_arg = CoefficientKernelArg( + kernel_arg = kernel_args.CoefficientKernelArg( name, finat_element, dtype, @@ -753,7 +408,7 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal assert isinstance(interior_facet, bool) if len(arguments) == 0: - return LocalScalarKernelArg(scalar_type) + return kernel_args.ScalarOutputKernelArg(scalar_type) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) @@ -770,10 +425,10 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal if len(arguments) == 1 or diagonal: finat_element, = elements - return LocalVectorKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal) + return kernel_args.VectorOutputKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal) elif len(arguments) == 2: rfinat_element, cfinat_element = elements - return LocalMatrixKernelArg( + return kernel_args.MatrixOutputKernelArg( rfinat_element, cfinat_element, scalar_type, interior_facet=interior_facet ) From d0dca018fa71a747a61daf82fe796842643efa3e Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 14 Oct 2021 23:20:44 +0100 Subject: [PATCH 671/809] Refactor facet kernel arguments --- tsfc/kernel_args.py | 33 ++++++++++-------------- tsfc/kernel_interface/firedrake_loopy.py | 4 +-- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index d7a424ab9a..2e6bf9783f 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -316,34 +316,27 @@ def loopy_arg(self): return lp.GlobalArg(self.name, self.dtype, shape=shape) -class FacetKernelArg(RankOneKernelArg): +class FacetKernelArg(RankOneKernelArg, abc.ABC): - def __init__(self, interior_facet=False): - self._interior_facet = interior_facet + name = "facet" + intent = Intent.IN + dtype = np.uint32 - @property - def name(self): - return "facet" + node_shape = (1,) @property - def shape(self): - return (2,) if self._interior_facet else (1,) + def loopy_arg(self): + return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - @property - def dtype(self): - return np.uint32 - @property - def intent(self): - return Intent.IN +class ExteriorFacetKernelArg(FacetKernelArg): - @property - def node_shape(self): - return (1,) + shape = (1,) - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) + +class InteriorFacetKernelArg(FacetKernelArg): + + shape = (2,) # class TabulationKernelArg(KernelArg): diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index d03dfb59ae..27beefd17b 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -316,9 +316,9 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): args.append(self.cell_sizes_arg) args.extend(self.coefficient_args) if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: - args.append(kernel_args.FacetKernelArg(interior_facet=False)) + args.append(kernel_args.ExteriorFacetKernelArg()) elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: - args.append(kernel_args.FacetKernelArg(interior_facet=True)) + args.append(kernel_args.InteriorFacetKernelArg()) for name_, shape in self.kernel.tabulations: args.append(kernel_args.TabulationKernelArg(name_, shape, self.scalar_type)) From e563c9eb4438ddfce0b77af34b946d65c9aa3414 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 15 Oct 2021 11:07:28 +0100 Subject: [PATCH 672/809] WIP --- tsfc/driver.py | 13 ++--- tsfc/kernel_args.py | 123 ++++++++------------------------------------ 2 files changed, 26 insertions(+), 110 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 4d1afda63c..e133292595 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -6,6 +6,7 @@ from functools import reduce from itertools import chain +import numpy as np from numpy import asarray import ufl @@ -355,16 +356,10 @@ def compile_expression_dual_evaluation(expression, to_element, *, # Build kernel body return_indices = basis_indices + tuple(chain(*argument_multiindices)) return_shape = tuple(i.extent for i in return_indices) - return_var = gem.Variable('A', return_shape) - if len(argument_multiindices) == 0: - return_arg = kernel_args.DualEvalVectorOutputKernelArg(return_shape, builder.scalar_type) - elif len(argument_multiindices) == 1: - return_arg = kernel_args.DualEvalMatrixOutputKernelArg(return_shape[0], return_shape[1], builder.scalar_type) - else: - raise AssertionError - - return_expr = gem.Indexed(return_var, return_indices) + # TODO I do not know how to determine tensor_shape and node_shape from this information + return_arg = kernel_args.DualEvalOutputKernelArg(return_shape, builder.scalar_type) + return_expr = gem.Indexed(gem.Variable("A", return_shape), return_indices) # TODO: one should apply some GEM optimisations as in assembly, # but we don't for now. diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 2e6bf9783f..1a4c54c3b8 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -76,96 +76,39 @@ def cnode_shape(self): ... -class DualEvalVectorOutputKernelArg(RankOneKernelArg): +class DualEvalOutputKernelArg(KernelArg): - def __init__(self, node_shape, dtype): - self._node_shape = node_shape - self._dtype = dtype - - @property - def name(self): - return "A" - - @property - def dtype(self): - return self._dtype - - @property - def intent(self): - return Intent.OUT - - @property - def shape(self): - return (1,) - - @property - def node_shape(self): - return self._node_shape - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.node_shape) - - -class DualEvalMatrixOutputKernelArg(RankTwoKernelArg): + name = "A" + intent = Intent.OUT - def __init__(self, rnode_shape, cnode_shape, dtype): - self._rnode_shape = rnode_shape - self._cnode_shape = cnode_shape + def __init__(self, shape, dtype): + self._shape = shape self._dtype = dtype - @property - def name(self): - return "A" - @property def dtype(self): return self._dtype - @property - def intent(self): - return Intent.OUT - @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=(self.rnode_shape, self.cnode_shape)) - - @property - def rshape(self): - return (1,) - - @property - def cshape(self): - return (1,) - - @property - def rnode_shape(self): - return self._rnode_shape - - @property - def cnode_shape(self): - return self._cnode_shape + return lp.GlobalArg(self.name, self.dtype, shape=self._shape) class CoordinatesKernelArg(RankOneKernelArg): + name = "coords" + intent = Intent.IN + def __init__(self, elem, dtype, interior_facet=False): self._elem = _ElementHandler(elem) self._dtype = dtype self._interior_facet = interior_facet - @property - def name(self): - return "coords" @property def dtype(self): return self._dtype - @property - def intent(self): - return Intent.IN - @property def shape(self): return self._elem.tensor_shape @@ -246,20 +189,14 @@ def loopy_arg(self): class CellOrientationsKernelArg(RankOneKernelArg): - def __init__(self, interior_facet=False): - self._interior_facet = interior_facet - - @property - def name(self): - return "cell_orientations" + name = "cell_orientations" + intent = Intent.IN + dtype = np.int32 - @property - def dtype(self): - return np.int32 + node_shape = 1 - @property - def intent(self): - return Intent.IN + def __init__(self, interior_facet=False): + self._interior_facet = interior_facet @property def loopy_arg(self): @@ -270,44 +207,28 @@ def loopy_arg(self): def shape(self): return (2,) if self._interior_facet else (1,) - @property - def node_shape(self): - return (1,) - class CellSizesKernelArg(RankOneKernelArg): + name = "cell_sizes" + intent = Intent.IN + def __init__(self, elem, dtype, *, interior_facet=False): - self._elem = elem + self._elem = _ElementHandler(elem) self._dtype = dtype self._interior_facet = interior_facet - @property - def name(self): - return "cell_sizes" - @property def dtype(self): return self._dtype - @property - def intent(self): - return Intent.IN - @property def shape(self): - if _is_tensor_element(self._elem): - return self._elem._shape - else: - return (1,) + return self._elem.tensor_shape @property def node_shape(self): - if _is_tensor_element(self._elem): - shape = self._elem.index_shape[:-len(self.shape)] - else: - shape = self._elem.index_shape - shape = np.prod(shape, dtype=int) + shape = self._elem.node_shape return 2*shape if self._interior_facet else shape @property @@ -322,7 +243,7 @@ class FacetKernelArg(RankOneKernelArg, abc.ABC): intent = Intent.IN dtype = np.uint32 - node_shape = (1,) + node_shape = None # Must be None because of direct addressing - this is obscure @property def loopy_arg(self): From c84a5c3099861db3497fefdc23453ab3e380194c Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 15 Oct 2021 13:40:43 +0100 Subject: [PATCH 673/809] Fix silly typo breaking mixed-space tests in Firedrake --- tsfc/kernel_args.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 1a4c54c3b8..2a65ffb343 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -372,8 +372,8 @@ def dtype(self): @property def loopy_arg(self): - rshape = np.prod([self.rnode_shape, *self.cshape], dtype=int) - cshape = np.prod([self.rnode_shape, *self.cshape], dtype=int) + rshape = np.prod([self.rnode_shape, *self.rshape], dtype=int) + cshape = np.prod([self.cnode_shape, *self.cshape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=(rshape, cshape)) @property From 9099a7e7aeb4d0050eb4163b8b465fa6386d2e7f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 15 Oct 2021 14:48:31 +0100 Subject: [PATCH 674/809] Linting --- tsfc/driver.py | 1 - tsfc/kernel_args.py | 1 - tsfc/kernel_interface/firedrake_loopy.py | 12 +++--------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index e133292595..d1718262e6 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -6,7 +6,6 @@ from functools import reduce from itertools import chain -import numpy as np from numpy import asarray import ufl diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 2a65ffb343..1e22f81a30 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -104,7 +104,6 @@ def __init__(self, elem, dtype, interior_facet=False): self._dtype = dtype self._interior_facet = interior_facet - @property def dtype(self): return self._dtype diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 27beefd17b..4cb80d172d 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -1,18 +1,11 @@ -import abc -import enum import numpy from collections import namedtuple -from itertools import chain, product from functools import partial -import finat from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import gem from gem.flop_count import count_flops -from gem.optimise import remove_componenttensors as prune - -import loopy as lp from tsfc import kernel_args from tsfc.finatinterface import create_element @@ -28,6 +21,8 @@ def make_builder(*args, **kwargs): return partial(KernelBuilder, *args, **kwargs) + + class Kernel: __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", @@ -122,7 +117,7 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - kernel_arg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) + kernel_arg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) self.cell_sizes_arg = kernel_arg self._cell_sizes = expression @@ -422,7 +417,6 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal elements = (element,) - if len(arguments) == 1 or diagonal: finat_element, = elements return kernel_args.VectorOutputKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal) From 604d69d59bb5c2d6e79af06e58340c8506df3cac Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 19 Oct 2021 11:20:24 +0100 Subject: [PATCH 675/809] Add function space ID information to kernel args This is so that we can tell PyOP2 whether or not the maps are the same (and hence only need to be unpacked once). I am not sure how robust id() is for this purpose. --- tsfc/kernel_args.py | 41 +++++++++++++++++++++--- tsfc/kernel_interface/firedrake_loopy.py | 29 ++++++++++++++--- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 1e22f81a30..597b3e981a 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -99,8 +99,9 @@ class CoordinatesKernelArg(RankOneKernelArg): name = "coords" intent = Intent.IN - def __init__(self, elem, dtype, interior_facet=False): + def __init__(self, elem, fs_id, dtype, interior_facet=False): self._elem = _ElementHandler(elem) + self._fs_id = fs_id self._dtype = dtype self._interior_facet = interior_facet @@ -122,6 +123,10 @@ def loopy_arg(self): shape = np.prod([self.node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) + @property + def function_space_id(self): + return self._fs_id + class ConstantKernelArg(RankZeroKernelArg): @@ -153,9 +158,10 @@ def loopy_arg(self): class CoefficientKernelArg(RankOneKernelArg): - def __init__(self, name, elem, dtype, *, interior_facet=False): + def __init__(self, name, elem, fs_id, dtype, *, interior_facet=False): self._name = name self._elem = _ElementHandler(elem) + self._fs_id = fs_id self._dtype = dtype self._interior_facet = interior_facet @@ -185,6 +191,10 @@ def loopy_arg(self): shape = np.prod([self.node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) + @property + def function_space_id(self): + return self._fs_id + class CellOrientationsKernelArg(RankOneKernelArg): @@ -212,8 +222,9 @@ class CellSizesKernelArg(RankOneKernelArg): name = "cell_sizes" intent = Intent.IN - def __init__(self, elem, dtype, *, interior_facet=False): + def __init__(self, elem, fs_id, dtype, *, interior_facet=False): self._elem = _ElementHandler(elem) + self._fs_id = fs_id self._dtype = dtype self._interior_facet = interior_facet @@ -235,6 +246,10 @@ def loopy_arg(self): shape = np.prod([self.node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) + @property + def function_space_id(self): + return self._fs_id + class FacetKernelArg(RankOneKernelArg, abc.ABC): @@ -259,6 +274,7 @@ class InteriorFacetKernelArg(FacetKernelArg): shape = (2,) +# TODO Find a case where we actually need to use this. # class TabulationKernelArg(KernelArg): # rank = 1 @@ -305,9 +321,10 @@ def make_gem_exprs(self, multiindices): class VectorOutputKernelArg(RankOneKernelArg, OutputKernelArg): def __init__( - self, elem, dtype, *, interior_facet=False, diagonal=False + self, elem, fs_id, dtype, *, interior_facet=False, diagonal=False ): self._elem = _ElementHandler(elem) + self._fs_id = fs_id self._dtype = dtype self._interior_facet = interior_facet @@ -331,6 +348,10 @@ def loopy_arg(self): shape = np.prod([self.node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) + @property + def function_space_id(self): + return self._fs_id + # TODO Function please def make_gem_exprs(self, multiindices): u_shape = np.array([np.prod(self._elem._elem.index_shape, dtype=int)]) @@ -359,9 +380,11 @@ def _make_expression(self, restricted, multiindices): class MatrixOutputKernelArg(RankTwoKernelArg, OutputKernelArg): - def __init__(self, relem, celem, dtype, *, interior_facet=False): + def __init__(self, relem, celem, rfs_id, cfs_id, dtype, *, interior_facet=False): self._relem = _ElementHandler(relem) self._celem = _ElementHandler(celem) + self._rfs_id = rfs_id + self._cfs_id = cfs_id self._dtype = dtype self._interior_facet = interior_facet @@ -393,6 +416,14 @@ def cnode_shape(self): shape = self._celem.node_shape return 2*shape if self._interior_facet else shape + @property + def rfunction_space_id(self): + return self._rfs_id + + @property + def cfunction_space_id(self): + return self._cfs_id + def make_gem_exprs(self, multiindices): u_shape = np.array([np.prod(elem._elem.index_shape, dtype=int) for elem in [self._relem, self._celem]]) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 4cb80d172d..ad80b11a88 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -2,6 +2,7 @@ from collections import namedtuple from functools import partial +import ufl from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import gem @@ -372,15 +373,20 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False): expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 + # A bad way to check if we are using the same function space (so we don't unpack + # too many maps in PyOP2) + fs_id = _get_function_space_id(coefficient.ufl_function_space()) + # This is truly disgusting, clean up ASAP if name == "cell_sizes": - kernel_arg = kernel_args.CellSizesKernelArg(finat_element, dtype, interior_facet=interior_facet) + kernel_arg = kernel_args.CellSizesKernelArg(finat_element, fs_id, dtype, interior_facet=interior_facet) elif name == "coords": - kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, dtype, interior_facet=interior_facet) + kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, fs_id, dtype, interior_facet=interior_facet) else: kernel_arg = kernel_args.CoefficientKernelArg( name, finat_element, + fs_id, dtype, interior_facet=interior_facet ) @@ -406,25 +412,40 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal return kernel_args.ScalarOutputKernelArg(scalar_type) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + fs_ids = tuple(_get_function_space_id(arg.ufl_function_space()) for arg in arguments) if diagonal: if len(arguments) != 2: raise ValueError("Diagonal only for 2-forms") try: element, = set(elements) + fs_id, = set(fs_ids) except ValueError: raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") elements = (element,) + fs_ids = (fs_id,) if len(arguments) == 1 or diagonal: finat_element, = elements - return kernel_args.VectorOutputKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal) + fs_id, = fs_ids + return kernel_args.VectorOutputKernelArg(finat_element, fs_id, scalar_type, interior_facet=interior_facet, diagonal=diagonal) elif len(arguments) == 2: rfinat_element, cfinat_element = elements + rfs_id, cfs_id = fs_ids return kernel_args.MatrixOutputKernelArg( - rfinat_element, cfinat_element, scalar_type, + rfinat_element, cfinat_element, rfs_id, cfs_id, scalar_type, interior_facet=interior_facet ) else: raise AssertionError + + +def _get_function_space_id(func_space): + # We need to kill any tensor structure here + elem = func_space.ufl_element() + if isinstance(elem, (ufl.VectorElement, ufl.TensorElement)): + elem = elem._sub_element + + # Here we copy func_space._ufl_hash_data_ but with new element + return (func_space.ufl_domain()._ufl_hash_data_(), elem._ufl_hash_data_()) From 5d984781afdb5e6cae1a915ca773e3e24ad1a324 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 25 Oct 2021 16:46:26 +0100 Subject: [PATCH 676/809] Empty kernels included in output --- tsfc/driver.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2e89ecca32..bf6b8c78e4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -57,8 +57,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr for integral_data in fd.integral_data: start = time.time() kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface, coffee=coffee, diagonal=diagonal) - if kernel is not None: - kernels.append(kernel) + kernels.append(kernel) logger.info(GREEN % "compile_integral finished in %g seconds.", time.time() - start) logger.info(GREEN % "TSFC finished in %g seconds.", time.time() - cpu_time) From 3153275fb97ad09b5e3739392f9efd9cdc23a24a Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 1 Nov 2021 11:49:52 +0000 Subject: [PATCH 677/809] Expunge function_space_id This was a misguided attempt to try and determine from the element information whether or not Firedrake would reuse maps. The better approach is to use exactly the same logic which is what we now do in assemble. --- tsfc/kernel_args.py | 40 +++--------------------- tsfc/kernel_interface/firedrake_loopy.py | 29 +++-------------- 2 files changed, 9 insertions(+), 60 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 597b3e981a..9bba5d2c32 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -99,9 +99,8 @@ class CoordinatesKernelArg(RankOneKernelArg): name = "coords" intent = Intent.IN - def __init__(self, elem, fs_id, dtype, interior_facet=False): + def __init__(self, elem, dtype, interior_facet=False): self._elem = _ElementHandler(elem) - self._fs_id = fs_id self._dtype = dtype self._interior_facet = interior_facet @@ -123,10 +122,6 @@ def loopy_arg(self): shape = np.prod([self.node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) - @property - def function_space_id(self): - return self._fs_id - class ConstantKernelArg(RankZeroKernelArg): @@ -158,10 +153,9 @@ def loopy_arg(self): class CoefficientKernelArg(RankOneKernelArg): - def __init__(self, name, elem, fs_id, dtype, *, interior_facet=False): + def __init__(self, name, elem, dtype, *, interior_facet=False): self._name = name self._elem = _ElementHandler(elem) - self._fs_id = fs_id self._dtype = dtype self._interior_facet = interior_facet @@ -191,10 +185,6 @@ def loopy_arg(self): shape = np.prod([self.node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) - @property - def function_space_id(self): - return self._fs_id - class CellOrientationsKernelArg(RankOneKernelArg): @@ -222,9 +212,8 @@ class CellSizesKernelArg(RankOneKernelArg): name = "cell_sizes" intent = Intent.IN - def __init__(self, elem, fs_id, dtype, *, interior_facet=False): + def __init__(self, elem, dtype, *, interior_facet=False): self._elem = _ElementHandler(elem) - self._fs_id = fs_id self._dtype = dtype self._interior_facet = interior_facet @@ -246,10 +235,6 @@ def loopy_arg(self): shape = np.prod([self.node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) - @property - def function_space_id(self): - return self._fs_id - class FacetKernelArg(RankOneKernelArg, abc.ABC): @@ -321,10 +306,9 @@ def make_gem_exprs(self, multiindices): class VectorOutputKernelArg(RankOneKernelArg, OutputKernelArg): def __init__( - self, elem, fs_id, dtype, *, interior_facet=False, diagonal=False + self, elem, dtype, *, interior_facet=False, diagonal=False ): self._elem = _ElementHandler(elem) - self._fs_id = fs_id self._dtype = dtype self._interior_facet = interior_facet @@ -348,10 +332,6 @@ def loopy_arg(self): shape = np.prod([self.node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) - @property - def function_space_id(self): - return self._fs_id - # TODO Function please def make_gem_exprs(self, multiindices): u_shape = np.array([np.prod(self._elem._elem.index_shape, dtype=int)]) @@ -380,11 +360,9 @@ def _make_expression(self, restricted, multiindices): class MatrixOutputKernelArg(RankTwoKernelArg, OutputKernelArg): - def __init__(self, relem, celem, rfs_id, cfs_id, dtype, *, interior_facet=False): + def __init__(self, relem, celem, dtype, *, interior_facet=False): self._relem = _ElementHandler(relem) self._celem = _ElementHandler(celem) - self._rfs_id = rfs_id - self._cfs_id = cfs_id self._dtype = dtype self._interior_facet = interior_facet @@ -416,14 +394,6 @@ def cnode_shape(self): shape = self._celem.node_shape return 2*shape if self._interior_facet else shape - @property - def rfunction_space_id(self): - return self._rfs_id - - @property - def cfunction_space_id(self): - return self._cfs_id - def make_gem_exprs(self, multiindices): u_shape = np.array([np.prod(elem._elem.index_shape, dtype=int) for elem in [self._relem, self._celem]]) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index ad80b11a88..4cb80d172d 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -2,7 +2,6 @@ from collections import namedtuple from functools import partial -import ufl from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import gem @@ -373,20 +372,15 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False): expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - # A bad way to check if we are using the same function space (so we don't unpack - # too many maps in PyOP2) - fs_id = _get_function_space_id(coefficient.ufl_function_space()) - # This is truly disgusting, clean up ASAP if name == "cell_sizes": - kernel_arg = kernel_args.CellSizesKernelArg(finat_element, fs_id, dtype, interior_facet=interior_facet) + kernel_arg = kernel_args.CellSizesKernelArg(finat_element, dtype, interior_facet=interior_facet) elif name == "coords": - kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, fs_id, dtype, interior_facet=interior_facet) + kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, dtype, interior_facet=interior_facet) else: kernel_arg = kernel_args.CoefficientKernelArg( name, finat_element, - fs_id, dtype, interior_facet=interior_facet ) @@ -412,40 +406,25 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal return kernel_args.ScalarOutputKernelArg(scalar_type) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - fs_ids = tuple(_get_function_space_id(arg.ufl_function_space()) for arg in arguments) if diagonal: if len(arguments) != 2: raise ValueError("Diagonal only for 2-forms") try: element, = set(elements) - fs_id, = set(fs_ids) except ValueError: raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") elements = (element,) - fs_ids = (fs_id,) if len(arguments) == 1 or diagonal: finat_element, = elements - fs_id, = fs_ids - return kernel_args.VectorOutputKernelArg(finat_element, fs_id, scalar_type, interior_facet=interior_facet, diagonal=diagonal) + return kernel_args.VectorOutputKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal) elif len(arguments) == 2: rfinat_element, cfinat_element = elements - rfs_id, cfs_id = fs_ids return kernel_args.MatrixOutputKernelArg( - rfinat_element, cfinat_element, rfs_id, cfs_id, scalar_type, + rfinat_element, cfinat_element, scalar_type, interior_facet=interior_facet ) else: raise AssertionError - - -def _get_function_space_id(func_space): - # We need to kill any tensor structure here - elem = func_space.ufl_element() - if isinstance(elem, (ufl.VectorElement, ufl.TensorElement)): - elem = elem._sub_element - - # Here we copy func_space._ufl_hash_data_ but with new element - return (func_space.ufl_domain()._ufl_hash_data_(), elem._ufl_hash_data_()) From 344212428b6ae88f4b3f97428f3493b8e54b20ed Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 3 Nov 2021 12:20:52 +0000 Subject: [PATCH 678/809] Stop returning empty kernels --- tsfc/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index bf6b8c78e4..2e89ecca32 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -57,7 +57,8 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr for integral_data in fd.integral_data: start = time.time() kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface, coffee=coffee, diagonal=diagonal) - kernels.append(kernel) + if kernel is not None: + kernels.append(kernel) logger.info(GREEN % "compile_integral finished in %g seconds.", time.time() - start) logger.info(GREEN % "TSFC finished in %g seconds.", time.time() - cpu_time) From ad653a66342b38e574729db14360c6198d33e6e1 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 4 Nov 2021 17:17:35 +0000 Subject: [PATCH 679/809] Ugly hack for interior facet horiz integrals For reasons unbeknownst to me, interior facet horizontal integrals need to have double size inside the kernel but only single sized map arity for the wrapper kernel (they use the cell->node map not the interior_facet->node map). I track this and can now generate the right wrapper code for these integrals. A nicer solution would be to pass the integral_type around. --- tsfc/kernel_args.py | 105 +++++++++++++++++++---- tsfc/kernel_interface/firedrake_loopy.py | 34 +++++--- 2 files changed, 108 insertions(+), 31 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 9bba5d2c32..624c9678ed 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -2,6 +2,7 @@ import enum import itertools +import FIAT import finat import gem from gem.optimise import remove_componenttensors as prune @@ -99,10 +100,11 @@ class CoordinatesKernelArg(RankOneKernelArg): name = "coords" intent = Intent.IN - def __init__(self, elem, dtype, interior_facet=False): + def __init__(self, elem, dtype, interior_facet=False, interior_facet_horiz=False): self._elem = _ElementHandler(elem) self._dtype = dtype self._interior_facet = interior_facet + self._interior_facet_horiz = interior_facet_horiz @property def dtype(self): @@ -115,11 +117,19 @@ def shape(self): @property def node_shape(self): shape = self._elem.node_shape - return 2*shape if self._interior_facet else shape + if self._interior_facet: + if self._interior_facet_horiz: + return shape + else: + return 2 * shape + else: + return shape @property def loopy_arg(self): - shape = np.prod([self.node_shape, *self.shape], dtype=int) + node_shape = self._elem.node_shape + node_shape = 2*node_shape if self._interior_facet else node_shape + shape = np.prod([node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) @@ -153,11 +163,12 @@ def loopy_arg(self): class CoefficientKernelArg(RankOneKernelArg): - def __init__(self, name, elem, dtype, *, interior_facet=False): + def __init__(self, name, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): self._name = name self._elem = _ElementHandler(elem) self._dtype = dtype self._interior_facet = interior_facet + self._interior_facet_horiz = interior_facet_horiz @property def name(self): @@ -178,11 +189,20 @@ def shape(self): @property def node_shape(self): shape = self._elem.node_shape - return 2*shape if self._interior_facet else shape + + if self._interior_facet: + if self._interior_facet_horiz: + return shape + else: + return 2 * shape + else: + return shape @property def loopy_arg(self): - shape = np.prod([self.node_shape, *self.shape], dtype=int) + node_shape = self._elem.node_shape + node_shape = 2*node_shape if self._interior_facet else node_shape + shape = np.prod([node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) @@ -194,8 +214,9 @@ class CellOrientationsKernelArg(RankOneKernelArg): node_shape = 1 - def __init__(self, interior_facet=False): + def __init__(self, interior_facet=False, interior_facet_horiz=False): self._interior_facet = interior_facet + assert not interior_facet_horiz @property def loopy_arg(self): @@ -212,10 +233,11 @@ class CellSizesKernelArg(RankOneKernelArg): name = "cell_sizes" intent = Intent.IN - def __init__(self, elem, dtype, *, interior_facet=False): + def __init__(self, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): self._elem = _ElementHandler(elem) self._dtype = dtype self._interior_facet = interior_facet + self._interior_facet_horiz = interior_facet_horiz @property def dtype(self): @@ -228,11 +250,19 @@ def shape(self): @property def node_shape(self): shape = self._elem.node_shape - return 2*shape if self._interior_facet else shape + if self._interior_facet: + if self._interior_facet_horiz: + return shape + else: + return 2 * shape + else: + return shape @property def loopy_arg(self): - shape = np.prod([self.node_shape, *self.shape], dtype=int) + node_shape = self._elem.node_shape + node_shape = 2*node_shape if self._interior_facet else node_shape + shape = np.prod([node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) @@ -306,12 +336,13 @@ def make_gem_exprs(self, multiindices): class VectorOutputKernelArg(RankOneKernelArg, OutputKernelArg): def __init__( - self, elem, dtype, *, interior_facet=False, diagonal=False + self, elem, dtype, *, interior_facet=False, diagonal=False, interior_facet_horiz=False ): self._elem = _ElementHandler(elem) self._dtype = dtype self._interior_facet = interior_facet + self._interior_facet_horiz = interior_facet_horiz self._diagonal = diagonal @property @@ -325,11 +356,19 @@ def shape(self): @property def node_shape(self): shape = self._elem.node_shape - return 2*shape if self._interior_facet else shape + if self._interior_facet: + if self._interior_facet_horiz: + return shape + else: + return 2 * shape + else: + return shape @property def loopy_arg(self): - shape = np.prod([self.node_shape, *self.shape], dtype=int) + node_shape = self._elem.node_shape + node_shape = 2*node_shape if self._interior_facet else node_shape + shape = np.prod([node_shape, *self.shape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=shape) # TODO Function please @@ -360,11 +399,12 @@ def _make_expression(self, restricted, multiindices): class MatrixOutputKernelArg(RankTwoKernelArg, OutputKernelArg): - def __init__(self, relem, celem, dtype, *, interior_facet=False): + def __init__(self, relem, celem, dtype, *, interior_facet=False, interior_facet_horiz=False): self._relem = _ElementHandler(relem) self._celem = _ElementHandler(celem) self._dtype = dtype self._interior_facet = interior_facet + self._interior_facet_horiz = interior_facet_horiz @property def dtype(self): @@ -372,8 +412,14 @@ def dtype(self): @property def loopy_arg(self): - rshape = np.prod([self.rnode_shape, *self.rshape], dtype=int) - cshape = np.prod([self.cnode_shape, *self.cshape], dtype=int) + rnode_shape = self._relem.node_shape + cnode_shape = self._celem.node_shape + + rnode_shape = 2*rnode_shape if self._interior_facet else rnode_shape + cnode_shape = 2*cnode_shape if self._interior_facet else cnode_shape + + rshape = np.prod([rnode_shape, *self.rshape], dtype=int) + cshape = np.prod([cnode_shape, *self.cshape], dtype=int) return lp.GlobalArg(self.name, self.dtype, shape=(rshape, cshape)) @property @@ -387,12 +433,24 @@ def cshape(self): @property def rnode_shape(self): shape = self._relem.node_shape - return 2*shape if self._interior_facet else shape + if self._interior_facet: + if self._interior_facet_horiz: + return shape + else: + return 2 * shape + else: + return shape @property def cnode_shape(self): shape = self._celem.node_shape - return 2*shape if self._interior_facet else shape + if self._interior_facet: + if self._interior_facet_horiz: + return shape + else: + return 2 * shape + else: + return shape def make_gem_exprs(self, multiindices): u_shape = np.array([np.prod(elem._elem.index_shape, dtype=int) @@ -434,6 +492,17 @@ def node_shape(self): def tensor_shape(self): return self._elem._shape if self._is_tensor_element else (1,) + @property + def is_mixed(self): + return (isinstance(self._elem, finat.EnrichedElement) + and isinstance(self._elem.fiat_equivalent, FIAT.MixedElement)) + + def split(self): + if not self.is_mixed: + raise ValueError("Cannot split a non-mixed element") + + return tuple([type(self)(subelem.element) for subelem in self._elem.elements]) + @property def _is_tensor_element(self): return isinstance(self._elem, finat.TensorFiniteElement) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 4cb80d172d..655b59436d 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -65,13 +65,14 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, class KernelBuilderBase(_KernelBuilderBase): - def __init__(self, scalar_type, interior_facet=False): + def __init__(self, scalar_type, interior_facet=False, interior_facet_horiz=False): """Initialise a kernel builder. :arg interior_facet: kernel accesses two cells """ super(KernelBuilderBase, self).__init__(scalar_type=scalar_type, interior_facet=interior_facet) + self.interior_facet_horiz = interior_facet_horiz # Cell orientation if self.interior_facet: @@ -95,7 +96,8 @@ def _coefficient(self, coefficient, name): :arg name: coefficient name :returns: loopy argument for the coefficient """ - kernel_arg, expression = prepare_coefficient(coefficient, name, self.scalar_type, self.interior_facet) + kernel_arg, expression = prepare_coefficient(coefficient, name, self.scalar_type, + self.interior_facet, self.interior_facet_horiz) self.coefficient_map[coefficient] = expression return kernel_arg @@ -117,7 +119,9 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - kernel_arg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) + kernel_arg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, + interior_facet=self.interior_facet, + interior_facet_horiz=self.interior_facet_horiz) self.cell_sizes_arg = kernel_arg self._cell_sizes = expression @@ -199,7 +203,9 @@ class KernelBuilder(KernelBuilderBase): def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont_split=(), diagonal=False): """Initialise a kernel builder.""" - super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) + super(KernelBuilder, self).__init__(scalar_type, + integral_type.startswith("interior_facet"), + integral_type=="interior_facet_horiz") self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) @@ -232,7 +238,7 @@ def set_arguments(self, arguments, multiindices): """ kernel_arg = prepare_arguments( arguments, self.scalar_type, interior_facet=self.interior_facet, - diagonal=self.diagonal) + interior_facet_horiz=self.interior_facet_horiz, diagonal=self.diagonal) self.local_tensor = kernel_arg return kernel_arg.make_gem_exprs(multiindices) @@ -246,7 +252,9 @@ def set_coordinates(self, domain): self.domain_coordinate[domain] = f # TODO Copy-pasted from _coefficient - needs refactor # self.coordinates_arg = self._coefficient(f, "coords") - kernel_arg, expression = prepare_coefficient(f, "coords", self.scalar_type, self.interior_facet) + kernel_arg, expression = prepare_coefficient(f, "coords", self.scalar_type, + self.interior_facet, + self.interior_facet_horiz) self.coefficient_map[f] = expression self.coordinates_arg = kernel_arg @@ -337,7 +345,7 @@ def construct_empty_kernel(self, name): # TODO Returning is_constant is nasty. Refactor. -def prepare_coefficient(coefficient, name, dtype, interior_facet=False): +def prepare_coefficient(coefficient, name, dtype, interior_facet=False, interior_facet_horiz=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. @@ -374,20 +382,20 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False): # This is truly disgusting, clean up ASAP if name == "cell_sizes": - kernel_arg = kernel_args.CellSizesKernelArg(finat_element, dtype, interior_facet=interior_facet) + kernel_arg = kernel_args.CellSizesKernelArg(finat_element, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) elif name == "coords": - kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, dtype, interior_facet=interior_facet) + kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) else: kernel_arg = kernel_args.CoefficientKernelArg( name, finat_element, dtype, - interior_facet=interior_facet + interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz ) return kernel_arg, expression -def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=False): +def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_facet_horiz=False, diagonal=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. @@ -419,12 +427,12 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, diagonal=Fal if len(arguments) == 1 or diagonal: finat_element, = elements - return kernel_args.VectorOutputKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal) + return kernel_args.VectorOutputKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal, interior_facet_horiz=interior_facet_horiz) elif len(arguments) == 2: rfinat_element, cfinat_element = elements return kernel_args.MatrixOutputKernelArg( rfinat_element, cfinat_element, scalar_type, - interior_facet=interior_facet + interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz ) else: raise AssertionError From af4c715e692465a70807ba0fb456a695d1c9f38a Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 5 Nov 2021 00:04:27 +0000 Subject: [PATCH 680/809] Refactor kernel_args.py The motivation here is to simplify the difficult logic where we worry about interior_facet vs interior_facet_horiz. Seems to work now on the Firedrake side. --- tsfc/kernel_args.py | 151 +++++++++-------------- tsfc/kernel_interface/firedrake_loopy.py | 2 +- 2 files changed, 57 insertions(+), 96 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 624c9678ed..db9e7ba070 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -2,7 +2,6 @@ import enum import itertools -import FIAT import finat import gem from gem.optimise import remove_componenttensors as prune @@ -101,10 +100,9 @@ class CoordinatesKernelArg(RankOneKernelArg): intent = Intent.IN def __init__(self, elem, dtype, interior_facet=False, interior_facet_horiz=False): - self._elem = _ElementHandler(elem) + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) self._dtype = dtype self._interior_facet = interior_facet - self._interior_facet_horiz = interior_facet_horiz @property def dtype(self): @@ -116,21 +114,11 @@ def shape(self): @property def node_shape(self): - shape = self._elem.node_shape - if self._interior_facet: - if self._interior_facet_horiz: - return shape - else: - return 2 * shape - else: - return shape + return self._elem.node_shape @property def loopy_arg(self): - node_shape = self._elem.node_shape - node_shape = 2*node_shape if self._interior_facet else node_shape - shape = np.prod([node_shape, *self.shape], dtype=int) - return lp.GlobalArg(self.name, self.dtype, shape=shape) + return lp.GlobalArg(self.name, self.dtype, shape=self._elem.loopy_shape) class ConstantKernelArg(RankZeroKernelArg): @@ -165,10 +153,9 @@ class CoefficientKernelArg(RankOneKernelArg): def __init__(self, name, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): self._name = name - self._elem = _ElementHandler(elem) + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) self._dtype = dtype self._interior_facet = interior_facet - self._interior_facet_horiz = interior_facet_horiz @property def name(self): @@ -188,36 +175,28 @@ def shape(self): @property def node_shape(self): - shape = self._elem.node_shape - - if self._interior_facet: - if self._interior_facet_horiz: - return shape - else: - return 2 * shape - else: - return shape + return self._elem.node_shape @property def loopy_arg(self): - node_shape = self._elem.node_shape - node_shape = 2*node_shape if self._interior_facet else node_shape - shape = np.prod([node_shape, *self.shape], dtype=int) - return lp.GlobalArg(self.name, self.dtype, shape=shape) + return lp.GlobalArg(self.name, self.dtype, shape=self._elem.loopy_shape) class CellOrientationsKernelArg(RankOneKernelArg): name = "cell_orientations" intent = Intent.IN - dtype = np.int32 - - node_shape = 1 - def __init__(self, interior_facet=False, interior_facet_horiz=False): + def __init__(self, shape=(1,), dtype=np.int32, interior_facet=False, interior_facet_horiz=False): + self._shape = shape + self._dtype = dtype self._interior_facet = interior_facet assert not interior_facet_horiz + @property + def dtype(self): + return self._dtype + @property def loopy_arg(self): shape = np.prod([self.node_shape, *self.shape], dtype=int) @@ -225,7 +204,11 @@ def loopy_arg(self): @property def shape(self): - return (2,) if self._interior_facet else (1,) + return self._shape + + @property + def node_shape(self): + return 2 if self._interior_facet else 1 class CellSizesKernelArg(RankOneKernelArg): @@ -234,10 +217,8 @@ class CellSizesKernelArg(RankOneKernelArg): intent = Intent.IN def __init__(self, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): - self._elem = _ElementHandler(elem) + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) self._dtype = dtype - self._interior_facet = interior_facet - self._interior_facet_horiz = interior_facet_horiz @property def dtype(self): @@ -249,21 +230,11 @@ def shape(self): @property def node_shape(self): - shape = self._elem.node_shape - if self._interior_facet: - if self._interior_facet_horiz: - return shape - else: - return 2 * shape - else: - return shape + return self._elem.node_shape @property def loopy_arg(self): - node_shape = self._elem.node_shape - node_shape = 2*node_shape if self._interior_facet else node_shape - shape = np.prod([node_shape, *self.shape], dtype=int) - return lp.GlobalArg(self.name, self.dtype, shape=shape) + return lp.GlobalArg(self.name, self.dtype, shape=self._elem.loopy_shape) class FacetKernelArg(RankOneKernelArg, abc.ABC): @@ -338,11 +309,10 @@ class VectorOutputKernelArg(RankOneKernelArg, OutputKernelArg): def __init__( self, elem, dtype, *, interior_facet=False, diagonal=False, interior_facet_horiz=False ): - self._elem = _ElementHandler(elem) + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) self._dtype = dtype self._interior_facet = interior_facet - self._interior_facet_horiz = interior_facet_horiz self._diagonal = diagonal @property @@ -355,21 +325,11 @@ def shape(self): @property def node_shape(self): - shape = self._elem.node_shape - if self._interior_facet: - if self._interior_facet_horiz: - return shape - else: - return 2 * shape - else: - return shape + return self._elem.node_shape @property def loopy_arg(self): - node_shape = self._elem.node_shape - node_shape = 2*node_shape if self._interior_facet else node_shape - shape = np.prod([node_shape, *self.shape], dtype=int) - return lp.GlobalArg(self.name, self.dtype, shape=shape) + return lp.GlobalArg(self.name, self.dtype, shape=self._elem.loopy_shape) # TODO Function please def make_gem_exprs(self, multiindices): @@ -400,11 +360,10 @@ def _make_expression(self, restricted, multiindices): class MatrixOutputKernelArg(RankTwoKernelArg, OutputKernelArg): def __init__(self, relem, celem, dtype, *, interior_facet=False, interior_facet_horiz=False): - self._relem = _ElementHandler(relem) - self._celem = _ElementHandler(celem) + self._relem = _ElementHandler(relem, interior_facet, interior_facet_horiz) + self._celem = _ElementHandler(celem, interior_facet, interior_facet_horiz) self._dtype = dtype self._interior_facet = interior_facet - self._interior_facet_horiz = interior_facet_horiz @property def dtype(self): @@ -412,14 +371,8 @@ def dtype(self): @property def loopy_arg(self): - rnode_shape = self._relem.node_shape - cnode_shape = self._celem.node_shape - - rnode_shape = 2*rnode_shape if self._interior_facet else rnode_shape - cnode_shape = 2*cnode_shape if self._interior_facet else cnode_shape - - rshape = np.prod([rnode_shape, *self.rshape], dtype=int) - cshape = np.prod([cnode_shape, *self.cshape], dtype=int) + rshape = self._relem.loopy_shape + cshape = self._celem.loopy_shape return lp.GlobalArg(self.name, self.dtype, shape=(rshape, cshape)) @property @@ -432,25 +385,11 @@ def cshape(self): @property def rnode_shape(self): - shape = self._relem.node_shape - if self._interior_facet: - if self._interior_facet_horiz: - return shape - else: - return 2 * shape - else: - return shape + return self._relem.node_shape @property def cnode_shape(self): - shape = self._celem.node_shape - if self._interior_facet: - if self._interior_facet_horiz: - return shape - else: - return 2 * shape - else: - return shape + return self._celem.node_shape def make_gem_exprs(self, multiindices): u_shape = np.array([np.prod(elem._elem.index_shape, dtype=int) @@ -477,8 +416,10 @@ def _make_expression(self, restricted, multiindices): class _ElementHandler: - def __init__(self, elem): + def __init__(self, elem, interior_facet=False, interior_facet_horiz=False): self._elem = elem + self._interior_facet = interior_facet + self._interior_facet_horiz = interior_facet_horiz @property def node_shape(self): @@ -486,16 +427,36 @@ def node_shape(self): shape = self._elem.index_shape[:-len(self.tensor_shape)] else: shape = self._elem.index_shape - return np.prod(shape, dtype=int) + + shape = np.prod(shape, dtype=int) + + if self._interior_facet and not self._interior_facet_horiz: + return 2 * shape + else: + return shape @property def tensor_shape(self): return self._elem._shape if self._is_tensor_element else (1,) + @property + def loopy_shape(self): + if self._is_tensor_element: + shape = self._elem.index_shape[:-len(self.tensor_shape)] + else: + shape = self._elem.index_shape + + shape = np.prod(shape, dtype=int) + + # We have to treat facets carefully as the local kernel needs double size but + # external map does not. + if self._interior_facet: + shape *= 2 + return np.prod([shape, *self.tensor_shape], dtype=int) + @property def is_mixed(self): - return (isinstance(self._elem, finat.EnrichedElement) - and isinstance(self._elem.fiat_equivalent, FIAT.MixedElement)) + return isinstance(self._elem, finat.EnrichedElement) and self._elem.is_mixed def split(self): if not self.is_mixed: diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 655b59436d..21e714ed9a 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -205,7 +205,7 @@ def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet"), - integral_type=="interior_facet_horiz") + integral_type == "interior_facet_horiz") self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) From 20e79726335b2609c1653958db6900ed935988ad Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 5 Nov 2021 12:27:35 +0000 Subject: [PATCH 681/809] Add TabulationKernelArg --- tsfc/kernel_args.py | 34 +++++++++++++++--------- tsfc/kernel_interface/firedrake_loopy.py | 2 +- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index db9e7ba070..0772f82bd3 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -260,21 +260,31 @@ class InteriorFacetKernelArg(FacetKernelArg): shape = (2,) -# TODO Find a case where we actually need to use this. -# class TabulationKernelArg(KernelArg): +class TabulationKernelArg(RankOneKernelArg): -# rank = 1 -# intent = Intent.IN + intent = Intent.IN + shape = (1,) + + def __init__(self, name, shape, dtype): + self._name = name + self._shape = shape + self._dtype = dtype -# def __init__(self, name, shape, dtype, interior_facet=False): -# self.name = name -# self.shape = shape -# self.dtype = dtype -# self.interior_facet = interior_facet + @property + def name(self): + return self._name -# @property -# def loopy_arg(self): -# raise NotImplementedError + @property + def node_shape(self): + return np.prod(self._shape, dtype=int) + + @property + def dtype(self): + return self._dtype + + @property + def loopy_arg(self): + return lp.GlobalArg(self._name, self._dtype, shape=self._shape) class OutputKernelArg(KernelArg, abc.ABC): diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 21e714ed9a..7dac4b801c 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -185,7 +185,7 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords) args.append(self.cell_sizes_arg) args.extend(self.kernel_args) for name_, shape in self.tabulations: - args.append(kernel_args.TabulationKernelArg(name_, self.scalar_type, shape)) + args.append(kernel_args.TabulationKernelArg(name_, shape, self.scalar_type)) loopy_args = [arg.loopy_arg for arg in args] From 2c3f3a7a64560da2fe5a0407f1f7d39fda80e55f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 8 Nov 2021 18:11:04 +0000 Subject: [PATCH 682/809] Add empty arguments to old interface kernel Needed for MG tests in Firedrake to pass as these use this older interface. --- tsfc/kernel_interface/firedrake.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 5eb15d2180..e1b503e6fc 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -21,7 +21,7 @@ def make_builder(*args, **kwargs): class Kernel(object): - __slots__ = ("ast", "integral_type", "oriented", "subdomain_id", + __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", "coefficient_numbers", "name", "__weakref__", "flop_count") @@ -41,13 +41,15 @@ class Kernel(object): :kwarg needs_cell_sizes: Does the kernel require cell sizes. :kwarg flop_count: Estimated total flops for this kernel. """ - def __init__(self, ast=None, integral_type=None, oriented=False, + def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, quadrature_rule=None, coefficient_numbers=(), needs_cell_sizes=False, flop_count=0): # Defaults self.ast = ast + assert arguments is None # only valid for loopy kernels + self.arguments = None self.integral_type = integral_type self.oriented = oriented self.domain_number = domain_number From b5f57c2cfe6644e9b9a1eb3872c28b3a491d5516 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 10 Nov 2021 13:47:14 +0000 Subject: [PATCH 683/809] Track real tensor product information We can't determine this from the FInAT element so we pass this as extra information. --- tsfc/kernel_args.py | 25 ++++++++++++------------ tsfc/kernel_interface/firedrake_loopy.py | 25 +++++++++++++++++++----- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 0772f82bd3..4d0a467b82 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -99,8 +99,8 @@ class CoordinatesKernelArg(RankOneKernelArg): name = "coords" intent = Intent.IN - def __init__(self, elem, dtype, interior_facet=False, interior_facet_horiz=False): - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) + def __init__(self, elem, isreal, dtype, interior_facet=False, interior_facet_horiz=False): + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz, isreal) self._dtype = dtype self._interior_facet = interior_facet @@ -151,9 +151,9 @@ def loopy_arg(self): class CoefficientKernelArg(RankOneKernelArg): - def __init__(self, name, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): + def __init__(self, name, elem, isreal, dtype, *, interior_facet=False, interior_facet_horiz=False): self._name = name - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz, isreal) self._dtype = dtype self._interior_facet = interior_facet @@ -216,8 +216,8 @@ class CellSizesKernelArg(RankOneKernelArg): name = "cell_sizes" intent = Intent.IN - def __init__(self, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) + def __init__(self, elem, isreal, dtype, *, interior_facet=False, interior_facet_horiz=False): + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz, isreal) self._dtype = dtype @property @@ -317,9 +317,9 @@ def make_gem_exprs(self, multiindices): class VectorOutputKernelArg(RankOneKernelArg, OutputKernelArg): def __init__( - self, elem, dtype, *, interior_facet=False, diagonal=False, interior_facet_horiz=False + self, elem, isreal, dtype, *, interior_facet=False, diagonal=False, interior_facet_horiz=False ): - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz, isreal) self._dtype = dtype self._interior_facet = interior_facet @@ -369,9 +369,9 @@ def _make_expression(self, restricted, multiindices): class MatrixOutputKernelArg(RankTwoKernelArg, OutputKernelArg): - def __init__(self, relem, celem, dtype, *, interior_facet=False, interior_facet_horiz=False): - self._relem = _ElementHandler(relem, interior_facet, interior_facet_horiz) - self._celem = _ElementHandler(celem, interior_facet, interior_facet_horiz) + def __init__(self, relem, risreal, celem, cisreal, dtype, *, interior_facet=False, interior_facet_horiz=False): + self._relem = _ElementHandler(relem, interior_facet, interior_facet_horiz, risreal) + self._celem = _ElementHandler(celem, interior_facet, interior_facet_horiz, cisreal) self._dtype = dtype self._interior_facet = interior_facet @@ -426,10 +426,11 @@ def _make_expression(self, restricted, multiindices): class _ElementHandler: - def __init__(self, elem, interior_facet=False, interior_facet_horiz=False): + def __init__(self, elem, interior_facet=False, interior_facet_horiz=False, is_real_tensor_product=False): self._elem = elem self._interior_facet = interior_facet self._interior_facet_horiz = interior_facet_horiz + self._is_real_tensor_product = is_real_tensor_product @property def node_shape(self): diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 7dac4b801c..d9f5eeabbe 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -2,6 +2,7 @@ from collections import namedtuple from functools import partial +import ufl from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import gem @@ -367,6 +368,7 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False, interior return kernel_arg, expression finat_element = create_element(coefficient.ufl_element()) + isreal = _is_real_tensor_product_element(coefficient.ufl_element()) shape = finat_element.index_shape size = numpy.prod(shape, dtype=int) @@ -382,13 +384,13 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False, interior # This is truly disgusting, clean up ASAP if name == "cell_sizes": - kernel_arg = kernel_args.CellSizesKernelArg(finat_element, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) + kernel_arg = kernel_args.CellSizesKernelArg(finat_element, isreal, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) elif name == "coords": - kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) + kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, isreal, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) else: kernel_arg = kernel_args.CoefficientKernelArg( name, - finat_element, + finat_element, isreal, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz ) @@ -414,6 +416,8 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_fac return kernel_args.ScalarOutputKernelArg(scalar_type) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + is_real_tensor_products = tuple(_is_real_tensor_product_element(arg.ufl_element()) + for arg in arguments) if diagonal: if len(arguments) != 2: @@ -424,15 +428,26 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_fac raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") elements = (element,) + is_real_tensor_products = (is_real_tensor_products[0],) if len(arguments) == 1 or diagonal: finat_element, = elements - return kernel_args.VectorOutputKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal, interior_facet_horiz=interior_facet_horiz) + is_real_tensor_product, = is_real_tensor_products + return kernel_args.VectorOutputKernelArg(finat_element, is_real_tensor_product, scalar_type, interior_facet=interior_facet, diagonal=diagonal, interior_facet_horiz=interior_facet_horiz) elif len(arguments) == 2: rfinat_element, cfinat_element = elements + ris_real, cisreal = is_real_tensor_products return kernel_args.MatrixOutputKernelArg( - rfinat_element, cfinat_element, scalar_type, + rfinat_element, ris_real, cfinat_element, cisreal, scalar_type, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz ) else: raise AssertionError + + +def _is_real_tensor_product_element(elem): + if isinstance(elem, ufl.TensorProductElement): + a, b = elem.sub_elements() + return b.family() == "Real" + + return False From 88a3edca9c4e8a00a5359ae471c8846b747136ee Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 10 Nov 2021 15:55:03 +0000 Subject: [PATCH 684/809] Fix tensor tensorproduct elements --- tsfc/kernel_interface/firedrake_loopy.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index d9f5eeabbe..ce0ac3ee47 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -446,8 +446,11 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_fac def _is_real_tensor_product_element(elem): - if isinstance(elem, ufl.TensorProductElement): - a, b = elem.sub_elements() + scalar_element = elem + if isinstance(elem, (ufl.VectorElement, ufl.TensorElement)): + scalar_element = elem.sub_elements()[0] + if isinstance(scalar_element, ufl.TensorProductElement): + a, b = scalar_element.sub_elements() return b.family() == "Real" return False From b3b1a1d565ea9a14a9a88eab38e7234661f41a0c Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 17 Nov 2021 13:25:06 +0000 Subject: [PATCH 685/809] Fix linting --- tsfc/kernel_args.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 4d0a467b82..cb4c76b694 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -286,6 +286,7 @@ def dtype(self): def loopy_arg(self): return lp.GlobalArg(self._name, self._dtype, shape=self._shape) + class OutputKernelArg(KernelArg, abc.ABC): name = "A" From 8143b72c9946b9c783efc092e61222e0d0f3eddb Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 17 Nov 2021 14:26:21 +0000 Subject: [PATCH 686/809] Expunge isreal nonsense FInAT now knows the difference between DG0 and Real so this extra parameter can go away. --- tsfc/finatinterface.py | 2 +- tsfc/kernel_args.py | 25 ++++++++++----------- tsfc/kernel_interface/firedrake_loopy.py | 28 +++++------------------- 3 files changed, 18 insertions(+), 37 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index ced2398140..42cb97ebc1 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -68,7 +68,7 @@ "RTCF": None, "NCE": None, "NCF": None, - "Real": finat.DiscontinuousLagrange, + "Real": finat.Real, "DPC": finat.DPC, "S": finat.Serendipity, "DPC L2": finat.DPC, diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index cb4c76b694..b5f05eebf6 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -99,8 +99,8 @@ class CoordinatesKernelArg(RankOneKernelArg): name = "coords" intent = Intent.IN - def __init__(self, elem, isreal, dtype, interior_facet=False, interior_facet_horiz=False): - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz, isreal) + def __init__(self, elem, dtype, interior_facet=False, interior_facet_horiz=False): + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) self._dtype = dtype self._interior_facet = interior_facet @@ -151,9 +151,9 @@ def loopy_arg(self): class CoefficientKernelArg(RankOneKernelArg): - def __init__(self, name, elem, isreal, dtype, *, interior_facet=False, interior_facet_horiz=False): + def __init__(self, name, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): self._name = name - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz, isreal) + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) self._dtype = dtype self._interior_facet = interior_facet @@ -216,8 +216,8 @@ class CellSizesKernelArg(RankOneKernelArg): name = "cell_sizes" intent = Intent.IN - def __init__(self, elem, isreal, dtype, *, interior_facet=False, interior_facet_horiz=False): - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz, isreal) + def __init__(self, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) self._dtype = dtype @property @@ -318,9 +318,9 @@ def make_gem_exprs(self, multiindices): class VectorOutputKernelArg(RankOneKernelArg, OutputKernelArg): def __init__( - self, elem, isreal, dtype, *, interior_facet=False, diagonal=False, interior_facet_horiz=False + self, elem, dtype, *, interior_facet=False, diagonal=False, interior_facet_horiz=False ): - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz, isreal) + self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) self._dtype = dtype self._interior_facet = interior_facet @@ -370,9 +370,9 @@ def _make_expression(self, restricted, multiindices): class MatrixOutputKernelArg(RankTwoKernelArg, OutputKernelArg): - def __init__(self, relem, risreal, celem, cisreal, dtype, *, interior_facet=False, interior_facet_horiz=False): - self._relem = _ElementHandler(relem, interior_facet, interior_facet_horiz, risreal) - self._celem = _ElementHandler(celem, interior_facet, interior_facet_horiz, cisreal) + def __init__(self, relem, celem, dtype, *, interior_facet=False, interior_facet_horiz=False): + self._relem = _ElementHandler(relem, interior_facet, interior_facet_horiz) + self._celem = _ElementHandler(celem, interior_facet, interior_facet_horiz) self._dtype = dtype self._interior_facet = interior_facet @@ -427,11 +427,10 @@ def _make_expression(self, restricted, multiindices): class _ElementHandler: - def __init__(self, elem, interior_facet=False, interior_facet_horiz=False, is_real_tensor_product=False): + def __init__(self, elem, interior_facet=False, interior_facet_horiz=False): self._elem = elem self._interior_facet = interior_facet self._interior_facet_horiz = interior_facet_horiz - self._is_real_tensor_product = is_real_tensor_product @property def node_shape(self): diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index ce0ac3ee47..7dac4b801c 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -2,7 +2,6 @@ from collections import namedtuple from functools import partial -import ufl from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import gem @@ -368,7 +367,6 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False, interior return kernel_arg, expression finat_element = create_element(coefficient.ufl_element()) - isreal = _is_real_tensor_product_element(coefficient.ufl_element()) shape = finat_element.index_shape size = numpy.prod(shape, dtype=int) @@ -384,13 +382,13 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False, interior # This is truly disgusting, clean up ASAP if name == "cell_sizes": - kernel_arg = kernel_args.CellSizesKernelArg(finat_element, isreal, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) + kernel_arg = kernel_args.CellSizesKernelArg(finat_element, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) elif name == "coords": - kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, isreal, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) + kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) else: kernel_arg = kernel_args.CoefficientKernelArg( name, - finat_element, isreal, + finat_element, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz ) @@ -416,8 +414,6 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_fac return kernel_args.ScalarOutputKernelArg(scalar_type) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - is_real_tensor_products = tuple(_is_real_tensor_product_element(arg.ufl_element()) - for arg in arguments) if diagonal: if len(arguments) != 2: @@ -428,29 +424,15 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_fac raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") elements = (element,) - is_real_tensor_products = (is_real_tensor_products[0],) if len(arguments) == 1 or diagonal: finat_element, = elements - is_real_tensor_product, = is_real_tensor_products - return kernel_args.VectorOutputKernelArg(finat_element, is_real_tensor_product, scalar_type, interior_facet=interior_facet, diagonal=diagonal, interior_facet_horiz=interior_facet_horiz) + return kernel_args.VectorOutputKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal, interior_facet_horiz=interior_facet_horiz) elif len(arguments) == 2: rfinat_element, cfinat_element = elements - ris_real, cisreal = is_real_tensor_products return kernel_args.MatrixOutputKernelArg( - rfinat_element, ris_real, cfinat_element, cisreal, scalar_type, + rfinat_element, cfinat_element, scalar_type, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz ) else: raise AssertionError - - -def _is_real_tensor_product_element(elem): - scalar_element = elem - if isinstance(elem, (ufl.VectorElement, ufl.TensorElement)): - scalar_element = elem.sub_elements()[0] - if isinstance(scalar_element, ufl.TensorProductElement): - a, b = scalar_element.sub_elements() - return b.family() == "Real" - - return False From 523ce6dab923f606cbd8649615a652333930cffd Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 3 Dec 2021 13:26:44 +0000 Subject: [PATCH 687/809] Make some changes I have moved a lot of content out of kernel_args.py and into Firedrake. Also we now track coefficient numbers so we can retrieve them from the form during assembly. --- tsfc/kernel_args.py | 138 ++++++----------------- tsfc/kernel_interface/firedrake_loopy.py | 51 ++++----- 2 files changed, 61 insertions(+), 128 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index b5f05eebf6..6d66449dad 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -35,45 +35,15 @@ def loopy_arg(self): class RankZeroKernelArg(KernelArg, abc.ABC): - - @abc.abstractproperty - def shape(self): - """The shape of the per-node tensor. - - For example, a scalar-valued element will have shape == (1,) whilst a 3-vector - would have shape (3,). - """ - ... + ... class RankOneKernelArg(KernelArg, abc.ABC): - - @abc.abstractproperty - def shape(self): - ... - - @abc.abstractproperty - def node_shape(self): - ... + ... class RankTwoKernelArg(KernelArg, abc.ABC): - - @abc.abstractproperty - def rshape(self): - ... - - @abc.abstractproperty - def cshape(self): - ... - - @abc.abstractproperty - def rnode_shape(self): - ... - - @abc.abstractproperty - def cnode_shape(self): - ... + ... class DualEvalOutputKernelArg(KernelArg): @@ -99,8 +69,8 @@ class CoordinatesKernelArg(RankOneKernelArg): name = "coords" intent = Intent.IN - def __init__(self, elem, dtype, interior_facet=False, interior_facet_horiz=False): - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) + def __init__(self, size, dtype, interior_facet=False): + self._size = size self._dtype = dtype self._interior_facet = interior_facet @@ -108,23 +78,16 @@ def __init__(self, elem, dtype, interior_facet=False, interior_facet_horiz=False def dtype(self): return self._dtype - @property - def shape(self): - return self._elem.tensor_shape - - @property - def node_shape(self): - return self._elem.node_shape - @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self._elem.loopy_shape) + return lp.GlobalArg(self.name, self.dtype, shape=(self._size,)) class ConstantKernelArg(RankZeroKernelArg): - def __init__(self, name, shape, dtype): + def __init__(self, name, number, shape, dtype): self._name = name + self.number = number self._shape = shape self._dtype = dtype @@ -151,11 +114,11 @@ def loopy_arg(self): class CoefficientKernelArg(RankOneKernelArg): - def __init__(self, name, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): + def __init__(self, name, number, size, dtype): self._name = name - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) + self.number = number + self._size = size self._dtype = dtype - self._interior_facet = interior_facet @property def name(self): @@ -169,17 +132,9 @@ def dtype(self): def intent(self): return Intent.IN - @property - def shape(self): - return self._elem.tensor_shape - - @property - def node_shape(self): - return self._elem.node_shape - @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self._elem.loopy_shape) + return lp.GlobalArg(self.name, self.dtype, shape=(self._size,)) class CellOrientationsKernelArg(RankOneKernelArg): @@ -216,25 +171,17 @@ class CellSizesKernelArg(RankOneKernelArg): name = "cell_sizes" intent = Intent.IN - def __init__(self, elem, dtype, *, interior_facet=False, interior_facet_horiz=False): - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) + def __init__(self, size, dtype): + self._size = size self._dtype = dtype @property def dtype(self): return self._dtype - @property - def shape(self): - return self._elem.tensor_shape - - @property - def node_shape(self): - return self._elem.node_shape - @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self._elem.loopy_shape) + return lp.GlobalArg(self.name, self.dtype, shape=(self._size,)) class FacetKernelArg(RankOneKernelArg, abc.ABC): @@ -317,10 +264,8 @@ def make_gem_exprs(self, multiindices): class VectorOutputKernelArg(RankOneKernelArg, OutputKernelArg): - def __init__( - self, elem, dtype, *, interior_facet=False, diagonal=False, interior_facet_horiz=False - ): - self._elem = _ElementHandler(elem, interior_facet, interior_facet_horiz) + def __init__(self, index_shape, dtype, *, interior_facet=False, diagonal=False): + self.index_shape, = index_shape self._dtype = dtype self._interior_facet = interior_facet @@ -331,21 +276,21 @@ def dtype(self): return self._dtype @property - def shape(self): - return self._elem.tensor_shape + def ushape(self): + return np.array([np.prod(self.index_shape, dtype=int)]) @property - def node_shape(self): - return self._elem.node_shape + def cshape(self): + return tuple(2*self.ushape) if self._interior_facet else tuple(self.ushape) @property def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self._elem.loopy_shape) + return lp.GlobalArg(self.name, self.dtype, shape=self.cshape) # TODO Function please def make_gem_exprs(self, multiindices): - u_shape = np.array([np.prod(self._elem._elem.index_shape, dtype=int)]) - c_shape = tuple(2*u_shape) if self._interior_facet else tuple(u_shape) + u_shape = self.ushape + c_shape = self.cshape if self._diagonal: multiindices = multiindices[:1] @@ -364,15 +309,14 @@ def make_gem_exprs(self, multiindices): # TODO More descriptive name def _make_expression(self, restricted, multiindices): - return gem.Indexed(gem.reshape(restricted, self._elem._elem.index_shape), + return gem.Indexed(gem.reshape(restricted, self.index_shape), tuple(itertools.chain(*multiindices))) class MatrixOutputKernelArg(RankTwoKernelArg, OutputKernelArg): - def __init__(self, relem, celem, dtype, *, interior_facet=False, interior_facet_horiz=False): - self._relem = _ElementHandler(relem, interior_facet, interior_facet_horiz) - self._celem = _ElementHandler(celem, interior_facet, interior_facet_horiz) + def __init__(self, shapes, dtype, *, interior_facet=False): + self._rshape, self._cshape = shapes self._dtype = dtype self._interior_facet = interior_facet @@ -382,30 +326,20 @@ def dtype(self): @property def loopy_arg(self): - rshape = self._relem.loopy_shape - cshape = self._celem.loopy_shape - return lp.GlobalArg(self.name, self.dtype, shape=(rshape, cshape)) - - @property - def rshape(self): - return self._relem.tensor_shape - - @property - def cshape(self): - return self._celem.tensor_shape + return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape) @property - def rnode_shape(self): - return self._relem.node_shape + def u_shape(self): + return np.array([np.prod(s, dtype=int) + for s in [self._rshape, self._cshape]]) @property - def cnode_shape(self): - return self._celem.node_shape + def c_shape(self): + return tuple(2*self.u_shape) if self._interior_facet else tuple(self.u_shape) def make_gem_exprs(self, multiindices): - u_shape = np.array([np.prod(elem._elem.index_shape, dtype=int) - for elem in [self._relem, self._celem]]) - c_shape = tuple(2*u_shape) if self._interior_facet else tuple(u_shape) + u_shape = self.u_shape + c_shape = self.c_shape if self._interior_facet: slicez = [ @@ -421,7 +355,7 @@ def make_gem_exprs(self, multiindices): # TODO More descriptive name def _make_expression(self, restricted, multiindices): - return gem.Indexed(gem.reshape(restricted, self._relem._elem.index_shape, self._celem._elem.index_shape), + return gem.Indexed(gem.reshape(restricted, self._rshape, self._cshape), tuple(itertools.chain(*multiindices))) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 7dac4b801c..3c5bf9b5e4 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -1,3 +1,4 @@ +import itertools import numpy from collections import namedtuple from functools import partial @@ -88,7 +89,7 @@ def __init__(self, scalar_type, interior_facet=False, interior_facet_horiz=False interior_facet=self.interior_facet ) - def _coefficient(self, coefficient, name): + def _coefficient(self, coefficient, name, number): """Prepare a coefficient. Adds glue code for the coefficient and adds the coefficient to the coefficient map. @@ -96,7 +97,7 @@ def _coefficient(self, coefficient, name): :arg name: coefficient name :returns: loopy argument for the coefficient """ - kernel_arg, expression = prepare_coefficient(coefficient, name, self.scalar_type, + kernel_arg, expression = prepare_coefficient(coefficient, name, number, self.scalar_type, self.interior_facet, self.interior_facet_horiz) self.coefficient_map[coefficient] = expression return kernel_arg @@ -119,7 +120,7 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - kernel_arg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, + kernel_arg, expression = prepare_coefficient(f, "cell_sizes", None, self.scalar_type, interior_facet=self.interior_facet, interior_facet_horiz=self.interior_facet_horiz) self.cell_sizes_arg = kernel_arg @@ -153,11 +154,11 @@ def set_coefficients(self, coefficients): subcoeffs = coefficient.split() # Firedrake-specific self.coefficients.extend(subcoeffs) self.coefficient_split[coefficient] = subcoeffs - self.kernel_args += [self._coefficient(subcoeff, "w_%d_%d" % (i, j)) + self.kernel_args += [self._coefficient(subcoeff, "w_%d_%d" % (i, j), None) for j, subcoeff in enumerate(subcoeffs)] else: self.coefficients.append(coefficient) - self.kernel_args.append(self._coefficient(coefficient, "w_%d" % (i,))) + self.kernel_args.append(self._coefficient(coefficient, "w_%d" % (i,), None)) def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be @@ -252,7 +253,7 @@ def set_coordinates(self, domain): self.domain_coordinate[domain] = f # TODO Copy-pasted from _coefficient - needs refactor # self.coordinates_arg = self._coefficient(f, "coords") - kernel_arg, expression = prepare_coefficient(f, "coords", self.scalar_type, + kernel_arg, expression = prepare_coefficient(f, "coords", None, self.scalar_type, self.interior_facet, self.interior_facet_horiz) self.coefficient_map[f] = expression @@ -266,6 +267,7 @@ def set_coefficients(self, integral_data, form_data): """ coefficients = [] coefficient_numbers = [] + ctr = itertools.count() # enabled_coefficients is a boolean array that indicates which # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): @@ -279,18 +281,19 @@ def set_coefficients(self, integral_data, form_data): else: split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) for element in coefficient.ufl_element().sub_elements()] - coefficients.extend(split) + for c_ in split: + number = next(ctr) + self.coefficient_args.append(self._coefficient(c_, f"w_{number}", number)) self.coefficient_split[coefficient] = split else: - coefficients.append(coefficient) + number = next(ctr) + self.coefficient_args.append(self._coefficient(coefficient, f"w_{number}", number)) # This is which coefficient in the original form the # current coefficient is. # Consider f*v*dx + g*v*ds, the full form contains two # coefficients, but each integral only requires one. coefficient_numbers.append(form_data.original_coefficient_positions[i]) - for i, coefficient in enumerate(coefficients): - self.coefficient_args.append( - self._coefficient(coefficient, "w_%d" % i)) + self.kernel.coefficient_numbers = tuple(coefficient_numbers) def register_requirements(self, ir): @@ -345,7 +348,7 @@ def construct_empty_kernel(self, name): # TODO Returning is_constant is nasty. Refactor. -def prepare_coefficient(coefficient, name, dtype, interior_facet=False, interior_facet_horiz=False): +def prepare_coefficient(coefficient, name, number, dtype, interior_facet=False, interior_facet_horiz=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. @@ -361,7 +364,7 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False, interior if coefficient.ufl_element().family() == 'Real': value_size = coefficient.ufl_element().value_size() - kernel_arg = kernel_args.ConstantKernelArg(name, (value_size,), dtype) + kernel_arg = kernel_args.ConstantKernelArg(name, number, (value_size,), dtype) expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) return kernel_arg, expression @@ -382,16 +385,13 @@ def prepare_coefficient(coefficient, name, dtype, interior_facet=False, interior # This is truly disgusting, clean up ASAP if name == "cell_sizes": - kernel_arg = kernel_args.CellSizesKernelArg(finat_element, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) + assert number is None + kernel_arg = kernel_args.CellSizesKernelArg(size, dtype) elif name == "coords": - kernel_arg = kernel_args.CoordinatesKernelArg(finat_element, dtype, interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz) + assert number is None + kernel_arg = kernel_args.CoordinatesKernelArg(size, dtype) else: - kernel_arg = kernel_args.CoefficientKernelArg( - name, - finat_element, - dtype, - interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz - ) + kernel_arg = kernel_args.CoefficientKernelArg(name, number, size, dtype) return kernel_arg, expression @@ -414,6 +414,7 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_fac return kernel_args.ScalarOutputKernelArg(scalar_type) elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + shapes = tuple(element.index_shape for element in elements) if diagonal: if len(arguments) != 2: @@ -424,15 +425,13 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_fac raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") elements = (element,) + shapes = tuple(element.index_shape for element in elements) if len(arguments) == 1 or diagonal: - finat_element, = elements - return kernel_args.VectorOutputKernelArg(finat_element, scalar_type, interior_facet=interior_facet, diagonal=diagonal, interior_facet_horiz=interior_facet_horiz) + return kernel_args.VectorOutputKernelArg(shapes, scalar_type, interior_facet=interior_facet, diagonal=diagonal) elif len(arguments) == 2: - rfinat_element, cfinat_element = elements return kernel_args.MatrixOutputKernelArg( - rfinat_element, cfinat_element, scalar_type, - interior_facet=interior_facet, interior_facet_horiz=interior_facet_horiz + shapes, scalar_type, interior_facet=interior_facet ) else: raise AssertionError From 0637ec5692df4ef8adbdcf99e7a452b8e2149bea Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 7 Dec 2021 12:36:44 +0000 Subject: [PATCH 688/809] Cleanup --- tsfc/driver.py | 10 +- tsfc/kernel_args.py | 414 ----------------------- tsfc/kernel_interface/firedrake_loopy.py | 214 +++++++----- 3 files changed, 134 insertions(+), 504 deletions(-) delete mode 100644 tsfc/kernel_args.py diff --git a/tsfc/driver.py b/tsfc/driver.py index 2e89ecca32..21db6d9a07 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -24,7 +24,7 @@ import finat from finat.quadrature import AbstractQuadratureRule, make_quadrature -from tsfc import fem, kernel_args, ufl_utils +from tsfc import fem, ufl_utils from tsfc.finatinterface import as_fiat_cell from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex @@ -355,10 +355,8 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, # Build kernel body return_indices = basis_indices + tuple(chain(*argument_multiindices)) return_shape = tuple(i.extent for i in return_indices) - - # TODO I do not know how to determine tensor_shape and node_shape from this information - return_arg = kernel_args.DualEvalOutputKernelArg(return_shape, builder.scalar_type) - return_expr = gem.Indexed(gem.Variable("A", return_shape), return_indices) + return_var = gem.Variable('A', return_shape) + return_expr = gem.Indexed(return_var, return_indices) # TODO: one should apply some GEM optimisations as in assembly, # but we don't for now. @@ -367,7 +365,7 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, index_names = dict((idx, "p%d" % i) for (i, idx) in enumerate(basis_indices)) # Handle kernel interface requirements builder.register_requirements([evaluation]) - builder.set_output(return_arg) + builder.set_output(return_var) # Build kernel tuple return builder.construct_kernel(impero_c, index_names, first_coefficient_fake_coords) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py deleted file mode 100644 index 6d66449dad..0000000000 --- a/tsfc/kernel_args.py +++ /dev/null @@ -1,414 +0,0 @@ -import abc -import enum -import itertools - -import finat -import gem -from gem.optimise import remove_componenttensors as prune -import loopy as lp -import numpy as np - - -class Intent(enum.IntEnum): - IN = enum.auto() - OUT = enum.auto() - - -class KernelArg(abc.ABC): - """Class encapsulating information about kernel arguments.""" - - @abc.abstractproperty - def name(self): - ... - - @abc.abstractproperty - def dtype(self): - ... - - @abc.abstractproperty - def intent(self): - ... - - @abc.abstractproperty - def loopy_arg(self): - ... - - -class RankZeroKernelArg(KernelArg, abc.ABC): - ... - - -class RankOneKernelArg(KernelArg, abc.ABC): - ... - - -class RankTwoKernelArg(KernelArg, abc.ABC): - ... - - -class DualEvalOutputKernelArg(KernelArg): - - name = "A" - intent = Intent.OUT - - def __init__(self, shape, dtype): - self._shape = shape - self._dtype = dtype - - @property - def dtype(self): - return self._dtype - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self._shape) - - -class CoordinatesKernelArg(RankOneKernelArg): - - name = "coords" - intent = Intent.IN - - def __init__(self, size, dtype, interior_facet=False): - self._size = size - self._dtype = dtype - self._interior_facet = interior_facet - - @property - def dtype(self): - return self._dtype - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=(self._size,)) - - -class ConstantKernelArg(RankZeroKernelArg): - - def __init__(self, name, number, shape, dtype): - self._name = name - self.number = number - self._shape = shape - self._dtype = dtype - - @property - def name(self): - return self._name - - @property - def shape(self): - return self._shape - - @property - def dtype(self): - return self._dtype - - @property - def intent(self): - return Intent.IN - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - - -class CoefficientKernelArg(RankOneKernelArg): - - def __init__(self, name, number, size, dtype): - self._name = name - self.number = number - self._size = size - self._dtype = dtype - - @property - def name(self): - return self._name - - @property - def dtype(self): - return self._dtype - - @property - def intent(self): - return Intent.IN - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=(self._size,)) - - -class CellOrientationsKernelArg(RankOneKernelArg): - - name = "cell_orientations" - intent = Intent.IN - - def __init__(self, shape=(1,), dtype=np.int32, interior_facet=False, interior_facet_horiz=False): - self._shape = shape - self._dtype = dtype - self._interior_facet = interior_facet - assert not interior_facet_horiz - - @property - def dtype(self): - return self._dtype - - @property - def loopy_arg(self): - shape = np.prod([self.node_shape, *self.shape], dtype=int) - return lp.GlobalArg(self.name, self.dtype, shape=shape) - - @property - def shape(self): - return self._shape - - @property - def node_shape(self): - return 2 if self._interior_facet else 1 - - -class CellSizesKernelArg(RankOneKernelArg): - - name = "cell_sizes" - intent = Intent.IN - - def __init__(self, size, dtype): - self._size = size - self._dtype = dtype - - @property - def dtype(self): - return self._dtype - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=(self._size,)) - - -class FacetKernelArg(RankOneKernelArg, abc.ABC): - - name = "facet" - intent = Intent.IN - dtype = np.uint32 - - node_shape = None # Must be None because of direct addressing - this is obscure - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape) - - -class ExteriorFacetKernelArg(FacetKernelArg): - - shape = (1,) - - -class InteriorFacetKernelArg(FacetKernelArg): - - shape = (2,) - - -class TabulationKernelArg(RankOneKernelArg): - - intent = Intent.IN - shape = (1,) - - def __init__(self, name, shape, dtype): - self._name = name - self._shape = shape - self._dtype = dtype - - @property - def name(self): - return self._name - - @property - def node_shape(self): - return np.prod(self._shape, dtype=int) - - @property - def dtype(self): - return self._dtype - - @property - def loopy_arg(self): - return lp.GlobalArg(self._name, self._dtype, shape=self._shape) - - -class OutputKernelArg(KernelArg, abc.ABC): - - name = "A" - intent = Intent.OUT - - -class ScalarOutputKernelArg(RankZeroKernelArg, OutputKernelArg): - - def __init__(self, dtype): - self._dtype = dtype - - @property - def dtype(self): - return self._dtype - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.shape, is_output=True) - - @property - def shape(self): - return (1,) - - def make_gem_exprs(self, multiindices): - assert len(multiindices) == 0 - return [gem.Indexed(gem.Variable(self.name, self.shape), (0,))] - - -class VectorOutputKernelArg(RankOneKernelArg, OutputKernelArg): - - def __init__(self, index_shape, dtype, *, interior_facet=False, diagonal=False): - self.index_shape, = index_shape - self._dtype = dtype - - self._interior_facet = interior_facet - self._diagonal = diagonal - - @property - def dtype(self): - return self._dtype - - @property - def ushape(self): - return np.array([np.prod(self.index_shape, dtype=int)]) - - @property - def cshape(self): - return tuple(2*self.ushape) if self._interior_facet else tuple(self.ushape) - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.cshape) - - # TODO Function please - def make_gem_exprs(self, multiindices): - u_shape = self.ushape - c_shape = self.cshape - - if self._diagonal: - multiindices = multiindices[:1] - - if self._interior_facet: - slicez = [ - [slice(r*s, (r + 1)*s) for r, s in zip(restrictions, u_shape)] - for restrictions in [(0,), (1,)] - ] - else: - slicez = [[slice(s) for s in u_shape]] - - var = gem.Variable(self.name, c_shape) - exprs = [self._make_expression(gem.view(var, *slices), multiindices) for slices in slicez] - return prune(exprs) - - # TODO More descriptive name - def _make_expression(self, restricted, multiindices): - return gem.Indexed(gem.reshape(restricted, self.index_shape), - tuple(itertools.chain(*multiindices))) - - -class MatrixOutputKernelArg(RankTwoKernelArg, OutputKernelArg): - - def __init__(self, shapes, dtype, *, interior_facet=False): - self._rshape, self._cshape = shapes - self._dtype = dtype - self._interior_facet = interior_facet - - @property - def dtype(self): - return self._dtype - - @property - def loopy_arg(self): - return lp.GlobalArg(self.name, self.dtype, shape=self.c_shape) - - @property - def u_shape(self): - return np.array([np.prod(s, dtype=int) - for s in [self._rshape, self._cshape]]) - - @property - def c_shape(self): - return tuple(2*self.u_shape) if self._interior_facet else tuple(self.u_shape) - - def make_gem_exprs(self, multiindices): - u_shape = self.u_shape - c_shape = self.c_shape - - if self._interior_facet: - slicez = [ - [slice(r*s, (r + 1)*s) for r, s in zip(restrictions, u_shape)] - for restrictions in itertools.product((0, 1), repeat=2) - ] - else: - slicez = [[slice(s) for s in u_shape]] - - var = gem.Variable(self.name, c_shape) - exprs = [self._make_expression(gem.view(var, *slices), multiindices) for slices in slicez] - return prune(exprs) - - # TODO More descriptive name - def _make_expression(self, restricted, multiindices): - return gem.Indexed(gem.reshape(restricted, self._rshape, self._cshape), - tuple(itertools.chain(*multiindices))) - - -class _ElementHandler: - - def __init__(self, elem, interior_facet=False, interior_facet_horiz=False): - self._elem = elem - self._interior_facet = interior_facet - self._interior_facet_horiz = interior_facet_horiz - - @property - def node_shape(self): - if self._is_tensor_element: - shape = self._elem.index_shape[:-len(self.tensor_shape)] - else: - shape = self._elem.index_shape - - shape = np.prod(shape, dtype=int) - - if self._interior_facet and not self._interior_facet_horiz: - return 2 * shape - else: - return shape - - @property - def tensor_shape(self): - return self._elem._shape if self._is_tensor_element else (1,) - - @property - def loopy_shape(self): - if self._is_tensor_element: - shape = self._elem.index_shape[:-len(self.tensor_shape)] - else: - shape = self._elem.index_shape - - shape = np.prod(shape, dtype=int) - - # We have to treat facets carefully as the local kernel needs double size but - # external map does not. - if self._interior_facet: - shape *= 2 - return np.prod([shape, *self.tensor_shape], dtype=int) - - @property - def is_mixed(self): - return isinstance(self._elem, finat.EnrichedElement) and self._elem.is_mixed - - def split(self): - if not self.is_mixed: - raise ValueError("Cannot split a non-mixed element") - - return tuple([type(self)(subelem.element) for subelem in self._elem.elements]) - - @property - def _is_tensor_element(self): - return isinstance(self._elem, finat.TensorFiniteElement) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 3c5bf9b5e4..8404b96c6a 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -1,14 +1,18 @@ -import itertools +import abc +from dataclasses import dataclass import numpy from collections import namedtuple +from itertools import chain, product from functools import partial from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import gem from gem.flop_count import count_flops +from gem.optimise import remove_componenttensors as prune + +import loopy as lp -from tsfc import kernel_args from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase from tsfc.kernel_interface.firedrake import check_requirements @@ -64,16 +68,59 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, super(Kernel, self).__init__() +class KernelArg(abc.ABC): + + def __init__(self, loopy_arg): + self.loopy_arg = loopy_arg + + +class OutputKernelArg(KernelArg): + ... + + +class CoordinatesKernelArg(KernelArg): + ... + + +class CoefficientKernelArg(KernelArg): + ... + + +class CellOrientationsKernelArg(KernelArg): + ... + + +class CellSizesKernelArg(KernelArg): + ... + + +class TabulationKernelArg(KernelArg): + ... + + +class ExteriorFacetKernelArg(KernelArg): + + def __init__(self): + loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(1,)) + super().__init__(loopy_arg) + + +class InteriorFacetKernelArg(KernelArg): + + def __init__(self): + loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(2,)) + super().__init__(loopy_arg) + + class KernelBuilderBase(_KernelBuilderBase): - def __init__(self, scalar_type, interior_facet=False, interior_facet_horiz=False): + def __init__(self, scalar_type, interior_facet=False): """Initialise a kernel builder. :arg interior_facet: kernel accesses two cells """ super(KernelBuilderBase, self).__init__(scalar_type=scalar_type, interior_facet=interior_facet) - self.interior_facet_horiz = interior_facet_horiz # Cell orientation if self.interior_facet: @@ -85,11 +132,10 @@ def __init__(self, scalar_type, interior_facet=False, interior_facet_horiz=False shape = (1,) cell_orientations = gem.Variable("cell_orientations", shape) self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) - self.cell_orientations_loopy_arg = kernel_args.CellOrientationsKernelArg( - interior_facet=self.interior_facet - ) + loopy_arg = lp.GlobalArg("cell_orientations", dtype=numpy.int32, shape=shape) + self.cell_orientations_arg = CellOrientationsKernelArg(loopy_arg) - def _coefficient(self, coefficient, name, number): + def _coefficient(self, coefficient, name): """Prepare a coefficient. Adds glue code for the coefficient and adds the coefficient to the coefficient map. @@ -97,10 +143,13 @@ def _coefficient(self, coefficient, name, number): :arg name: coefficient name :returns: loopy argument for the coefficient """ - kernel_arg, expression = prepare_coefficient(coefficient, name, number, self.scalar_type, - self.interior_facet, self.interior_facet_horiz) + funarg, expression = prepare_coefficient(coefficient, name, self.scalar_type, interior_facet=self.interior_facet) self.coefficient_map[coefficient] = expression - return kernel_arg + + if name == "coords": + return CoordinatesKernelArg(funarg) + else: + return CoefficientKernelArg(funarg) def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -120,10 +169,8 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - kernel_arg, expression = prepare_coefficient(f, "cell_sizes", None, self.scalar_type, - interior_facet=self.interior_facet, - interior_facet_horiz=self.interior_facet_horiz) - self.cell_sizes_arg = kernel_arg + funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) + self.cell_sizes_arg = CellSizesKernelArg(funarg) self._cell_sizes = expression def create_element(self, element, **kwargs): @@ -154,39 +201,40 @@ def set_coefficients(self, coefficients): subcoeffs = coefficient.split() # Firedrake-specific self.coefficients.extend(subcoeffs) self.coefficient_split[coefficient] = subcoeffs - self.kernel_args += [self._coefficient(subcoeff, "w_%d_%d" % (i, j), None) + self.kernel_args += [self._coefficient(subcoeff, f"w_{i}_{j}") for j, subcoeff in enumerate(subcoeffs)] else: self.coefficients.append(coefficient) - self.kernel_args.append(self._coefficient(coefficient, "w_%d" % (i,), None)) + self.kernel_args.append(self._coefficient(coefficient, f"w_{i}")) def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be provided by the kernel interface.""" self.oriented, self.cell_sizes, self.tabulations = check_requirements(ir) - def set_output(self, kernel_arg): + def set_output(self, o): """Produce the kernel return argument""" - self.return_arg = kernel_arg + loopy_arg = lp.GlobalArg(o.name, dtype=self.scalar_type, shape=o.shape) + self.local_tensor_arg = OutputKernelArg(loopy_arg) def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords): """Constructs an :class:`ExpressionKernel`. - :arg return_arg: loopy.GlobalArg for the return value :arg impero_c: gem.ImperoC object that represents the kernel :arg index_names: pre-assigned index names :arg first_coefficient_fake_coords: If true, the kernel's first coefficient is a constructed UFL coordinate field :returns: :class:`ExpressionKernel` object """ - args = [self.return_arg] + args = [self.local_tensor_arg] if self.oriented: - args.append(self.cell_orientations_loopy_arg) + args.append(self.cell_orientations_arg) if self.cell_sizes: args.append(self.cell_sizes_arg) args.extend(self.kernel_args) for name_, shape in self.tabulations: - args.append(kernel_args.TabulationKernelArg(name_, shape, self.scalar_type)) + tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) + args.append(TabulationKernelArg(tab_loopy_arg)) loopy_args = [arg.loopy_arg for arg in args] @@ -205,8 +253,7 @@ def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont diagonal=False): """Initialise a kernel builder.""" super(KernelBuilder, self).__init__(scalar_type, - integral_type.startswith("interior_facet"), - integral_type == "interior_facet_horiz") + integral_type.startswith("interior_facet")) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) @@ -237,11 +284,11 @@ def set_arguments(self, arguments, multiindices): :arg multiindices: GEM argument multiindices :returns: GEM expression representing the return variable """ - kernel_arg = prepare_arguments( - arguments, self.scalar_type, interior_facet=self.interior_facet, - interior_facet_horiz=self.interior_facet_horiz, diagonal=self.diagonal) - self.local_tensor = kernel_arg - return kernel_arg.make_gem_exprs(multiindices) + funarg, expressions = prepare_arguments( + arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet, + diagonal=self.diagonal) + self.local_tensor_arg = OutputKernelArg(funarg) + return expressions def set_coordinates(self, domain): """Prepare the coordinate field. @@ -251,13 +298,7 @@ def set_coordinates(self, domain): # Create a fake coordinate coefficient for a domain. f = Coefficient(FunctionSpace(domain, domain.ufl_coordinate_element())) self.domain_coordinate[domain] = f - # TODO Copy-pasted from _coefficient - needs refactor - # self.coordinates_arg = self._coefficient(f, "coords") - kernel_arg, expression = prepare_coefficient(f, "coords", None, self.scalar_type, - self.interior_facet, - self.interior_facet_horiz) - self.coefficient_map[f] = expression - self.coordinates_arg = kernel_arg + self.coordinates_arg = self._coefficient(f, "coords") def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -267,7 +308,6 @@ def set_coefficients(self, integral_data, form_data): """ coefficients = [] coefficient_numbers = [] - ctr = itertools.count() # enabled_coefficients is a boolean array that indicates which # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): @@ -281,19 +321,17 @@ def set_coefficients(self, integral_data, form_data): else: split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) for element in coefficient.ufl_element().sub_elements()] - for c_ in split: - number = next(ctr) - self.coefficient_args.append(self._coefficient(c_, f"w_{number}", number)) + coefficients.extend(split) self.coefficient_split[coefficient] = split else: - number = next(ctr) - self.coefficient_args.append(self._coefficient(coefficient, f"w_{number}", number)) + coefficients.append(coefficient) # This is which coefficient in the original form the # current coefficient is. # Consider f*v*dx + g*v*ds, the full form contains two # coefficients, but each integral only requires one. coefficient_numbers.append(form_data.original_coefficient_positions[i]) - + for i, coefficient in enumerate(coefficients): + self.coefficient_args.append(self._coefficient(coefficient, f"w_{i}")) self.kernel.coefficient_numbers = tuple(coefficient_numbers) def register_requirements(self, ir): @@ -315,26 +353,25 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): :returns: :class:`Kernel` object """ - args = [self.local_tensor, self.coordinates_arg] + args = [self.local_tensor_arg, self.coordinates_arg] if self.kernel.oriented: - args.append(self.cell_orientations_loopy_arg) + args.append(self.cell_orientations_arg) if self.kernel.needs_cell_sizes: args.append(self.cell_sizes_arg) args.extend(self.coefficient_args) if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: - args.append(kernel_args.ExteriorFacetKernelArg()) + args.append(ExteriorFacetKernelArg()) elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: - args.append(kernel_args.InteriorFacetKernelArg()) - + args.append(InteriorFacetKernelArg()) for name_, shape in self.kernel.tabulations: - args.append(kernel_args.TabulationKernelArg(name_, shape, self.scalar_type)) + tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) + args.append(TabulationKernelArg(tab_loopy_arg)) - loopy_args = [arg.loopy_arg for arg in args] + self.kernel.ast = generate_loopy(impero_c, [arg.loopy_arg for arg in args], + self.scalar_type, name, index_names) self.kernel.arguments = args - - self.kernel.quadrature_rule = quadrature_rule - self.kernel.ast = generate_loopy(impero_c, loopy_args, self.scalar_type, name, index_names) self.kernel.name = name + self.kernel.quadrature_rule = quadrature_rule self.kernel.flop_count = count_flops(impero_c) return self.kernel @@ -347,27 +384,28 @@ def construct_empty_kernel(self, name): return None -# TODO Returning is_constant is nasty. Refactor. -def prepare_coefficient(coefficient, name, number, dtype, interior_facet=False, interior_facet_horiz=False): +def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. :arg coefficient: UFL Coefficient :arg name: unique name to refer to the Coefficient in the kernel :arg interior_facet: interior facet integral? - :returns: (expression, shape) - expression - GEM expression referring to the Coefficient values - shape - TODO + :returns: (funarg, expression) + funarg - :class:`loopy.GlobalArg` function argument + expression - GEM expression referring to the Coefficient + values """ - # TODO Return expression and kernel arg... assert isinstance(interior_facet, bool) if coefficient.ufl_element().family() == 'Real': + # Constant value_size = coefficient.ufl_element().value_size() - kernel_arg = kernel_args.ConstantKernelArg(name, number, (value_size,), dtype) + funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(value_size,)) expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) - return kernel_arg, expression + + return funarg, expression finat_element = create_element(coefficient.ufl_element()) @@ -382,25 +420,17 @@ def prepare_coefficient(coefficient, name, number, dtype, interior_facet=False, minus = gem.view(varexp, slice(size, 2*size)) expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) size = size * 2 - - # This is truly disgusting, clean up ASAP - if name == "cell_sizes": - assert number is None - kernel_arg = kernel_args.CellSizesKernelArg(size, dtype) - elif name == "coords": - assert number is None - kernel_arg = kernel_args.CoordinatesKernelArg(size, dtype) - else: - kernel_arg = kernel_args.CoefficientKernelArg(name, number, size, dtype) - return kernel_arg, expression + funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(size,)) + return funarg, expression -def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_facet_horiz=False, diagonal=False): +def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): """Bridges the kernel interface and the GEM abstraction for Arguments. Vector Arguments are rearranged here for interior facet integrals. :arg arguments: UFL Arguments + :arg multiindices: Argument multiindices :arg interior_facet: interior facet integral? :arg diagonal: Are we assembling the diagonal of a rank-2 element tensor? :returns: (funarg, expression) @@ -408,10 +438,15 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_fac expressions - GEM expressions referring to the argument tensor """ + assert isinstance(interior_facet, bool) if len(arguments) == 0: - return kernel_args.ScalarOutputKernelArg(scalar_type) + # No arguments + funarg = lp.GlobalArg("A", dtype=scalar_type, shape=(1,)) + expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) + + return funarg, [expression] elements = tuple(create_element(arg.ufl_element()) for arg in arguments) shapes = tuple(element.index_shape for element in elements) @@ -424,14 +459,25 @@ def prepare_arguments(arguments, scalar_type, interior_facet=False, interior_fac except ValueError: raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") - elements = (element,) + elements = (element, ) shapes = tuple(element.index_shape for element in elements) - - if len(arguments) == 1 or diagonal: - return kernel_args.VectorOutputKernelArg(shapes, scalar_type, interior_facet=interior_facet, diagonal=diagonal) - elif len(arguments) == 2: - return kernel_args.MatrixOutputKernelArg( - shapes, scalar_type, interior_facet=interior_facet - ) + multiindices = multiindices[:1] + + def expression(restricted): + return gem.Indexed(gem.reshape(restricted, *shapes), + tuple(chain(*multiindices))) + + u_shape = numpy.array([numpy.prod(shape, dtype=int) for shape in shapes]) + if interior_facet: + c_shape = tuple(2 * u_shape) + slicez = [[slice(r * s, (r + 1) * s) + for r, s in zip(restrictions, u_shape)] + for restrictions in product((0, 1), repeat=len(arguments))] else: - raise AssertionError + c_shape = tuple(u_shape) + slicez = [[slice(s) for s in u_shape]] + + funarg = lp.GlobalArg("A", dtype=scalar_type, shape=c_shape) + varexp = gem.Variable("A", c_shape) + expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] + return funarg, prune(expressions) From 20a87a10e1935df33b4a67924032460802bf854e Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 7 Dec 2021 14:53:28 +0000 Subject: [PATCH 689/809] Also works for COFFEE --- tsfc/kernel_args.py | 70 ++++++++++++++++++++ tsfc/kernel_interface/firedrake.py | 81 +++++++++++++----------- tsfc/kernel_interface/firedrake_loopy.py | 76 +++++----------------- 3 files changed, 131 insertions(+), 96 deletions(-) create mode 100644 tsfc/kernel_args.py diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py new file mode 100644 index 0000000000..757c469a40 --- /dev/null +++ b/tsfc/kernel_args.py @@ -0,0 +1,70 @@ +import abc + +import coffee.base as coffee +import loopy as lp + + +class KernelArg(abc.ABC): + + def __init__(self, ast_arg): + self._ast_arg = ast_arg + + @property + def dtype(self): + if self._is_coffee_backend: + return self._ast_arg.typ + elif self._is_loopy_backend: + return self._ast_arg.dtype + + @property + def coffee_arg(self): + if not self._is_coffee_backend: + raise RuntimeError("Invalid type requested") + return self._ast_arg + + @property + def loopy_arg(self): + if not self._is_loopy_backend: + raise RuntimeError("Invalid type requested") + return self._ast_arg + + @property + def _is_coffee_backend(self): + return isinstance(self._ast_arg, coffee.Decl) + + @property + def _is_loopy_backend(self): + return isinstance(self._ast_arg, lp.ArrayArg) + +class OutputKernelArg(KernelArg): + ... + + +class CoordinatesKernelArg(KernelArg): + ... + + +class CoefficientKernelArg(KernelArg): + ... + + +class CellOrientationsKernelArg(KernelArg): + ... + + +class CellSizesKernelArg(KernelArg): + ... + + +class TabulationKernelArg(KernelArg): + ... + + +class ExteriorFacetKernelArg(KernelArg): + ... + + +class InteriorFacetKernelArg(KernelArg): + ... + + diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index e1b503e6fc..25977d8e3f 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -11,9 +11,10 @@ from gem.node import traversal from gem.optimise import remove_componenttensors as prune +from tsfc import kernel_args +from tsfc.coffee import generate as generate_coffee from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase -from tsfc.coffee import generate as generate_coffee def make_builder(*args, **kwargs): @@ -48,8 +49,7 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, flop_count=0): # Defaults self.ast = ast - assert arguments is None # only valid for loopy kernels - self.arguments = None + self.arguments = arguments self.integral_type = integral_type self.oriented = oriented self.domain_number = domain_number @@ -87,8 +87,10 @@ def _coefficient(self, coefficient, name): :arg name: coefficient name :returns: COFFEE function argument for the coefficient """ - funarg, expression = prepare_coefficient(coefficient, name, self.scalar_type, interior_facet=self.interior_facet) - self.coefficient_map[coefficient] = expression + funarg, expr = prepare_coefficient(coefficient, name, + self.scalar_type, + interior_facet=self.interior_facet) + self.coefficient_map[coefficient] = expr return funarg def set_cell_sizes(self, domain): @@ -109,9 +111,11 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) - self.cell_sizes_arg = funarg - self._cell_sizes = expression + funarg, expr = prepare_coefficient(f, "cell_sizes", + self.scalar_type, + interior_facet=self.interior_facet) + self.cell_sizes_arg = kernel_args.CellSizesKernelArg(funarg) + self._cell_sizes = expr def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given @@ -156,10 +160,12 @@ def set_arguments(self, arguments, multiindices): :arg multiindices: GEM argument multiindices :returns: GEM expression representing the return variable """ - self.local_tensor, expressions = prepare_arguments( - arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet, - diagonal=self.diagonal) - return expressions + funarg, exprs = prepare_arguments(arguments, multiindices, + self.scalar_type, + interior_facet=self.interior_facet, + diagonal=self.diagonal) + self.output_arg = kernel_args.OutputKernelArg(funarg) + return exprs def set_coordinates(self, domain): """Prepare the coordinate field. @@ -169,7 +175,8 @@ def set_coordinates(self, domain): # Create a fake coordinate coefficient for a domain. f = Coefficient(FunctionSpace(domain, domain.ufl_coordinate_element())) self.domain_coordinate[domain] = f - self.coordinates_arg = self._coefficient(f, "coords") + coords_coffee_arg = self._coefficient(f, "coords") + self.coordinates_arg = kernel_args.CoordinatesKernelArg(coords_coffee_arg) def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -202,8 +209,8 @@ def set_coefficients(self, integral_data, form_data): # coefficients, but each integral only requires one. coefficient_numbers.append(form_data.original_coefficient_positions[i]) for i, coefficient in enumerate(coefficients): - self.coefficient_args.append( - self._coefficient(coefficient, "w_%d" % i)) + coeff_coffee_arg = self._coefficient(coefficient, f"w_{i}") + self.coefficient_args.append(kernel_args.CoefficientKernelArg(coeff_coffee_arg)) self.kernel.coefficient_numbers = tuple(coefficient_numbers) def register_requirements(self, ir): @@ -224,30 +231,38 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object """ - body = generate_coffee(impero_c, index_names, self.scalar_type) - - args = [self.local_tensor, self.coordinates_arg] + args = [self.output_arg, self.coordinates_arg] if self.kernel.oriented: - args.append(cell_orientations_coffee_arg) + ori_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), + pointers=[("restrict",)], + qualifiers=["const"]) + args.append(kernel_args.CellOrientationsKernelArg(ori_coffee_arg)) if self.kernel.needs_cell_sizes: args.append(self.cell_sizes_arg) args.extend(self.coefficient_args) if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: - args.append(coffee.Decl("unsigned int", - coffee.Symbol("facet", rank=(1,)), - qualifiers=["const"])) + ext_coffee_arg = coffee.Decl("unsigned int", + coffee.Symbol("facet", rank=(1,)), + qualifiers=["const"]) + args.append(kernel_args.ExteriorFacetKernelArg(ext_coffee_arg)) elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: - args.append(coffee.Decl("unsigned int", - coffee.Symbol("facet", rank=(2,)), - qualifiers=["const"])) - - for name_, shape in self.kernel.tabulations: - args.append(coffee.Decl(self.scalar_type, coffee.Symbol( - name_, rank=shape), qualifiers=["const"])) + int_coffee_arg = coffee.Decl("unsigned int", + coffee.Symbol("facet", rank=(2,)), + qualifiers=["const"]) + args.append(kernel_args.InteriorFacetKernelArg(int_coffee_arg)) + for n, shape in self.kernel.tabulations: + tab_coffee_arg = coffee.Decl(self.scalar_type, + coffee.Symbol(n, rank=shape), + qualifiers=["const"]) + args.append(kernel_args.TabulationKernelArg(tab_coffee_arg)) + + coffee_args = [a.coffee_arg for a in args] + body = generate_coffee(impero_c, index_names, self.scalar_type) + self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, coffee_args, body) + self.kernel.arguments = tuple(args) self.kernel.quadrature_rule = quadrature_rule self.kernel.name = name - self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, args, body) self.kernel.flop_count = count_flops(impero_c) return self.kernel @@ -377,9 +392,3 @@ def expression(restricted): varexp = gem.Variable("A", c_shape) expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] return funarg, prune(expressions) - - -cell_orientations_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), - pointers=[("restrict",)], - qualifiers=["const"]) -"""COFFEE function argument for cell orientations""" diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 8404b96c6a..34b65bce53 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -1,5 +1,3 @@ -import abc -from dataclasses import dataclass import numpy from collections import namedtuple from itertools import chain, product @@ -13,6 +11,7 @@ import loopy as lp +from tsfc import kernel_args from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase from tsfc.kernel_interface.firedrake import check_requirements @@ -68,50 +67,6 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, super(Kernel, self).__init__() -class KernelArg(abc.ABC): - - def __init__(self, loopy_arg): - self.loopy_arg = loopy_arg - - -class OutputKernelArg(KernelArg): - ... - - -class CoordinatesKernelArg(KernelArg): - ... - - -class CoefficientKernelArg(KernelArg): - ... - - -class CellOrientationsKernelArg(KernelArg): - ... - - -class CellSizesKernelArg(KernelArg): - ... - - -class TabulationKernelArg(KernelArg): - ... - - -class ExteriorFacetKernelArg(KernelArg): - - def __init__(self): - loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(1,)) - super().__init__(loopy_arg) - - -class InteriorFacetKernelArg(KernelArg): - - def __init__(self): - loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(2,)) - super().__init__(loopy_arg) - - class KernelBuilderBase(_KernelBuilderBase): def __init__(self, scalar_type, interior_facet=False): @@ -133,7 +88,7 @@ def __init__(self, scalar_type, interior_facet=False): cell_orientations = gem.Variable("cell_orientations", shape) self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) loopy_arg = lp.GlobalArg("cell_orientations", dtype=numpy.int32, shape=shape) - self.cell_orientations_arg = CellOrientationsKernelArg(loopy_arg) + self.cell_orientations_arg = kernel_args.CellOrientationsKernelArg(loopy_arg) def _coefficient(self, coefficient, name): """Prepare a coefficient. Adds glue code for the coefficient @@ -147,9 +102,9 @@ def _coefficient(self, coefficient, name): self.coefficient_map[coefficient] = expression if name == "coords": - return CoordinatesKernelArg(funarg) + return kernel_args.CoordinatesKernelArg(funarg) else: - return CoefficientKernelArg(funarg) + return kernel_args.CoefficientKernelArg(funarg) def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -170,7 +125,7 @@ def set_cell_sizes(self, domain): # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) - self.cell_sizes_arg = CellSizesKernelArg(funarg) + self.cell_sizes_arg = kernel_args.CellSizesKernelArg(funarg) self._cell_sizes = expression def create_element(self, element, **kwargs): @@ -215,7 +170,7 @@ def register_requirements(self, ir): def set_output(self, o): """Produce the kernel return argument""" loopy_arg = lp.GlobalArg(o.name, dtype=self.scalar_type, shape=o.shape) - self.local_tensor_arg = OutputKernelArg(loopy_arg) + self.output_arg = kernel_args.OutputKernelArg(loopy_arg) def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords): """Constructs an :class:`ExpressionKernel`. @@ -226,7 +181,7 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords) coefficient is a constructed UFL coordinate field :returns: :class:`ExpressionKernel` object """ - args = [self.local_tensor_arg] + args = [self.output_arg] if self.oriented: args.append(self.cell_orientations_arg) if self.cell_sizes: @@ -234,7 +189,7 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords) args.extend(self.kernel_args) for name_, shape in self.tabulations: tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) - args.append(TabulationKernelArg(tab_loopy_arg)) + args.append(kernel_args.TabulationKernelArg(tab_loopy_arg)) loopy_args = [arg.loopy_arg for arg in args] @@ -287,7 +242,7 @@ def set_arguments(self, arguments, multiindices): funarg, expressions = prepare_arguments( arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet, diagonal=self.diagonal) - self.local_tensor_arg = OutputKernelArg(funarg) + self.output_arg = kernel_args.OutputKernelArg(funarg) return expressions def set_coordinates(self, domain): @@ -352,24 +307,25 @@ def construct_kernel(self, name, impero_c, index_names, quadrature_rule): :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object """ - - args = [self.local_tensor_arg, self.coordinates_arg] + args = [self.output_arg, self.coordinates_arg] if self.kernel.oriented: args.append(self.cell_orientations_arg) if self.kernel.needs_cell_sizes: args.append(self.cell_sizes_arg) args.extend(self.coefficient_args) if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: - args.append(ExteriorFacetKernelArg()) + ext_loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(1,)) + args.append(kernel_args.ExteriorFacetKernelArg(ext_loopy_arg)) elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: - args.append(InteriorFacetKernelArg()) + int_loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(2,)) + args.append(kernel_args.InteriorFacetKernelArg(int_loopy_arg)) for name_, shape in self.kernel.tabulations: tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) - args.append(TabulationKernelArg(tab_loopy_arg)) + args.append(kernel_args.TabulationKernelArg(tab_loopy_arg)) self.kernel.ast = generate_loopy(impero_c, [arg.loopy_arg for arg in args], self.scalar_type, name, index_names) - self.kernel.arguments = args + self.kernel.arguments = tuple(args) self.kernel.name = name self.kernel.quadrature_rule = quadrature_rule self.kernel.flop_count = count_flops(impero_c) From 01005cc08d0651ae3eb57a103e06dd16c02a3464 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 7 Dec 2021 14:59:08 +0000 Subject: [PATCH 690/809] Tidy up --- tsfc/kernel_interface/firedrake_loopy.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 34b65bce53..d2c6ef649e 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -100,11 +100,7 @@ def _coefficient(self, coefficient, name): """ funarg, expression = prepare_coefficient(coefficient, name, self.scalar_type, interior_facet=self.interior_facet) self.coefficient_map[coefficient] = expression - - if name == "coords": - return kernel_args.CoordinatesKernelArg(funarg) - else: - return kernel_args.CoefficientKernelArg(funarg) + return funarg def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -156,11 +152,14 @@ def set_coefficients(self, coefficients): subcoeffs = coefficient.split() # Firedrake-specific self.coefficients.extend(subcoeffs) self.coefficient_split[coefficient] = subcoeffs - self.kernel_args += [self._coefficient(subcoeff, f"w_{i}_{j}") - for j, subcoeff in enumerate(subcoeffs)] + coeff_loopy_args = [self._coefficient(subcoeff, f"w_{i}_{j}") + for j, subcoeff in enumerate(subcoeffs)] + self.kernel_args += [kernel_args.CoefficientKernelArg(a) + for a in coeff_loopy_args] else: self.coefficients.append(coefficient) - self.kernel_args.append(self._coefficient(coefficient, f"w_{i}")) + coeff_loopy_arg = self._coefficient(coefficient, f"w_{i}") + self.kernel_args.append(kernel_args.CoefficientKernelArg(coeff_loopy_arg)) def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be @@ -253,7 +252,8 @@ def set_coordinates(self, domain): # Create a fake coordinate coefficient for a domain. f = Coefficient(FunctionSpace(domain, domain.ufl_coordinate_element())) self.domain_coordinate[domain] = f - self.coordinates_arg = self._coefficient(f, "coords") + coords_loopy_arg = self._coefficient(f, "coords") + self.coordinates_arg = kernel_args.CoordinatesKernelArg(coords_loopy_arg) def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -286,7 +286,8 @@ def set_coefficients(self, integral_data, form_data): # coefficients, but each integral only requires one. coefficient_numbers.append(form_data.original_coefficient_positions[i]) for i, coefficient in enumerate(coefficients): - self.coefficient_args.append(self._coefficient(coefficient, f"w_{i}")) + coeff_loopy_arg = self._coefficient(coefficient, f"w_{i}") + self.coefficient_args.append(kernel_args.CoefficientKernelArg(coeff_loopy_arg)) self.kernel.coefficient_numbers = tuple(coefficient_numbers) def register_requirements(self, ir): From ee84be7f9c1e241190782d89928bba42dbe7aab8 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 7 Dec 2021 15:00:20 +0000 Subject: [PATCH 691/809] Tidying up --- tsfc/kernel_interface/firedrake_loopy.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index d2c6ef649e..38585948cc 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -64,7 +64,6 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, self.coefficient_numbers = coefficient_numbers self.needs_cell_sizes = needs_cell_sizes self.flop_count = flop_count - super(Kernel, self).__init__() class KernelBuilderBase(_KernelBuilderBase): @@ -74,8 +73,7 @@ def __init__(self, scalar_type, interior_facet=False): :arg interior_facet: kernel accesses two cells """ - super(KernelBuilderBase, self).__init__(scalar_type=scalar_type, - interior_facet=interior_facet) + super().__init__(scalar_type=scalar_type, interior_facet=interior_facet) # Cell orientation if self.interior_facet: @@ -206,8 +204,7 @@ class KernelBuilder(KernelBuilderBase): def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont_split=(), diagonal=False): """Initialise a kernel builder.""" - super(KernelBuilder, self).__init__(scalar_type, - integral_type.startswith("interior_facet")) + super().__init__(scalar_type, integral_type.startswith("interior_facet")) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) From cb1059581093de5bc191f448a717367d5be42de4 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 7 Dec 2021 15:13:22 +0000 Subject: [PATCH 692/809] Added fdm variant for TP elements --- tsfc/finatinterface.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index ced2398140..59d9688d8b 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -140,6 +140,8 @@ def convert_finiteelement(element, **kwargs): if element.family() == "Lagrange": if kind == 'equispaced': lmbda = finat.Lagrange + elif kind == 'fdm' and element.cell().cellname() == 'interval': + lmbda = finat.FDMElement elif kind == 'spectral' and element.cell().cellname() == 'interval': lmbda = finat.GaussLobattoLegendre elif kind in ['mgd', 'feec', 'qb', 'mse']: @@ -158,6 +160,8 @@ def convert_finiteelement(element, **kwargs): lmbda = finat.DiscontinuousLagrange elif kind == 'spectral' and element.cell().cellname() == 'interval': lmbda = finat.GaussLegendre + elif kind == 'fdm' and element.cell().cellname() == 'interval': + lmbda = lambda c, d: finat.FDMElement(c, d, formdegree=1) elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] From 1d29686c46c6460c54779ada69dc411761dbaaae Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 7 Dec 2021 16:01:52 +0000 Subject: [PATCH 693/809] reorder CG variants --- tsfc/finatinterface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 59d9688d8b..dbdab9ca43 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -140,10 +140,10 @@ def convert_finiteelement(element, **kwargs): if element.family() == "Lagrange": if kind == 'equispaced': lmbda = finat.Lagrange - elif kind == 'fdm' and element.cell().cellname() == 'interval': - lmbda = finat.FDMElement elif kind == 'spectral' and element.cell().cellname() == 'interval': lmbda = finat.GaussLobattoLegendre + elif kind == 'fdm' and element.cell().cellname() == 'interval': + lmbda = finat.FDMElement elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] From 2c50e0da9242604f2da334f57f48d4fdcd385336 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 13 Jan 2022 22:29:11 +0000 Subject: [PATCH 694/809] Linting and docstrings --- tsfc/kernel_args.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 757c469a40..17868249d6 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -5,6 +5,13 @@ class KernelArg(abc.ABC): + """Abstract base class wrapping either a COFFEE or loopy argument. + + Defining this type system allows Firedrake (or other libraries) to + prepare arguments for the kernel without needing to worry about their + ordering. Instead this can be offloaded to tools such as + :func:`functools.singledispatch`. + """ def __init__(self, ast_arg): self._ast_arg = ast_arg @@ -36,6 +43,7 @@ def _is_coffee_backend(self): def _is_loopy_backend(self): return isinstance(self._ast_arg, lp.ArrayArg) + class OutputKernelArg(KernelArg): ... @@ -66,5 +74,3 @@ class ExteriorFacetKernelArg(KernelArg): class InteriorFacetKernelArg(KernelArg): ... - - From 3b3153190b88a643d082ea7d010bf176164d2e45 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 13 Jan 2022 22:49:34 +0000 Subject: [PATCH 695/809] Remove deprecated argument to get_op_map --- tests/test_impero_loopy_flop_counts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_impero_loopy_flop_counts.py b/tests/test_impero_loopy_flop_counts.py index db393203f1..2a1f460cb9 100644 --- a/tests/test_impero_loopy_flop_counts.py +++ b/tests/test_impero_loopy_flop_counts.py @@ -22,7 +22,6 @@ def count_loopy_flops(kernel): ) op_map = loopy.get_op_map(program .with_entrypoints(kernel.name), - numpy_types=None, subgroup_size=1) return op_map.filter_by(name=['add', 'sub', 'mul', 'div', 'func:abs'], From b0baabb4bb0d932d6f2f3155bc7a946515ee79f5 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 17 Jan 2022 13:58:28 +0000 Subject: [PATCH 696/809] add FDMHermite, and wrap FDMLagrange with DiscontinuousElement for DG(fdm) --- tsfc/finatinterface.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index dbdab9ca43..c49d929f6f 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -143,7 +143,9 @@ def convert_finiteelement(element, **kwargs): elif kind == 'spectral' and element.cell().cellname() == 'interval': lmbda = finat.GaussLobattoLegendre elif kind == 'fdm' and element.cell().cellname() == 'interval': - lmbda = finat.FDMElement + lmbda = finat.FDMLagrange + elif kind == 'fdmhermite' and element.cell().cellname() == 'interval': + lmbda = finat.FDMHermite elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] @@ -161,7 +163,7 @@ def convert_finiteelement(element, **kwargs): elif kind == 'spectral' and element.cell().cellname() == 'interval': lmbda = finat.GaussLegendre elif kind == 'fdm' and element.cell().cellname() == 'interval': - lmbda = lambda c, d: finat.FDMElement(c, d, formdegree=1) + lmbda = lambda *args: finat.DiscontinuousElement(finat.FDMLagrange(*args)) elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] From 36d269046c190107923c5202dcc0305ea88fae9d Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Wed, 1 Sep 2021 12:33:21 +0100 Subject: [PATCH 697/809] refactor: introduce prepare_parameters() --- tsfc/driver.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 21db6d9a07..baf8bdf53e 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -76,12 +76,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co :arg diagonal: Are we building a kernel for the diagonal of a rank-2 element tensor? :returns: a kernel constructed by the kernel interface """ - if parameters is None: - parameters = default_parameters() - else: - _ = default_parameters() - _.update(parameters) - parameters = _ + parameters = preprocess_parameters(parameters) + if interface is None: if coffee: import tsfc.kernel_interface.firedrake as firedrake_interface_coffee @@ -95,12 +91,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co else: scalar_type = parameters["scalar_type"] - # Remove these here, they're handled below. - if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: - del parameters["quadrature_degree"] - if parameters.get("quadrature_rule") in ["auto", "default", None]: - del parameters["quadrature_rule"] - integral_type = integral_data.integral_type interior_facet = integral_type.startswith("interior_facet") mesh = integral_data.domain @@ -266,6 +256,21 @@ def name_multiindex(multiindex, name): return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) +def preprocess_parameters(parameters): + if parameters is None: + parameters = default_parameters() + else: + _ = default_parameters() + _.update(parameters) + parameters = _ + # Remove these here, they're handled later on. + if parameters.get("quadrature_degree") in ["auto", "default", None, -1, "-1"]: + del parameters["quadrature_degree"] + if parameters.get("quadrature_rule") in ["auto", "default", None]: + del parameters["quadrature_rule"] + return parameters + + def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, domain=None, interface=None, parameters=None): From 99ed32e19eee9021b8bcf656629fcdc87a8d790a Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Wed, 1 Sep 2021 14:01:38 +0100 Subject: [PATCH 698/809] refactor: add set_quad_rule() --- tsfc/driver.py | 63 ++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index baf8bdf53e..1a67c6d948 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -150,42 +150,15 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co for integral in integral_data.integrals: params = parameters.copy() params.update(integral.metadata()) # integral metadata overrides - if params.get("quadrature_rule") == "default": - del params["quadrature_rule"] mode = pick_mode(params["mode"]) mode_irs.setdefault(mode, collections.OrderedDict()) integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) - - # Check if the integral has a quad degree attached, otherwise use - # the estimated polynomial degree attached by compute_form_data - quadrature_degree = params.get("quadrature_degree", - params["estimated_polynomial_degree"]) - try: - quadrature_degree = params["quadrature_degree"] - except KeyError: - quadrature_degree = params["estimated_polynomial_degree"] - functions = list(arguments) + [builder.coordinate(mesh)] + list(integral_data.integral_coefficients) - function_degrees = [f.ufl_function_space().ufl_element().degree() for f in functions] - if all((asarray(quadrature_degree) > 10 * asarray(degree)).all() - for degree in function_degrees): - logger.warning("Estimated quadrature degree %s more " - "than tenfold greater than any " - "argument/coefficient degree (max %s)", - quadrature_degree, max_degree(function_degrees)) - - try: - quad_rule = params["quadrature_rule"] - except KeyError: - integration_cell = fiat_cell.construct_subelement(integration_dim) - quad_rule = make_quadrature(integration_cell, quadrature_degree) - - if not isinstance(quad_rule, AbstractQuadratureRule): - raise ValueError("Expected to find a QuadratureRule object, not a %s" % - type(quad_rule)) - + functions = list(arguments) + [builder.coordinate(mesh)] + list(integral_data.integral_coefficients) + set_quad_rule(params, cell, integral_type, functions) + quad_rule = params["quadrature_rule"] quadrature_multiindex = quad_rule.point_set.indices quadrature_indices.extend(quadrature_multiindex) @@ -436,6 +409,36 @@ def __call__(self, ps): return gem_expr +def set_quad_rule(params, cell, integral_type, functions): + # Check if the integral has a quad degree attached, otherwise use + # the estimated polynomial degree attached by compute_form_data + try: + quadrature_degree = params["quadrature_degree"] + except KeyError: + quadrature_degree = params["estimated_polynomial_degree"] + function_degrees = [f.ufl_function_space().ufl_element().degree() for f in functions] + if all((asarray(quadrature_degree) > 10 * asarray(degree)).all() + for degree in function_degrees): + logger.warning("Estimated quadrature degree %s more " + "than tenfold greater than any " + "argument/coefficient degree (max %s)", + quadrature_degree, max_degree(function_degrees)) + if params.get("quadrature_rule") == "default": + del params["quadrature_rule"] + try: + quad_rule = params["quadrature_rule"] + except KeyError: + fiat_cell = as_fiat_cell(cell) + integration_dim, _ = lower_integral_type(fiat_cell, integral_type) + integration_cell = fiat_cell.construct_subelement(integration_dim) + quad_rule = make_quadrature(integration_cell, quadrature_degree) + params["quadrature_rule"] = quad_rule + + if not isinstance(quad_rule, AbstractQuadratureRule): + raise ValueError("Expected to find a QuadratureRule object, not a %s" % + type(quad_rule)) + + def lower_integral_type(fiat_cell, integral_type): """Lower integral type into the dimension of the integration subentity and a list of entity numbers for that dimension. From df1ea4a61eeaa82093b518ecc27054bec8d39d4e Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 10:57:29 +0100 Subject: [PATCH 699/809] refactor: add get_index_ordering()/get_index_names() --- tsfc/driver.py | 53 +++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 1a67c6d948..3232df9bc0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -196,9 +196,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co builder.register_requirements(expressions) # Construct ImperoC - split_argument_indices = tuple(chain(*[var.index_ordering() - for var in return_variables])) - index_ordering = tuple(quadrature_indices) + split_argument_indices + index_ordering = get_index_ordering(quadrature_indices, return_variables) try: impero_c = impero_utils.compile_gem(assignments, index_ordering, remove_zeros=True) except impero_utils.NoopError: @@ -206,25 +204,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co return builder.construct_empty_kernel(kernel_name) # Generate COFFEE - index_names = [] - - def name_index(index, name): - index_names.append((index, name)) - if index in index_cache: - for multiindex, suffix in zip(index_cache[index], - string.ascii_lowercase): - name_multiindex(multiindex, name + suffix) - - def name_multiindex(multiindex, name): - if len(multiindex) == 1: - name_index(multiindex[0], name) - else: - for i, index in enumerate(multiindex): - name_index(index, name + str(i)) - - name_multiindex(quadrature_indices, 'ip') - for multiindex, name in zip(argument_multiindices, ['j', 'k']): - name_multiindex(multiindex, name) + index_names = get_index_names(quadrature_indices, argument_multiindices, index_cache) return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) @@ -439,6 +419,35 @@ def set_quad_rule(params, cell, integral_type, functions): type(quad_rule)) +def get_index_ordering(quadrature_indices, return_variables): + split_argument_indices = tuple(chain(*[var.index_ordering() + for var in return_variables])) + return tuple(quadrature_indices) + split_argument_indices + + +def get_index_names(quadrature_indices, argument_multiindices, index_cache): + index_names = [] + + def name_index(index, name): + index_names.append((index, name)) + if index in index_cache: + for multiindex, suffix in zip(index_cache[index], + string.ascii_lowercase): + name_multiindex(multiindex, name + suffix) + + def name_multiindex(multiindex, name): + if len(multiindex) == 1: + name_index(multiindex[0], name) + else: + for i, index in enumerate(multiindex): + name_index(index, name + str(i)) + + name_multiindex(quadrature_indices, 'ip') + for multiindex, name in zip(argument_multiindices, ['j', 'k']): + name_multiindex(multiindex, name) + return index_names + + def lower_integral_type(fiat_cell, integral_type): """Lower integral type into the dimension of the integration subentity and a list of entity numbers for that dimension. From f49b3ff69064afc6f34f0979b59be3110b01f8c5 Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Wed, 1 Sep 2021 21:05:52 +0100 Subject: [PATCH 700/809] refactor: KernelBuilder takes TSFCIntegralDataInfo --- tsfc/driver.py | 41 ++++++++++++++++++++++-- tsfc/kernel_interface/firedrake.py | 15 ++++----- tsfc/kernel_interface/firedrake_loopy.py | 17 +++++----- tsfc/kernel_interface/ufc.py | 5 ++- 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 3232df9bc0..a28f4bf744 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -34,6 +34,28 @@ sys.setrecursionlimit(3000) +TSFCIntegralDataInfo = collections.namedtuple("TSFCIntegralDataInfo", + ["domain", "integral_type", "subdomain_id", "domain_number", + "arguments", + "coefficients", "coefficient_numbers"]) +TSFCIntegralDataInfo.__doc__ = """ + Minimal set of objects for kernel builders. + + domain - The mesh. + integral_type - The type of integral. + subdomain_id - What is the subdomain id for this kernel. + domain_number - Which domain number in the original form + does this kernel correspond to (can be used to index into + original_form.ufl_domains() to get the correct domain). + coefficients - A list of coefficients. + coefficient_numbers - A list of which coefficients from the + form the kernel needs. + + This is a minimal set of objects that kernel builders need to + construct a kernel from :attr:`integrals` of :class:`~ufl.IntegralData`. + """ + + def compile_form(form, prefix="form", parameters=None, interface=None, coffee=True, diagonal=False): """Compiles a UFL form into a set of assembly kernels. @@ -107,8 +129,23 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() - builder = interface(integral_type, integral_data.subdomain_id, - domain_numbering[integral_data.domain], + domain_number = domain_numbering[integral_data.domain] + coefficients = [form_data.function_replace_map[c] for c in integral_data.integral_coefficients] + # This is which coefficient in the original form the + # current coefficient is. + # Consider f*v*dx + g*v*ds, the full form contains two + # coefficients, but each integral only requires one. + coefficient_numbers = tuple(form_data.original_coefficient_positions[i] + for i, (_, enabled) in enumerate(zip(form_data.reduced_coefficients, integral_data.enabled_coefficients)) + if enabled) + integral_data_info = TSFCIntegralDataInfo(domain=integral_data.domain, + integral_type=integral_data.integral_type, + subdomain_id=integral_data.subdomain_id, + domain_number=domain_number, + arguments=arguments, + coefficients=coefficients, + coefficient_numbers=coefficient_numbers) + builder = interface(integral_data_info, scalar_type, diagonal=diagonal) argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 25977d8e3f..0f4bd00a27 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -126,9 +126,12 @@ def create_element(self, element, **kwargs): class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, + def __init__(self, integral_data_info, scalar_type, dont_split=(), diagonal=False): """Initialise a kernel builder.""" + integral_type = integral_data_info.integral_type + subdomain_id = integral_data_info.subdomain_id + domain_number = integral_data_info.domain_number super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, @@ -153,6 +156,8 @@ def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, elif integral_type == 'interior_facet_horiz': self._entity_number = {'+': 1, '-': 0} + self.integral_data_info = integral_data_info + def set_arguments(self, arguments, multiindices): """Process arguments. @@ -185,7 +190,6 @@ def set_coefficients(self, integral_data, form_data): :arg form_data: UFL form data """ coefficients = [] - coefficient_numbers = [] # enabled_coefficients is a boolean array that indicates which # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): @@ -203,15 +207,10 @@ def set_coefficients(self, integral_data, form_data): self.coefficient_split[coefficient] = split else: coefficients.append(coefficient) - # This is which coefficient in the original form the - # current coefficient is. - # Consider f*v*dx + g*v*ds, the full form contains two - # coefficients, but each integral only requires one. - coefficient_numbers.append(form_data.original_coefficient_positions[i]) for i, coefficient in enumerate(coefficients): coeff_coffee_arg = self._coefficient(coefficient, f"w_{i}") self.coefficient_args.append(kernel_args.CoefficientKernelArg(coeff_coffee_arg)) - self.kernel.coefficient_numbers = tuple(coefficient_numbers) + self.kernel.coefficient_numbers = tuple(self.integral_data_info.coefficient_numbers) def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 38585948cc..c067ae1249 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -201,9 +201,12 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords) class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont_split=(), - diagonal=False): + def __init__(self, integral_data_info, scalar_type, + dont_split=(), diagonal=False): """Initialise a kernel builder.""" + integral_type = integral_data_info.integral_type + subdomain_id = integral_data_info.subdomain_id + domain_number = integral_data_info.domain_number super().__init__(scalar_type, integral_type.startswith("interior_facet")) self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, @@ -228,6 +231,8 @@ def __init__(self, integral_type, subdomain_id, domain_number, scalar_type, dont elif integral_type == 'interior_facet_horiz': self._entity_number = {'+': 1, '-': 0} + self.integral_data_info = integral_data_info + def set_arguments(self, arguments, multiindices): """Process arguments. @@ -259,7 +264,6 @@ def set_coefficients(self, integral_data, form_data): :arg form_data: UFL form data """ coefficients = [] - coefficient_numbers = [] # enabled_coefficients is a boolean array that indicates which # of reduced_coefficients the integral requires. for i in range(len(integral_data.enabled_coefficients)): @@ -277,15 +281,10 @@ def set_coefficients(self, integral_data, form_data): self.coefficient_split[coefficient] = split else: coefficients.append(coefficient) - # This is which coefficient in the original form the - # current coefficient is. - # Consider f*v*dx + g*v*ds, the full form contains two - # coefficients, but each integral only requires one. - coefficient_numbers.append(form_data.original_coefficient_positions[i]) for i, coefficient in enumerate(coefficients): coeff_loopy_arg = self._coefficient(coefficient, f"w_{i}") self.coefficient_args.append(kernel_args.CoefficientKernelArg(coeff_loopy_arg)) - self.kernel.coefficient_numbers = tuple(coefficient_numbers) + self.kernel.coefficient_numbers = tuple(self.integral_data_info.coefficient_numbers) def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 305e098899..080f578523 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -22,8 +22,9 @@ class KernelBuilder(KernelBuilderBase): """Helper class for building a :class:`Kernel` object.""" - def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None, diagonal=False): + def __init__(self, integral_data_info, scalar_type, diagonal=False): """Initialise a kernel builder.""" + integral_type = integral_data_info.integral_type if diagonal: raise NotImplementedError("Assembly of diagonal not implemented yet, sorry") super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) @@ -50,6 +51,8 @@ def __init__(self, integral_type, subdomain_id, domain_number, scalar_type=None, elif integral_type == "vertex": self._entity_number = {None: gem.VariableIndex(gem.Variable("vertex", ()))} + self.integral_data_info = integral_data_info + def set_arguments(self, arguments, multiindices): """Process arguments. From 75a2fac4d8531802ee2b03ec6da67731e1b230b2 Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Wed, 1 Sep 2021 12:43:11 +0100 Subject: [PATCH 701/809] refactor: introduce KernelBuilderMixin() --- tsfc/kernel_interface/common.py | 5 +++++ tsfc/kernel_interface/firedrake.py | 4 ++-- tsfc/kernel_interface/firedrake_loopy.py | 4 ++-- tsfc/kernel_interface/ufc.py | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 9b78817c3e..483cbad068 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -107,3 +107,8 @@ def register_requirements(self, ir): """ # Nothing is required by default pass + + +class KernelBuilderMixin(object): + """Mixin for KernelBuilder classes.""" + pass diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 0f4bd00a27..3191940d2a 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -14,7 +14,7 @@ from tsfc import kernel_args from tsfc.coffee import generate as generate_coffee from tsfc.finatinterface import create_element -from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase +from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin def make_builder(*args, **kwargs): @@ -123,7 +123,7 @@ def create_element(self, element, **kwargs): return create_element(element, **kwargs) -class KernelBuilder(KernelBuilderBase): +class KernelBuilder(KernelBuilderBase, KernelBuilderMixin): """Helper class for building a :class:`Kernel` object.""" def __init__(self, integral_data_info, scalar_type, diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index c067ae1249..89999a38b9 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -13,7 +13,7 @@ from tsfc import kernel_args from tsfc.finatinterface import create_element -from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase +from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin from tsfc.kernel_interface.firedrake import check_requirements from tsfc.loopy import generate as generate_loopy @@ -198,7 +198,7 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords) self.tabulations, name, args, count_flops(impero_c)) -class KernelBuilder(KernelBuilderBase): +class KernelBuilder(KernelBuilderBase, KernelBuilderMixin): """Helper class for building a :class:`Kernel` object.""" def __init__(self, integral_data_info, scalar_type, diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 080f578523..06786eb686 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -11,7 +11,7 @@ import ufl -from tsfc.kernel_interface.common import KernelBuilderBase +from tsfc.kernel_interface.common import KernelBuilderBase, KernelBuilderMixin from tsfc.finatinterface import create_element as _create_element @@ -19,7 +19,7 @@ create_element = functools.partial(_create_element, shape_innermost=False) -class KernelBuilder(KernelBuilderBase): +class KernelBuilder(KernelBuilderBase, KernelBuilderMixin): """Helper class for building a :class:`Kernel` object.""" def __init__(self, integral_data_info, scalar_type, diagonal=False): From 7993ae071bfc59248bdb3908b74901b3c533982b Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Thu, 2 Sep 2021 13:03:58 +0100 Subject: [PATCH 702/809] refactor: add builder.create_context() --- tsfc/driver.py | 16 +++-------- tsfc/kernel_interface/common.py | 49 ++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index a28f4bf744..dcc06eea41 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -125,8 +125,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co fiat_cell = as_fiat_cell(cell) integration_dim, entity_ids = lower_integral_type(fiat_cell, integral_type) - quadrature_indices = [] - # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() domain_number = domain_numbering[integral_data.domain] @@ -164,15 +162,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co builder.set_coefficients(integral_data, form_data) - # Map from UFL FiniteElement objects to multiindices. This is - # so we reuse Index instances when evaluating the same coefficient - # multiple times with the same table. - # - # We also use the same dict for the unconcatenate index cache, - # which maps index objects to tuples of multiindices. These two - # caches shall never conflict as their keys have different types - # (UFL finite elements vs. GEM index objects). - index_cache = {} + ctx = builder.create_context() + index_cache = ctx['index_cache'] + quadrature_indices = ctx['quadrature_indices'] + mode_irs = ctx['mode_irs'] kernel_cfg = dict(interface=builder, ufl_cell=cell, @@ -183,7 +176,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co index_cache=index_cache, scalar_type=parameters["scalar_type"]) - mode_irs = collections.OrderedDict() for integral in integral_data.integrals: params = parameters.copy() params.update(integral.metadata()) # integral metadata overrides diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 483cbad068..d7a4462fda 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -1,3 +1,5 @@ +import collections + import numpy import coffee.base as coffee @@ -111,4 +113,49 @@ def register_requirements(self, ir): class KernelBuilderMixin(object): """Mixin for KernelBuilder classes.""" - pass + + def create_context(self): + """Create builder context. + + *index_cache* + + Map from UFL FiniteElement objects to multiindices. + This is so we reuse Index instances when evaluating the same + coefficient multiple times with the same table. + + We also use the same dict for the unconcatenate index cache, + which maps index objects to tuples of multiindices. These two + caches shall never conflict as their keys have different types + (UFL finite elements vs. GEM index objects). + + *quadrature_indices* + + List of quadrature indices used. + + *mode_irs* + + Dict for mode representations. + + For each set of integrals to make a kernel for (i,e., + `integral_data.integrals`), one must first create a ctx object by + calling :meth:`create_context` method. + This ctx object collects objects associated with the integrals that + are eventually used to construct the kernel. + The following is a typical calling sequence: + + .. code-block:: python3 + + builder = KernelBuilder(...) + params = {"mode": "spectral"} + ctx = builder.create_context() + for integral in integral_data.integrals: + integrand = integral.integrand() + integrand_exprs = builder.compile_integrand(integrand, params, ctx) + integral_exprs = builder.construct_integrals(integrand_exprs, params) + builder.stash_integrals(integral_exprs, params, ctx) + kernel = builder.construct_kernel(kernel_name, ctx) + + """ + return {'index_cache': {}, + 'quadrature_indices': [], + 'mode_irs': collections.OrderedDict()} From 9c246fb5a259c4901bd1104e7906f5817ade72db Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Fri, 3 Sep 2021 16:44:44 +0100 Subject: [PATCH 703/809] refactor: change builder.set_arguments() --- tsfc/driver.py | 13 +++---------- tsfc/kernel_interface/firedrake.py | 24 +++++++++++++++++------- tsfc/kernel_interface/firedrake_loopy.py | 23 +++++++++++++++++------ tsfc/kernel_interface/ufc.py | 21 +++++++++++++++++---- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index dcc06eea41..2362496ce4 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -146,16 +146,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co builder = interface(integral_data_info, scalar_type, diagonal=diagonal) - argument_multiindices = tuple(builder.create_element(arg.ufl_element()).get_indices() - for arg in arguments) - if diagonal: - # Error checking occurs in the builder constructor. - # Diagonal assembly is obtained by using the test indices for - # the trial space as well. - a, _ = argument_multiindices - argument_multiindices = (a, a) - - return_variables = builder.set_arguments(arguments, argument_multiindices) + + return_variables = builder.return_variables + argument_multiindices = builder.argument_multiindices builder.set_coordinates(mesh) builder.set_cell_sizes(mesh) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 3191940d2a..0880bd8b45 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -156,21 +156,31 @@ def __init__(self, integral_data_info, scalar_type, elif integral_type == 'interior_facet_horiz': self._entity_number = {'+': 1, '-': 0} + self.set_arguments(integral_data_info.arguments) self.integral_data_info = integral_data_info - def set_arguments(self, arguments, multiindices): + def set_arguments(self, arguments): """Process arguments. :arg arguments: :class:`ufl.Argument`s - :arg multiindices: GEM argument multiindices :returns: GEM expression representing the return variable """ - funarg, exprs = prepare_arguments(arguments, multiindices, - self.scalar_type, - interior_facet=self.interior_facet, - diagonal=self.diagonal) + argument_multiindices = tuple(create_element(arg.ufl_element()).get_indices() + for arg in arguments) + if self.diagonal: + # Error checking occurs in the builder constructor. + # Diagonal assembly is obtained by using the test indices for + # the trial space as well. + a, _ = argument_multiindices + argument_multiindices = (a, a) + funarg, return_variables = prepare_arguments(arguments, + argument_multiindices, + self.scalar_type, + interior_facet=self.interior_facet, + diagonal=self.diagonal) self.output_arg = kernel_args.OutputKernelArg(funarg) - return exprs + self.return_variables = return_variables + self.argument_multiindices = argument_multiindices def set_coordinates(self, domain): """Prepare the coordinate field. diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 89999a38b9..366853c444 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -231,20 +231,31 @@ def __init__(self, integral_data_info, scalar_type, elif integral_type == 'interior_facet_horiz': self._entity_number = {'+': 1, '-': 0} + self.set_arguments(integral_data_info.arguments) self.integral_data_info = integral_data_info - def set_arguments(self, arguments, multiindices): + def set_arguments(self, arguments): """Process arguments. :arg arguments: :class:`ufl.Argument`s - :arg multiindices: GEM argument multiindices :returns: GEM expression representing the return variable """ - funarg, expressions = prepare_arguments( - arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet, - diagonal=self.diagonal) + argument_multiindices = tuple(create_element(arg.ufl_element()).get_indices() + for arg in arguments) + if self.diagonal: + # Error checking occurs in the builder constructor. + # Diagonal assembly is obtained by using the test indices for + # the trial space as well. + a, _ = argument_multiindices + argument_multiindices = (a, a) + funarg, return_variables = prepare_arguments(arguments, + argument_multiindices, + self.scalar_type, + interior_facet=self.interior_facet, + diagonal=self.diagonal) self.output_arg = kernel_args.OutputKernelArg(funarg) - return expressions + self.return_variables = return_variables + self.argument_multiindices = argument_multiindices def set_coordinates(self, domain): """Prepare the coordinate field. diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 06786eb686..98c58910bc 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -51,19 +51,32 @@ def __init__(self, integral_data_info, scalar_type, diagonal=False): elif integral_type == "vertex": self._entity_number = {None: gem.VariableIndex(gem.Variable("vertex", ()))} + self.set_arguments(integral_data_info.arguments) self.integral_data_info = integral_data_info - def set_arguments(self, arguments, multiindices): + def set_arguments(self, arguments): """Process arguments. :arg arguments: :class:`ufl.Argument`s :arg multiindices: GEM argument multiindices :returns: GEM expression representing the return variable """ - self.local_tensor, prepare, expressions = prepare_arguments( - arguments, multiindices, self.scalar_type, interior_facet=self.interior_facet) + argument_multiindices = tuple(create_element(arg.ufl_element()).get_indices() + for arg in arguments) + if self.diagonal: + # Error checking occurs in the builder constructor. + # Diagonal assembly is obtained by using the test indices for + # the trial space as well. + a, _ = argument_multiindices + argument_multiindices = (a, a) + local_tensor, prepare, return_variables = prepare_arguments(arguments, + argument_multiindices, + self.scalar_type, + interior_facet=self.interior_facet) self.apply_glue(prepare) - return expressions + self.local_tensor = local_tensor + self.return_variables = return_variables + self.argument_multiindices = argument_multiindices def set_coordinates(self, domain): """Prepare the coordinate field. From 0a1421c4e46670a0bab5e050987c9c0b5e92f8c9 Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Fri, 3 Sep 2021 14:14:13 +0100 Subject: [PATCH 704/809] refactor: add builder.fem_config() --- tsfc/driver.py | 21 ++++----------------- tsfc/kernel_interface/common.py | 21 +++++++++++++++++++++ tsfc/kernel_interface/firedrake.py | 3 ++- tsfc/kernel_interface/firedrake_loopy.py | 3 ++- tsfc/kernel_interface/ufc.py | 1 + 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 2362496ce4..bec9384d73 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -108,23 +108,15 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co # Delayed import, loopy is a runtime dependency import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy interface = firedrake_interface_loopy.KernelBuilder - if coffee: - scalar_type = parameters["scalar_type_c"] - else: - scalar_type = parameters["scalar_type"] - + scalar_type = parameters["scalar_type"] integral_type = integral_data.integral_type interior_facet = integral_type.startswith("interior_facet") mesh = integral_data.domain - cell = integral_data.domain.ufl_cell() arguments = form_data.preprocessed_form.arguments() kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) # Handle negative subdomain_id kernel_name = kernel_name.replace("-", "_") - fiat_cell = as_fiat_cell(cell) - integration_dim, entity_ids = lower_integral_type(fiat_cell, integral_type) - # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() domain_number = domain_numbering[integral_data.domain] @@ -160,14 +152,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co quadrature_indices = ctx['quadrature_indices'] mode_irs = ctx['mode_irs'] - kernel_cfg = dict(interface=builder, - ufl_cell=cell, - integral_type=integral_type, - integration_dim=integration_dim, - entity_ids=entity_ids, - argument_multiindices=argument_multiindices, - index_cache=index_cache, - scalar_type=parameters["scalar_type"]) + kernel_cfg = builder.fem_config.copy() + kernel_cfg['argument_multiindices'] = argument_multiindices + kernel_cfg['index_cache'] = ctx['index_cache'] for integral in integral_data.integrals: params = parameters.copy() diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index d7a4462fda..63064857f6 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -8,7 +8,9 @@ from gem.utils import cached_property +from tsfc.driver import lower_integral_type from tsfc.kernel_interface import KernelInterface +from tsfc.finatinterface import as_fiat_cell class KernelBuilderBase(KernelInterface): @@ -114,6 +116,25 @@ def register_requirements(self, ir): class KernelBuilderMixin(object): """Mixin for KernelBuilder classes.""" + def fem_config(self): + """Return a dictionary used with fem.compile_ufl. + + One needs to update this dictionary with "argument_multiindices", + "quadrature_rule", and "index_cache" before using this with + fem.compile_ufl. + """ + info = self.integral_data_info + integral_type = info.integral_type + cell = info.domain.ufl_cell() + fiat_cell = as_fiat_cell(cell) + integration_dim, entity_ids = lower_integral_type(fiat_cell, integral_type) + return dict(interface=self, + ufl_cell=cell, + integral_type=integral_type, + integration_dim=integration_dim, + entity_ids=entity_ids, + scalar_type=self.fem_scalar_type) + def create_context(self): """Create builder context. diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 0880bd8b45..475db8b0a6 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -132,7 +132,8 @@ def __init__(self, integral_data_info, scalar_type, integral_type = integral_data_info.integral_type subdomain_id = integral_data_info.subdomain_id domain_number = integral_data_info.domain_number - super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) + super(KernelBuilder, self).__init__(coffee.as_cstr(scalar_type), integral_type.startswith("interior_facet")) + self.fem_scalar_type = scalar_type self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 366853c444..a79f8ca1d8 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -207,7 +207,8 @@ def __init__(self, integral_data_info, scalar_type, integral_type = integral_data_info.integral_type subdomain_id = integral_data_info.subdomain_id domain_number = integral_data_info.domain_number - super().__init__(scalar_type, integral_type.startswith("interior_facet")) + super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) + self.fem_scalar_type = scalar_type self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, domain_number=domain_number) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 98c58910bc..2488a4d2fa 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -28,6 +28,7 @@ def __init__(self, integral_data_info, scalar_type, diagonal=False): if diagonal: raise NotImplementedError("Assembly of diagonal not implemented yet, sorry") super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) + self.fem_scalar_type = scalar_type self.integral_type = integral_type self.local_tensor = None From e06901dccb6b01413eacac5cd447c7176a60753c Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 00:23:02 +0100 Subject: [PATCH 705/809] refactor: add builder.compile_integrand() --- tsfc/driver.py | 20 +++----------------- tsfc/kernel_interface/common.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index bec9384d73..4c028b184b 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -110,7 +110,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co interface = firedrake_interface_loopy.KernelBuilder scalar_type = parameters["scalar_type"] integral_type = integral_data.integral_type - interior_facet = integral_type.startswith("interior_facet") mesh = integral_data.domain arguments = form_data.preprocessed_form.arguments() kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) @@ -152,10 +151,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co quadrature_indices = ctx['quadrature_indices'] mode_irs = ctx['mode_irs'] - kernel_cfg = builder.fem_config.copy() - kernel_cfg['argument_multiindices'] = argument_multiindices - kernel_cfg['index_cache'] = ctx['index_cache'] - for integral in integral_data.integrals: params = parameters.copy() params.update(integral.metadata()) # integral metadata overrides @@ -164,19 +159,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co mode_irs.setdefault(mode, collections.OrderedDict()) integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) - integrand = ufl_utils.split_coefficients(integrand, builder.coefficient_split) - functions = list(arguments) + [builder.coordinate(mesh)] + list(integral_data.integral_coefficients) - set_quad_rule(params, cell, integral_type, functions) + integrand_exprs = builder.compile_integrand(integrand, params, ctx) + quad_rule = params["quadrature_rule"] - quadrature_multiindex = quad_rule.point_set.indices - quadrature_indices.extend(quadrature_multiindex) - - config = kernel_cfg.copy() - config.update(quadrature_rule=quad_rule) - expressions = fem.compile_ufl(integrand, - fem.PointSetContext(**config), - interior_facet=interior_facet) - reps = mode.Integrals(expressions, quadrature_multiindex, + reps = mode.Integrals(integrand_exprs, quad_rule.point_set.indices, argument_multiindices, params) for var, rep in zip(return_variables, reps): mode_irs[mode].setdefault(var, []).append(rep) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 63064857f6..84349d2dde 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -8,7 +8,8 @@ from gem.utils import cached_property -from tsfc.driver import lower_integral_type +from tsfc.driver import lower_integral_type, set_quad_rule +from tsfc import fem, ufl_utils from tsfc.kernel_interface import KernelInterface from tsfc.finatinterface import as_fiat_cell @@ -116,6 +117,33 @@ def register_requirements(self, ir): class KernelBuilderMixin(object): """Mixin for KernelBuilder classes.""" + def compile_integrand(self, integrand, params, ctx): + """Compile UFL integrand. + + :arg integrand: UFL integrand. + :arg params: a dict containing "quadrature_rule". + :arg ctx: context created with :meth:`create_context` method. + + See :meth:`create_context` for typical calling sequence. + """ + # Split Coefficients + if self.coefficient_split: + integrand = ufl_utils.split_coefficients(integrand, self.coefficient_split) + # Compile: ufl -> gem + info = self.integral_data_info + functions = list(info.arguments) + [self.coordinate(info.domain)] + list(info.coefficients) + set_quad_rule(params, info.domain.ufl_cell(), info.integral_type, functions) + quad_rule = params["quadrature_rule"] + config = self.fem_config() + config['argument_multiindices'] = self.argument_multiindices + config['quadrature_rule'] = quad_rule + config['index_cache'] = ctx['index_cache'] + expressions = fem.compile_ufl(integrand, + fem.PointSetContext(**config), + interior_facet=self.interior_facet) + ctx['quadrature_indices'].extend(quad_rule.point_set.indices) + return expressions + def fem_config(self): """Return a dictionary used with fem.compile_ufl. From db77ffb44c4a7fe909e05315237cf994c60ea3fa Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 01:53:36 +0100 Subject: [PATCH 706/809] refactor: add builder.construct_integrals() --- tsfc/driver.py | 6 ++---- tsfc/kernel_interface/common.py | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 4c028b184b..211a86512c 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -160,11 +160,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) integrand_exprs = builder.compile_integrand(integrand, params, ctx) - + integral_exprs = builder.construct_integrals(integrand_exprs, params) quad_rule = params["quadrature_rule"] - reps = mode.Integrals(integrand_exprs, quad_rule.point_set.indices, - argument_multiindices, params) - for var, rep in zip(return_variables, reps): + for var, rep in zip(return_variables, integral_exprs): mode_irs[mode].setdefault(var, []).append(rep) # Finalise mode representations into a set of assignments diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 84349d2dde..9dd948691d 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -8,7 +8,7 @@ from gem.utils import cached_property -from tsfc.driver import lower_integral_type, set_quad_rule +from tsfc.driver import lower_integral_type, set_quad_rule, pick_mode from tsfc import fem, ufl_utils from tsfc.kernel_interface import KernelInterface from tsfc.finatinterface import as_fiat_cell @@ -144,6 +144,25 @@ def compile_integrand(self, integrand, params, ctx): ctx['quadrature_indices'].extend(quad_rule.point_set.indices) return expressions + def construct_integrals(self, integrand_expressions, params): + """Construct integrals from integrand expressions. + + :arg integrand_expressions: gem expressions for integrands. + :arg params: a dict containing "mode" and "quadrature_rule". + + integrand_expressions must be indexed with :attr:`argument_multiindices`; + these gem expressions are obtained by calling :meth:`compile_integrand` + method or by modifying the gem expressions returned by + :meth:`compile_integrand`. + + See :meth:`create_context` for typical calling sequence. + """ + mode = pick_mode(params["mode"]) + return mode.Integrals(integrand_expressions, + params["quadrature_rule"].point_set.indices, + self.argument_multiindices, + params) + def fem_config(self): """Return a dictionary used with fem.compile_ufl. From fe135bdf54c14648450da31abc593d0747cfd3c6 Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 02:10:06 +0100 Subject: [PATCH 707/809] refactor: add builder.stash_integrals() --- tsfc/driver.py | 6 +----- tsfc/kernel_interface/common.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 211a86512c..881b8ef014 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -155,15 +155,11 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co params = parameters.copy() params.update(integral.metadata()) # integral metadata overrides - mode = pick_mode(params["mode"]) - mode_irs.setdefault(mode, collections.OrderedDict()) - integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) integrand_exprs = builder.compile_integrand(integrand, params, ctx) integral_exprs = builder.construct_integrals(integrand_exprs, params) + builder.stash_integrals(integral_exprs, params, ctx) quad_rule = params["quadrature_rule"] - for var, rep in zip(return_variables, integral_exprs): - mode_irs[mode].setdefault(var, []).append(rep) # Finalise mode representations into a set of assignments assignments = [] diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 9dd948691d..39eba5c0c3 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -163,6 +163,21 @@ def construct_integrals(self, integrand_expressions, params): self.argument_multiindices, params) + def stash_integrals(self, reps, params, ctx): + """Stash integral representations in ctx. + + :arg reps: integral representations. + :arg params: a dict containing "mode". + :arg ctx: context in which reps are stored. + + See :meth:`create_context` for typical calling sequence. + """ + mode = pick_mode(params["mode"]) + mode_irs = ctx['mode_irs'] + mode_irs.setdefault(mode, collections.OrderedDict()) + for var, rep in zip(self.return_variables, reps): + mode_irs[mode].setdefault(var, []).append(rep) + def fem_config(self): """Return a dictionary used with fem.compile_ufl. From 137ec770acb8a21321959ed97a3f1932aebf70c5 Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 11:51:50 +0100 Subject: [PATCH 708/809] refactor: add builder.compile_gem() --- tsfc/driver.py | 35 +----------------- tsfc/kernel_interface/common.py | 47 +++++++++++++++++++++++- tsfc/kernel_interface/firedrake.py | 3 +- tsfc/kernel_interface/firedrake_loopy.py | 3 +- tsfc/kernel_interface/ufc.py | 5 +++ 5 files changed, 54 insertions(+), 39 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 881b8ef014..45ce0b3656 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,9 +1,7 @@ import collections -import operator import string import time import sys -from functools import reduce from itertools import chain from finat.physically_mapped import DirectlyDefinedElement, PhysicallyMappedElement @@ -160,39 +158,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co integral_exprs = builder.construct_integrals(integrand_exprs, params) builder.stash_integrals(integral_exprs, params, ctx) quad_rule = params["quadrature_rule"] + impero_c, oriented, needs_cell_sizes, tabulations = builder.compile_gem(ctx) # Put this in builder.construct_kernel() - # Finalise mode representations into a set of assignments - assignments = [] - for mode, var_reps in mode_irs.items(): - assignments.extend(mode.flatten(var_reps.items(), index_cache)) - - if assignments: - return_variables, expressions = zip(*assignments) - else: - return_variables = [] - expressions = [] - - # Need optimised roots - options = dict(reduce(operator.and_, - [mode.finalise_options.items() - for mode in mode_irs.keys()])) - expressions = impero_utils.preprocess_gem(expressions, **options) - assignments = list(zip(return_variables, expressions)) - - # Let the kernel interface inspect the optimised IR to register - # what kind of external data is required (e.g., cell orientations, - # cell sizes, etc.). - builder.register_requirements(expressions) - - # Construct ImperoC - index_ordering = get_index_ordering(quadrature_indices, return_variables) - try: - impero_c = impero_utils.compile_gem(assignments, index_ordering, remove_zeros=True) - except impero_utils.NoopError: - # No operations, construct empty kernel - return builder.construct_empty_kernel(kernel_name) - - # Generate COFFEE index_names = get_index_names(quadrature_indices, argument_multiindices, index_cache) return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 39eba5c0c3..baf05dbf58 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -1,4 +1,6 @@ import collections +import operator +from functools import reduce import numpy @@ -7,8 +9,9 @@ import gem from gem.utils import cached_property +import gem.impero_utils as impero_utils -from tsfc.driver import lower_integral_type, set_quad_rule, pick_mode +from tsfc.driver import lower_integral_type, set_quad_rule, pick_mode, get_index_ordering from tsfc import fem, ufl_utils from tsfc.kernel_interface import KernelInterface from tsfc.finatinterface import as_fiat_cell @@ -178,6 +181,48 @@ def stash_integrals(self, reps, params, ctx): for var, rep in zip(self.return_variables, reps): mode_irs[mode].setdefault(var, []).append(rep) + def compile_gem(self, ctx): + """Compile gem representation of integrals to impero_c. + + :arg ctx: the context containing the gem representation of integrals. + :returns: a tuple of impero_c, oriented, needs_cell_sizes, tabulations + required to finally construct a kernel in :meth:`construct_kernel`. + + See :meth:`create_context` for typical calling sequence. + """ + # Finalise mode representations into a set of assignments + mode_irs = ctx['mode_irs'] + + assignments = [] + for mode, var_reps in mode_irs.items(): + assignments.extend(mode.flatten(var_reps.items(), ctx['index_cache'])) + + if assignments: + return_variables, expressions = zip(*assignments) + else: + return_variables = [] + expressions = [] + + # Need optimised roots + options = dict(reduce(operator.and_, + [mode.finalise_options.items() + for mode in mode_irs.keys()])) + expressions = impero_utils.preprocess_gem(expressions, **options) + + # Let the kernel interface inspect the optimised IR to register + # what kind of external data is required (e.g., cell orientations, + # cell sizes, etc.). + oriented, needs_cell_sizes, tabulations = self.register_requirements(expressions) + + # Construct ImperoC + assignments = list(zip(return_variables, expressions)) + index_ordering = get_index_ordering(ctx['quadrature_indices'], return_variables) + try: + impero_c = impero_utils.compile_gem(assignments, index_ordering, remove_zeros=True) + except impero_utils.NoopError: + impero_c = None + return impero_c, oriented, needs_cell_sizes, tabulations + def fem_config(self): """Return a dictionary used with fem.compile_ufl. diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 475db8b0a6..2280c79d67 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -226,8 +226,7 @@ def set_coefficients(self, integral_data, form_data): def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be provided by the kernel interface.""" - knl = self.kernel - knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) + return check_requirements(ir) def construct_kernel(self, name, impero_c, index_names, quadrature_rule): """Construct a fully built :class:`Kernel`. diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index a79f8ca1d8..f63dde01a7 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -301,8 +301,7 @@ def set_coefficients(self, integral_data, form_data): def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be provided by the kernel interface.""" - knl = self.kernel - knl.oriented, knl.needs_cell_sizes, knl.tabulations = check_requirements(ir) + return check_requirements(ir) def construct_kernel(self, name, impero_c, index_names, quadrature_rule): """Construct a fully built :class:`Kernel`. diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 2488a4d2fa..876ee5a379 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -121,6 +121,11 @@ def set_coefficients(self, integral_data, form_data): expression = prepare_coefficient(coefficient, n, name, self.interior_facet) self.coefficient_map[coefficient] = expression + def register_requirements(self, ir): + """Inspect what is referenced by the IR that needs to be + provided by the kernel interface.""" + return None, None, None + def construct_kernel(self, name, impero_c, index_names, quadrature_rule=None): """Construct a fully built kernel function. From 47143ac62ae5700fedafbff10a4e5cb47f0c57e2 Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 12:19:52 +0100 Subject: [PATCH 709/809] refactor: pass ctx to builder.construct_kernel() --- tsfc/driver.py | 3 +-- tsfc/kernel_interface/firedrake.py | 15 ++++++++++++--- tsfc/kernel_interface/firedrake_loopy.py | 13 ++++++++++--- tsfc/kernel_interface/ufc.py | 10 +++++++--- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 45ce0b3656..68324207d3 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -158,11 +158,10 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co integral_exprs = builder.construct_integrals(integrand_exprs, params) builder.stash_integrals(integral_exprs, params, ctx) quad_rule = params["quadrature_rule"] - impero_c, oriented, needs_cell_sizes, tabulations = builder.compile_gem(ctx) # Put this in builder.construct_kernel() index_names = get_index_names(quadrature_indices, argument_multiindices, index_cache) - return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) + return builder.construct_kernel(kernel_name, ctx, index_names, quad_rule) def preprocess_parameters(parameters): diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 2280c79d67..5f386df152 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -228,18 +228,27 @@ def register_requirements(self, ir): provided by the kernel interface.""" return check_requirements(ir) - def construct_kernel(self, name, impero_c, index_names, quadrature_rule): + def construct_kernel(self, name, ctx, index_names, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument list for assembly kernels. - :arg name: function name - :arg impero_c: ImperoC tuple with Impero AST and other data + :arg name: kernel name + :arg ctx: kernel builder context to get impero_c from :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object """ + impero_c, oriented, needs_cell_sizes, tabulations = self.compile_gem(ctx) + if impero_c is None: + return self.construct_empty_kernel(name) + self.kernel.oriented = oriented + self.kernel.needs_cell_sizes = needs_cell_sizes + self.kernel.tabulations = tabulations + + body = generate_coffee(impero_c, index_names, self.scalar_type) + args = [self.output_arg, self.coordinates_arg] if self.kernel.oriented: ori_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index f63dde01a7..38ce200874 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -303,18 +303,25 @@ def register_requirements(self, ir): provided by the kernel interface.""" return check_requirements(ir) - def construct_kernel(self, name, impero_c, index_names, quadrature_rule): + def construct_kernel(self, name, ctx, index_names, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument list for assembly kernels. - :arg name: function name - :arg impero_c: ImperoC tuple with Impero AST and other data + :arg name: kernel name + :arg ctx: kernel builder context to get impero_c from :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object """ + impero_c, oriented, needs_cell_sizes, tabulations = self.compile_gem(ctx) + if impero_c is None: + return self.construct_empty_kernel(name) + self.kernel.oriented = oriented + self.kernel.needs_cell_sizes = needs_cell_sizes + self.kernel.tabulations = tabulations + args = [self.output_arg, self.coordinates_arg] if self.kernel.oriented: args.append(self.cell_orientations_arg) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 876ee5a379..aa6f3c8229 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -126,19 +126,23 @@ def register_requirements(self, ir): provided by the kernel interface.""" return None, None, None - def construct_kernel(self, name, impero_c, index_names, quadrature_rule=None): + def construct_kernel(self, name, ctx, index_names, quadrature_rule=None): """Construct a fully built kernel function. This function contains the logic for building the argument list for assembly kernels. - :arg name: function name - :arg impero_c: ImperoC tuple with Impero AST and other data + :arg name: kernel name + :arg ctx: kernel builder context to get impero_c from :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule (not used, stubbed out for Themis integration) :returns: a COFFEE function definition object """ from tsfc.coffee import generate as generate_coffee + + impero_c, _, _, _ = self.compile_gem(ctx) + if impero_c is None: + return self.construct_empty_kernel(name) body = generate_coffee(impero_c, index_names, scalar_type=self.scalar_type) return self._construct_kernel_from_body(name, body) From 46fc000d352c29bef2049f05806a9a0774049236 Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 12:42:16 +0100 Subject: [PATCH 710/809] refactor: move get_index_names() --- tsfc/driver.py | 28 +----------------------- tsfc/kernel_interface/common.py | 24 ++++++++++++++++++++ tsfc/kernel_interface/firedrake.py | 6 ++--- tsfc/kernel_interface/firedrake_loopy.py | 6 ++--- tsfc/kernel_interface/ufc.py | 6 ++--- 5 files changed, 34 insertions(+), 36 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 68324207d3..90f01a5bbf 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -1,5 +1,4 @@ import collections -import string import time import sys from itertools import chain @@ -159,9 +158,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co builder.stash_integrals(integral_exprs, params, ctx) quad_rule = params["quadrature_rule"] - index_names = get_index_names(quadrature_indices, argument_multiindices, index_cache) - - return builder.construct_kernel(kernel_name, ctx, index_names, quad_rule) + return builder.construct_kernel(kernel_name, ctx, quad_rule) def preprocess_parameters(parameters): @@ -380,29 +377,6 @@ def get_index_ordering(quadrature_indices, return_variables): return tuple(quadrature_indices) + split_argument_indices -def get_index_names(quadrature_indices, argument_multiindices, index_cache): - index_names = [] - - def name_index(index, name): - index_names.append((index, name)) - if index in index_cache: - for multiindex, suffix in zip(index_cache[index], - string.ascii_lowercase): - name_multiindex(multiindex, name + suffix) - - def name_multiindex(multiindex, name): - if len(multiindex) == 1: - name_index(multiindex[0], name) - else: - for i, index in enumerate(multiindex): - name_index(index, name + str(i)) - - name_multiindex(quadrature_indices, 'ip') - for multiindex, name in zip(argument_multiindices, ['j', 'k']): - name_multiindex(multiindex, name) - return index_names - - def lower_integral_type(fiat_cell, integral_type): """Lower integral type into the dimension of the integration subentity and a list of entity numbers for that dimension. diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index baf05dbf58..61ad37107c 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -1,4 +1,5 @@ import collections +import string import operator from functools import reduce @@ -287,3 +288,26 @@ def create_context(self): return {'index_cache': {}, 'quadrature_indices': [], 'mode_irs': collections.OrderedDict()} + + +def get_index_names(quadrature_indices, argument_multiindices, index_cache): + index_names = [] + + def name_index(index, name): + index_names.append((index, name)) + if index in index_cache: + for multiindex, suffix in zip(index_cache[index], + string.ascii_lowercase): + name_multiindex(multiindex, name + suffix) + + def name_multiindex(multiindex, name): + if len(multiindex) == 1: + name_index(multiindex[0], name) + else: + for i, index in enumerate(multiindex): + name_index(index, name + str(i)) + + name_multiindex(quadrature_indices, 'ip') + for multiindex, name in zip(argument_multiindices, ['j', 'k']): + name_multiindex(multiindex, name) + return index_names diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 5f386df152..345d83d63a 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -14,7 +14,7 @@ from tsfc import kernel_args from tsfc.coffee import generate as generate_coffee from tsfc.finatinterface import create_element -from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin +from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names def make_builder(*args, **kwargs): @@ -228,7 +228,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" return check_requirements(ir) - def construct_kernel(self, name, ctx, index_names, quadrature_rule): + def construct_kernel(self, name, ctx, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -236,7 +236,6 @@ def construct_kernel(self, name, ctx, index_names, quadrature_rule): :arg name: kernel name :arg ctx: kernel builder context to get impero_c from - :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object """ @@ -247,6 +246,7 @@ def construct_kernel(self, name, ctx, index_names, quadrature_rule): self.kernel.needs_cell_sizes = needs_cell_sizes self.kernel.tabulations = tabulations + index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) body = generate_coffee(impero_c, index_names, self.scalar_type) args = [self.output_arg, self.coordinates_arg] diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 38ce200874..ba446fcebf 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -13,7 +13,7 @@ from tsfc import kernel_args from tsfc.finatinterface import create_element -from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin +from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names from tsfc.kernel_interface.firedrake import check_requirements from tsfc.loopy import generate as generate_loopy @@ -303,7 +303,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" return check_requirements(ir) - def construct_kernel(self, name, ctx, index_names, quadrature_rule): + def construct_kernel(self, name, ctx, quadrature_rule): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -311,7 +311,6 @@ def construct_kernel(self, name, ctx, index_names, quadrature_rule): :arg name: kernel name :arg ctx: kernel builder context to get impero_c from - :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object """ @@ -338,6 +337,7 @@ def construct_kernel(self, name, ctx, index_names, quadrature_rule): tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) args.append(kernel_args.TabulationKernelArg(tab_loopy_arg)) + index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) self.kernel.ast = generate_loopy(impero_c, [arg.loopy_arg for arg in args], self.scalar_type, name, index_names) self.kernel.arguments = tuple(args) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index aa6f3c8229..d9648d0eb5 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -11,7 +11,7 @@ import ufl -from tsfc.kernel_interface.common import KernelBuilderBase, KernelBuilderMixin +from tsfc.kernel_interface.common import KernelBuilderBase, KernelBuilderMixin, get_index_names from tsfc.finatinterface import create_element as _create_element @@ -126,7 +126,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" return None, None, None - def construct_kernel(self, name, ctx, index_names, quadrature_rule=None): + def construct_kernel(self, name, ctx, quadrature_rule=None): """Construct a fully built kernel function. This function contains the logic for building the argument @@ -134,7 +134,6 @@ def construct_kernel(self, name, ctx, index_names, quadrature_rule=None): :arg name: kernel name :arg ctx: kernel builder context to get impero_c from - :arg index_names: pre-assigned index names :arg quadrature rule: quadrature rule (not used, stubbed out for Themis integration) :returns: a COFFEE function definition object """ @@ -143,6 +142,7 @@ def construct_kernel(self, name, ctx, index_names, quadrature_rule=None): impero_c, _, _, _ = self.compile_gem(ctx) if impero_c is None: return self.construct_empty_kernel(name) + index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) body = generate_coffee(impero_c, index_names, scalar_type=self.scalar_type) return self._construct_kernel_from_body(name, body) From a37d85b485f236ca79bab0e6df69fa1aee0815fb Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 13:11:45 +0100 Subject: [PATCH 711/809] refactor: remove unused 'quadrature_rule' attribute from Kernel --- tsfc/driver.py | 10 +--------- tsfc/kernel_interface/firedrake.py | 9 +++------ tsfc/kernel_interface/firedrake_loopy.py | 8 +++----- tsfc/kernel_interface/ufc.py | 4 ++-- 4 files changed, 9 insertions(+), 22 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 90f01a5bbf..bcd49a9816 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -135,19 +135,12 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co scalar_type, diagonal=diagonal) - return_variables = builder.return_variables - argument_multiindices = builder.argument_multiindices - builder.set_coordinates(mesh) builder.set_cell_sizes(mesh) builder.set_coefficients(integral_data, form_data) ctx = builder.create_context() - index_cache = ctx['index_cache'] - quadrature_indices = ctx['quadrature_indices'] - mode_irs = ctx['mode_irs'] - for integral in integral_data.integrals: params = parameters.copy() params.update(integral.metadata()) # integral metadata overrides @@ -156,9 +149,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co integrand_exprs = builder.compile_integrand(integrand, params, ctx) integral_exprs = builder.construct_integrals(integrand_exprs, params) builder.stash_integrals(integral_exprs, params, ctx) - quad_rule = params["quadrature_rule"] - return builder.construct_kernel(kernel_name, ctx, quad_rule) + return builder.construct_kernel(kernel_name, ctx) def preprocess_parameters(parameters): diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 345d83d63a..fbb0b8a5dc 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -23,7 +23,7 @@ def make_builder(*args, **kwargs): class Kernel(object): __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", - "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", + "domain_number", "needs_cell_sizes", "tabulations", "coefficient_numbers", "name", "__weakref__", "flop_count") """A compiled Kernel object. @@ -37,13 +37,12 @@ class Kernel(object): original_form.ufl_domains() to get the correct domain). :kwarg coefficient_numbers: A list of which coefficients from the form the kernel needs. - :kwarg quadrature_rule: The finat quadrature rule used to generate this kernel :kwarg tabulations: The runtime tabulations this kernel requires :kwarg needs_cell_sizes: Does the kernel require cell sizes. :kwarg flop_count: Estimated total flops for this kernel. """ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, - subdomain_id=None, domain_number=None, quadrature_rule=None, + subdomain_id=None, domain_number=None, coefficient_numbers=(), needs_cell_sizes=False, flop_count=0): @@ -228,7 +227,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" return check_requirements(ir) - def construct_kernel(self, name, ctx, quadrature_rule): + def construct_kernel(self, name, ctx): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -236,7 +235,6 @@ def construct_kernel(self, name, ctx, quadrature_rule): :arg name: kernel name :arg ctx: kernel builder context to get impero_c from - :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object """ impero_c, oriented, needs_cell_sizes, tabulations = self.compile_gem(ctx) @@ -279,7 +277,6 @@ def construct_kernel(self, name, ctx, quadrature_rule): self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, coffee_args, body) self.kernel.arguments = tuple(args) - self.kernel.quadrature_rule = quadrature_rule self.kernel.name = name self.kernel.flop_count = count_flops(impero_c) return self.kernel diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index ba446fcebf..1528374536 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -29,7 +29,7 @@ def make_builder(*args, **kwargs): class Kernel: __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", - "domain_number", "needs_cell_sizes", "tabulations", "quadrature_rule", + "domain_number", "needs_cell_sizes", "tabulations", "coefficient_numbers", "name", "flop_count", "__weakref__") """A compiled Kernel object. @@ -43,14 +43,13 @@ class Kernel: original_form.ufl_domains() to get the correct domain). :kwarg coefficient_numbers: A list of which coefficients from the form the kernel needs. - :kwarg quadrature_rule: The finat quadrature rule used to generate this kernel :kwarg tabulations: The runtime tabulations this kernel requires :kwarg needs_cell_sizes: Does the kernel require cell sizes. :kwarg name: The name of this kernel. :kwarg flop_count: Estimated total flops for this kernel. """ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, - subdomain_id=None, domain_number=None, quadrature_rule=None, + subdomain_id=None, domain_number=None, coefficient_numbers=(), needs_cell_sizes=False, flop_count=0): @@ -303,7 +302,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" return check_requirements(ir) - def construct_kernel(self, name, ctx, quadrature_rule): + def construct_kernel(self, name, ctx): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -311,7 +310,6 @@ def construct_kernel(self, name, ctx, quadrature_rule): :arg name: kernel name :arg ctx: kernel builder context to get impero_c from - :arg quadrature rule: quadrature rule :returns: :class:`Kernel` object """ impero_c, oriented, needs_cell_sizes, tabulations = self.compile_gem(ctx) diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index d9648d0eb5..65c91e51b4 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -126,7 +126,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" return None, None, None - def construct_kernel(self, name, ctx, quadrature_rule=None): + def construct_kernel(self, name, ctx): """Construct a fully built kernel function. This function contains the logic for building the argument @@ -146,7 +146,7 @@ def construct_kernel(self, name, ctx, quadrature_rule=None): body = generate_coffee(impero_c, index_names, scalar_type=self.scalar_type) return self._construct_kernel_from_body(name, body) - def _construct_kernel_from_body(self, name, body, quadrature_rule): + def _construct_kernel_from_body(self, name, body): """Construct a fully built kernel function. This function contains the logic for building the argument From f1019d63899a4902fabb16df12abe24eb81c31cb Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 15:16:01 +0100 Subject: [PATCH 712/809] refactor: construct Kernel in one shot --- tsfc/kernel_interface/firedrake.py | 53 ++++++++++++------------ tsfc/kernel_interface/firedrake_loopy.py | 48 +++++++++++---------- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index fbb0b8a5dc..6f17f1bb6b 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -45,7 +45,9 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, coefficient_numbers=(), needs_cell_sizes=False, - flop_count=0): + tabulations=None, + flop_count=0, + name=None): # Defaults self.ast = ast self.arguments = arguments @@ -55,7 +57,9 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, self.subdomain_id = subdomain_id self.coefficient_numbers = coefficient_numbers self.needs_cell_sizes = needs_cell_sizes + self.tabulations = tabulations self.flop_count = flop_count + self.name = name super(Kernel, self).__init__() @@ -129,13 +133,9 @@ def __init__(self, integral_data_info, scalar_type, dont_split=(), diagonal=False): """Initialise a kernel builder.""" integral_type = integral_data_info.integral_type - subdomain_id = integral_data_info.subdomain_id - domain_number = integral_data_info.domain_number super(KernelBuilder, self).__init__(coffee.as_cstr(scalar_type), integral_type.startswith("interior_facet")) self.fem_scalar_type = scalar_type - self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, - domain_number=domain_number) self.diagonal = diagonal self.local_tensor = None self.coordinates_arg = None @@ -220,7 +220,6 @@ def set_coefficients(self, integral_data, form_data): for i, coefficient in enumerate(coefficients): coeff_coffee_arg = self._coefficient(coefficient, f"w_{i}") self.coefficient_args.append(kernel_args.CoefficientKernelArg(coeff_coffee_arg)) - self.kernel.coefficient_numbers = tuple(self.integral_data_info.coefficient_numbers) def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be @@ -240,46 +239,46 @@ def construct_kernel(self, name, ctx): impero_c, oriented, needs_cell_sizes, tabulations = self.compile_gem(ctx) if impero_c is None: return self.construct_empty_kernel(name) - self.kernel.oriented = oriented - self.kernel.needs_cell_sizes = needs_cell_sizes - self.kernel.tabulations = tabulations - - index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) - body = generate_coffee(impero_c, index_names, self.scalar_type) - + info = self.integral_data_info args = [self.output_arg, self.coordinates_arg] - if self.kernel.oriented: + if oriented: ori_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), pointers=[("restrict",)], qualifiers=["const"]) args.append(kernel_args.CellOrientationsKernelArg(ori_coffee_arg)) - if self.kernel.needs_cell_sizes: + if needs_cell_sizes: args.append(self.cell_sizes_arg) args.extend(self.coefficient_args) - if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: + if info.integral_type in ["exterior_facet", "exterior_facet_vert"]: ext_coffee_arg = coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(1,)), qualifiers=["const"]) args.append(kernel_args.ExteriorFacetKernelArg(ext_coffee_arg)) - elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: + elif info.integral_type in ["interior_facet", "interior_facet_vert"]: int_coffee_arg = coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(2,)), qualifiers=["const"]) args.append(kernel_args.InteriorFacetKernelArg(int_coffee_arg)) - for n, shape in self.kernel.tabulations: + for name_, shape in tabulations: tab_coffee_arg = coffee.Decl(self.scalar_type, - coffee.Symbol(n, rank=shape), + coffee.Symbol(name_, rank=shape), qualifiers=["const"]) args.append(kernel_args.TabulationKernelArg(tab_coffee_arg)) - - coffee_args = [a.coffee_arg for a in args] + index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) body = generate_coffee(impero_c, index_names, self.scalar_type) - - self.kernel.ast = KernelBuilderBase.construct_kernel(self, name, coffee_args, body) - self.kernel.arguments = tuple(args) - self.kernel.name = name - self.kernel.flop_count = count_flops(impero_c) - return self.kernel + ast = KernelBuilderBase.construct_kernel(self, name, [a.coffee_arg for a in args], body) + flop_count = count_flops(impero_c) # Estimated total flops for this kernel. + return Kernel(ast=ast, + arguments=tuple(args), + integral_type=info.integral_type, + subdomain_id=info.subdomain_id, + domain_number=info.domain_number, + coefficient_numbers=info.coefficient_numbers, + oriented=oriented, + needs_cell_sizes=needs_cell_sizes, + tabulations=tabulations, + flop_count=flop_count, + name=name) def construct_empty_kernel(self, name): """Return None, since Firedrake needs no empty kernels. diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 1528374536..1bb35a5ba1 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -52,7 +52,9 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, coefficient_numbers=(), needs_cell_sizes=False, - flop_count=0): + tabulations=None, + flop_count=0, + name=None): # Defaults self.ast = ast self.arguments = arguments @@ -62,7 +64,9 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, self.subdomain_id = subdomain_id self.coefficient_numbers = coefficient_numbers self.needs_cell_sizes = needs_cell_sizes + self.tabulations = tabulations self.flop_count = flop_count + self.name = name class KernelBuilderBase(_KernelBuilderBase): @@ -204,13 +208,9 @@ def __init__(self, integral_data_info, scalar_type, dont_split=(), diagonal=False): """Initialise a kernel builder.""" integral_type = integral_data_info.integral_type - subdomain_id = integral_data_info.subdomain_id - domain_number = integral_data_info.domain_number super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) self.fem_scalar_type = scalar_type - self.kernel = Kernel(integral_type=integral_type, subdomain_id=subdomain_id, - domain_number=domain_number) self.diagonal = diagonal self.local_tensor = None self.coordinates_arg = None @@ -295,7 +295,6 @@ def set_coefficients(self, integral_data, form_data): for i, coefficient in enumerate(coefficients): coeff_loopy_arg = self._coefficient(coefficient, f"w_{i}") self.coefficient_args.append(kernel_args.CoefficientKernelArg(coeff_loopy_arg)) - self.kernel.coefficient_numbers = tuple(self.integral_data_info.coefficient_numbers) def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be @@ -315,34 +314,37 @@ def construct_kernel(self, name, ctx): impero_c, oriented, needs_cell_sizes, tabulations = self.compile_gem(ctx) if impero_c is None: return self.construct_empty_kernel(name) - self.kernel.oriented = oriented - self.kernel.needs_cell_sizes = needs_cell_sizes - self.kernel.tabulations = tabulations - + info = self.integral_data_info args = [self.output_arg, self.coordinates_arg] - if self.kernel.oriented: + if oriented: args.append(self.cell_orientations_arg) - if self.kernel.needs_cell_sizes: + if needs_cell_sizes: args.append(self.cell_sizes_arg) args.extend(self.coefficient_args) - if self.kernel.integral_type in ["exterior_facet", "exterior_facet_vert"]: + if info.integral_type in ["exterior_facet", "exterior_facet_vert"]: ext_loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(1,)) args.append(kernel_args.ExteriorFacetKernelArg(ext_loopy_arg)) - elif self.kernel.integral_type in ["interior_facet", "interior_facet_vert"]: + elif info.integral_type in ["interior_facet", "interior_facet_vert"]: int_loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(2,)) args.append(kernel_args.InteriorFacetKernelArg(int_loopy_arg)) - for name_, shape in self.kernel.tabulations: + for name_, shape in tabulations: tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) args.append(kernel_args.TabulationKernelArg(tab_loopy_arg)) - index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) - self.kernel.ast = generate_loopy(impero_c, [arg.loopy_arg for arg in args], - self.scalar_type, name, index_names) - self.kernel.arguments = tuple(args) - self.kernel.name = name - self.kernel.quadrature_rule = quadrature_rule - self.kernel.flop_count = count_flops(impero_c) - return self.kernel + ast = generate_loopy(impero_c, [arg.loopy_arg for arg in args], + self.scalar_type, name, index_names) + flop_count = count_flops(impero_c) # Estimated total flops for this kernel. + return Kernel(ast=ast, + arguments=tuple(args), + integral_type=info.integral_type, + subdomain_id=info.subdomain_id, + domain_number=info.domain_number, + coefficient_numbers=info.coefficient_numbers, + oriented=oriented, + needs_cell_sizes=needs_cell_sizes, + tabulations=tabulations, + flop_count=flop_count, + name=name) def construct_empty_kernel(self, name): """Return None, since Firedrake needs no empty kernels. From 8076e8b09c7e66819de7b27ab565502fdc75eb9f Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sat, 4 Sep 2021 16:26:39 +0100 Subject: [PATCH 713/809] refactor: move some functions from driver to kernel_interface/common --- tsfc/driver.py | 118 -------------------------------- tsfc/kernel_interface/common.py | 114 +++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 119 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index bcd49a9816..d69bf848c6 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -4,25 +4,18 @@ from itertools import chain from finat.physically_mapped import DirectlyDefinedElement, PhysicallyMappedElement -from numpy import asarray - import ufl from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type from ufl.classes import Form, GeometricQuantity from ufl.log import GREEN -from ufl.utils.sequences import max_degree import gem import gem.impero_utils as impero_utils -from FIAT.reference_element import TensorProductCell - import finat -from finat.quadrature import AbstractQuadratureRule, make_quadrature from tsfc import fem, ufl_utils -from tsfc.finatinterface import as_fiat_cell from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex from tsfc.ufl_utils import apply_mapping @@ -96,7 +89,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co :returns: a kernel constructed by the kernel interface """ parameters = preprocess_parameters(parameters) - if interface is None: if coffee: import tsfc.kernel_interface.firedrake as firedrake_interface_coffee @@ -112,7 +104,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) # Handle negative subdomain_id kernel_name = kernel_name.replace("-", "_") - # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() domain_number = domain_numbering[integral_data.domain] @@ -134,22 +125,17 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co builder = interface(integral_data_info, scalar_type, diagonal=diagonal) - builder.set_coordinates(mesh) builder.set_cell_sizes(mesh) - builder.set_coefficients(integral_data, form_data) - ctx = builder.create_context() for integral in integral_data.integrals: params = parameters.copy() params.update(integral.metadata()) # integral metadata overrides - integrand = ufl.replace(integral.integrand(), form_data.function_replace_map) integrand_exprs = builder.compile_integrand(integrand, params, ctx) integral_exprs = builder.construct_integrals(integrand_exprs, params) builder.stash_integrals(integral_exprs, params, ctx) - return builder.construct_kernel(kernel_name, ctx) @@ -331,107 +317,3 @@ def __call__(self, ps): assert set(gem_expr.free_indices) <= set(chain(ps.indices, *argument_multiindices)) return gem_expr - - -def set_quad_rule(params, cell, integral_type, functions): - # Check if the integral has a quad degree attached, otherwise use - # the estimated polynomial degree attached by compute_form_data - try: - quadrature_degree = params["quadrature_degree"] - except KeyError: - quadrature_degree = params["estimated_polynomial_degree"] - function_degrees = [f.ufl_function_space().ufl_element().degree() for f in functions] - if all((asarray(quadrature_degree) > 10 * asarray(degree)).all() - for degree in function_degrees): - logger.warning("Estimated quadrature degree %s more " - "than tenfold greater than any " - "argument/coefficient degree (max %s)", - quadrature_degree, max_degree(function_degrees)) - if params.get("quadrature_rule") == "default": - del params["quadrature_rule"] - try: - quad_rule = params["quadrature_rule"] - except KeyError: - fiat_cell = as_fiat_cell(cell) - integration_dim, _ = lower_integral_type(fiat_cell, integral_type) - integration_cell = fiat_cell.construct_subelement(integration_dim) - quad_rule = make_quadrature(integration_cell, quadrature_degree) - params["quadrature_rule"] = quad_rule - - if not isinstance(quad_rule, AbstractQuadratureRule): - raise ValueError("Expected to find a QuadratureRule object, not a %s" % - type(quad_rule)) - - -def get_index_ordering(quadrature_indices, return_variables): - split_argument_indices = tuple(chain(*[var.index_ordering() - for var in return_variables])) - return tuple(quadrature_indices) + split_argument_indices - - -def lower_integral_type(fiat_cell, integral_type): - """Lower integral type into the dimension of the integration - subentity and a list of entity numbers for that dimension. - - :arg fiat_cell: FIAT reference cell - :arg integral_type: integral type (string) - """ - vert_facet_types = ['exterior_facet_vert', 'interior_facet_vert'] - horiz_facet_types = ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz'] - - dim = fiat_cell.get_dimension() - if integral_type == 'cell': - integration_dim = dim - elif integral_type in ['exterior_facet', 'interior_facet']: - if isinstance(fiat_cell, TensorProductCell): - raise ValueError("{} integral cannot be used with a TensorProductCell; need to distinguish between vertical and horizontal contributions.".format(integral_type)) - integration_dim = dim - 1 - elif integral_type == 'vertex': - integration_dim = 0 - elif integral_type in vert_facet_types + horiz_facet_types: - # Extrusion case - if not isinstance(fiat_cell, TensorProductCell): - raise ValueError("{} integral requires a TensorProductCell.".format(integral_type)) - basedim, extrdim = dim - assert extrdim == 1 - - if integral_type in vert_facet_types: - integration_dim = (basedim - 1, 1) - elif integral_type in horiz_facet_types: - integration_dim = (basedim, 0) - else: - raise NotImplementedError("integral type %s not supported" % integral_type) - - if integral_type == 'exterior_facet_bottom': - entity_ids = [0] - elif integral_type == 'exterior_facet_top': - entity_ids = [1] - else: - entity_ids = list(range(len(fiat_cell.get_topology()[integration_dim]))) - - return integration_dim, entity_ids - - -def pick_mode(mode): - "Return one of the specialized optimisation modules from a mode string." - try: - from firedrake_citations import Citations - cites = {"vanilla": ("Homolya2017", ), - "coffee": ("Luporini2016", "Homolya2017", ), - "spectral": ("Luporini2016", "Homolya2017", "Homolya2017a"), - "tensor": ("Kirby2006", "Homolya2017", )} - for c in cites[mode]: - Citations().register(c) - except ImportError: - pass - if mode == "vanilla": - import tsfc.vanilla as m - elif mode == "coffee": - import tsfc.coffee_mode as m - elif mode == "spectral": - import tsfc.spectral as m - elif mode == "tensor": - import tsfc.tensor as m - else: - raise ValueError("Unknown mode: {}".format(mode)) - return m diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 61ad37107c..fa88e67843 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -2,20 +2,28 @@ import string import operator from functools import reduce +from itertools import chain import numpy +from numpy import asarray + +from ufl.utils.sequences import max_degree import coffee.base as coffee +from FIAT.reference_element import TensorProductCell + +from finat.quadrature import AbstractQuadratureRule, make_quadrature + import gem from gem.utils import cached_property import gem.impero_utils as impero_utils -from tsfc.driver import lower_integral_type, set_quad_rule, pick_mode, get_index_ordering from tsfc import fem, ufl_utils from tsfc.kernel_interface import KernelInterface from tsfc.finatinterface import as_fiat_cell +from tsfc.logging import logger class KernelBuilderBase(KernelInterface): @@ -290,6 +298,42 @@ def create_context(self): 'mode_irs': collections.OrderedDict()} +def set_quad_rule(params, cell, integral_type, functions): + # Check if the integral has a quad degree attached, otherwise use + # the estimated polynomial degree attached by compute_form_data + try: + quadrature_degree = params["quadrature_degree"] + except KeyError: + quadrature_degree = params["estimated_polynomial_degree"] + function_degrees = [f.ufl_function_space().ufl_element().degree() for f in functions] + if all((asarray(quadrature_degree) > 10 * asarray(degree)).all() + for degree in function_degrees): + logger.warning("Estimated quadrature degree %s more " + "than tenfold greater than any " + "argument/coefficient degree (max %s)", + quadrature_degree, max_degree(function_degrees)) + if params.get("quadrature_rule") == "default": + del params["quadrature_rule"] + try: + quad_rule = params["quadrature_rule"] + except KeyError: + fiat_cell = as_fiat_cell(cell) + integration_dim, _ = lower_integral_type(fiat_cell, integral_type) + integration_cell = fiat_cell.construct_subelement(integration_dim) + quad_rule = make_quadrature(integration_cell, quadrature_degree) + params["quadrature_rule"] = quad_rule + + if not isinstance(quad_rule, AbstractQuadratureRule): + raise ValueError("Expected to find a QuadratureRule object, not a %s" % + type(quad_rule)) + + +def get_index_ordering(quadrature_indices, return_variables): + split_argument_indices = tuple(chain(*[var.index_ordering() + for var in return_variables])) + return tuple(quadrature_indices) + split_argument_indices + + def get_index_names(quadrature_indices, argument_multiindices, index_cache): index_names = [] @@ -311,3 +355,71 @@ def name_multiindex(multiindex, name): for multiindex, name in zip(argument_multiindices, ['j', 'k']): name_multiindex(multiindex, name) return index_names + + +def lower_integral_type(fiat_cell, integral_type): + """Lower integral type into the dimension of the integration + subentity and a list of entity numbers for that dimension. + + :arg fiat_cell: FIAT reference cell + :arg integral_type: integral type (string) + """ + vert_facet_types = ['exterior_facet_vert', 'interior_facet_vert'] + horiz_facet_types = ['exterior_facet_bottom', 'exterior_facet_top', 'interior_facet_horiz'] + + dim = fiat_cell.get_dimension() + if integral_type == 'cell': + integration_dim = dim + elif integral_type in ['exterior_facet', 'interior_facet']: + if isinstance(fiat_cell, TensorProductCell): + raise ValueError("{} integral cannot be used with a TensorProductCell; need to distinguish between vertical and horizontal contributions.".format(integral_type)) + integration_dim = dim - 1 + elif integral_type == 'vertex': + integration_dim = 0 + elif integral_type in vert_facet_types + horiz_facet_types: + # Extrusion case + if not isinstance(fiat_cell, TensorProductCell): + raise ValueError("{} integral requires a TensorProductCell.".format(integral_type)) + basedim, extrdim = dim + assert extrdim == 1 + + if integral_type in vert_facet_types: + integration_dim = (basedim - 1, 1) + elif integral_type in horiz_facet_types: + integration_dim = (basedim, 0) + else: + raise NotImplementedError("integral type %s not supported" % integral_type) + + if integral_type == 'exterior_facet_bottom': + entity_ids = [0] + elif integral_type == 'exterior_facet_top': + entity_ids = [1] + else: + entity_ids = list(range(len(fiat_cell.get_topology()[integration_dim]))) + + return integration_dim, entity_ids + + +def pick_mode(mode): + "Return one of the specialized optimisation modules from a mode string." + try: + from firedrake_citations import Citations + cites = {"vanilla": ("Homolya2017", ), + "coffee": ("Luporini2016", "Homolya2017", ), + "spectral": ("Luporini2016", "Homolya2017", "Homolya2017a"), + "tensor": ("Kirby2006", "Homolya2017", )} + for c in cites[mode]: + Citations().register(c) + except ImportError: + pass + if mode == "vanilla": + import tsfc.vanilla as m + elif mode == "coffee": + import tsfc.coffee_mode as m + elif mode == "spectral": + import tsfc.spectral as m + elif mode == "tensor": + import tsfc.tensor as m + else: + raise ValueError("Unknown mode: {}".format(mode)) + return m From f5d59b27339c3db95c666d0d5ea828cf102a7d69 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Fri, 25 Feb 2022 18:11:17 +0000 Subject: [PATCH 714/809] driver: raise NotImplementedError for diagonal + interior_facet Fixes firedrakeproject/firedrake#2300 by making the error loud rather than quiet. --- tsfc/driver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index d69bf848c6..0188f37e3a 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -99,6 +99,8 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co interface = firedrake_interface_loopy.KernelBuilder scalar_type = parameters["scalar_type"] integral_type = integral_data.integral_type + if integral_type.startswith("interior_facet") and diagonal: + raise NotImplementedError("Sorry, we can't assemble the diagonal of a form for interior facet integrals") mesh = integral_data.domain arguments = form_data.preprocessed_form.arguments() kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) From ff2842ce91665ed39da95a41386fae7251e18bf4 Mon Sep 17 00:00:00 2001 From: Sophia Vorderwuelbecke Date: Tue, 1 Mar 2022 10:07:28 +0100 Subject: [PATCH 715/809] TSFC loopy: translate bessel functions with right naming depending on the backend. --- tsfc/loopy.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index e9305934a9..dc632644f1 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -96,12 +96,13 @@ def assign_dtypes(expressions, scalar_type): class LoopyContext(object): - def __init__(self): + def __init__(self, target=None): self.indices = {} # indices for declarations and referencing values, from ImperoC self.active_indices = {} # gem index -> pymbolic variable self.index_extent = OrderedDict() # pymbolic variable for indices -> extent self.gem_to_pymbolic = {} # gem node -> pymbolic variable self.name_gen = UniqueNameGenerator() + self.target = target def fetch_multiindex(self, multiindex): indices = [] @@ -192,7 +193,7 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name :arg return_increments: Does codegen for Return nodes increment the lvalue, or assign? :returns: loopy kernel """ - ctx = LoopyContext() + ctx = LoopyContext(target=lp.CTarget()) ctx.indices = impero_c.indices ctx.index_names = defaultdict(lambda: "i", index_names) ctx.epsilon = numpy.finfo(scalar_type).resolution @@ -398,22 +399,28 @@ def _expression_mathfunction(expr, ctx): nu, arg = expr.children nu_ = expression(nu, ctx) arg_ = expression(arg, ctx) - # Modified Bessel functions (C++ only) - # - # These mappings work for FEniCS only, and fail with Firedrake - # since no Boost available. - if expr.name in {'cyl_bessel_i', 'cyl_bessel_k'}: - name = 'boost::math::' + expr.name - return p.Variable(name)(nu_, arg_) + if ctx.target == lp.ExecutableCTarget(): + # Generate right functions calls to gnulibc bessel functions + # cyl_bessel_{jy} -> bessel_{jy} + name = expr.name[4:] + return p.Variable(f"{name}n")(int(nu_), arg_) else: - # cyl_bessel_{jy} -> {jy} - name = expr.name[-1:] - if nu == gem.Zero(): - return p.Variable(f"{name}0")(arg_) - elif nu == gem.one: - return p.Variable(f"{name}1")(arg_) + # Modified Bessel functions (C++ only) + # These mappings work for FEniCS only, and fail with Firedrake + # since no Boost available. + # Is this actually still supported/has ever been used by anyone? + if expr.name in {'cyl_bessel_i', 'cyl_bessel_k'}: + name = 'boost::math::' + expr.name + return p.Variable(name)(nu_, arg_) else: - return p.Variable(f"{name}n")(nu_, arg_) + # cyl_bessel_{jy} -> {jy} + name = expr.name[-1:] + if nu == gem.Zero(): + return p.Variable(f"{name}0")(arg_) + elif nu == gem.one: + return p.Variable(f"{name}1")(arg_) + else: + return p.Variable(f"{name}n")(nu_, arg_) else: if expr.name == "ln": name = "log" From e28cd46e806093888a9724d55733a5c367ea5740 Mon Sep 17 00:00:00 2001 From: Sophia Vorderwuelbecke Date: Wed, 2 Mar 2022 14:20:49 +0100 Subject: [PATCH 716/809] Loopy: set standard target to CWithGNULibcTarget in params. --- tests/test_impero_loopy_flop_counts.py | 3 ++- tsfc/loopy.py | 7 ++++--- tsfc/parameters.py | 4 ++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/test_impero_loopy_flop_counts.py b/tests/test_impero_loopy_flop_counts.py index 2a1f460cb9..29071c7963 100644 --- a/tests/test_impero_loopy_flop_counts.py +++ b/tests/test_impero_loopy_flop_counts.py @@ -9,6 +9,7 @@ TrialFunction, VectorElement, dx, grad, inner, interval, triangle, quadrilateral, TensorProductCell) +from tsfc.parameters import target def count_loopy_flops(kernel): @@ -16,7 +17,7 @@ def count_loopy_flops(kernel): program = kernel.ast program = program.with_kernel( program[name].copy( - target=loopy.CTarget(), + target=target, silenced_warnings=["insn_count_subgroups_upper_bound", "get_x_map_guessing_subgroup_size"]) ) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index dc632644f1..19d32af6ca 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -20,6 +20,7 @@ from tsfc.parameters import is_complex from contextlib import contextmanager +from tsfc.parameters import target @singledispatch @@ -193,7 +194,7 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name :arg return_increments: Does codegen for Return nodes increment the lvalue, or assign? :returns: loopy kernel """ - ctx = LoopyContext(target=lp.CTarget()) + ctx = LoopyContext(target=target) ctx.indices = impero_c.indices ctx.index_names = defaultdict(lambda: "i", index_names) ctx.epsilon = numpy.finfo(scalar_type).resolution @@ -218,7 +219,7 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name domains = create_domains(ctx.index_extent.items()) # Create loopy kernel - knl = lp.make_function(domains, instructions, data, name=kernel_name, target=lp.CTarget(), + knl = lp.make_function(domains, instructions, data, name=kernel_name, target=target, seq_dependencies=True, silenced_warnings=["summing_if_branches_ops"], lang_version=(2018, 2)) @@ -399,7 +400,7 @@ def _expression_mathfunction(expr, ctx): nu, arg = expr.children nu_ = expression(nu, ctx) arg_ = expression(arg, ctx) - if ctx.target == lp.ExecutableCTarget(): + if isinstance(ctx.target, lp.target.c.CWithGNULibcTarget): # Generate right functions calls to gnulibc bessel functions # cyl_bessel_{jy} -> bessel_{jy} name = expr.name[4:] diff --git a/tsfc/parameters.py b/tsfc/parameters.py index 640fcfcb56..1277713ad5 100644 --- a/tsfc/parameters.py +++ b/tsfc/parameters.py @@ -1,4 +1,5 @@ import numpy +from loopy.target.c import CWithGNULibcTarget PARAMETERS = { @@ -22,6 +23,9 @@ } +target = CWithGNULibcTarget() + + def default_parameters(): return PARAMETERS.copy() From e51d39e007669bf26919d24d1e1cee84a094f5c0 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 16 Feb 2022 11:28:16 +0000 Subject: [PATCH 717/809] Replace ExpressionKernel coefficients with coefficient_numbers This was necessary because UFL coefficients are large data structures which inhibited caching of the generated kernel. --- tsfc/driver.py | 11 +++++++++-- tsfc/kernel_interface/firedrake_loopy.py | 20 ++++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 0188f37e3a..cd24a1fedc 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -183,6 +183,9 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, if isinstance(to_element, (PhysicallyMappedElement, DirectlyDefinedElement)): raise NotImplementedError("Don't know how to interpolate onto zany spaces, sorry") + + orig_expression = expression + # Map into reference space expression = apply_mapping(expression, ufl_element, domain) @@ -205,9 +208,13 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, domain = expression.ufl_domain() assert domain is not None - # Collect required coefficients - first_coefficient_fake_coords = False + # Collect required coefficients and determine numbering coefficients = extract_coefficients(expression) + orig_coefficients = extract_coefficients(orig_expression) + coefficient_numbers = tuple(orig_coefficients.index(c) for c in coefficients) + builder.set_coefficient_numbers(coefficient_numbers) + + first_coefficient_fake_coords = False if has_type(expression, GeometricQuantity) or any(fem.needs_coordinate_mapping(c.ufl_element()) for c in coefficients): # Create a fake coordinate coefficient for a domain. coords_coefficient = ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 1bb35a5ba1..ac49d51f7d 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -19,8 +19,11 @@ # Expression kernel description type -ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', - 'first_coefficient_fake_coords', 'tabulations', 'name', 'arguments', 'flop_count']) +ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', + 'coefficient_numbers', + 'first_coefficient_fake_coords', + 'tabulations', 'name', 'arguments', + 'flop_count']) def make_builder(*args, **kwargs): @@ -144,24 +147,29 @@ def set_coefficients(self, coefficients): :arg coefficients: UFL coefficients from Firedrake """ - self.coefficients = [] # Firedrake coefficients for calling the kernel self.coefficient_split = {} self.kernel_args = [] for i, coefficient in enumerate(coefficients): if type(coefficient.ufl_element()) == ufl_MixedElement: subcoeffs = coefficient.split() # Firedrake-specific - self.coefficients.extend(subcoeffs) self.coefficient_split[coefficient] = subcoeffs coeff_loopy_args = [self._coefficient(subcoeff, f"w_{i}_{j}") for j, subcoeff in enumerate(subcoeffs)] self.kernel_args += [kernel_args.CoefficientKernelArg(a) for a in coeff_loopy_args] else: - self.coefficients.append(coefficient) coeff_loopy_arg = self._coefficient(coefficient, f"w_{i}") self.kernel_args.append(kernel_args.CoefficientKernelArg(coeff_loopy_arg)) + def set_coefficient_numbers(self, coefficient_numbers): + """Store the coefficient indices of the original form. + + :arg coefficient_numbers: Iterable of indices describing which coefficients + from the input expression need to be passed in to the kernel. + """ + self.coefficient_numbers = coefficient_numbers + def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be provided by the kernel interface.""" @@ -197,7 +205,7 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords) loopy_kernel = generate_loopy(impero_c, loopy_args, self.scalar_type, name, index_names) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, - self.coefficients, first_coefficient_fake_coords, + self.coefficient_numbers, first_coefficient_fake_coords, self.tabulations, name, args, count_flops(impero_c)) From 6160a176733edb4e72b7e7b69c8c9a155b2cb02e Mon Sep 17 00:00:00 2001 From: Sophia Vorderwuelbecke Date: Fri, 11 Feb 2022 17:58:39 +0100 Subject: [PATCH 718/809] Profiling: add profiling to local kernels. Memory for events is allocated in C. --- tsfc/driver.py | 15 +++++---- tsfc/kernel_interface/firedrake.py | 2 +- tsfc/kernel_interface/firedrake_loopy.py | 10 +++--- tsfc/loopy.py | 41 ++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 0188f37e3a..d52352eb63 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -46,7 +46,7 @@ """ -def compile_form(form, prefix="form", parameters=None, interface=None, coffee=True, diagonal=False): +def compile_form(form, prefix="form", parameters=None, interface=None, coffee=True, diagonal=False, log=False): """Compiles a UFL form into a set of assembly kernels. :arg form: UFL form @@ -54,6 +54,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr :arg parameters: parameters object :arg coffee: compile coffee kernel instead of loopy kernel :arg diagonal: Are we building a kernel for the diagonal of a rank-2 element tensor? + :arg log: bool if the Kernel should be profiled with Log events :returns: list of kernels """ cpu_time = time.time() @@ -68,7 +69,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr kernels = [] for integral_data in fd.integral_data: start = time.time() - kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface, coffee=coffee, diagonal=diagonal) + kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface, coffee=coffee, diagonal=diagonal, log=log) if kernel is not None: kernels.append(kernel) logger.info(GREEN % "compile_integral finished in %g seconds.", time.time() - start) @@ -77,7 +78,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr return kernels -def compile_integral(integral_data, form_data, prefix, parameters, interface, coffee, *, diagonal=False): +def compile_integral(integral_data, form_data, prefix, parameters, interface, coffee, *, diagonal=False, log=False): """Compiles a UFL integral into an assembly kernel. :arg integral_data: UFL integral data @@ -86,6 +87,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co :arg parameters: parameters object :arg interface: backend module for the kernel interface :arg diagonal: Are we building a kernel for the diagonal of a rank-2 element tensor? + :arg log: bool if the Kernel should be profiled with Log events :returns: a kernel constructed by the kernel interface """ parameters = preprocess_parameters(parameters) @@ -138,7 +140,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co integrand_exprs = builder.compile_integrand(integrand, params, ctx) integral_exprs = builder.construct_integrals(integrand_exprs, params) builder.stash_integrals(integral_exprs, params, ctx) - return builder.construct_kernel(kernel_name, ctx) + return builder.construct_kernel(kernel_name, ctx, log) def preprocess_parameters(parameters): @@ -158,7 +160,7 @@ def preprocess_parameters(parameters): def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, domain=None, interface=None, - parameters=None): + parameters=None, log=False): """Compile a UFL expression to be evaluated against a compile-time known reference element's dual basis. Useful for interpolating UFL expressions into e.g. N1curl spaces. @@ -169,6 +171,7 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, :arg domain: optional UFL domain the expression is defined on (required when expression contains no domain). :arg interface: backend module for the kernel interface :arg parameters: parameters object + :arg log: bool if the Kernel should be profiled with Log events :returns: Loopy-based ExpressionKernel object. """ if parameters is None: @@ -257,7 +260,7 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, builder.register_requirements([evaluation]) builder.set_output(return_var) # Build kernel tuple - return builder.construct_kernel(impero_c, index_names, first_coefficient_fake_coords) + return builder.construct_kernel(impero_c, index_names, first_coefficient_fake_coords, log=log) class DualEvaluationCallable(object): diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 6f17f1bb6b..90ed4ea145 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -226,7 +226,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" return check_requirements(ir) - def construct_kernel(self, name, ctx): + def construct_kernel(self, name, ctx, log=False): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 1bb35a5ba1..320ede00a1 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -172,13 +172,14 @@ def set_output(self, o): loopy_arg = lp.GlobalArg(o.name, dtype=self.scalar_type, shape=o.shape) self.output_arg = kernel_args.OutputKernelArg(loopy_arg) - def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords): + def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords, log=False): """Constructs an :class:`ExpressionKernel`. :arg impero_c: gem.ImperoC object that represents the kernel :arg index_names: pre-assigned index names :arg first_coefficient_fake_coords: If true, the kernel's first coefficient is a constructed UFL coordinate field + :arg log: bool if the Kernel should be profiled with Log events :returns: :class:`ExpressionKernel` object """ args = [self.output_arg] @@ -195,7 +196,7 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords) name = "expression_kernel" loopy_kernel = generate_loopy(impero_c, loopy_args, self.scalar_type, - name, index_names) + name, index_names, log=log) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, self.coefficients, first_coefficient_fake_coords, self.tabulations, name, args, count_flops(impero_c)) @@ -301,7 +302,7 @@ def register_requirements(self, ir): provided by the kernel interface.""" return check_requirements(ir) - def construct_kernel(self, name, ctx): + def construct_kernel(self, name, ctx, log=False): """Construct a fully built :class:`Kernel`. This function contains the logic for building the argument @@ -309,6 +310,7 @@ def construct_kernel(self, name, ctx): :arg name: kernel name :arg ctx: kernel builder context to get impero_c from + :arg log: bool if the Kernel should be profiled with Log events :returns: :class:`Kernel` object """ impero_c, oriented, needs_cell_sizes, tabulations = self.compile_gem(ctx) @@ -332,7 +334,7 @@ def construct_kernel(self, name, ctx): args.append(kernel_args.TabulationKernelArg(tab_loopy_arg)) index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) ast = generate_loopy(impero_c, [arg.loopy_arg for arg in args], - self.scalar_type, name, index_names) + self.scalar_type, name, index_names, log=log) flop_count = count_flops(impero_c) # Estimated total flops for this kernel. return Kernel(ast=ast, arguments=tuple(args), diff --git a/tsfc/loopy.py b/tsfc/loopy.py index 19d32af6ca..eab001fc63 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -15,7 +15,7 @@ import pymbolic.primitives as p from loopy.symbolic import SubArrayRef -from pytools import UniqueNameGenerator +from pytools import UniqueNameGenerator, ImmutableRecord from tsfc.parameters import is_complex @@ -23,6 +23,36 @@ from tsfc.parameters import target + +def profile_insns(kernel_name, instructions, log=False): + if log: + # Petsc functions + start_event = "PetscLogEventBegin" + end_event = "PetscLogEventEnd" + register_event = "PetscLogEventRegister" + # The variables + event_id_var_name = "event_id_" + kernel_name + event_name = "Log_Event_" + kernel_name + # Logging registration # TODO offport this so we don't register every single time + prepend = [lp.CInstruction("", "PetscLogEvent "+event_id_var_name+";"), + lp.CInstruction("", register_event+"(\""+event_name+"\", PETSC_OBJECT_CLASSID, &"+event_id_var_name+");")] + # Profiling + prepend += [lp.CInstruction("", start_event+"("+event_id_var_name+",0,0,0,0);")] + append = [lp.CInstruction("", end_event+"("+event_id_var_name+",0,0,0,0);")] + instructions = prepend + instructions + append + return instructions + + +class _PreambleGen(ImmutableRecord): + fields = set(("preamble", )) + + def __init__(self, preamble): + self.preamble = preamble + + def __call__(self, preamble_info): + yield ("0_tsfc", self.preamble) + + @singledispatch def _assign_dtype(expression, self): return set.union(*map(self, expression.children)) @@ -183,7 +213,7 @@ def active_indices(mapping, ctx): def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_names=[], - return_increments=True): + return_increments=True, log=False): """Generates loopy code. :arg impero_c: ImperoC tuple with Impero AST and other data @@ -192,6 +222,7 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name :arg kernel_name: function name of the kernel :arg index_names: pre-assigned index names :arg return_increments: Does codegen for Return nodes increment the lvalue, or assign? + :arg log: bool if the Kernel should be profiled with Log events :returns: loopy kernel """ ctx = LoopyContext(target=target) @@ -215,6 +246,9 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name # Create instructions instructions = statement(impero_c.tree, ctx) + # Profile the instructions + instructions = profile_insns(kernel_name, instructions, log) + # Create domains domains = create_domains(ctx.index_extent.items()) @@ -223,6 +257,9 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name seq_dependencies=True, silenced_warnings=["summing_if_branches_ops"], lang_version=(2018, 2)) + if PETSc.Log.isActive(): + knl = lp.register_preamble_generators(knl, [_PreambleGen("#include ")]) + # Prevent loopy interchange by loopy knl = lp.prioritize_loops(knl, ",".join(ctx.index_extent.keys())) From 57c9d7fdc2dac40f23f21e5b063bc20dcfb8c302 Mon Sep 17 00:00:00 2001 From: Sophia Vorderwuelbecke Date: Tue, 15 Feb 2022 14:53:31 +0100 Subject: [PATCH 719/809] Profiling: Set event id to default value and let PyOP2 generate the event and link its id to the one in the local kernel --- tsfc/kernel_interface/firedrake.py | 7 ++-- tsfc/kernel_interface/firedrake_loopy.py | 23 ++++++++----- tsfc/loopy.py | 43 +++++++----------------- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 90ed4ea145..3fd160543d 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -25,7 +25,7 @@ class Kernel(object): __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", "coefficient_numbers", "name", "__weakref__", - "flop_count") + "flop_count", "event") """A compiled Kernel object. :kwarg ast: The COFFEE ast for the kernel. @@ -40,6 +40,7 @@ class Kernel(object): :kwarg tabulations: The runtime tabulations this kernel requires :kwarg needs_cell_sizes: Does the kernel require cell sizes. :kwarg flop_count: Estimated total flops for this kernel. + :kwarg event: name for logging event """ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, @@ -47,7 +48,8 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, needs_cell_sizes=False, tabulations=None, flop_count=0, - name=None): + name=None, + event=None): # Defaults self.ast = ast self.arguments = arguments @@ -60,6 +62,7 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, self.tabulations = tabulations self.flop_count = flop_count self.name = name + self.event = None super(Kernel, self).__init__() diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 320ede00a1..b59431f6f1 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -20,7 +20,8 @@ # Expression kernel description type ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficients', - 'first_coefficient_fake_coords', 'tabulations', 'name', 'arguments', 'flop_count']) + 'first_coefficient_fake_coords', 'tabulations', 'name', 'arguments', 'flop_count', + 'event']) def make_builder(*args, **kwargs): @@ -30,7 +31,7 @@ def make_builder(*args, **kwargs): class Kernel: __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", "domain_number", "needs_cell_sizes", "tabulations", - "coefficient_numbers", "name", "flop_count", + "coefficient_numbers", "name", "flop_count", "event", "__weakref__") """A compiled Kernel object. @@ -47,6 +48,7 @@ class Kernel: :kwarg needs_cell_sizes: Does the kernel require cell sizes. :kwarg name: The name of this kernel. :kwarg flop_count: Estimated total flops for this kernel. + :kwarg event: name for logging event """ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, subdomain_id=None, domain_number=None, @@ -54,7 +56,8 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, needs_cell_sizes=False, tabulations=None, flop_count=0, - name=None): + name=None, + event=None): # Defaults self.ast = ast self.arguments = arguments @@ -67,6 +70,7 @@ def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, self.tabulations = tabulations self.flop_count = flop_count self.name = name + self.event = event class KernelBuilderBase(_KernelBuilderBase): @@ -195,11 +199,11 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords, loopy_args = [arg.loopy_arg for arg in args] name = "expression_kernel" - loopy_kernel = generate_loopy(impero_c, loopy_args, self.scalar_type, - name, index_names, log=log) + loopy_kernel, event = generate_loopy(impero_c, loopy_args, self.scalar_type, + name, index_names, log=log) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, self.coefficients, first_coefficient_fake_coords, - self.tabulations, name, args, count_flops(impero_c)) + self.tabulations, name, args, count_flops(impero_c), event) class KernelBuilder(KernelBuilderBase, KernelBuilderMixin): @@ -333,8 +337,8 @@ def construct_kernel(self, name, ctx, log=False): tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) args.append(kernel_args.TabulationKernelArg(tab_loopy_arg)) index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) - ast = generate_loopy(impero_c, [arg.loopy_arg for arg in args], - self.scalar_type, name, index_names, log=log) + ast, event_name = generate_loopy(impero_c, [arg.loopy_arg for arg in args], + self.scalar_type, name, index_names, log=log) flop_count = count_flops(impero_c) # Estimated total flops for this kernel. return Kernel(ast=ast, arguments=tuple(args), @@ -346,7 +350,8 @@ def construct_kernel(self, name, ctx, log=False): needs_cell_sizes=needs_cell_sizes, tabulations=tabulations, flop_count=flop_count, - name=name) + name=name, + event=event_name) def construct_empty_kernel(self, name): """Return None, since Firedrake needs no empty kernels. diff --git a/tsfc/loopy.py b/tsfc/loopy.py index eab001fc63..ae8f93e5ba 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -15,7 +15,7 @@ import pymbolic.primitives as p from loopy.symbolic import SubArrayRef -from pytools import UniqueNameGenerator, ImmutableRecord +from pytools import UniqueNameGenerator from tsfc.parameters import is_complex @@ -23,34 +23,20 @@ from tsfc.parameters import target - def profile_insns(kernel_name, instructions, log=False): if log: - # Petsc functions - start_event = "PetscLogEventBegin" - end_event = "PetscLogEventEnd" - register_event = "PetscLogEventRegister" - # The variables - event_id_var_name = "event_id_" + kernel_name event_name = "Log_Event_" + kernel_name - # Logging registration # TODO offport this so we don't register every single time - prepend = [lp.CInstruction("", "PetscLogEvent "+event_id_var_name+";"), - lp.CInstruction("", register_event+"(\""+event_name+"\", PETSC_OBJECT_CLASSID, &"+event_id_var_name+");")] + event_id_var_name = "ID_" + event_name + # Logging registration + # The events are registered in PyOP2 and the event id is passed onto the dll + preamble = "PetscLogEvent "+event_id_var_name+" = -1;" # Profiling - prepend += [lp.CInstruction("", start_event+"("+event_id_var_name+",0,0,0,0);")] - append = [lp.CInstruction("", end_event+"("+event_id_var_name+",0,0,0,0);")] + prepend = [lp.CInstruction("", "PetscLogEventBegin("+event_id_var_name+",0,0,0,0);")] + append = [lp.CInstruction("", "PetscLogEventEnd("+event_id_var_name+",0,0,0,0);")] instructions = prepend + instructions + append - return instructions - - -class _PreambleGen(ImmutableRecord): - fields = set(("preamble", )) - - def __init__(self, preamble): - self.preamble = preamble - - def __call__(self, preamble_info): - yield ("0_tsfc", self.preamble) + return instructions, event_name, [(str(2**31-1)+"_"+kernel_name, preamble)] + else: + return instructions, None, None @singledispatch @@ -247,7 +233,7 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name instructions = statement(impero_c.tree, ctx) # Profile the instructions - instructions = profile_insns(kernel_name, instructions, log) + instructions, event_name, preamble = profile_insns(kernel_name, instructions, log) # Create domains domains = create_domains(ctx.index_extent.items()) @@ -255,15 +241,12 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name # Create loopy kernel knl = lp.make_function(domains, instructions, data, name=kernel_name, target=target, seq_dependencies=True, silenced_warnings=["summing_if_branches_ops"], - lang_version=(2018, 2)) - - if PETSc.Log.isActive(): - knl = lp.register_preamble_generators(knl, [_PreambleGen("#include ")]) + lang_version=(2018, 2), preambles=preamble) # Prevent loopy interchange by loopy knl = lp.prioritize_loops(knl, ",".join(ctx.index_extent.keys())) - return knl + return knl, event_name def create_domains(indices): From 2f59112d26e0f973d63c2d1b5c1925338e390d50 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 1 Apr 2022 10:51:03 +0100 Subject: [PATCH 720/809] Rename first_coefficient_fake_coords to needs_external_coords --- tests/test_dual_evaluation.py | 8 ++++---- tsfc/driver.py | 6 +++--- tsfc/kernel_interface/firedrake_loopy.py | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_dual_evaluation.py b/tests/test_dual_evaluation.py index 3e7eabf4ca..461226c253 100644 --- a/tests/test_dual_evaluation.py +++ b/tests/test_dual_evaluation.py @@ -12,7 +12,7 @@ def test_ufl_only_simple(): W = V to_element = create_element(W.ufl_element()) kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) - assert kernel.first_coefficient_fake_coords is False + assert kernel.needs_external_coords is False def test_ufl_only_spatialcoordinate(): @@ -23,7 +23,7 @@ def test_ufl_only_spatialcoordinate(): W = V to_element = create_element(W.ufl_element()) kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) - assert kernel.first_coefficient_fake_coords is True + assert kernel.needs_external_coords is True def test_ufl_only_from_contravariant_piola(): @@ -34,7 +34,7 @@ def test_ufl_only_from_contravariant_piola(): W = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) to_element = create_element(W.ufl_element()) kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) - assert kernel.first_coefficient_fake_coords is True + assert kernel.needs_external_coords is True def test_ufl_only_to_contravariant_piola(): @@ -45,7 +45,7 @@ def test_ufl_only_to_contravariant_piola(): W = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) to_element = create_element(W.ufl_element()) kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) - assert kernel.first_coefficient_fake_coords is True + assert kernel.needs_external_coords is True def test_ufl_only_shape_mismatch(): diff --git a/tsfc/driver.py b/tsfc/driver.py index cd24a1fedc..0f135d5c8f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -214,14 +214,14 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, coefficient_numbers = tuple(orig_coefficients.index(c) for c in coefficients) builder.set_coefficient_numbers(coefficient_numbers) - first_coefficient_fake_coords = False + needs_external_coords = False if has_type(expression, GeometricQuantity) or any(fem.needs_coordinate_mapping(c.ufl_element()) for c in coefficients): # Create a fake coordinate coefficient for a domain. coords_coefficient = ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) builder.domain_coordinate[domain] = coords_coefficient builder.set_cell_sizes(domain) coefficients = [coords_coefficient] + coefficients - first_coefficient_fake_coords = True + needs_external_coords = True builder.set_coefficients(coefficients) # Split mixed coefficients @@ -264,7 +264,7 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, builder.register_requirements([evaluation]) builder.set_output(return_var) # Build kernel tuple - return builder.construct_kernel(impero_c, index_names, first_coefficient_fake_coords) + return builder.construct_kernel(impero_c, index_names, needs_external_coords) class DualEvaluationCallable(object): diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index ac49d51f7d..74b878bab3 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -21,7 +21,7 @@ # Expression kernel description type ExpressionKernel = namedtuple('ExpressionKernel', ['ast', 'oriented', 'needs_cell_sizes', 'coefficient_numbers', - 'first_coefficient_fake_coords', + 'needs_external_coords', 'tabulations', 'name', 'arguments', 'flop_count']) @@ -180,13 +180,13 @@ def set_output(self, o): loopy_arg = lp.GlobalArg(o.name, dtype=self.scalar_type, shape=o.shape) self.output_arg = kernel_args.OutputKernelArg(loopy_arg) - def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords): + def construct_kernel(self, impero_c, index_names, needs_external_coords): """Constructs an :class:`ExpressionKernel`. :arg impero_c: gem.ImperoC object that represents the kernel :arg index_names: pre-assigned index names - :arg first_coefficient_fake_coords: If true, the kernel's first - coefficient is a constructed UFL coordinate field + :arg needs_external_coords: If ``True``, the first argument to + the kernel is an externally provided coordinate field. :returns: :class:`ExpressionKernel` object """ args = [self.output_arg] @@ -205,7 +205,7 @@ def construct_kernel(self, impero_c, index_names, first_coefficient_fake_coords) loopy_kernel = generate_loopy(impero_c, loopy_args, self.scalar_type, name, index_names) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, - self.coefficient_numbers, first_coefficient_fake_coords, + self.coefficient_numbers, needs_external_coords, self.tabulations, name, args, count_flops(impero_c)) From dc05052c3c55c777ad4e20377bc5c25716e7dc0d Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 7 Apr 2022 19:07:46 +0100 Subject: [PATCH 721/809] fem: Constant fold literal zeros when compiling UFL --- tsfc/fem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 2c5c865c2d..923ee153db 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -27,7 +27,7 @@ import gem from gem.node import traversal -from gem.optimise import ffc_rounding +from gem.optimise import ffc_rounding, constant_fold_zero from gem.unconcatenate import unconcatenate from gem.utils import cached_property @@ -603,6 +603,7 @@ def fiat_to_ufl(fiat_dict, order): tensor = gem.Indexed(gem.ListTensor(tensor), delta) else: tensor = tensor[()] + tensor, = constant_fold_zero([tensor]) return gem.ComponentTensor(tensor, sigma + delta) @@ -718,4 +719,4 @@ def compile_ufl(expression, context, interior_facet=False, point_sum=False): result = map_expr_dags(context.translator, expressions) if point_sum: result = [gem.index_sum(expr, context.point_indices) for expr in result] - return result + return constant_fold_zero(result) From 98cb64dfd8c9c0ace48aa058a20098cacd7b741c Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 7 Apr 2022 19:08:11 +0100 Subject: [PATCH 722/809] tests: Introduce test for #274 --- tests/test_tsfc_274.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/test_tsfc_274.py diff --git a/tests/test_tsfc_274.py b/tests/test_tsfc_274.py new file mode 100644 index 0000000000..00f7a96a95 --- /dev/null +++ b/tests/test_tsfc_274.py @@ -0,0 +1,40 @@ +import gem +import numpy +from finat.point_set import PointSet +from gem.interpreter import evaluate +from tsfc.finatinterface import create_element +from ufl import FiniteElement, RestrictedElement, quadrilateral + + +def test_issue_274(): + # See https://github.com/firedrakeproject/tsfc/issues/274 + ufl_element = RestrictedElement( + FiniteElement("Q", quadrilateral, 2), restriction_domain="facet" + ) + ps = PointSet([[0.5]]) + finat_element = create_element(ufl_element) + evaluations = [] + for eid in range(4): + (val,) = finat_element.basis_evaluation(0, ps, (1, eid)).values() + evaluations.append(val) + + i = gem.Index() + j = gem.Index() + (expr,) = evaluate( + [ + gem.ComponentTensor( + gem.Indexed(gem.select_expression(evaluations, i), (j,)), + (*ps.indices, i, j), + ) + ] + ) + + (expect,) = evaluate( + [ + gem.ComponentTensor( + gem.Indexed(gem.ListTensor(evaluations), (i, j)), (*ps.indices, i, j) + ) + ] + ) + + assert numpy.allclose(expr.arr, expect.arr) From 1cffc2e42d6623df5945c91182dd6ecd60fd58e2 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 8 Apr 2022 10:41:30 +0100 Subject: [PATCH 723/809] Fix for conflicting merges --- tsfc/kernel_interface/firedrake_loopy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 46fc5be1cc..589ca7b642 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -210,7 +210,7 @@ def construct_kernel(self, impero_c, index_names, needs_external_coords, log=Fal loopy_kernel, event = generate_loopy(impero_c, loopy_args, self.scalar_type, name, index_names, log=log) return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, - self.coefficients, needs_external_coords, + self.coefficient_numbers, needs_external_coords, self.tabulations, name, args, count_flops(impero_c), event) From a3a0c004b2c18e0b778ad2f57667b2b5669b702f Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Mon, 28 Feb 2022 19:56:22 +0000 Subject: [PATCH 724/809] kernel builder: generate kernel args in builder.construct_kernel --- tsfc/kernel_interface/common.py | 113 ++++++++++++- tsfc/kernel_interface/firedrake.py | 198 ++++++---------------- tsfc/kernel_interface/firedrake_loopy.py | 202 +++++++---------------- 3 files changed, 218 insertions(+), 295 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index fa88e67843..c2c9ca34da 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -2,7 +2,7 @@ import string import operator from functools import reduce -from itertools import chain +from itertools import chain, product import numpy from numpy import asarray @@ -17,12 +17,14 @@ import gem +from gem.node import traversal from gem.utils import cached_property import gem.impero_utils as impero_utils +from gem.optimise import remove_componenttensors as prune from tsfc import fem, ufl_utils from tsfc.kernel_interface import KernelInterface -from tsfc.finatinterface import as_fiat_cell +from tsfc.finatinterface import as_fiat_cell, create_element from tsfc.logging import logger @@ -45,7 +47,7 @@ def __init__(self, scalar_type, interior_facet=False): self.domain_coordinate = {} # Coefficients - self.coefficient_map = {} + self.coefficient_map = collections.OrderedDict() @cached_property def unsummed_coefficient_indices(self): @@ -423,3 +425,108 @@ def pick_mode(mode): else: raise ValueError("Unknown mode: {}".format(mode)) return m + + +def check_requirements(ir): + """Look for cell orientations, cell sizes, and collect tabulations + in one pass.""" + cell_orientations = False + cell_sizes = False + rt_tabs = {} + for node in traversal(ir): + if isinstance(node, gem.Variable): + if node.name == "cell_orientations": + cell_orientations = True + elif node.name == "cell_sizes": + cell_sizes = True + elif node.name.startswith("rt_"): + rt_tabs[node.name] = node.shape + return cell_orientations, cell_sizes, tuple(sorted(rt_tabs.items())) + + +def prepare_coefficient(coefficient, name, interior_facet=False): + """Bridges the kernel interface and the GEM abstraction for + Coefficients. + + :arg coefficient: UFL Coefficient + :arg name: unique name to refer to the Coefficient in the kernel + :arg interior_facet: interior facet integral? + :returns: (funarg, expression) + expression - GEM expression referring to the Coefficient + values + """ + assert isinstance(interior_facet, bool) + + if coefficient.ufl_element().family() == 'Real': + # Constant + value_size = coefficient.ufl_element().value_size() + expression = gem.reshape(gem.Variable(name, (value_size,)), + coefficient.ufl_shape) + return expression + + finat_element = create_element(coefficient.ufl_element()) + shape = finat_element.index_shape + size = numpy.prod(shape, dtype=int) + + if not interior_facet: + expression = gem.reshape(gem.Variable(name, (size,)), shape) + else: + varexp = gem.Variable(name, (2 * size,)) + plus = gem.view(varexp, slice(size)) + minus = gem.view(varexp, slice(size, 2 * size)) + expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) + return expression + + +def prepare_arguments(arguments, multiindices, interior_facet=False, diagonal=False): + """Bridges the kernel interface and the GEM abstraction for + Arguments. Vector Arguments are rearranged here for interior + facet integrals. + + :arg arguments: UFL Arguments + :arg multiindices: Argument multiindices + :arg interior_facet: interior facet integral? + :arg diagonal: Are we assembling the diagonal of a rank-2 element tensor? + :returns: (funarg, expression) + expressions - GEM expressions referring to the argument + tensor + """ + assert isinstance(interior_facet, bool) + + if len(arguments) == 0: + # No arguments + expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) + return (expression, ) + + elements = tuple(create_element(arg.ufl_element()) for arg in arguments) + shapes = tuple(element.index_shape for element in elements) + + if diagonal: + if len(arguments) != 2: + raise ValueError("Diagonal only for 2-forms") + try: + element, = set(elements) + except ValueError: + raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") + + elements = (element, ) + shapes = tuple(element.index_shape for element in elements) + multiindices = multiindices[:1] + + def expression(restricted): + return gem.Indexed(gem.reshape(restricted, *shapes), + tuple(chain(*multiindices))) + + u_shape = numpy.array([numpy.prod(shape, dtype=int) for shape in shapes]) + if interior_facet: + c_shape = tuple(2 * u_shape) + slicez = [[slice(r * s, (r + 1) * s) + for r, s in zip(restrictions, u_shape)] + for restrictions in product((0, 1), repeat=len(arguments))] + else: + c_shape = tuple(u_shape) + slicez = [[slice(s) for s in u_shape]] + + varexp = gem.Variable("A", c_shape) + expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] + return tuple(prune(expressions)) diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 3fd160543d..37171d6d55 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -1,5 +1,3 @@ -import numpy -from itertools import chain, product from functools import partial from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement @@ -8,13 +6,11 @@ import gem from gem.flop_count import count_flops -from gem.node import traversal -from gem.optimise import remove_componenttensors as prune from tsfc import kernel_args from tsfc.coffee import generate as generate_coffee from tsfc.finatinterface import create_element -from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names +from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names, check_requirements, prepare_coefficient, prepare_arguments def make_builder(*args, **kwargs): @@ -39,6 +35,7 @@ class Kernel(object): form the kernel needs. :kwarg tabulations: The runtime tabulations this kernel requires :kwarg needs_cell_sizes: Does the kernel require cell sizes. + :kwarg name: The name of this kernel. :kwarg flop_count: Estimated total flops for this kernel. :kwarg event: name for logging event """ @@ -73,8 +70,7 @@ def __init__(self, scalar_type, interior_facet=False): :arg interior_facet: kernel accesses two cells """ - super(KernelBuilderBase, self).__init__(scalar_type=scalar_type, - interior_facet=interior_facet) + super().__init__(scalar_type=scalar_type, interior_facet=interior_facet) # Cell orientation if self.interior_facet: @@ -91,13 +87,9 @@ def _coefficient(self, coefficient, name): :arg coefficient: :class:`ufl.Coefficient` :arg name: coefficient name - :returns: COFFEE function argument for the coefficient """ - funarg, expr = prepare_coefficient(coefficient, name, - self.scalar_type, - interior_facet=self.interior_facet) + expr = prepare_coefficient(coefficient, name, interior_facet=self.interior_facet) self.coefficient_map[coefficient] = expr - return funarg def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -117,10 +109,7 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - funarg, expr = prepare_coefficient(f, "cell_sizes", - self.scalar_type, - interior_facet=self.interior_facet) - self.cell_sizes_arg = kernel_args.CellSizesKernelArg(funarg) + expr = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) self._cell_sizes = expr def create_element(self, element, **kwargs): @@ -128,6 +117,28 @@ def create_element(self, element, **kwargs): a UFL element.""" return create_element(element, **kwargs) + def generate_arg_from_variable(self, var, is_output=False): + """Generate kernel arg from a :class:`gem.Variable`. + + :arg var: a :class:`gem.Variable` + :arg is_output: if expr represents the output or not + :returns: kernel arg + """ + if is_output: + return coffee.Decl(self.scalar_type, coffee.Symbol(var.name, rank=var.shape)) + else: + return coffee.Decl(self.scalar_type, coffee.Symbol(var.name), pointers=[("restrict",)], qualifiers=["const"]) + + def generate_arg_from_expression(self, expr, is_output=False): + """Generate kernel arg from gem expression(s). + + :arg expr: gem expression(s) representing a coefficient or the output tensor + :arg is_output: if expr represents the output or not + :returns: kernel arg + """ + var, = gem.extract_type(expr if isinstance(expr, tuple) else (expr, ), gem.Variable) + return self.generate_arg_from_variable(var, is_output=is_output) + class KernelBuilder(KernelBuilderBase, KernelBuilderMixin): """Helper class for building a :class:`Kernel` object.""" @@ -141,8 +152,6 @@ def __init__(self, integral_data_info, scalar_type, self.diagonal = diagonal self.local_tensor = None - self.coordinates_arg = None - self.coefficient_args = [] self.coefficient_split = {} self.dont_split = frozenset(dont_split) @@ -176,12 +185,10 @@ def set_arguments(self, arguments): # the trial space as well. a, _ = argument_multiindices argument_multiindices = (a, a) - funarg, return_variables = prepare_arguments(arguments, - argument_multiindices, - self.scalar_type, - interior_facet=self.interior_facet, - diagonal=self.diagonal) - self.output_arg = kernel_args.OutputKernelArg(funarg) + return_variables = prepare_arguments(arguments, + argument_multiindices, + interior_facet=self.interior_facet, + diagonal=self.diagonal) self.return_variables = return_variables self.argument_multiindices = argument_multiindices @@ -193,8 +200,7 @@ def set_coordinates(self, domain): # Create a fake coordinate coefficient for a domain. f = Coefficient(FunctionSpace(domain, domain.ufl_coordinate_element())) self.domain_coordinate[domain] = f - coords_coffee_arg = self._coefficient(f, "coords") - self.coordinates_arg = kernel_args.CoordinatesKernelArg(coords_coffee_arg) + self._coefficient(f, "coords") def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -221,8 +227,7 @@ def set_coefficients(self, integral_data, form_data): else: coefficients.append(coefficient) for i, coefficient in enumerate(coefficients): - coeff_coffee_arg = self._coefficient(coefficient, f"w_{i}") - self.coefficient_args.append(kernel_args.CoefficientKernelArg(coeff_coffee_arg)) + self._coefficient(coefficient, f"w_{i}") def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be @@ -243,15 +248,27 @@ def construct_kernel(self, name, ctx, log=False): if impero_c is None: return self.construct_empty_kernel(name) info = self.integral_data_info - args = [self.output_arg, self.coordinates_arg] + # Add return arg + funarg = self.generate_arg_from_expression(self.return_variables, is_output=True) + args = [kernel_args.OutputKernelArg(funarg)] + # Add coordinates arg + coord = self.domain_coordinate[info.domain] + expr = self.coefficient_map[coord] + funarg = self.generate_arg_from_expression(expr) + args.append(kernel_args.CoordinatesKernelArg(funarg)) if oriented: ori_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), pointers=[("restrict",)], qualifiers=["const"]) args.append(kernel_args.CellOrientationsKernelArg(ori_coffee_arg)) if needs_cell_sizes: - args.append(self.cell_sizes_arg) - args.extend(self.coefficient_args) + funarg = self.generate_arg_from_expression(self._cell_sizes) + args.append(kernel_args.CellSizesKernelArg(funarg)) + for coeff, expr in self.coefficient_map.items(): + if coeff in self.domain_coordinate.values(): + continue + funarg = self.generate_arg_from_expression(expr) + args.append(kernel_args.CoefficientKernelArg(funarg)) if info.integral_type in ["exterior_facet", "exterior_facet_vert"]: ext_coffee_arg = coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(1,)), @@ -290,122 +307,3 @@ def construct_empty_kernel(self, name): :returns: None """ return None - - -def check_requirements(ir): - """Look for cell orientations, cell sizes, and collect tabulations - in one pass.""" - cell_orientations = False - cell_sizes = False - rt_tabs = {} - for node in traversal(ir): - if isinstance(node, gem.Variable): - if node.name == "cell_orientations": - cell_orientations = True - elif node.name == "cell_sizes": - cell_sizes = True - elif node.name.startswith("rt_"): - rt_tabs[node.name] = node.shape - return cell_orientations, cell_sizes, tuple(sorted(rt_tabs.items())) - - -def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): - """Bridges the kernel interface and the GEM abstraction for - Coefficients. - - :arg coefficient: UFL Coefficient - :arg name: unique name to refer to the Coefficient in the kernel - :arg interior_facet: interior facet integral? - :returns: (funarg, expression) - funarg - :class:`coffee.Decl` function argument - expression - GEM expression referring to the Coefficient - values - """ - assert isinstance(interior_facet, bool) - - if coefficient.ufl_element().family() == 'Real': - # Constant - funarg = coffee.Decl(scalar_type, coffee.Symbol(name), - pointers=[("restrict",)], - qualifiers=["const"]) - value_size = coefficient.ufl_element().value_size() - expression = gem.reshape(gem.Variable(name, (value_size,)), - coefficient.ufl_shape) - - return funarg, expression - - finat_element = create_element(coefficient.ufl_element()) - shape = finat_element.index_shape - size = numpy.prod(shape, dtype=int) - - funarg = coffee.Decl(scalar_type, coffee.Symbol(name), - pointers=[("restrict",)], - qualifiers=["const"]) - - if not interior_facet: - expression = gem.reshape(gem.Variable(name, (size,)), shape) - else: - varexp = gem.Variable(name, (2 * size,)) - plus = gem.view(varexp, slice(size)) - minus = gem.view(varexp, slice(size, 2 * size)) - expression = (gem.reshape(plus, shape), - gem.reshape(minus, shape)) - return funarg, expression - - -def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): - """Bridges the kernel interface and the GEM abstraction for - Arguments. Vector Arguments are rearranged here for interior - facet integrals. - - :arg arguments: UFL Arguments - :arg multiindices: Argument multiindices - :arg interior_facet: interior facet integral? - :arg diagonal: Are we assembling the diagonal of a rank-2 element tensor? - :returns: (funarg, expression) - funarg - :class:`coffee.Decl` function argument - expressions - GEM expressions referring to the argument - tensor - """ - assert isinstance(interior_facet, bool) - - if len(arguments) == 0: - # No arguments - funarg = coffee.Decl(scalar_type, coffee.Symbol("A", rank=(1,))) - expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) - - return funarg, [expression] - - elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - shapes = tuple(element.index_shape for element in elements) - - if diagonal: - if len(arguments) != 2: - raise ValueError("Diagonal only for 2-forms") - try: - element, = set(elements) - except ValueError: - raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") - - elements = (element, ) - shapes = tuple(element.index_shape for element in elements) - multiindices = multiindices[:1] - - def expression(restricted): - return gem.Indexed(gem.reshape(restricted, *shapes), - tuple(chain(*multiindices))) - - u_shape = numpy.array([numpy.prod(shape, dtype=int) for shape in shapes]) - if interior_facet: - c_shape = tuple(2 * u_shape) - slicez = [[slice(r * s, (r + 1) * s) - for r, s in zip(restrictions, u_shape)] - for restrictions in product((0, 1), repeat=len(arguments))] - else: - c_shape = tuple(u_shape) - slicez = [[slice(s) for s in u_shape]] - - funarg = coffee.Decl(scalar_type, coffee.Symbol("A", rank=c_shape)) - varexp = gem.Variable("A", c_shape) - expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] - return funarg, prune(expressions) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 589ca7b642..12de5fd424 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -1,20 +1,17 @@ import numpy from collections import namedtuple -from itertools import chain, product from functools import partial from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement import gem from gem.flop_count import count_flops -from gem.optimise import remove_componenttensors as prune import loopy as lp from tsfc import kernel_args from tsfc.finatinterface import create_element -from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names -from tsfc.kernel_interface.firedrake import check_requirements +from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names, check_requirements, prepare_coefficient, prepare_arguments from tsfc.loopy import generate as generate_loopy @@ -86,16 +83,12 @@ def __init__(self, scalar_type, interior_facet=False): # Cell orientation if self.interior_facet: - shape = (2,) - cell_orientations = gem.Variable("cell_orientations", shape) + cell_orientations = gem.Variable("cell_orientations", (2,)) self._cell_orientations = (gem.Indexed(cell_orientations, (0,)), gem.Indexed(cell_orientations, (1,))) else: - shape = (1,) - cell_orientations = gem.Variable("cell_orientations", shape) + cell_orientations = gem.Variable("cell_orientations", (1,)) self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) - loopy_arg = lp.GlobalArg("cell_orientations", dtype=numpy.int32, shape=shape) - self.cell_orientations_arg = kernel_args.CellOrientationsKernelArg(loopy_arg) def _coefficient(self, coefficient, name): """Prepare a coefficient. Adds glue code for the coefficient @@ -103,11 +96,9 @@ def _coefficient(self, coefficient, name): :arg coefficient: :class:`ufl.Coefficient` :arg name: coefficient name - :returns: loopy argument for the coefficient """ - funarg, expression = prepare_coefficient(coefficient, name, self.scalar_type, interior_facet=self.interior_facet) - self.coefficient_map[coefficient] = expression - return funarg + expr = prepare_coefficient(coefficient, name, interior_facet=self.interior_facet) + self.coefficient_map[coefficient] = expr def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". @@ -127,15 +118,33 @@ def set_cell_sizes(self, domain): # topological_dimension is 0 and the concept of "cell size" # is not useful for a vertex. f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - funarg, expression = prepare_coefficient(f, "cell_sizes", self.scalar_type, interior_facet=self.interior_facet) - self.cell_sizes_arg = kernel_args.CellSizesKernelArg(funarg) - self._cell_sizes = expression + expr = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) + self._cell_sizes = expr def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given a UFL element.""" return create_element(element, **kwargs) + def generate_arg_from_variable(self, var, dtype=None): + """Generate kernel arg from a :class:`gem.Variable`. + + :arg var: a :class:`gem.Variable` + :arg dtype: dtype of the kernel arg + :returns: kernel arg + """ + return lp.GlobalArg(var.name, dtype=dtype or self.scalar_type, shape=var.shape) + + def generate_arg_from_expression(self, expr, dtype=None): + """Generate kernel arg from gem expression(s). + + :arg expr: gem expression(s) representing a coefficient or the output tensor + :arg dtype: dtype of the kernel arg + :returns: kernel arg + """ + var, = gem.extract_type(expr if isinstance(expr, tuple) else (expr, ), gem.Variable) + return self.generate_arg_from_variable(var, dtype=dtype or self.scalar_type) + class ExpressionKernelBuilder(KernelBuilderBase): """Builds expression kernels for UFL interpolation in Firedrake.""" @@ -151,19 +160,15 @@ def set_coefficients(self, coefficients): :arg coefficients: UFL coefficients from Firedrake """ self.coefficient_split = {} - self.kernel_args = [] for i, coefficient in enumerate(coefficients): if type(coefficient.ufl_element()) == ufl_MixedElement: subcoeffs = coefficient.split() # Firedrake-specific self.coefficient_split[coefficient] = subcoeffs - coeff_loopy_args = [self._coefficient(subcoeff, f"w_{i}_{j}") - for j, subcoeff in enumerate(subcoeffs)] - self.kernel_args += [kernel_args.CoefficientKernelArg(a) - for a in coeff_loopy_args] + for j, subcoeff in enumerate(subcoeffs): + self._coefficient(subcoeff, f"w_{i}_{j}") else: - coeff_loopy_arg = self._coefficient(coefficient, f"w_{i}") - self.kernel_args.append(kernel_args.CoefficientKernelArg(coeff_loopy_arg)) + self._coefficient(coefficient, f"w_{i}") def set_coefficient_numbers(self, coefficient_numbers): """Store the coefficient indices of the original form. @@ -196,10 +201,15 @@ def construct_kernel(self, impero_c, index_names, needs_external_coords, log=Fal """ args = [self.output_arg] if self.oriented: - args.append(self.cell_orientations_arg) + funarg = self.generate_arg_from_expression(self._cell_orientations, dtype=numpy.int32) + args.append(kernel_args.CellOrientationsKernelArg(funarg)) if self.cell_sizes: - args.append(self.cell_sizes_arg) - args.extend(self.kernel_args) + funarg = self.generate_arg_from_expression(self._cell_sizes) + args.append(kernel_args.CellSizesKernelArg(funarg)) + for _, expr in self.coefficient_map.items(): + # coefficient_map is OrderedDict. + funarg = self.generate_arg_from_expression(expr) + args.append(kernel_args.CoefficientKernelArg(funarg)) for name_, shape in self.tabulations: tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) args.append(kernel_args.TabulationKernelArg(tab_loopy_arg)) @@ -226,8 +236,6 @@ def __init__(self, integral_data_info, scalar_type, self.diagonal = diagonal self.local_tensor = None - self.coordinates_arg = None - self.coefficient_args = [] self.coefficient_split = {} self.dont_split = frozenset(dont_split) @@ -261,12 +269,10 @@ def set_arguments(self, arguments): # the trial space as well. a, _ = argument_multiindices argument_multiindices = (a, a) - funarg, return_variables = prepare_arguments(arguments, - argument_multiindices, - self.scalar_type, - interior_facet=self.interior_facet, - diagonal=self.diagonal) - self.output_arg = kernel_args.OutputKernelArg(funarg) + return_variables = prepare_arguments(arguments, + argument_multiindices, + interior_facet=self.interior_facet, + diagonal=self.diagonal) self.return_variables = return_variables self.argument_multiindices = argument_multiindices @@ -278,8 +284,7 @@ def set_coordinates(self, domain): # Create a fake coordinate coefficient for a domain. f = Coefficient(FunctionSpace(domain, domain.ufl_coordinate_element())) self.domain_coordinate[domain] = f - coords_loopy_arg = self._coefficient(f, "coords") - self.coordinates_arg = kernel_args.CoordinatesKernelArg(coords_loopy_arg) + self._coefficient(f, "coords") def set_coefficients(self, integral_data, form_data): """Prepare the coefficients of the form. @@ -306,8 +311,7 @@ def set_coefficients(self, integral_data, form_data): else: coefficients.append(coefficient) for i, coefficient in enumerate(coefficients): - coeff_loopy_arg = self._coefficient(coefficient, f"w_{i}") - self.coefficient_args.append(kernel_args.CoefficientKernelArg(coeff_loopy_arg)) + self._coefficient(coefficient, f"w_{i}") def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be @@ -329,12 +333,25 @@ def construct_kernel(self, name, ctx, log=False): if impero_c is None: return self.construct_empty_kernel(name) info = self.integral_data_info - args = [self.output_arg, self.coordinates_arg] + # Add return arg + funarg = self.generate_arg_from_expression(self.return_variables) + args = [kernel_args.OutputKernelArg(funarg)] + # Add coordinates arg + coord = self.domain_coordinate[info.domain] + expr = self.coefficient_map[coord] + funarg = self.generate_arg_from_expression(expr) + args.append(kernel_args.CoordinatesKernelArg(funarg)) if oriented: - args.append(self.cell_orientations_arg) + funarg = self.generate_arg_from_expression(self._cell_orientations, dtype=numpy.int32) + args.append(kernel_args.CellOrientationsKernelArg(funarg)) if needs_cell_sizes: - args.append(self.cell_sizes_arg) - args.extend(self.coefficient_args) + funarg = self.generate_arg_from_expression(self._cell_sizes) + args.append(kernel_args.CellSizesKernelArg(funarg)) + for coeff, expr in self.coefficient_map.items(): + if coeff in self.domain_coordinate.values(): + continue + funarg = self.generate_arg_from_expression(expr) + args.append(kernel_args.CoefficientKernelArg(funarg)) if info.integral_type in ["exterior_facet", "exterior_facet_vert"]: ext_loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(1,)) args.append(kernel_args.ExteriorFacetKernelArg(ext_loopy_arg)) @@ -368,102 +385,3 @@ def construct_empty_kernel(self, name): :returns: None """ return None - - -def prepare_coefficient(coefficient, name, scalar_type, interior_facet=False): - """Bridges the kernel interface and the GEM abstraction for - Coefficients. - - :arg coefficient: UFL Coefficient - :arg name: unique name to refer to the Coefficient in the kernel - :arg interior_facet: interior facet integral? - :returns: (funarg, expression) - funarg - :class:`loopy.GlobalArg` function argument - expression - GEM expression referring to the Coefficient - values - """ - assert isinstance(interior_facet, bool) - - if coefficient.ufl_element().family() == 'Real': - # Constant - value_size = coefficient.ufl_element().value_size() - funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(value_size,)) - expression = gem.reshape(gem.Variable(name, (value_size,)), - coefficient.ufl_shape) - - return funarg, expression - - finat_element = create_element(coefficient.ufl_element()) - - shape = finat_element.index_shape - size = numpy.prod(shape, dtype=int) - - if not interior_facet: - expression = gem.reshape(gem.Variable(name, (size,)), shape) - else: - varexp = gem.Variable(name, (2*size,)) - plus = gem.view(varexp, slice(size)) - minus = gem.view(varexp, slice(size, 2*size)) - expression = (gem.reshape(plus, shape), gem.reshape(minus, shape)) - size = size * 2 - funarg = lp.GlobalArg(name, dtype=scalar_type, shape=(size,)) - return funarg, expression - - -def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False, diagonal=False): - """Bridges the kernel interface and the GEM abstraction for - Arguments. Vector Arguments are rearranged here for interior - facet integrals. - - :arg arguments: UFL Arguments - :arg multiindices: Argument multiindices - :arg interior_facet: interior facet integral? - :arg diagonal: Are we assembling the diagonal of a rank-2 element tensor? - :returns: (funarg, expression) - funarg - :class:`loopy.GlobalArg` function argument - expressions - GEM expressions referring to the argument - tensor - """ - - assert isinstance(interior_facet, bool) - - if len(arguments) == 0: - # No arguments - funarg = lp.GlobalArg("A", dtype=scalar_type, shape=(1,)) - expression = gem.Indexed(gem.Variable("A", (1,)), (0,)) - - return funarg, [expression] - - elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - shapes = tuple(element.index_shape for element in elements) - - if diagonal: - if len(arguments) != 2: - raise ValueError("Diagonal only for 2-forms") - try: - element, = set(elements) - except ValueError: - raise ValueError("Diagonal only for diagonal blocks (test and trial spaces the same)") - - elements = (element, ) - shapes = tuple(element.index_shape for element in elements) - multiindices = multiindices[:1] - - def expression(restricted): - return gem.Indexed(gem.reshape(restricted, *shapes), - tuple(chain(*multiindices))) - - u_shape = numpy.array([numpy.prod(shape, dtype=int) for shape in shapes]) - if interior_facet: - c_shape = tuple(2 * u_shape) - slicez = [[slice(r * s, (r + 1) * s) - for r, s in zip(restrictions, u_shape)] - for restrictions in product((0, 1), repeat=len(arguments))] - else: - c_shape = tuple(u_shape) - slicez = [[slice(s) for s in u_shape]] - - funarg = lp.GlobalArg("A", dtype=scalar_type, shape=c_shape) - varexp = gem.Variable("A", c_shape) - expressions = [expression(gem.view(varexp, *slices)) for slices in slicez] - return funarg, prune(expressions) From 244d3d447ecad7bbbe8d4f89e29518abce22d2dd Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Wed, 2 Mar 2022 13:16:57 +0000 Subject: [PATCH 725/809] kernel builder: only generate/include kernel args for active coefficients --- tsfc/kernel_interface/common.py | 4 +- tsfc/kernel_interface/firedrake.py | 54 ++++++++++++++++------- tsfc/kernel_interface/firedrake_loopy.py | 56 +++++++++++++++++------- tsfc/kernel_interface/ufc.py | 2 +- 4 files changed, 81 insertions(+), 35 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index c2c9ca34da..4057bc16e1 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -225,6 +225,8 @@ def compile_gem(self, ctx): # cell sizes, etc.). oriented, needs_cell_sizes, tabulations = self.register_requirements(expressions) + # Extract Variables that are actually used + active_variables = gem.extract_type(expressions, gem.Variable) # Construct ImperoC assignments = list(zip(return_variables, expressions)) index_ordering = get_index_ordering(ctx['quadrature_indices'], return_variables) @@ -232,7 +234,7 @@ def compile_gem(self, ctx): impero_c = impero_utils.compile_gem(assignments, index_ordering, remove_zeros=True) except impero_utils.NoopError: impero_c = None - return impero_c, oriented, needs_cell_sizes, tabulations + return impero_c, oriented, needs_cell_sizes, tabulations, active_variables def fem_config(self): """Return a dictionary used with fem.compile_ufl. diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py index 37171d6d55..067d247268 100644 --- a/tsfc/kernel_interface/firedrake.py +++ b/tsfc/kernel_interface/firedrake.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from functools import partial from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement @@ -153,6 +154,7 @@ def __init__(self, integral_data_info, scalar_type, self.diagonal = diagonal self.local_tensor = None self.coefficient_split = {} + self.coefficient_number_index_map = OrderedDict() self.dont_split = frozenset(dont_split) # Facet number @@ -208,26 +210,32 @@ def set_coefficients(self, integral_data, form_data): :arg integral_data: UFL integral data :arg form_data: UFL form data """ - coefficients = [] # enabled_coefficients is a boolean array that indicates which # of reduced_coefficients the integral requires. + n, k = 0, 0 for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: original = form_data.reduced_coefficients[i] coefficient = form_data.function_replace_map[original] if type(coefficient.ufl_element()) == ufl_MixedElement: if original in self.dont_split: - coefficients.append(coefficient) self.coefficient_split[coefficient] = [coefficient] + self._coefficient(coefficient, f"w_{k}") + self.coefficient_number_index_map[coefficient] = (n, 0) + k += 1 else: - split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) - for element in coefficient.ufl_element().sub_elements()] - coefficients.extend(split) - self.coefficient_split[coefficient] = split + self.coefficient_split[coefficient] = [] + for j, element in enumerate(coefficient.ufl_element().sub_elements()): + c = Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) + self.coefficient_split[coefficient].append(c) + self._coefficient(c, f"w_{k}") + self.coefficient_number_index_map[c] = (n, j) + k += 1 else: - coefficients.append(coefficient) - for i, coefficient in enumerate(coefficients): - self._coefficient(coefficient, f"w_{i}") + self._coefficient(coefficient, f"w_{k}") + self.coefficient_number_index_map[coefficient] = (n, 0) + k += 1 + n += 1 def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be @@ -244,10 +252,19 @@ def construct_kernel(self, name, ctx, log=False): :arg ctx: kernel builder context to get impero_c from :returns: :class:`Kernel` object """ - impero_c, oriented, needs_cell_sizes, tabulations = self.compile_gem(ctx) + impero_c, oriented, needs_cell_sizes, tabulations, active_variables = self.compile_gem(ctx) if impero_c is None: return self.construct_empty_kernel(name) info = self.integral_data_info + # In the following funargs are only generated + # for gem expressions that are actually used; + # see `generate_arg_from_expression()` method. + # Specifically, funargs are not generated for + # unused components of mixed coefficients. + # Problem solving environment, such as Firedrake, + # will know which components have been included + # in the list of kernel arguments by investigating + # `Kernel.coefficient_numbers`. # Add return arg funarg = self.generate_arg_from_expression(self.return_variables, is_output=True) args = [kernel_args.OutputKernelArg(funarg)] @@ -264,11 +281,16 @@ def construct_kernel(self, name, ctx, log=False): if needs_cell_sizes: funarg = self.generate_arg_from_expression(self._cell_sizes) args.append(kernel_args.CellSizesKernelArg(funarg)) - for coeff, expr in self.coefficient_map.items(): - if coeff in self.domain_coordinate.values(): - continue - funarg = self.generate_arg_from_expression(expr) - args.append(kernel_args.CoefficientKernelArg(funarg)) + coefficient_indices = {} + for coeff, (number, index) in self.coefficient_number_index_map.items(): + a = coefficient_indices.setdefault(number, []) + expr = self.coefficient_map[coeff] + var, = gem.extract_type(expr if isinstance(expr, tuple) else (expr, ), gem.Variable) + if var in active_variables: + funarg = self.generate_arg_from_expression(expr) + args.append(kernel_args.CoefficientKernelArg(funarg)) + a.append(index) + coefficient_indices = tuple(tuple(v) for v in coefficient_indices.values()) if info.integral_type in ["exterior_facet", "exterior_facet_vert"]: ext_coffee_arg = coffee.Decl("unsigned int", coffee.Symbol("facet", rank=(1,)), @@ -293,7 +315,7 @@ def construct_kernel(self, name, ctx, log=False): integral_type=info.integral_type, subdomain_id=info.subdomain_id, domain_number=info.domain_number, - coefficient_numbers=info.coefficient_numbers, + coefficient_numbers=tuple(zip(info.coefficient_numbers, coefficient_indices)), oriented=oriented, needs_cell_sizes=needs_cell_sizes, tabulations=tabulations, diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 12de5fd424..856b2a11ea 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -1,5 +1,5 @@ import numpy -from collections import namedtuple +from collections import namedtuple, OrderedDict from functools import partial from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement @@ -237,6 +237,7 @@ def __init__(self, integral_data_info, scalar_type, self.diagonal = diagonal self.local_tensor = None self.coefficient_split = {} + self.coefficient_number_index_map = OrderedDict() self.dont_split = frozenset(dont_split) # Facet number @@ -292,26 +293,32 @@ def set_coefficients(self, integral_data, form_data): :arg integral_data: UFL integral data :arg form_data: UFL form data """ - coefficients = [] # enabled_coefficients is a boolean array that indicates which # of reduced_coefficients the integral requires. + n, k = 0, 0 for i in range(len(integral_data.enabled_coefficients)): if integral_data.enabled_coefficients[i]: original = form_data.reduced_coefficients[i] coefficient = form_data.function_replace_map[original] if type(coefficient.ufl_element()) == ufl_MixedElement: if original in self.dont_split: - coefficients.append(coefficient) self.coefficient_split[coefficient] = [coefficient] + self._coefficient(coefficient, f"w_{k}") + self.coefficient_number_index_map[coefficient] = (n, 0) + k += 1 else: - split = [Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) - for element in coefficient.ufl_element().sub_elements()] - coefficients.extend(split) - self.coefficient_split[coefficient] = split + self.coefficient_split[coefficient] = [] + for j, element in enumerate(coefficient.ufl_element().sub_elements()): + c = Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) + self.coefficient_split[coefficient].append(c) + self._coefficient(c, f"w_{k}") + self.coefficient_number_index_map[c] = (n, j) + k += 1 else: - coefficients.append(coefficient) - for i, coefficient in enumerate(coefficients): - self._coefficient(coefficient, f"w_{i}") + self._coefficient(coefficient, f"w_{k}") + self.coefficient_number_index_map[coefficient] = (n, 0) + k += 1 + n += 1 def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be @@ -329,10 +336,19 @@ def construct_kernel(self, name, ctx, log=False): :arg log: bool if the Kernel should be profiled with Log events :returns: :class:`Kernel` object """ - impero_c, oriented, needs_cell_sizes, tabulations = self.compile_gem(ctx) + impero_c, oriented, needs_cell_sizes, tabulations, active_variables = self.compile_gem(ctx) if impero_c is None: return self.construct_empty_kernel(name) info = self.integral_data_info + # In the following funargs are only generated + # for gem expressions that are actually used; + # see `generate_arg_from_expression()` method. + # Specifically, funargs are not generated for + # unused components of mixed coefficients. + # Problem solving environment, such as Firedrake, + # will know which components have been included + # in the list of kernel arguments by investigating + # `Kernel.coefficient_numbers`. # Add return arg funarg = self.generate_arg_from_expression(self.return_variables) args = [kernel_args.OutputKernelArg(funarg)] @@ -347,11 +363,17 @@ def construct_kernel(self, name, ctx, log=False): if needs_cell_sizes: funarg = self.generate_arg_from_expression(self._cell_sizes) args.append(kernel_args.CellSizesKernelArg(funarg)) - for coeff, expr in self.coefficient_map.items(): - if coeff in self.domain_coordinate.values(): - continue - funarg = self.generate_arg_from_expression(expr) - args.append(kernel_args.CoefficientKernelArg(funarg)) + coefficient_indices = OrderedDict() + for coeff, (number, index) in self.coefficient_number_index_map.items(): + a = coefficient_indices.setdefault(number, []) + expr = self.coefficient_map[coeff] + var, = gem.extract_type(expr if isinstance(expr, tuple) else (expr, ), gem.Variable) + if var in active_variables: + funarg = self.generate_arg_from_expression(expr) + args.append(kernel_args.CoefficientKernelArg(funarg)) + a.append(index) + coefficient_indices = tuple(tuple(v) for v in coefficient_indices.values()) + assert len(coefficient_indices) == len(info.coefficient_numbers) if info.integral_type in ["exterior_facet", "exterior_facet_vert"]: ext_loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(1,)) args.append(kernel_args.ExteriorFacetKernelArg(ext_loopy_arg)) @@ -370,7 +392,7 @@ def construct_kernel(self, name, ctx, log=False): integral_type=info.integral_type, subdomain_id=info.subdomain_id, domain_number=info.domain_number, - coefficient_numbers=info.coefficient_numbers, + coefficient_numbers=tuple(zip(info.coefficient_numbers, coefficient_indices)), oriented=oriented, needs_cell_sizes=needs_cell_sizes, tabulations=tabulations, diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py index 65c91e51b4..2217a16d3c 100644 --- a/tsfc/kernel_interface/ufc.py +++ b/tsfc/kernel_interface/ufc.py @@ -139,7 +139,7 @@ def construct_kernel(self, name, ctx): """ from tsfc.coffee import generate as generate_coffee - impero_c, _, _, _ = self.compile_gem(ctx) + impero_c, _, _, _, _ = self.compile_gem(ctx) if impero_c is None: return self.construct_empty_kernel(name) index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) From 61819538981df7f1b45d3bffbf70e0c9757e6b8b Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Tue, 28 Jun 2022 22:35:31 -0500 Subject: [PATCH 726/809] fiatinterface bye bye --- tsfc/fiatinterface.py | 257 ------------------------------------------ 1 file changed, 257 deletions(-) delete mode 100644 tsfc/fiatinterface.py diff --git a/tsfc/fiatinterface.py b/tsfc/fiatinterface.py deleted file mode 100644 index b8b0b8d2f1..0000000000 --- a/tsfc/fiatinterface.py +++ /dev/null @@ -1,257 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file was modified from FFC -# (http://bitbucket.org/fenics-project/ffc), copyright notice -# reproduced below. -# -# Copyright (C) 2009-2013 Kristian B. Oelgaard and Anders Logg -# -# This file is part of FFC. -# -# FFC is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# FFC is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with FFC. If not, see . - -from functools import singledispatch, partial -import weakref - -import FIAT -from FIAT.tensor_product import FlattenedDimensions - -import ufl - - -__all__ = ("create_element", "supported_elements", "as_fiat_cell") - - -supported_elements = { - # These all map directly to FIAT elements - "Bernstein": FIAT.Bernstein, - "Brezzi-Douglas-Marini": FIAT.BrezziDouglasMarini, - "Brezzi-Douglas-Fortin-Marini": FIAT.BrezziDouglasFortinMarini, - "Bubble": FIAT.Bubble, - "FacetBubble": FIAT.FacetBubble, - "Crouzeix-Raviart": FIAT.CrouzeixRaviart, - "Discontinuous Lagrange": FIAT.DiscontinuousLagrange, - "Discontinuous Taylor": FIAT.DiscontinuousTaylor, - "Discontinuous Raviart-Thomas": FIAT.DiscontinuousRaviartThomas, - "Gauss-Lobatto-Legendre": FIAT.GaussLobattoLegendre, - "Gauss-Legendre": FIAT.GaussLegendre, - "Lagrange": FIAT.Lagrange, - "Nedelec 1st kind H(curl)": FIAT.Nedelec, - "Nedelec 2nd kind H(curl)": FIAT.NedelecSecondKind, - "Raviart-Thomas": FIAT.RaviartThomas, - "HDiv Trace": FIAT.HDivTrace, - "Regge": FIAT.Regge, - "Hellan-Herrmann-Johnson": FIAT.HellanHerrmannJohnson, - # These require special treatment below - "DQ": None, - "Q": None, - "RTCE": None, - "RTCF": None, - "NCE": None, - "NCF": None, - "DPC": FIAT.DPC, - "S": FIAT.Serendipity, - "SminusF": FIAT.TrimmedSerendipityFace, - "SminusDiv": FIAT.TrimmedSerendipityDiv, - "SminusE": FIAT.TrimmedSerendipityEdge, - "SminusCurl": FIAT.TrimmedSerendipityCurl, - "DPC L2": FIAT.DPC, - "Discontinuous Lagrange L2": FIAT.DiscontinuousLagrange, - "Gauss-Legendre L2": FIAT.GaussLegendre, - "DQ L2": None, -} -"""A :class:`.dict` mapping UFL element family names to their -FIAT-equivalent constructors. If the value is ``None``, the UFL -element is supported, but must be handled specially because it doesn't -have a direct FIAT equivalent.""" - - -def as_fiat_cell(cell): - """Convert a ufl cell to a FIAT cell. - - :arg cell: the :class:`ufl.Cell` to convert.""" - if not isinstance(cell, ufl.AbstractCell): - raise ValueError("Expecting a UFL Cell") - return FIAT.ufc_cell(cell) - - -@singledispatch -def convert(element, vector_is_mixed): - """Handler for converting UFL elements to FIAT elements. - - :arg element: The UFL element to convert. - :arg vector_is_mixed: Should Vector and Tensor elements be treated - as Mixed? If ``False``, then just look at the sub-element. - - Do not use this function directly, instead call - :func:`create_element`.""" - if element.family() in supported_elements: - raise ValueError("Element %s supported, but no handler provided" % element) - raise ValueError("Unsupported element type %s" % type(element)) - - -# Base finite elements first -@convert.register(ufl.FiniteElement) -def convert_finiteelement(element, vector_is_mixed): - if element.family() == "Real": - # Real element is just DG0 - cell = element.cell() - return create_element(ufl.FiniteElement("DG", cell, 0), vector_is_mixed) - cell = as_fiat_cell(element.cell()) - if element.family() == "Quadrature": - degree = element.degree() - scheme = element.quadrature_scheme() - if degree is None or scheme is None: - raise ValueError("Quadrature scheme and degree must be specified!") - - quad_rule = FIAT.create_quadrature(cell, degree, scheme) - return FIAT.QuadratureElement(cell, quad_rule.get_points(), weights=quad_rule.get_weights()) - lmbda = supported_elements[element.family()] - if lmbda is None: - if element.cell().cellname() == "quadrilateral": - # Handle quadrilateral short names like RTCF and RTCE. - element = element.reconstruct(cell=quadrilateral_tpc) - elif element.cell().cellname() == "hexahedron": - # Handle hexahedron short names like NCF and NCE. - element = element.reconstruct(cell=hexahedron_tpc) - else: - raise ValueError("%s is supported, but handled incorrectly" % - element.family()) - return FlattenedDimensions(create_element(element, vector_is_mixed)) - - kind = element.variant() - if kind is None: - kind = 'spectral' if element.cell().cellname() == 'interval' else 'equispaced' # default variant - - if element.family() == "Lagrange": - if kind == 'equispaced': - lmbda = FIAT.Lagrange - elif kind == 'spectral' and element.cell().cellname() == 'interval': - lmbda = FIAT.GaussLobattoLegendre - else: - raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - elif element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", - "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)"}: - lmbda = partial(lmbda, variant=element.variant()) - elif element.family() in {"Discontinuous Lagrange", "Discontinuous Lagrange L2"}: - if kind == 'equispaced': - lmbda = FIAT.DiscontinuousLagrange - elif kind == 'spectral' and element.cell().cellname() == 'interval': - lmbda = FIAT.GaussLegendre - else: - raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) - return lmbda(cell, element.degree()) - - -# Element modifiers -@convert.register(ufl.RestrictedElement) -def convert_restrictedelement(element, vector_is_mixed): - return FIAT.RestrictedElement(create_element(element.sub_element(), vector_is_mixed), - restriction_domain=element.restriction_domain()) - - -@convert.register(ufl.EnrichedElement) -def convert_enrichedelement(element, vector_is_mixed): - return FIAT.EnrichedElement(*(create_element(e, vector_is_mixed) - for e in element._elements)) - - -@convert.register(ufl.NodalEnrichedElement) -def convert_nodalenrichedelement(element, vector_is_mixed): - return FIAT.NodalEnrichedElement(*(create_element(e, vector_is_mixed) - for e in element._elements)) - - -@convert.register(ufl.BrokenElement) -def convert_brokenelement(element, vector_is_mixed): - return FIAT.DiscontinuousElement(create_element(element._element, vector_is_mixed)) - - -# Now for the TPE-specific stuff -@convert.register(ufl.TensorProductElement) -def convert_tensorproductelement(element, vector_is_mixed): - cell = element.cell() - if type(cell) is not ufl.TensorProductCell: - raise ValueError("TPE not on TPC?") - A, B = element.sub_elements() - return FIAT.TensorProductElement(create_element(A, vector_is_mixed), - create_element(B, vector_is_mixed)) - - -@convert.register(ufl.HDivElement) -def convert_hdivelement(element, vector_is_mixed): - return FIAT.Hdiv(create_element(element._element, vector_is_mixed)) - - -@convert.register(ufl.HCurlElement) -def convert_hcurlelement(element, vector_is_mixed): - return FIAT.Hcurl(create_element(element._element, vector_is_mixed)) - - -# Finally the MixedElement case -@convert.register(ufl.MixedElement) -def convert_mixedelement(element, vector_is_mixed): - # If we're just trying to get the scalar part of a vector element? - if not vector_is_mixed: - assert isinstance(element, (ufl.VectorElement, - ufl.TensorElement)) - return create_element(element.sub_elements()[0], vector_is_mixed) - - elements = [] - - def rec(eles): - for ele in eles: - if isinstance(ele, ufl.MixedElement): - rec(ele.sub_elements()) - else: - elements.append(ele) - - rec(element.sub_elements()) - fiat_elements = map(partial(create_element, vector_is_mixed=vector_is_mixed), - elements) - return FIAT.MixedElement(fiat_elements) - - -hexahedron_tpc = ufl.TensorProductCell(ufl.quadrilateral, ufl.interval) -quadrilateral_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) -_cache = weakref.WeakKeyDictionary() - - -def create_element(element, vector_is_mixed=True): - """Create a FIAT element (suitable for tabulating with) given a UFL element. - - :arg element: The UFL element to create a FIAT element from. - - :arg vector_is_mixed: indicate whether VectorElement (or - TensorElement) should be treated as a MixedElement. Maybe - useful if you want a FIAT element that tells you how many - "nodes" the finite element has. - """ - try: - cache = _cache[element] - except KeyError: - _cache[element] = {} - cache = _cache[element] - - try: - return cache[vector_is_mixed] - except KeyError: - pass - - if element.cell() is None: - raise ValueError("Don't know how to build element when cell is not given") - - fiat_element = convert(element, vector_is_mixed) - cache[vector_is_mixed] = fiat_element - return fiat_element From cbac10521f054ab3e362176ff65a9ebf1744f587 Mon Sep 17 00:00:00 2001 From: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Date: Wed, 3 Aug 2022 14:30:52 +0100 Subject: [PATCH 727/809] Revert "Maybe fix issue 274" --- tests/test_tsfc_274.py | 40 ---------------------------------------- tsfc/fem.py | 5 ++--- 2 files changed, 2 insertions(+), 43 deletions(-) delete mode 100644 tests/test_tsfc_274.py diff --git a/tests/test_tsfc_274.py b/tests/test_tsfc_274.py deleted file mode 100644 index 00f7a96a95..0000000000 --- a/tests/test_tsfc_274.py +++ /dev/null @@ -1,40 +0,0 @@ -import gem -import numpy -from finat.point_set import PointSet -from gem.interpreter import evaluate -from tsfc.finatinterface import create_element -from ufl import FiniteElement, RestrictedElement, quadrilateral - - -def test_issue_274(): - # See https://github.com/firedrakeproject/tsfc/issues/274 - ufl_element = RestrictedElement( - FiniteElement("Q", quadrilateral, 2), restriction_domain="facet" - ) - ps = PointSet([[0.5]]) - finat_element = create_element(ufl_element) - evaluations = [] - for eid in range(4): - (val,) = finat_element.basis_evaluation(0, ps, (1, eid)).values() - evaluations.append(val) - - i = gem.Index() - j = gem.Index() - (expr,) = evaluate( - [ - gem.ComponentTensor( - gem.Indexed(gem.select_expression(evaluations, i), (j,)), - (*ps.indices, i, j), - ) - ] - ) - - (expect,) = evaluate( - [ - gem.ComponentTensor( - gem.Indexed(gem.ListTensor(evaluations), (i, j)), (*ps.indices, i, j) - ) - ] - ) - - assert numpy.allclose(expr.arr, expect.arr) diff --git a/tsfc/fem.py b/tsfc/fem.py index 923ee153db..2c5c865c2d 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -27,7 +27,7 @@ import gem from gem.node import traversal -from gem.optimise import ffc_rounding, constant_fold_zero +from gem.optimise import ffc_rounding from gem.unconcatenate import unconcatenate from gem.utils import cached_property @@ -603,7 +603,6 @@ def fiat_to_ufl(fiat_dict, order): tensor = gem.Indexed(gem.ListTensor(tensor), delta) else: tensor = tensor[()] - tensor, = constant_fold_zero([tensor]) return gem.ComponentTensor(tensor, sigma + delta) @@ -719,4 +718,4 @@ def compile_ufl(expression, context, interior_facet=False, point_sum=False): result = map_expr_dags(context.translator, expressions) if point_sum: result = [gem.index_sum(expr, context.point_indices) for expr in result] - return constant_fold_zero(result) + return result From b95735eb489173ac63929134134babf890785d49 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 3 Aug 2022 17:46:32 +0100 Subject: [PATCH 728/809] fem: Constant fold literal zeros when compiling UFL --- tsfc/fem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 2c5c865c2d..923ee153db 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -27,7 +27,7 @@ import gem from gem.node import traversal -from gem.optimise import ffc_rounding +from gem.optimise import ffc_rounding, constant_fold_zero from gem.unconcatenate import unconcatenate from gem.utils import cached_property @@ -603,6 +603,7 @@ def fiat_to_ufl(fiat_dict, order): tensor = gem.Indexed(gem.ListTensor(tensor), delta) else: tensor = tensor[()] + tensor, = constant_fold_zero([tensor]) return gem.ComponentTensor(tensor, sigma + delta) @@ -718,4 +719,4 @@ def compile_ufl(expression, context, interior_facet=False, point_sum=False): result = map_expr_dags(context.translator, expressions) if point_sum: result = [gem.index_sum(expr, context.point_indices) for expr in result] - return result + return constant_fold_zero(result) From 9057cb42ec44ccdd5597d5f12add1acfb7efa0d8 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 3 Aug 2022 17:48:15 +0100 Subject: [PATCH 729/809] tests: Introduce test for #274 --- tests/test_tsfc_274.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/test_tsfc_274.py diff --git a/tests/test_tsfc_274.py b/tests/test_tsfc_274.py new file mode 100644 index 0000000000..00f7a96a95 --- /dev/null +++ b/tests/test_tsfc_274.py @@ -0,0 +1,40 @@ +import gem +import numpy +from finat.point_set import PointSet +from gem.interpreter import evaluate +from tsfc.finatinterface import create_element +from ufl import FiniteElement, RestrictedElement, quadrilateral + + +def test_issue_274(): + # See https://github.com/firedrakeproject/tsfc/issues/274 + ufl_element = RestrictedElement( + FiniteElement("Q", quadrilateral, 2), restriction_domain="facet" + ) + ps = PointSet([[0.5]]) + finat_element = create_element(ufl_element) + evaluations = [] + for eid in range(4): + (val,) = finat_element.basis_evaluation(0, ps, (1, eid)).values() + evaluations.append(val) + + i = gem.Index() + j = gem.Index() + (expr,) = evaluate( + [ + gem.ComponentTensor( + gem.Indexed(gem.select_expression(evaluations, i), (j,)), + (*ps.indices, i, j), + ) + ] + ) + + (expect,) = evaluate( + [ + gem.ComponentTensor( + gem.Indexed(gem.ListTensor(evaluations), (i, j)), (*ps.indices, i, j) + ) + ] + ) + + assert numpy.allclose(expr.arr, expect.arr) From 7205eab4e062b03ecaa3ce8ab11c9598348acb32 Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Wed, 3 Aug 2022 20:24:40 +0100 Subject: [PATCH 730/809] optimise: New pass for ListTensor in constant_fold_zero() --- tsfc/fem.py | 1 - tsfc/kernel_interface/common.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 923ee153db..0988220879 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -603,7 +603,6 @@ def fiat_to_ufl(fiat_dict, order): tensor = gem.Indexed(gem.ListTensor(tensor), delta) else: tensor = tensor[()] - tensor, = constant_fold_zero([tensor]) return gem.ComponentTensor(tensor, sigma + delta) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 4057bc16e1..dd71ee2f8c 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -20,7 +20,7 @@ from gem.node import traversal from gem.utils import cached_property import gem.impero_utils as impero_utils -from gem.optimise import remove_componenttensors as prune +from gem.optimise import remove_componenttensors as prune, constant_fold_zero from tsfc import fem, ufl_utils from tsfc.kernel_interface import KernelInterface @@ -213,6 +213,7 @@ def compile_gem(self, ctx): else: return_variables = [] expressions = [] + expressions = constant_fold_zero(expressions) # Need optimised roots options = dict(reduce(operator.and_, From ae5681c44a5128408fefcef631ba639b4784884a Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Sun, 13 Nov 2022 22:38:29 +0000 Subject: [PATCH 731/809] finatinterface: set hexahedral_tcp = interval x interval x interval --- tsfc/finatinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0ea092e160..0c8b211a65 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -268,7 +268,7 @@ def convert_restrictedelement(element, **kwargs): return finat.RestrictedElement(finat_elem, element.restriction_domain()), deps -hexahedron_tpc = ufl.TensorProductCell(ufl.quadrilateral, ufl.interval) +hexahedron_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval, ufl.interval) quadrilateral_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) _cache = weakref.WeakKeyDictionary() From e4bb7370c578b5768bb8b5dda92a129f51839fe9 Mon Sep 17 00:00:00 2001 From: ksagiyam Date: Wed, 16 Nov 2022 11:40:41 +0000 Subject: [PATCH 732/809] driver: disallow interior facet integration in hex meshes --- tsfc/driver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsfc/driver.py b/tsfc/driver.py index 74df2fb523..39ceaa9ce7 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -90,6 +90,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co :arg log: bool if the Kernel should be profiled with Log events :returns: a kernel constructed by the kernel interface """ + if integral_data.domain.ufl_cell().cellname() == "hexahedron" and \ + integral_data.integral_type == "interior_facet": + raise NotImplementedError("interior facet integration in hex meshes not currently supported") parameters = preprocess_parameters(parameters) if interface is None: if coffee: From 95208e2d31492dec5eeb5dca16cfab0138ba1e6e Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 21 Dec 2022 16:00:51 +0000 Subject: [PATCH 733/809] don't import GREEN from ufl.log --- tsfc/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 39ceaa9ce7..e01f082007 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -8,7 +8,6 @@ from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type from ufl.classes import Form, GeometricQuantity -from ufl.log import GREEN import gem import gem.impero_utils as impero_utils @@ -61,6 +60,8 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr assert isinstance(form, Form) + GREEN = "\033[1;37;32m%s\033[0m" + # Determine whether in complex mode: complex_mode = parameters and is_complex(parameters.get("scalar_type")) fd = ufl_utils.compute_form_data(form, complex_mode=complex_mode) From 1282243534d08737e3b4b3edd448c7eb90c6f2ce Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 13 Feb 2023 12:04:20 +0000 Subject: [PATCH 734/809] Interface for hierarchical and FDM variants --- tsfc/finatinterface.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0c8b211a65..ee2bb4d975 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -136,17 +136,24 @@ def convert_finiteelement(element, **kwargs): return finat.FlattenedDimensions(finat_elem), deps kind = element.variant() + is_interval = element.cell().cellname() == 'interval' if kind is None: - kind = 'spectral' if element.cell().cellname() == 'interval' else 'equispaced' # default variant + kind = 'spectral' if is_interval else 'equispaced' # default variant if element.family() == "Lagrange": if kind == 'equispaced': lmbda = finat.Lagrange - elif kind == 'spectral' and element.cell().cellname() == 'interval': + elif kind == 'spectral' and is_interval: lmbda = finat.GaussLobattoLegendre - elif kind == 'fdm' and element.cell().cellname() == 'interval': + elif kind == 'hierarchical' and is_interval: + lmbda = finat.IntegratedLegendre + elif kind in ['fdm', 'fdm_ipdg'] and is_interval: lmbda = finat.FDMLagrange - elif kind == 'fdmhermite' and element.cell().cellname() == 'interval': + elif kind == 'fdm_quadrature' and is_interval: + lmbda = finat.FDMQuadrature + elif kind == 'fdm_broken' and is_interval: + lmbda = finat.FDMBrokenH1 + elif kind == 'fdm_hermite' and is_interval: lmbda = finat.FDMHermite elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() @@ -162,10 +169,16 @@ def convert_finiteelement(element, **kwargs): elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: if kind == 'equispaced': lmbda = finat.DiscontinuousLagrange - elif kind == 'spectral' and element.cell().cellname() == 'interval': + elif kind == 'spectral' and is_interval: lmbda = finat.GaussLegendre - elif kind == 'fdm' and element.cell().cellname() == 'interval': + elif kind == 'hierarchical' and is_interval: + lmbda = finat.Legendre + elif kind in ['fdm', 'fdm_quadrature'] and is_interval: + lmbda = finat.FDMDiscontinuousLagrange + elif kind == 'fdm_ipdg' and is_interval: lmbda = lambda *args: finat.DiscontinuousElement(finat.FDMLagrange(*args)) + elif kind in 'fdm_broken' and is_interval: + lmbda = finat.FDMBrokenL2 elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] From 45503fc44c252fff8623cd1c77f17fc9b076ba88 Mon Sep 17 00:00:00 2001 From: APaganini Date: Mon, 13 Feb 2023 20:16:01 +0000 Subject: [PATCH 735/809] replace ufl_domain with extract_unique_domain --- tsfc/fem.py | 4 ++-- tsfc/modified_terminals.py | 3 ++- tsfc/ufl_utils.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 0988220879..42323203ec 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -20,7 +20,7 @@ ReferenceCellEdgeVectors, ReferenceFacetVolume, ReferenceNormal, SpatialCoordinate) - +from ufl.domain import extract_unique_domain from FIAT.reference_element import make_affine_mapping from FIAT.reference_element import UFCSimplex @@ -485,7 +485,7 @@ def translate_facet_coordinate(terminal, mt, ctx): @translate.register(SpatialCoordinate) def translate_spatialcoordinate(terminal, mt, ctx): # Replace terminal with a Coefficient - terminal = ctx.coordinate(terminal.ufl_domain()) + terminal = ctx.coordinate(extract_unique_domain(terminal)) # Get back to reference space terminal = preprocess_expression(terminal, complex_mode=ctx.complex_mode) # Rebuild modified terminal diff --git a/tsfc/modified_terminals.py b/tsfc/modified_terminals.py index ebb00704ef..8c5162bf97 100644 --- a/tsfc/modified_terminals.py +++ b/tsfc/modified_terminals.py @@ -25,6 +25,7 @@ Restricted, ConstantValue, Jacobian, SpatialCoordinate, Zero) from ufl.checks import is_cellwise_constant +from ufl.domain import extract_unique_domain class ModifiedTerminal(object): @@ -157,7 +158,7 @@ def construct_modified_terminal(mt, terminal): if mt.reference_value: expr = ReferenceValue(expr) - dim = expr.ufl_domain().topological_dimension() + dim = extract_unique_domain(expr).topological_dimension() for n in range(mt.local_derivatives): # Return zero if expression is trivially constant. This has to # happen here because ReferenceGrad has no access to the diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index aefa552410..6cd90b0ab5 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -24,6 +24,7 @@ ComponentTensor, Expr, FloatValue, Division, MixedElement, MultiIndex, Product, ScalarValue, Sqrt, Zero, CellVolume, FacetArea) +from ufl.domain import extract_unique_domain from gem.node import MemoizerArg @@ -397,7 +398,7 @@ def apply_mapping(expression, element, domain): variants) are applied to a matrix-valued function, the appropriate mappings are applied row-by-row. """ - mesh = expression.ufl_domain() + mesh = extract_unique_domain(expression) if mesh is None: mesh = domain if domain is not None and mesh != domain: From c5bd9ecd07a936fbcfed742c290c1cec93d01d32 Mon Sep 17 00:00:00 2001 From: APaganini Date: Tue, 14 Feb 2023 13:12:02 +0000 Subject: [PATCH 736/809] more replacements in fem.py --- tsfc/fem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 42323203ec..95778e43ff 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -153,7 +153,7 @@ def cell_size(self): def jacobian_at(self, point): ps = PointSingleton(point) expr = Jacobian(self.mt.terminal.ufl_domain()) - assert ps.expression.shape == (expr.ufl_domain().topological_dimension(), ) + assert ps.expression.shape == (extract_unique_domain(expr).topological_dimension(), ) if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': @@ -551,7 +551,7 @@ def translate_cellorigin(terminal, mt, ctx): @translate.register(CellVertices) def translate_cell_vertices(terminal, mt, ctx): - coords = SpatialCoordinate(terminal.ufl_domain()) + coords = SpatialCoordinate(extract_unique_domain(terminal)) ufl_expr = construct_modified_terminal(mt, coords) ps = PointSet(numpy.array(ctx.fiat_cell.get_vertices())) @@ -569,7 +569,7 @@ def translate_cell_vertices(terminal, mt, ctx): @translate.register(CellEdgeVectors) def translate_cell_edge_vectors(terminal, mt, ctx): # WARNING: Assumes straight edges! - coords = CellVertices(terminal.ufl_domain()) + coords = CellVertices(extract_unique_domain(terminal)) ufl_expr = construct_modified_terminal(mt, coords) cell_vertices = ctx.translator(ufl_expr) From ee18d006f1a7ca88d5bd3ea34fa659098df2c38d Mon Sep 17 00:00:00 2001 From: Angus Gibson Date: Fri, 10 Mar 2023 11:10:22 +1100 Subject: [PATCH 737/809] Use .subfunctions instead of .split() Using .split() raises a deprecation warning in Firedrake. --- tsfc/kernel_interface/firedrake_loopy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 856b2a11ea..ee00a75b3f 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -163,7 +163,7 @@ def set_coefficients(self, coefficients): for i, coefficient in enumerate(coefficients): if type(coefficient.ufl_element()) == ufl_MixedElement: - subcoeffs = coefficient.split() # Firedrake-specific + subcoeffs = coefficient.subfunctions # Firedrake-specific self.coefficient_split[coefficient] = subcoeffs for j, subcoeff in enumerate(subcoeffs): self._coefficient(subcoeff, f"w_{i}_{j}") From 650004b6bdaf46882405174873c840eee085521d Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 10 May 2023 15:31:11 +0100 Subject: [PATCH 738/809] Expunge COFFEE (#291) --- tests/test_firedrake_972.py | 4 +- tests/test_flexibly_indexed.py | 13 +- tests/test_idempotency.py | 8 +- tests/test_impero_loopy_flop_counts.py | 3 +- tests/test_tensor.py | 6 +- tests/test_underintegration.py | 4 +- tsfc/coffee.py | 413 ----------------------- tsfc/driver.py | 28 +- tsfc/kernel_args.py | 34 +- tsfc/kernel_interface/common.py | 15 - tsfc/kernel_interface/firedrake.py | 331 ------------------ tsfc/kernel_interface/firedrake_loopy.py | 2 + tsfc/kernel_interface/ufc.py | 332 ------------------ tsfc/loopy.py | 7 + 14 files changed, 46 insertions(+), 1154 deletions(-) delete mode 100644 tsfc/coffee.py delete mode 100644 tsfc/kernel_interface/firedrake.py delete mode 100644 tsfc/kernel_interface/ufc.py diff --git a/tests/test_firedrake_972.py b/tests/test_firedrake_972.py index b8cc992239..0c0cab0e2b 100644 --- a/tests/test_firedrake_972.py +++ b/tests/test_firedrake_972.py @@ -1,8 +1,6 @@ import numpy import pytest -from coffee.visitors import EstimateFlops - from ufl import (Mesh, FunctionSpace, VectorElement, TensorElement, Coefficient, TestFunction, interval, indices, dx) from ufl.classes import IndexSum, Product, MultiIndex @@ -29,7 +27,7 @@ def count_flops(n): MultiIndex((i,))), MultiIndex((j,))) * dx)) kernel, = compile_form(L, parameters=dict(mode='spectral')) - return EstimateFlops().visit(kernel.ast) + return kernel.flop_count def test_convergence(): diff --git a/tests/test_flexibly_indexed.py b/tests/test_flexibly_indexed.py index 0bc833b02a..551e4b042b 100644 --- a/tests/test_flexibly_indexed.py +++ b/tests/test_flexibly_indexed.py @@ -5,10 +5,10 @@ import gem import tsfc -import tsfc.coffee +import tsfc.loopy -parameters = tsfc.coffee.Bunch() +parameters = tsfc.loopy.LoopyContext() parameters.names = {} @@ -16,7 +16,14 @@ def convert(expression, multiindex): assert not expression.free_indices element = gem.Indexed(expression, multiindex) element, = gem.optimise.remove_componenttensors((element,)) - return tsfc.coffee.expression(element, parameters).rank + subscript = tsfc.loopy.expression(element, parameters) + # Convert a pymbolic subscript expression to a rank tuple. For example + # the subscript: + # + # Subscript(Variable('A'), (Sum((3,)), Sum((5,)))) + # + # will yield a rank of (3, 5). + return sum((idx.children for idx in subscript.index), start=()) @pytest.fixture(scope='module') diff --git a/tests/test_idempotency.py b/tests/test_idempotency.py index a5243e9145..9ecb0f3723 100644 --- a/tests/test_idempotency.py +++ b/tests/test_idempotency.py @@ -1,5 +1,6 @@ import ufl from tsfc import compile_form +import loopy import pytest @@ -61,13 +62,6 @@ def test_idempotency(form): k1 = compile_form(form)[0] k2 = compile_form(form)[0] - assert k1.ast.gencode() == k2.ast.gencode() - - # Test loopy backend - import loopy - k1 = compile_form(form, coffee=False)[0] - k2 = compile_form(form, coffee=False)[0] - assert loopy.generate_code_v2(k1.ast).device_code() == loopy.generate_code_v2(k2.ast).device_code() diff --git a/tests/test_impero_loopy_flop_counts.py b/tests/test_impero_loopy_flop_counts.py index 29071c7963..da357cf2a5 100644 --- a/tests/test_impero_loopy_flop_counts.py +++ b/tests/test_impero_loopy_flop_counts.py @@ -53,8 +53,7 @@ def test_flop_count(cell, parameters): v = TestFunction(V) a = inner(u, v)*dx + inner(grad(u), grad(v))*dx kernel, = compile_form(a, prefix="form", - parameters=parameters, - coffee=False) + parameters=parameters) # Record new flops here, and compare asymptotics and # approximate order of magnitude. new_flops.append(kernel.flop_count) diff --git a/tests/test_tensor.py b/tests/test_tensor.py index f13ea79bd8..39485abb2a 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -1,8 +1,6 @@ import numpy import pytest -from coffee.visitors import EstimateFlops - from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, Coefficient, TestFunction, TrialFunction, dx, div, inner, interval, triangle, tetrahedron, dot, grad) @@ -47,7 +45,7 @@ def eps(u): def count_flops(form): kernel, = compile_form(form, parameters=dict(mode='tensor')) - return EstimateFlops().visit(kernel.ast) + return kernel.flop_count @pytest.mark.parametrize('form', [mass, poisson, helmholtz, elasticity]) @@ -93,7 +91,7 @@ def form(cell, degree): return div(f)*dx dim = cell.topological_dimension() - degrees = numpy.arange(1, 7 - dim) + (3 - dim) + degrees = numpy.arange(2, 8 - dim) + (3 - dim) flops = [count_flops(form(cell, int(degree))) for degree in degrees] rates = numpy.diff(numpy.log(flops)) / numpy.diff(numpy.log(degrees + 1)) diff --git a/tests/test_underintegration.py b/tests/test_underintegration.py index e502ecb5d6..053722615f 100644 --- a/tests/test_underintegration.py +++ b/tests/test_underintegration.py @@ -3,8 +3,6 @@ import numpy import pytest -from coffee.visitors import EstimateFlops - from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, TestFunction, TrialFunction, TensorProductCell, dx, action, interval, quadrilateral, dot, grad) @@ -64,7 +62,7 @@ def laplace(cell, degree): def count_flops(form): kernel, = compile_form(form, parameters=dict(mode='spectral')) - return EstimateFlops().visit(kernel.ast) + return kernel.flop_count @pytest.mark.parametrize('form', [mass_cg, mass_dg]) diff --git a/tsfc/coffee.py b/tsfc/coffee.py deleted file mode 100644 index 294d2626e2..0000000000 --- a/tsfc/coffee.py +++ /dev/null @@ -1,413 +0,0 @@ -"""Generate COFFEE AST from ImperoC tuple data. - -This is the final stage of code generation in TSFC.""" - -from collections import defaultdict -from cmath import isnan -from functools import singledispatch, reduce -import itertools - -import numpy - -import coffee.base as coffee - -from gem import gem, impero as imp - -from tsfc.parameters import is_complex - - -class Bunch(object): - pass - - -def generate(impero_c, index_names, scalar_type): - """Generates COFFEE code. - - :arg impero_c: ImperoC tuple with Impero AST and other data - :arg index_names: pre-assigned index names - :arg scalar_type: type of scalars as C typename string - :returns: COFFEE function body - """ - params = Bunch() - params.declare = impero_c.declare - params.indices = impero_c.indices - finfo = numpy.finfo(scalar_type) - params.precision = finfo.precision - params.epsilon = finfo.resolution - params.scalar_type = scalar_type - - params.names = {} - for i, temp in enumerate(impero_c.temporaries): - params.names[temp] = "t%d" % i - - counter = itertools.count() - params.index_names = defaultdict(lambda: "i_%d" % next(counter)) - params.index_names.update(index_names) - - return statement(impero_c.tree, params) - - -def _coffee_symbol(symbol, rank=()): - """Build a coffee Symbol, concatenating rank. - - :arg symbol: Either a symbol name, or else an existing coffee Symbol. - :arg rank: The ``rank`` argument to the coffee Symbol constructor. - - If symbol is a symbol, then the returned symbol has rank - ``symbol.rank + rank``.""" - if isinstance(symbol, coffee.Symbol): - rank = symbol.rank + rank - symbol = symbol.symbol - else: - assert isinstance(symbol, str) - return coffee.Symbol(symbol, rank=rank) - - -def _decl_symbol(expr, parameters): - """Build a COFFEE Symbol for declaration.""" - multiindex = parameters.indices[expr] - rank = tuple(index.extent for index in multiindex) + expr.shape - return _coffee_symbol(parameters.names[expr], rank=rank) - - -def _ref_symbol(expr, parameters): - """Build a COFFEE Symbol for referencing a value.""" - multiindex = parameters.indices[expr] - rank = tuple(parameters.index_names[index] for index in multiindex) - return _coffee_symbol(parameters.names[expr], rank=tuple(rank)) - - -@singledispatch -def statement(tree, parameters): - """Translates an Impero (sub)tree into a COFFEE AST corresponding - to a C statement. - - :arg tree: Impero (sub)tree - :arg parameters: miscellaneous code generation data - :returns: COFFEE AST - """ - raise AssertionError("cannot generate COFFEE from %s" % type(tree)) - - -@statement.register(imp.Block) -def statement_block(tree, parameters): - statements = [statement(child, parameters) for child in tree.children] - declares = [] - for expr in parameters.declare[tree]: - declares.append(coffee.Decl(parameters.scalar_type, _decl_symbol(expr, parameters))) - return coffee.Block(declares + statements, open_scope=True) - - -@statement.register(imp.For) -def statement_for(tree, parameters): - extent = tree.index.extent - assert extent - i = _coffee_symbol(parameters.index_names[tree.index]) - # TODO: symbolic constant for "int" - return coffee.For(coffee.Decl("int", i, init=0), - coffee.Less(i, extent), - coffee.Incr(i, 1), - statement(tree.children[0], parameters)) - - -@statement.register(imp.Initialise) -def statement_initialise(leaf, parameters): - if parameters.declare[leaf]: - return coffee.Decl(parameters.scalar_type, _decl_symbol(leaf.indexsum, parameters), 0.0) - else: - return coffee.Assign(_ref_symbol(leaf.indexsum, parameters), 0.0) - - -@statement.register(imp.Accumulate) -def statement_accumulate(leaf, parameters): - return coffee.Incr(_ref_symbol(leaf.indexsum, parameters), - expression(leaf.indexsum.children[0], parameters)) - - -@statement.register(imp.Return) -def statement_return(leaf, parameters): - return coffee.Incr(expression(leaf.variable, parameters), - expression(leaf.expression, parameters)) - - -@statement.register(imp.ReturnAccumulate) -def statement_returnaccumulate(leaf, parameters): - return coffee.Incr(expression(leaf.variable, parameters), - expression(leaf.indexsum.children[0], parameters)) - - -@statement.register(imp.Evaluate) -def statement_evaluate(leaf, parameters): - expr = leaf.expression - if isinstance(expr, gem.ListTensor): - if parameters.declare[leaf]: - array_expression = numpy.vectorize(lambda v: expression(v, parameters)) - return coffee.Decl(parameters.scalar_type, - _decl_symbol(expr, parameters), - coffee.ArrayInit(array_expression(expr.array), - precision=parameters.precision)) - else: - ops = [] - for multiindex, value in numpy.ndenumerate(expr.array): - coffee_sym = _coffee_symbol(_ref_symbol(expr, parameters), rank=multiindex) - ops.append(coffee.Assign(coffee_sym, expression(value, parameters))) - return coffee.Block(ops, open_scope=False) - elif isinstance(expr, gem.Constant): - assert parameters.declare[leaf] - return coffee.Decl(parameters.scalar_type, - _decl_symbol(expr, parameters), - coffee.ArrayInit(expr.array, parameters.precision), - qualifiers=["static", "const"]) - else: - code = expression(expr, parameters, top=True) - if parameters.declare[leaf]: - return coffee.Decl(parameters.scalar_type, _decl_symbol(expr, parameters), code) - else: - return coffee.Assign(_ref_symbol(expr, parameters), code) - - -def expression(expr, parameters, top=False): - """Translates GEM expression into a COFFEE snippet, stopping at - temporaries. - - :arg expr: GEM expression - :arg parameters: miscellaneous code generation data - :arg top: do not generate temporary reference for the root node - :returns: COFFEE expression - """ - if not top and expr in parameters.names: - return _ref_symbol(expr, parameters) - else: - return _expression(expr, parameters) - - -@singledispatch -def _expression(expr, parameters): - raise AssertionError("cannot generate COFFEE from %s" % type(expr)) - - -@_expression.register(gem.Failure) -def _expression_failure(expr, parameters): - raise expr.exception - - -@_expression.register(gem.Product) -def _expression_product(expr, parameters): - return coffee.Prod(*[expression(c, parameters) - for c in expr.children]) - - -@_expression.register(gem.Sum) -def _expression_sum(expr, parameters): - return coffee.Sum(*[expression(c, parameters) - for c in expr.children]) - - -@_expression.register(gem.Division) -def _expression_division(expr, parameters): - return coffee.Div(*[expression(c, parameters) - for c in expr.children]) - - -# Table of handled math functions in real and complex modes -# Copied from FFCX (ffc/language/ufl_to_cnodes.py) -math_table = { - 'sqrt': ('sqrt', 'csqrt'), - 'abs': ('fabs', 'cabs'), - 'cos': ('cos', 'ccos'), - 'sin': ('sin', 'csin'), - 'tan': ('tan', 'ctan'), - 'acos': ('acos', 'cacos'), - 'asin': ('asin', 'casin'), - 'atan': ('atan', 'catan'), - 'cosh': ('cosh', 'ccosh'), - 'sinh': ('sinh', 'csinh'), - 'tanh': ('tanh', 'ctanh'), - 'acosh': ('acosh', 'cacosh'), - 'asinh': ('asinh', 'casinh'), - 'atanh': ('atanh', 'catanh'), - 'power': ('pow', 'cpow'), - 'exp': ('exp', 'cexp'), - 'ln': ('log', 'clog'), - 'real': (None, 'creal'), - 'imag': (None, 'cimag'), - 'conj': (None, 'conj'), - 'erf': ('erf', None), - 'atan_2': ('atan2', None), - 'atan2': ('atan2', None), - 'min_value': ('fmin', None), - 'max_value': ('fmax', None) -} - - -@_expression.register(gem.Power) -def _expression_power(expr, parameters): - base, exponent = expr.children - complex_mode = int(is_complex(parameters.scalar_type)) - return coffee.FunCall(math_table['power'][complex_mode], - expression(base, parameters), - expression(exponent, parameters)) - - -@_expression.register(gem.MathFunction) -def _expression_mathfunction(expr, parameters): - complex_mode = int(is_complex(parameters.scalar_type)) - - # Bessel functions - if expr.name.startswith('cyl_bessel_'): - if complex_mode: - msg = "Bessel functions for complex numbers: missing implementation" - raise NotImplementedError(msg) - nu, arg = expr.children - nu_thunk = lambda: expression(nu, parameters) - arg_coffee = expression(arg, parameters) - if expr.name == 'cyl_bessel_j': - if nu == gem.Zero(): - return coffee.FunCall('j0', arg_coffee) - elif nu == gem.one: - return coffee.FunCall('j1', arg_coffee) - else: - return coffee.FunCall('jn', nu_thunk(), arg_coffee) - if expr.name == 'cyl_bessel_y': - if nu == gem.Zero(): - return coffee.FunCall('y0', arg_coffee) - elif nu == gem.one: - return coffee.FunCall('y1', arg_coffee) - else: - return coffee.FunCall('yn', nu_thunk(), arg_coffee) - - # Modified Bessel functions (C++ only) - # - # These mappings work for FEniCS only, and fail with Firedrake - # since no Boost available. - if expr.name in ['cyl_bessel_i', 'cyl_bessel_k']: - name = 'boost::math::' + expr.name - return coffee.FunCall(name, nu_thunk(), arg_coffee) - - assert False, "Unknown Bessel function: {}".format(expr.name) - - # Other math functions - name = math_table[expr.name][complex_mode] - if name is None: - raise RuntimeError("{} not supported in complex mode".format(expr.name)) - return coffee.FunCall(name, *[expression(c, parameters) for c in expr.children]) - - -@_expression.register(gem.MinValue) -def _expression_minvalue(expr, parameters): - return coffee.FunCall('fmin', *[expression(c, parameters) for c in expr.children]) - - -@_expression.register(gem.MaxValue) -def _expression_maxvalue(expr, parameters): - return coffee.FunCall('fmax', *[expression(c, parameters) for c in expr.children]) - - -@_expression.register(gem.Comparison) -def _expression_comparison(expr, parameters): - type_map = {">": coffee.Greater, - ">=": coffee.GreaterEq, - "==": coffee.Eq, - "!=": coffee.NEq, - "<": coffee.Less, - "<=": coffee.LessEq} - return type_map[expr.operator](*[expression(c, parameters) for c in expr.children]) - - -@_expression.register(gem.LogicalNot) -def _expression_logicalnot(expr, parameters): - return coffee.Not(*[expression(c, parameters) for c in expr.children]) - - -@_expression.register(gem.LogicalAnd) -def _expression_logicaland(expr, parameters): - return coffee.And(*[expression(c, parameters) for c in expr.children]) - - -@_expression.register(gem.LogicalOr) -def _expression_logicalor(expr, parameters): - return coffee.Or(*[expression(c, parameters) for c in expr.children]) - - -@_expression.register(gem.Conditional) -def _expression_conditional(expr, parameters): - return coffee.Ternary(*[expression(c, parameters) for c in expr.children]) - - -@_expression.register(gem.Constant) -def _expression_scalar(expr, parameters): - assert not expr.shape - if isnan(expr.value): - return coffee.Symbol("NAN") - else: - vr = expr.value.real - rr = round(vr, 1) - if rr and abs(vr - rr) < parameters.epsilon: - vr = rr # round to nonzero - - vi = expr.value.imag # also checks if v is purely real - if vi == 0.0: - return coffee.Symbol(("%%.%dg" % parameters.precision) % vr) - ri = round(vi, 1) - - if ri and abs(vi - ri) < parameters.epsilon: - vi = ri - return coffee.Symbol("({real:.{prec}g} + {imag:.{prec}g} * I)".format( - real=vr, imag=vi, prec=parameters.precision)) - - -@_expression.register(gem.Variable) -def _expression_variable(expr, parameters): - return _coffee_symbol(expr.name) - - -@_expression.register(gem.Indexed) -def _expression_indexed(expr, parameters): - rank = [] - for index in expr.multiindex: - if isinstance(index, gem.Index): - rank.append(parameters.index_names[index]) - elif isinstance(index, gem.VariableIndex): - rank.append(expression(index.expression, parameters).gencode()) - else: - rank.append(index) - return _coffee_symbol(expression(expr.children[0], parameters), - rank=tuple(rank)) - - -@_expression.register(gem.FlexiblyIndexed) -def _expression_flexiblyindexed(expr, parameters): - var = expression(expr.children[0], parameters) - assert isinstance(var, coffee.Symbol) - assert not var.rank - assert not var.offset - - rank = [] - offset = [] - for off, idxs in expr.dim2idxs: - for index, stride in idxs: - assert isinstance(index, gem.Index) - - if len(idxs) == 0: - rank.append(off) - offset.append((1, 0)) - elif len(idxs) == 1: - (index, stride), = idxs - rank.append(parameters.index_names[index]) - offset.append((stride, off)) - else: - parts = [] - if off: - parts += [coffee.Symbol(str(off))] - for index, stride in idxs: - index_sym = coffee.Symbol(parameters.index_names[index]) - assert stride - if stride == 1: - parts += [index_sym] - else: - parts += [coffee.Prod(index_sym, coffee.Symbol(str(stride)))] - assert parts - rank.append(reduce(coffee.Sum, parts)) - offset.append((1, 0)) - - return coffee.Symbol(var.symbol, rank=tuple(rank), offset=tuple(offset)) diff --git a/tsfc/driver.py b/tsfc/driver.py index e01f082007..d518b9e433 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -18,6 +18,7 @@ from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex from tsfc.ufl_utils import apply_mapping +import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy # To handle big forms. The various transformations might need a deeper stack sys.setrecursionlimit(3000) @@ -45,19 +46,30 @@ """ -def compile_form(form, prefix="form", parameters=None, interface=None, coffee=True, diagonal=False, log=False): +def compile_form(form, prefix="form", parameters=None, interface=None, diagonal=False, log=False, **kwargs): """Compiles a UFL form into a set of assembly kernels. :arg form: UFL form :arg prefix: kernel name will start with this string :arg parameters: parameters object - :arg coffee: compile coffee kernel instead of loopy kernel :arg diagonal: Are we building a kernel for the diagonal of a rank-2 element tensor? :arg log: bool if the Kernel should be profiled with Log events :returns: list of kernels """ cpu_time = time.time() + if "coffee" in kwargs: + use_coffee = kwargs.pop("coffee") + if use_coffee: + raise ValueError("COFFEE support has been removed from TSFC") + else: + import warnings + warnings.warn( + "The coffee kwarg has been removed from compile_form as COFFEE " + "is no longer a supported backend.", FutureWarning) + if kwargs: + raise ValueError("compile_form called with unexpected arguments") + assert isinstance(form, Form) GREEN = "\033[1;37;32m%s\033[0m" @@ -70,7 +82,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr kernels = [] for integral_data in fd.integral_data: start = time.time() - kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface, coffee=coffee, diagonal=diagonal, log=log) + kernel = compile_integral(integral_data, fd, prefix, parameters, interface=interface, diagonal=diagonal, log=log) if kernel is not None: kernels.append(kernel) logger.info(GREEN % "compile_integral finished in %g seconds.", time.time() - start) @@ -79,7 +91,7 @@ def compile_form(form, prefix="form", parameters=None, interface=None, coffee=Tr return kernels -def compile_integral(integral_data, form_data, prefix, parameters, interface, coffee, *, diagonal=False, log=False): +def compile_integral(integral_data, form_data, prefix, parameters, interface, *, diagonal=False, log=False): """Compiles a UFL integral into an assembly kernel. :arg integral_data: UFL integral data @@ -96,13 +108,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, co raise NotImplementedError("interior facet integration in hex meshes not currently supported") parameters = preprocess_parameters(parameters) if interface is None: - if coffee: - import tsfc.kernel_interface.firedrake as firedrake_interface_coffee - interface = firedrake_interface_coffee.KernelBuilder - else: - # Delayed import, loopy is a runtime dependency - import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy - interface = firedrake_interface_loopy.KernelBuilder + interface = firedrake_interface_loopy.KernelBuilder scalar_type = parameters["scalar_type"] integral_type = integral_data.integral_type if integral_type.startswith("interior_facet") and diagonal: diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 17868249d6..2a3cfbb0f3 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -1,11 +1,8 @@ import abc -import coffee.base as coffee -import loopy as lp - class KernelArg(abc.ABC): - """Abstract base class wrapping either a COFFEE or loopy argument. + """Abstract base class wrapping a loopy argument. Defining this type system allows Firedrake (or other libraries) to prepare arguments for the kernel without needing to worry about their @@ -13,35 +10,12 @@ class KernelArg(abc.ABC): :func:`functools.singledispatch`. """ - def __init__(self, ast_arg): - self._ast_arg = ast_arg + def __init__(self, arg): + self.loopy_arg = arg @property def dtype(self): - if self._is_coffee_backend: - return self._ast_arg.typ - elif self._is_loopy_backend: - return self._ast_arg.dtype - - @property - def coffee_arg(self): - if not self._is_coffee_backend: - raise RuntimeError("Invalid type requested") - return self._ast_arg - - @property - def loopy_arg(self): - if not self._is_loopy_backend: - raise RuntimeError("Invalid type requested") - return self._ast_arg - - @property - def _is_coffee_backend(self): - return isinstance(self._ast_arg, coffee.Decl) - - @property - def _is_loopy_backend(self): - return isinstance(self._ast_arg, lp.ArrayArg) + return self.loopy_arg.dtype class OutputKernelArg(KernelArg): diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index dd71ee2f8c..4ac64dd0ae 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -9,8 +9,6 @@ from ufl.utils.sequences import max_degree -import coffee.base as coffee - from FIAT.reference_element import TensorProductCell from finat.quadrature import AbstractQuadratureRule, make_quadrature @@ -105,19 +103,6 @@ def apply_glue(self, prepare=None, finalise=None): if finalise is not None: self.finalise.extend(finalise) - def construct_kernel(self, name, args, body): - """Construct a COFFEE function declaration with the - accumulated glue code. - - :arg name: function name - :arg args: function argument list - :arg body: function body (:class:`coffee.Block` node) - :returns: :class:`coffee.FunDecl` object - """ - assert isinstance(body, coffee.Block) - body_ = coffee.Block(self.prepare + body.children + self.finalise) - return coffee.FunDecl("void", name, args, body_, pred=["static", "inline"]) - def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be provided by the kernel interface. diff --git a/tsfc/kernel_interface/firedrake.py b/tsfc/kernel_interface/firedrake.py deleted file mode 100644 index 067d247268..0000000000 --- a/tsfc/kernel_interface/firedrake.py +++ /dev/null @@ -1,331 +0,0 @@ -from collections import OrderedDict -from functools import partial - -from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement - -import coffee.base as coffee - -import gem -from gem.flop_count import count_flops - -from tsfc import kernel_args -from tsfc.coffee import generate as generate_coffee -from tsfc.finatinterface import create_element -from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names, check_requirements, prepare_coefficient, prepare_arguments - - -def make_builder(*args, **kwargs): - return partial(KernelBuilder, *args, **kwargs) - - -class Kernel(object): - __slots__ = ("ast", "arguments", "integral_type", "oriented", "subdomain_id", - "domain_number", "needs_cell_sizes", "tabulations", - "coefficient_numbers", "name", "__weakref__", - "flop_count", "event") - """A compiled Kernel object. - - :kwarg ast: The COFFEE ast for the kernel. - :kwarg integral_type: The type of integral. - :kwarg oriented: Does the kernel require cell_orientations. - :kwarg subdomain_id: What is the subdomain id for this kernel. - :kwarg domain_number: Which domain number in the original form - does this kernel correspond to (can be used to index into - original_form.ufl_domains() to get the correct domain). - :kwarg coefficient_numbers: A list of which coefficients from the - form the kernel needs. - :kwarg tabulations: The runtime tabulations this kernel requires - :kwarg needs_cell_sizes: Does the kernel require cell sizes. - :kwarg name: The name of this kernel. - :kwarg flop_count: Estimated total flops for this kernel. - :kwarg event: name for logging event - """ - def __init__(self, ast=None, arguments=None, integral_type=None, oriented=False, - subdomain_id=None, domain_number=None, - coefficient_numbers=(), - needs_cell_sizes=False, - tabulations=None, - flop_count=0, - name=None, - event=None): - # Defaults - self.ast = ast - self.arguments = arguments - self.integral_type = integral_type - self.oriented = oriented - self.domain_number = domain_number - self.subdomain_id = subdomain_id - self.coefficient_numbers = coefficient_numbers - self.needs_cell_sizes = needs_cell_sizes - self.tabulations = tabulations - self.flop_count = flop_count - self.name = name - self.event = None - super(Kernel, self).__init__() - - -class KernelBuilderBase(_KernelBuilderBase): - - def __init__(self, scalar_type, interior_facet=False): - """Initialise a kernel builder. - - :arg interior_facet: kernel accesses two cells - """ - super().__init__(scalar_type=scalar_type, interior_facet=interior_facet) - - # Cell orientation - if self.interior_facet: - cell_orientations = gem.Variable("cell_orientations", (2,)) - self._cell_orientations = (gem.Indexed(cell_orientations, (0,)), - gem.Indexed(cell_orientations, (1,))) - else: - cell_orientations = gem.Variable("cell_orientations", (1,)) - self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) - - def _coefficient(self, coefficient, name): - """Prepare a coefficient. Adds glue code for the coefficient - and adds the coefficient to the coefficient map. - - :arg coefficient: :class:`ufl.Coefficient` - :arg name: coefficient name - """ - expr = prepare_coefficient(coefficient, name, interior_facet=self.interior_facet) - self.coefficient_map[coefficient] = expr - - def set_cell_sizes(self, domain): - """Setup a fake coefficient for "cell sizes". - - :arg domain: The domain of the integral. - - This is required for scaling of derivative basis functions on - physically mapped elements (Argyris, Bell, etc...). We need a - measure of the mesh size around each vertex (hence this lives - in P1). - - Should the domain have topological dimension 0 this does - nothing. - """ - if domain.ufl_cell().topological_dimension() > 0: - # Can't create P1 since only P0 is a valid finite element if - # topological_dimension is 0 and the concept of "cell size" - # is not useful for a vertex. - f = Coefficient(FunctionSpace(domain, FiniteElement("P", domain.ufl_cell(), 1))) - expr = prepare_coefficient(f, "cell_sizes", interior_facet=self.interior_facet) - self._cell_sizes = expr - - def create_element(self, element, **kwargs): - """Create a FInAT element (suitable for tabulating with) given - a UFL element.""" - return create_element(element, **kwargs) - - def generate_arg_from_variable(self, var, is_output=False): - """Generate kernel arg from a :class:`gem.Variable`. - - :arg var: a :class:`gem.Variable` - :arg is_output: if expr represents the output or not - :returns: kernel arg - """ - if is_output: - return coffee.Decl(self.scalar_type, coffee.Symbol(var.name, rank=var.shape)) - else: - return coffee.Decl(self.scalar_type, coffee.Symbol(var.name), pointers=[("restrict",)], qualifiers=["const"]) - - def generate_arg_from_expression(self, expr, is_output=False): - """Generate kernel arg from gem expression(s). - - :arg expr: gem expression(s) representing a coefficient or the output tensor - :arg is_output: if expr represents the output or not - :returns: kernel arg - """ - var, = gem.extract_type(expr if isinstance(expr, tuple) else (expr, ), gem.Variable) - return self.generate_arg_from_variable(var, is_output=is_output) - - -class KernelBuilder(KernelBuilderBase, KernelBuilderMixin): - """Helper class for building a :class:`Kernel` object.""" - - def __init__(self, integral_data_info, scalar_type, - dont_split=(), diagonal=False): - """Initialise a kernel builder.""" - integral_type = integral_data_info.integral_type - super(KernelBuilder, self).__init__(coffee.as_cstr(scalar_type), integral_type.startswith("interior_facet")) - self.fem_scalar_type = scalar_type - - self.diagonal = diagonal - self.local_tensor = None - self.coefficient_split = {} - self.coefficient_number_index_map = OrderedDict() - self.dont_split = frozenset(dont_split) - - # Facet number - if integral_type in ['exterior_facet', 'exterior_facet_vert']: - facet = gem.Variable('facet', (1,)) - self._entity_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} - elif integral_type in ['interior_facet', 'interior_facet_vert']: - facet = gem.Variable('facet', (2,)) - self._entity_number = { - '+': gem.VariableIndex(gem.Indexed(facet, (0,))), - '-': gem.VariableIndex(gem.Indexed(facet, (1,))) - } - elif integral_type == 'interior_facet_horiz': - self._entity_number = {'+': 1, '-': 0} - - self.set_arguments(integral_data_info.arguments) - self.integral_data_info = integral_data_info - - def set_arguments(self, arguments): - """Process arguments. - - :arg arguments: :class:`ufl.Argument`s - :returns: GEM expression representing the return variable - """ - argument_multiindices = tuple(create_element(arg.ufl_element()).get_indices() - for arg in arguments) - if self.diagonal: - # Error checking occurs in the builder constructor. - # Diagonal assembly is obtained by using the test indices for - # the trial space as well. - a, _ = argument_multiindices - argument_multiindices = (a, a) - return_variables = prepare_arguments(arguments, - argument_multiindices, - interior_facet=self.interior_facet, - diagonal=self.diagonal) - self.return_variables = return_variables - self.argument_multiindices = argument_multiindices - - def set_coordinates(self, domain): - """Prepare the coordinate field. - - :arg domain: :class:`ufl.Domain` - """ - # Create a fake coordinate coefficient for a domain. - f = Coefficient(FunctionSpace(domain, domain.ufl_coordinate_element())) - self.domain_coordinate[domain] = f - self._coefficient(f, "coords") - - def set_coefficients(self, integral_data, form_data): - """Prepare the coefficients of the form. - - :arg integral_data: UFL integral data - :arg form_data: UFL form data - """ - # enabled_coefficients is a boolean array that indicates which - # of reduced_coefficients the integral requires. - n, k = 0, 0 - for i in range(len(integral_data.enabled_coefficients)): - if integral_data.enabled_coefficients[i]: - original = form_data.reduced_coefficients[i] - coefficient = form_data.function_replace_map[original] - if type(coefficient.ufl_element()) == ufl_MixedElement: - if original in self.dont_split: - self.coefficient_split[coefficient] = [coefficient] - self._coefficient(coefficient, f"w_{k}") - self.coefficient_number_index_map[coefficient] = (n, 0) - k += 1 - else: - self.coefficient_split[coefficient] = [] - for j, element in enumerate(coefficient.ufl_element().sub_elements()): - c = Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) - self.coefficient_split[coefficient].append(c) - self._coefficient(c, f"w_{k}") - self.coefficient_number_index_map[c] = (n, j) - k += 1 - else: - self._coefficient(coefficient, f"w_{k}") - self.coefficient_number_index_map[coefficient] = (n, 0) - k += 1 - n += 1 - - def register_requirements(self, ir): - """Inspect what is referenced by the IR that needs to be - provided by the kernel interface.""" - return check_requirements(ir) - - def construct_kernel(self, name, ctx, log=False): - """Construct a fully built :class:`Kernel`. - - This function contains the logic for building the argument - list for assembly kernels. - - :arg name: kernel name - :arg ctx: kernel builder context to get impero_c from - :returns: :class:`Kernel` object - """ - impero_c, oriented, needs_cell_sizes, tabulations, active_variables = self.compile_gem(ctx) - if impero_c is None: - return self.construct_empty_kernel(name) - info = self.integral_data_info - # In the following funargs are only generated - # for gem expressions that are actually used; - # see `generate_arg_from_expression()` method. - # Specifically, funargs are not generated for - # unused components of mixed coefficients. - # Problem solving environment, such as Firedrake, - # will know which components have been included - # in the list of kernel arguments by investigating - # `Kernel.coefficient_numbers`. - # Add return arg - funarg = self.generate_arg_from_expression(self.return_variables, is_output=True) - args = [kernel_args.OutputKernelArg(funarg)] - # Add coordinates arg - coord = self.domain_coordinate[info.domain] - expr = self.coefficient_map[coord] - funarg = self.generate_arg_from_expression(expr) - args.append(kernel_args.CoordinatesKernelArg(funarg)) - if oriented: - ori_coffee_arg = coffee.Decl("int", coffee.Symbol("cell_orientations"), - pointers=[("restrict",)], - qualifiers=["const"]) - args.append(kernel_args.CellOrientationsKernelArg(ori_coffee_arg)) - if needs_cell_sizes: - funarg = self.generate_arg_from_expression(self._cell_sizes) - args.append(kernel_args.CellSizesKernelArg(funarg)) - coefficient_indices = {} - for coeff, (number, index) in self.coefficient_number_index_map.items(): - a = coefficient_indices.setdefault(number, []) - expr = self.coefficient_map[coeff] - var, = gem.extract_type(expr if isinstance(expr, tuple) else (expr, ), gem.Variable) - if var in active_variables: - funarg = self.generate_arg_from_expression(expr) - args.append(kernel_args.CoefficientKernelArg(funarg)) - a.append(index) - coefficient_indices = tuple(tuple(v) for v in coefficient_indices.values()) - if info.integral_type in ["exterior_facet", "exterior_facet_vert"]: - ext_coffee_arg = coffee.Decl("unsigned int", - coffee.Symbol("facet", rank=(1,)), - qualifiers=["const"]) - args.append(kernel_args.ExteriorFacetKernelArg(ext_coffee_arg)) - elif info.integral_type in ["interior_facet", "interior_facet_vert"]: - int_coffee_arg = coffee.Decl("unsigned int", - coffee.Symbol("facet", rank=(2,)), - qualifiers=["const"]) - args.append(kernel_args.InteriorFacetKernelArg(int_coffee_arg)) - for name_, shape in tabulations: - tab_coffee_arg = coffee.Decl(self.scalar_type, - coffee.Symbol(name_, rank=shape), - qualifiers=["const"]) - args.append(kernel_args.TabulationKernelArg(tab_coffee_arg)) - index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) - body = generate_coffee(impero_c, index_names, self.scalar_type) - ast = KernelBuilderBase.construct_kernel(self, name, [a.coffee_arg for a in args], body) - flop_count = count_flops(impero_c) # Estimated total flops for this kernel. - return Kernel(ast=ast, - arguments=tuple(args), - integral_type=info.integral_type, - subdomain_id=info.subdomain_id, - domain_number=info.domain_number, - coefficient_numbers=tuple(zip(info.coefficient_numbers, coefficient_indices)), - oriented=oriented, - needs_cell_sizes=needs_cell_sizes, - tabulations=tabulations, - flop_count=flop_count, - name=name) - - def construct_empty_kernel(self, name): - """Return None, since Firedrake needs no empty kernels. - - :arg name: function name - :returns: None - """ - return None diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index ee00a75b3f..d46a1acd0d 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -96,9 +96,11 @@ def _coefficient(self, coefficient, name): :arg coefficient: :class:`ufl.Coefficient` :arg name: coefficient name + :returns: GEM expression representing the coefficient """ expr = prepare_coefficient(coefficient, name, interior_facet=self.interior_facet) self.coefficient_map[coefficient] = expr + return expr def set_cell_sizes(self, domain): """Setup a fake coefficient for "cell sizes". diff --git a/tsfc/kernel_interface/ufc.py b/tsfc/kernel_interface/ufc.py deleted file mode 100644 index 2217a16d3c..0000000000 --- a/tsfc/kernel_interface/ufc.py +++ /dev/null @@ -1,332 +0,0 @@ -import numpy -import functools -from itertools import chain, product - -import coffee.base as coffee - -import gem -from gem.optimise import remove_componenttensors as prune - -from finat import TensorFiniteElement - -import ufl - -from tsfc.kernel_interface.common import KernelBuilderBase, KernelBuilderMixin, get_index_names -from tsfc.finatinterface import create_element as _create_element - - -# UFC DoF ordering for vector/tensor elements is XXXX YYYY ZZZZ. -create_element = functools.partial(_create_element, shape_innermost=False) - - -class KernelBuilder(KernelBuilderBase, KernelBuilderMixin): - """Helper class for building a :class:`Kernel` object.""" - - def __init__(self, integral_data_info, scalar_type, diagonal=False): - """Initialise a kernel builder.""" - integral_type = integral_data_info.integral_type - if diagonal: - raise NotImplementedError("Assembly of diagonal not implemented yet, sorry") - super(KernelBuilder, self).__init__(scalar_type, integral_type.startswith("interior_facet")) - self.fem_scalar_type = scalar_type - self.integral_type = integral_type - - self.local_tensor = None - self.coordinates_args = None - self.coefficient_args = None - self.coefficient_split = None - - if self.interior_facet: - self._cell_orientations = (gem.Variable("cell_orientation_0", ()), - gem.Variable("cell_orientation_1", ())) - else: - self._cell_orientations = (gem.Variable("cell_orientation", ()),) - - if integral_type == "exterior_facet": - self._entity_number = {None: gem.VariableIndex(gem.Variable("facet", ()))} - elif integral_type == "interior_facet": - self._entity_number = { - '+': gem.VariableIndex(gem.Variable("facet_0", ())), - '-': gem.VariableIndex(gem.Variable("facet_1", ())) - } - elif integral_type == "vertex": - self._entity_number = {None: gem.VariableIndex(gem.Variable("vertex", ()))} - - self.set_arguments(integral_data_info.arguments) - self.integral_data_info = integral_data_info - - def set_arguments(self, arguments): - """Process arguments. - - :arg arguments: :class:`ufl.Argument`s - :arg multiindices: GEM argument multiindices - :returns: GEM expression representing the return variable - """ - argument_multiindices = tuple(create_element(arg.ufl_element()).get_indices() - for arg in arguments) - if self.diagonal: - # Error checking occurs in the builder constructor. - # Diagonal assembly is obtained by using the test indices for - # the trial space as well. - a, _ = argument_multiindices - argument_multiindices = (a, a) - local_tensor, prepare, return_variables = prepare_arguments(arguments, - argument_multiindices, - self.scalar_type, - interior_facet=self.interior_facet) - self.apply_glue(prepare) - self.local_tensor = local_tensor - self.return_variables = return_variables - self.argument_multiindices = argument_multiindices - - def set_coordinates(self, domain): - """Prepare the coordinate field. - - :arg domain: :class:`ufl.Domain` - """ - # Create a fake coordinate coefficient for a domain. - f = ufl.Coefficient(ufl.FunctionSpace(domain, domain.ufl_coordinate_element())) - self.domain_coordinate[domain] = f - self.coordinates_args, expression = prepare_coordinates( - f, "coordinate_dofs", self.scalar_type, interior_facet=self.interior_facet) - self.coefficient_map[f] = expression - - def set_cell_sizes(self, domain): - """Prepare cell sizes field. - - :arg domain: :class:`ufl.Domain` - """ - pass - - def set_coefficients(self, integral_data, form_data): - """Prepare the coefficients of the form. - - :arg integral_data: UFL integral data - :arg form_data: UFL form data - """ - name = "w" - self.coefficient_args = [ - coffee.Decl(self.scalar_type, coffee.Symbol(name), - pointers=[("const",), ()], - qualifiers=["const"]) - ] - - # enabled_coefficients is a boolean array that indicates which - # of reduced_coefficients the integral requires. - for n in range(len(integral_data.enabled_coefficients)): - if not integral_data.enabled_coefficients[n]: - continue - - coefficient = form_data.function_replace_map[form_data.reduced_coefficients[n]] - expression = prepare_coefficient(coefficient, n, name, self.interior_facet) - self.coefficient_map[coefficient] = expression - - def register_requirements(self, ir): - """Inspect what is referenced by the IR that needs to be - provided by the kernel interface.""" - return None, None, None - - def construct_kernel(self, name, ctx): - """Construct a fully built kernel function. - - This function contains the logic for building the argument - list for assembly kernels. - - :arg name: kernel name - :arg ctx: kernel builder context to get impero_c from - :arg quadrature rule: quadrature rule (not used, stubbed out for Themis integration) - :returns: a COFFEE function definition object - """ - from tsfc.coffee import generate as generate_coffee - - impero_c, _, _, _, _ = self.compile_gem(ctx) - if impero_c is None: - return self.construct_empty_kernel(name) - index_names = get_index_names(ctx['quadrature_indices'], self.argument_multiindices, ctx['index_cache']) - body = generate_coffee(impero_c, index_names, scalar_type=self.scalar_type) - return self._construct_kernel_from_body(name, body) - - def _construct_kernel_from_body(self, name, body): - """Construct a fully built kernel function. - - This function contains the logic for building the argument - list for assembly kernels. - - :arg name: function name - :arg body: function body (:class:`coffee.Block` node) - :arg quadrature rule: quadrature rule (ignored) - :returns: a COFFEE function definition object - """ - args = [self.local_tensor] - args.extend(self.coefficient_args) - args.extend(self.coordinates_args) - - # Facet and vertex number(s) - if self.integral_type == "exterior_facet": - args.append(coffee.Decl("std::size_t", coffee.Symbol("facet"))) - elif self.integral_type == "interior_facet": - args.append(coffee.Decl("std::size_t", coffee.Symbol("facet_0"))) - args.append(coffee.Decl("std::size_t", coffee.Symbol("facet_1"))) - elif self.integral_type == "vertex": - args.append(coffee.Decl("std::size_t", coffee.Symbol("vertex"))) - - # Cell orientation(s) - if self.interior_facet: - args.append(coffee.Decl("int", coffee.Symbol("cell_orientation_0"))) - args.append(coffee.Decl("int", coffee.Symbol("cell_orientation_1"))) - else: - args.append(coffee.Decl("int", coffee.Symbol("cell_orientation"))) - - return KernelBuilderBase.construct_kernel(self, name, args, body) - - def construct_empty_kernel(self, name): - """Construct an empty kernel function. - - Kernel will just zero the return buffer and do nothing else. - - :arg name: function name - :returns: a COFFEE function definition object - """ - body = coffee.Block([]) # empty block - return self._construct_kernel_from_body(name, body) - - def create_element(self, element, **kwargs): - """Create a FInAT element (suitable for tabulating with) given - a UFL element.""" - return create_element(element, **kwargs) - - -def prepare_coefficient(coefficient, num, name, interior_facet=False): - """Bridges the kernel interface and the GEM abstraction for - Coefficients. - - :arg coefficient: UFL Coefficient - :arg num: coefficient index in the original form - :arg name: unique name to refer to the Coefficient in the kernel - :arg interior_facet: interior facet integral? - :returns: GEM expression referring to the Coefficient value - """ - varexp = gem.Variable(name, (None, None)) - - if coefficient.ufl_element().family() == 'Real': - size = numpy.prod(coefficient.ufl_shape, dtype=int) - data = gem.view(varexp, slice(num, num + 1), slice(size)) - return gem.reshape(data, (), coefficient.ufl_shape) - - element = create_element(coefficient.ufl_element()) - size = numpy.prod(element.index_shape, dtype=int) - - def expression(data): - result, = prune([gem.reshape(gem.view(data, slice(size)), element.index_shape)]) - return result - - if not interior_facet: - data = gem.view(varexp, slice(num, num + 1), slice(size)) - return expression(gem.reshape(data, (), (size,))) - else: - data_p = gem.view(varexp, slice(num, num + 1), slice(size)) - data_m = gem.view(varexp, slice(num, num + 1), slice(size, 2 * size)) - return (expression(gem.reshape(data_p, (), (size,))), - expression(gem.reshape(data_m, (), (size,)))) - - -def prepare_coordinates(coefficient, name, scalar_type, interior_facet=False): - """Bridges the kernel interface and the GEM abstraction for - coordinates. - - :arg coefficient: UFL Coefficient - :arg name: unique name to refer to the Coefficient in the kernel - :arg interior_facet: interior facet integral? - :returns: (funarg, expression) - funarg - :class:`coffee.Decl` function argument - expression - GEM expression referring to the Coefficient - values - """ - finat_element = create_element(coefficient.ufl_element()) - shape = finat_element.index_shape - size = numpy.prod(shape, dtype=int) - - assert isinstance(finat_element, TensorFiniteElement) - scalar_shape = finat_element.base_element.index_shape - tensor_shape = finat_element._shape - transposed_shape = scalar_shape + tensor_shape - scalar_rank = len(scalar_shape) - - def transpose(expr): - indices = tuple(gem.Index(extent=extent) for extent in expr.shape) - transposed_indices = indices[scalar_rank:] + indices[:scalar_rank] - return gem.ComponentTensor(gem.Indexed(expr, indices), - transposed_indices) - - if not interior_facet: - funargs = [coffee.Decl(scalar_type, coffee.Symbol(name), - pointers=[("",)], - qualifiers=["const"])] - variable = gem.Variable(name, (size,)) - expression = transpose(gem.reshape(variable, transposed_shape)) - else: - funargs = [coffee.Decl(scalar_type, coffee.Symbol(name+"_0"), - pointers=[("",)], - qualifiers=["const"]), - coffee.Decl(scalar_type, coffee.Symbol(name+"_1"), - pointers=[("",)], - qualifiers=["const"])] - variable0 = gem.Variable(name+"_0", (size,)) - variable1 = gem.Variable(name+"_1", (size,)) - expression = (transpose(gem.reshape(variable0, transposed_shape)), - transpose(gem.reshape(variable1, transposed_shape))) - - return funargs, expression - - -def prepare_arguments(arguments, multiindices, scalar_type, interior_facet=False): - """Bridges the kernel interface and the GEM abstraction for - Arguments. Vector Arguments are rearranged here for interior - facet integrals. - - :arg arguments: UFL Arguments - :arg multiindices: Argument multiindices - :arg interior_facet: interior facet integral? - :returns: (funarg, prepare, expressions) - funarg - :class:`coffee.Decl` function argument - prepare - list of COFFEE nodes to be prepended to the - kernel body - expressions - GEM expressions referring to the argument - tensor - """ - funarg = coffee.Decl(scalar_type, coffee.Symbol("A"), pointers=[()]) - varexp = gem.Variable("A", (None,)) - - if len(arguments) == 0: - # No arguments - zero = coffee.FlatBlock( - "memset({name}, 0, sizeof(*{name}));\n".format(name=funarg.sym.gencode()) - ) - return funarg, [zero], [gem.reshape(varexp, ())] - - elements = tuple(create_element(arg.ufl_element()) for arg in arguments) - shapes = [element.index_shape for element in elements] - indices = tuple(chain(*multiindices)) - - def expression(restricted): - return gem.Indexed(gem.reshape(restricted, *shapes), indices) - - u_shape = numpy.array([numpy.prod(element.index_shape, dtype=int) - for element in elements]) - if interior_facet: - c_shape = tuple(2 * u_shape) - slicez = [[slice(r * s, (r + 1) * s) - for r, s in zip(restrictions, u_shape)] - for restrictions in product((0, 1), repeat=len(arguments))] - else: - c_shape = tuple(u_shape) - slicez = [[slice(s) for s in u_shape]] - - expressions = [expression(gem.view(gem.reshape(varexp, c_shape), *slices)) - for slices in slicez] - - zero = coffee.FlatBlock( - str.format("memset({name}, 0, {size} * sizeof(*{name}));\n", - name=funarg.sym.gencode(), size=numpy.product(c_shape, dtype=int)) - ) - return funarg, [zero], prune(expressions) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index ae8f93e5ba..c11b36af9b 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -232,6 +232,13 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name # Create instructions instructions = statement(impero_c.tree, ctx) + # add a no-op touching all kernel arguments to make sure they + # are not silently dropped + noop = lp.CInstruction( + (), "", read_variables=frozenset({a.name for a in args}), + within_inames=frozenset(), within_inames_is_final=True) + instructions.append(noop) + # Profile the instructions instructions, event_name, preamble = profile_insns(kernel_name, instructions, log) From 3da48a0eb7e9885aec7ab2df0344114ca11109ff Mon Sep 17 00:00:00 2001 From: Jack Betteridge <43041811+JDBetteridge@users.noreply.github.com> Date: Wed, 14 Jun 2023 16:49:08 +0100 Subject: [PATCH 739/809] Constant refactoring (#294) --------- Co-authored-by: Connor Ward --- tsfc/driver.py | 8 ++++++- tsfc/fem.py | 30 +++++++++++++++--------- tsfc/kernel_args.py | 4 ++++ tsfc/kernel_interface/common.py | 16 +++++++++++++ tsfc/kernel_interface/firedrake_loopy.py | 24 ++++++++++++++++++- tsfc/ufl_utils.py | 17 +++++++++++++- 6 files changed, 85 insertions(+), 14 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index d518b9e433..4f7cc4e0a0 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -17,7 +17,7 @@ from tsfc import fem, ufl_utils from tsfc.logging import logger from tsfc.parameters import default_parameters, is_complex -from tsfc.ufl_utils import apply_mapping +from tsfc.ufl_utils import apply_mapping, extract_firedrake_constants import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy # To handle big forms. The various transformations might need a deeper stack @@ -142,6 +142,9 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, *, builder.set_coordinates(mesh) builder.set_cell_sizes(mesh) builder.set_coefficients(integral_data, form_data) + # TODO: We do not want pass constants to kernels that do not need them + # so we should attach the constants to integral data instead + builder.set_constants(form_data.constants) ctx = builder.create_context() for integral in integral_data.integrals: params = parameters.copy() @@ -237,6 +240,9 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, needs_external_coords = True builder.set_coefficients(coefficients) + constants = extract_firedrake_constants(expression) + builder.set_constants(constants) + # Split mixed coefficients expression = ufl_utils.split_coefficients(expression, builder.coefficient_split) diff --git a/tsfc/fem.py b/tsfc/fem.py index 95778e43ff..febcf4c52b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -10,16 +10,14 @@ import ufl from ufl.corealg.map_dag import map_expr_dag, map_expr_dags from ufl.corealg.multifunction import MultiFunction -from ufl.classes import (Argument, CellCoordinate, CellEdgeVectors, - CellFacetJacobian, CellOrientation, - CellOrigin, CellVertices, CellVolume, - Coefficient, FacetArea, FacetCoordinate, - GeometricQuantity, Jacobian, JacobianDeterminant, - NegativeRestricted, QuadratureWeight, - PositiveRestricted, ReferenceCellVolume, - ReferenceCellEdgeVectors, - ReferenceFacetVolume, ReferenceNormal, - SpatialCoordinate) +from ufl.classes import ( + Argument, CellCoordinate, CellEdgeVectors, CellFacetJacobian, + CellOrientation, CellOrigin, CellVertices, CellVolume, Coefficient, + FacetArea, FacetCoordinate, GeometricQuantity, Jacobian, + JacobianDeterminant, NegativeRestricted, QuadratureWeight, + PositiveRestricted, ReferenceCellVolume, ReferenceCellEdgeVectors, + ReferenceFacetVolume, ReferenceNormal, SpatialCoordinate +) from ufl.domain import extract_unique_domain from FIAT.reference_element import make_affine_mapping @@ -43,7 +41,7 @@ from tsfc.parameters import is_complex from tsfc.ufl_utils import (ModifiedTerminalMixin, PickRestriction, entity_avg, one_times, simplify_abs, - preprocess_expression) + preprocess_expression, TSFCConstantMixin) class ContextBase(ProxyKernelInterface): @@ -629,6 +627,16 @@ def callback(entity_id): return gem.ComponentTensor(gem.Indexed(table, argument_multiindex + sigma), sigma) +@translate.register(TSFCConstantMixin) +def translate_constant_value(terminal, mt, ctx): + value_size = numpy.prod(terminal.ufl_shape, dtype=int) + expression = gem.reshape( + gem.Variable(terminal.name, (value_size,)), + terminal.ufl_shape + ) + return expression + + @translate.register(Coefficient) def translate_coefficient(terminal, mt, ctx): vec = ctx.coefficient(terminal, mt.restriction) diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 2a3cfbb0f3..1c79bdaef2 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -30,6 +30,10 @@ class CoefficientKernelArg(KernelArg): ... +class ConstantKernelArg(KernelArg): + ... + + class CellOrientationsKernelArg(KernelArg): ... diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 4ac64dd0ae..1ff3de1d4b 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -47,6 +47,9 @@ def __init__(self, scalar_type, interior_facet=False): # Coefficients self.coefficient_map = collections.OrderedDict() + # Constants + self.constant_map = collections.OrderedDict() + @cached_property def unsummed_coefficient_indices(self): return frozenset() @@ -432,6 +435,19 @@ def check_requirements(ir): return cell_orientations, cell_sizes, tuple(sorted(rt_tabs.items())) +def prepare_constant(constant): + """Bridges the kernel interface and the GEM abstraction for + Constants. + + :arg constant: Firedrake Constant + :returns: (funarg, expression) + expression - GEM expression referring to the Constant value(s) + """ + value_size = numpy.prod(constant.ufl_shape, dtype=int) + return gem.reshape(gem.Variable(constant.name, (value_size,)), + constant.ufl_shape) + + def prepare_coefficient(coefficient, name, interior_facet=False): """Bridges the kernel interface and the GEM abstraction for Coefficients. diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index d46a1acd0d..0059de9a54 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -11,7 +11,7 @@ from tsfc import kernel_args from tsfc.finatinterface import create_element -from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names, check_requirements, prepare_coefficient, prepare_arguments +from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names, check_requirements, prepare_coefficient, prepare_arguments, prepare_constant from tsfc.loopy import generate as generate_loopy @@ -172,6 +172,11 @@ def set_coefficients(self, coefficients): else: self._coefficient(coefficient, f"w_{i}") + def set_constants(self, constants): + for const in constants: + gemexpr = prepare_constant(const) + self.constant_map[const] = gemexpr + def set_coefficient_numbers(self, coefficient_numbers): """Store the coefficient indices of the original form. @@ -212,6 +217,12 @@ def construct_kernel(self, impero_c, index_names, needs_external_coords, log=Fal # coefficient_map is OrderedDict. funarg = self.generate_arg_from_expression(expr) args.append(kernel_args.CoefficientKernelArg(funarg)) + + # now constants + for gemexpr in self.constant_map.values(): + funarg = self.generate_arg_from_expression(gemexpr) + args.append(kernel_args.ConstantKernelArg(funarg)) + for name_, shape in self.tabulations: tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) args.append(kernel_args.TabulationKernelArg(tab_loopy_arg)) @@ -322,6 +333,11 @@ def set_coefficients(self, integral_data, form_data): k += 1 n += 1 + def set_constants(self, constants): + for const in constants: + gemexpr = prepare_constant(const) + self.constant_map[const] = gemexpr + def register_requirements(self, ir): """Inspect what is referenced by the IR that needs to be provided by the kernel interface.""" @@ -374,6 +390,12 @@ def construct_kernel(self, name, ctx, log=False): funarg = self.generate_arg_from_expression(expr) args.append(kernel_args.CoefficientKernelArg(funarg)) a.append(index) + + # now constants + for gemexpr in self.constant_map.values(): + funarg = self.generate_arg_from_expression(gemexpr) + args.append(kernel_args.ConstantKernelArg(funarg)) + coefficient_indices = tuple(tuple(v) for v in coefficient_indices.values()) assert len(coefficient_indices) == len(info.coefficient_numbers) if info.integral_type in ["exterior_facet", "exterior_facet_vert"]: diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 6cd90b0ab5..21f94d3745 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -8,7 +8,7 @@ from ufl import as_tensor, indices, replace from ufl.algorithms import compute_form_data as ufl_compute_form_data from ufl.algorithms import estimate_total_polynomial_degree -from ufl.algorithms.analysis import extract_arguments +from ufl.algorithms.analysis import extract_arguments, extract_type from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives @@ -25,6 +25,7 @@ MixedElement, MultiIndex, Product, ScalarValue, Sqrt, Zero, CellVolume, FacetArea) from ufl.domain import extract_unique_domain +from ufl.utils.sorting import sorted_by_count from gem.node import MemoizerArg @@ -61,9 +62,16 @@ def compute_form_data(form, do_estimate_degrees=do_estimate_degrees, complex_mode=complex_mode ) + constants = extract_firedrake_constants(form) + fd.constants = constants return fd +def extract_firedrake_constants(a): + """Build a sorted list of all constants in a""" + return sorted_by_count(extract_type(a, TSFCConstantMixin)) + + def one_times(measure): # Workaround for UFL issue #80: # https://bitbucket.org/fenics-project/ufl/issues/80 @@ -467,3 +475,10 @@ def apply_mapping(expression, element, domain): if rexpression.ufl_shape != element.reference_value_shape(): raise ValueError(f"Mismatching reference shapes, got {rexpression.ufl_shape} expected {element.reference_value_shape()}") return rexpression + + +class TSFCConstantMixin: + """ Mixin class to identify Constants """ + + def __init__(self): + pass From f2c26af1d4f88e272d6b4ee126c2f85546ace76e Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 26 Jun 2023 09:36:05 +0100 Subject: [PATCH 740/809] Fix logical not --- tsfc/loopy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index c11b36af9b..be668d5797 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -478,7 +478,8 @@ def _expression_comparison(expr, ctx): @_expression.register(gem.LogicalNot) def _expression_logicalnot(expr, ctx): - return p.LogicalNot(tuple([expression(c, ctx) for c in expr.children])) + child, = expr.children + return p.LogicalNot(expression(child, ctx)) @_expression.register(gem.LogicalAnd) From 96b42c04ba028a8ae55279ece42b3f11794927ed Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 12 Sep 2023 13:46:27 +0100 Subject: [PATCH 741/809] Adapt to UFL changes in subdomain_id (#297) * Adapt to UFL changes in subdomain_id * Give kernels a simpler name --- tsfc/driver.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 4f7cc4e0a0..b38427e92f 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -115,9 +115,7 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, *, raise NotImplementedError("Sorry, we can't assemble the diagonal of a form for interior facet integrals") mesh = integral_data.domain arguments = form_data.preprocessed_form.arguments() - kernel_name = "%s_%s_integral_%s" % (prefix, integral_type, integral_data.subdomain_id) - # Handle negative subdomain_id - kernel_name = kernel_name.replace("-", "_") + kernel_name = f"{prefix}_{integral_type}_integral" # Dict mapping domains to index in original_form.ufl_domains() domain_numbering = form_data.original_form.domain_numbering() domain_number = domain_numbering[integral_data.domain] From 2da821c083eedec15684c17ad845482ac675f8f6 Mon Sep 17 00:00:00 2001 From: cyruscycheng21 <46732264+cyruscycheng21@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:52:38 +0100 Subject: [PATCH 742/809] Register BDMc elements (#197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * removed ufl cell transformation * fixed syntax * register BDMCE element * register bdmc elements * name correction * register bdmc * register bdmc * update bdmc to ensure we are only adding BDMC spaces * further minimise changes from this PR --------- Co-authored-by: David Ham Co-authored-by: Miklós Homolya Co-authored-by: Rob Kirby Co-authored-by: Patrick Farrell Co-authored-by: Tom Bendall Co-authored-by: Tom Bendall Co-authored-by: Jack Betteridge --- tsfc/finatinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index ee2bb4d975..fb4e9cefdd 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -59,6 +59,8 @@ "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, "Raviart-Thomas": finat.RaviartThomas, "Regge": finat.Regge, + "BDMCE": finat.BrezziDouglasMariniCubeEdge, + "BDMCF": finat.BrezziDouglasMariniCubeFace, # These require special treatment below "DQ": None, "Q": None, From f37037acf526a7013e20ab3230c17bee8f521b4e Mon Sep 17 00:00:00 2001 From: David Ham Date: Fri, 22 Sep 2023 11:13:43 +0100 Subject: [PATCH 743/809] UFL have changed spelling of atan2 --- tsfc/ufl2gem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/ufl2gem.py b/tsfc/ufl2gem.py index fc1c760dc8..e283fb1414 100644 --- a/tsfc/ufl2gem.py +++ b/tsfc/ufl2gem.py @@ -78,7 +78,7 @@ def power(self, o, base, exponent): def math_function(self, o, expr): return MathFunction(o._name, expr) - def atan_2(self, o, y, x): + def atan2(self, o, y, x): return MathFunction("atan2", y, x) def bessel_i(self, o, nu, arg): From 06e4ec267f012844ac6ddc00eacac3007f3901e2 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 26 Sep 2023 14:43:36 +0100 Subject: [PATCH 744/809] Do not use constant names in generated code (#298) * Do not use constant names in generated code --- tsfc/fem.py | 7 +------ tsfc/kernel_interface/__init__.py | 4 ++++ tsfc/kernel_interface/common.py | 8 ++++++-- tsfc/kernel_interface/firedrake_loopy.py | 8 ++++---- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index febcf4c52b..7ef40b8b2a 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -629,12 +629,7 @@ def callback(entity_id): @translate.register(TSFCConstantMixin) def translate_constant_value(terminal, mt, ctx): - value_size = numpy.prod(terminal.ufl_shape, dtype=int) - expression = gem.reshape( - gem.Variable(terminal.name, (value_size,)), - terminal.ufl_shape - ) - return expression + return ctx.constant(terminal) @translate.register(Coefficient) diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index bf81360906..fc1942607e 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -17,6 +17,10 @@ def coefficient(self, ufl_coefficient, restriction): """A function that maps :class:`ufl.Coefficient`s to GEM expressions.""" + @abstractmethod + def constant(self, const): + """Return the GEM expression corresponding to the constant.""" + @abstractmethod def cell_orientation(self, restriction): """Cell orientation as a GEM expression.""" diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 1ff3de1d4b..db7cf53c6a 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -68,6 +68,9 @@ def coefficient(self, ufl_coefficient, restriction): else: return kernel_arg[{'+': 0, '-': 1}[restriction]] + def constant(self, const): + return self.constant_map[const] + def cell_orientation(self, restriction): """Cell orientation as a GEM expression.""" f = {None: 0, '+': 0, '-': 1}[restriction] @@ -435,16 +438,17 @@ def check_requirements(ir): return cell_orientations, cell_sizes, tuple(sorted(rt_tabs.items())) -def prepare_constant(constant): +def prepare_constant(constant, number): """Bridges the kernel interface and the GEM abstraction for Constants. :arg constant: Firedrake Constant + :arg number: Value to uniquely identify the constant :returns: (funarg, expression) expression - GEM expression referring to the Constant value(s) """ value_size = numpy.prod(constant.ufl_shape, dtype=int) - return gem.reshape(gem.Variable(constant.name, (value_size,)), + return gem.reshape(gem.Variable(f"c_{number}", (value_size,)), constant.ufl_shape) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index 0059de9a54..f538acfb41 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -173,8 +173,8 @@ def set_coefficients(self, coefficients): self._coefficient(coefficient, f"w_{i}") def set_constants(self, constants): - for const in constants: - gemexpr = prepare_constant(const) + for i, const in enumerate(constants): + gemexpr = prepare_constant(const, i) self.constant_map[const] = gemexpr def set_coefficient_numbers(self, coefficient_numbers): @@ -334,8 +334,8 @@ def set_coefficients(self, integral_data, form_data): n += 1 def set_constants(self, constants): - for const in constants: - gemexpr = prepare_constant(const) + for i, const in enumerate(constants): + gemexpr = prepare_constant(const, i) self.constant_map[const] = gemexpr def register_requirements(self, ir): From 92f8d1e869b7e12619071ec68863259bc5c50306 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 14 Oct 2023 21:15:01 +0100 Subject: [PATCH 745/809] Default spectral variant on simplices --- tsfc/finatinterface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index fb4e9cefdd..3683edcf05 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -140,12 +140,12 @@ def convert_finiteelement(element, **kwargs): kind = element.variant() is_interval = element.cell().cellname() == 'interval' if kind is None: - kind = 'spectral' if is_interval else 'equispaced' # default variant + kind = 'spectral' # default variant if element.family() == "Lagrange": if kind == 'equispaced': lmbda = finat.Lagrange - elif kind == 'spectral' and is_interval: + elif kind == 'spectral': lmbda = finat.GaussLobattoLegendre elif kind == 'hierarchical' and is_interval: lmbda = finat.IntegratedLegendre @@ -171,7 +171,7 @@ def convert_finiteelement(element, **kwargs): elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: if kind == 'equispaced': lmbda = finat.DiscontinuousLagrange - elif kind == 'spectral' and is_interval: + elif kind == 'spectral': lmbda = finat.GaussLegendre elif kind == 'hierarchical' and is_interval: lmbda = finat.Legendre From dc674da3997b0eb5959a7e1e1dc31ed4943d2fb2 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 10 Nov 2023 14:00:04 +0000 Subject: [PATCH 746/809] fix most tests --- tests/test_create_fiat_element.py | 10 ++++------ tests/test_create_finat_element.py | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 0924edb39c..02aaf3ad71 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -100,16 +100,14 @@ def test_interval_variant(family, variant, expected_cls): assert isinstance(create_element(ufl_element), expected_cls) -def test_triangle_variant_spectral_fail(): +def test_triangle_variant_spectral(): ufl_element = ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') - with pytest.raises(ValueError): - create_element(ufl_element) + create_element(ufl_element) -def test_triangle_variant_spectral_fail_l2(): +def test_triangle_variant_spectral_l2(): ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') - with pytest.raises(ValueError): - create_element(ufl_element) + create_element(ufl_element) def test_quadrilateral_variant_spectral_q(): diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index 2f937a8677..d55c69d11e 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -89,16 +89,14 @@ def test_interval_variant(family, variant, expected_cls): assert isinstance(create_element(ufl_element), expected_cls) -def test_triangle_variant_spectral_fail(): +def test_triangle_variant_spectral(): ufl_element = ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') - with pytest.raises(ValueError): - create_element(ufl_element) + create_element(ufl_element) -def test_triangle_variant_spectral_fail_l2(): +def test_triangle_variant_spectral_l2(): ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') - with pytest.raises(ValueError): - create_element(ufl_element) + create_element(ufl_element) def test_quadrilateral_variant_spectral_q(): From c8b080526927715c668d1f37c2d32e4b5aace7ff Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 15 Nov 2023 13:34:12 +0000 Subject: [PATCH 747/809] Update UFL element interface: move elements to FInAT (#302) * use ufl.legacy * cell() -> cell * sub_elements() -> sub_elements * value_shape() -> value_shape * Pass in space, not element * remove unnecessary legacy import * FunctionSpace, not element * Use branch of FEniCS UFL * change README to make tests run * Revert "change README to make tests run" This reverts commit b5a0ff98a87fdba6880ac9ea8337a9f2289c6af3. * move ufl.legacy to finat.ufl * ufl main * flake * Use branch on firedrake fork of UFL * Apply suggestions from code review --------- Co-authored-by: Jack Betteridge <43041811+JDBetteridge@users.noreply.github.com> --- tests/test_create_fiat_element.py | 25 ++++----- tests/test_create_finat_element.py | 25 ++++----- tests/test_dual_evaluation.py | 25 ++++----- tests/test_estimated_degree.py | 5 +- tests/test_firedrake_972.py | 3 +- tests/test_gem_failure.py | 15 ++++-- tests/test_idempotency.py | 9 ++-- tests/test_impero_loopy_flop_counts.py | 5 +- tests/test_interpolation_factorisation.py | 10 ++-- tests/test_sum_factorisation.py | 8 +-- tests/test_tensor.py | 3 +- tests/test_tsfc_182.py | 10 ++-- tests/test_tsfc_204.py | 5 +- tests/test_tsfc_274.py | 3 +- tests/test_underintegration.py | 4 +- tsfc/finatinterface.py | 63 ++++++++++++----------- tsfc/kernel_interface/common.py | 2 +- tsfc/kernel_interface/firedrake_loopy.py | 5 +- tsfc/ufl_utils.py | 25 ++++----- 19 files changed, 135 insertions(+), 115 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 0924edb39c..b54d978281 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -4,6 +4,7 @@ from FIAT.discontinuous_lagrange import HigherOrderDiscontinuousLagrange as FIAT_DiscontinuousLagrange import ufl +import finat.ufl from tsfc.finatinterface import create_element as _create_element @@ -40,7 +41,7 @@ def triangle_names(request): @pytest.fixture def ufl_element(triangle_names): - return ufl.FiniteElement(triangle_names, ufl.triangle, 2) + return finat.ufl.FiniteElement(triangle_names, ufl.triangle, 2) def test_triangle_basic(ufl_element): @@ -58,16 +59,16 @@ def tensor_name(request): ids=lambda x: x.cellname(), scope="module") def ufl_A(request, tensor_name): - return ufl.FiniteElement(tensor_name, request.param, 1) + return finat.ufl.FiniteElement(tensor_name, request.param, 1) @pytest.fixture def ufl_B(tensor_name): - return ufl.FiniteElement(tensor_name, ufl.interval, 1) + return finat.ufl.FiniteElement(tensor_name, ufl.interval, 1) def test_tensor_prod_simple(ufl_A, ufl_B): - tensor_ufl = ufl.TensorProductElement(ufl_A, ufl_B) + tensor_ufl = finat.ufl.TensorProductElement(ufl_A, ufl_B) tensor = create_element(tensor_ufl) A = create_element(ufl_A) @@ -84,7 +85,7 @@ def test_tensor_prod_simple(ufl_A, ufl_B): ('DP', FIAT.GaussLegendre), ('DP L2', FIAT.GaussLegendre)]) def test_interval_variant_default(family, expected_cls): - ufl_element = ufl.FiniteElement(family, ufl.interval, 3) + ufl_element = finat.ufl.FiniteElement(family, ufl.interval, 3) assert isinstance(create_element(ufl_element), expected_cls) @@ -96,42 +97,42 @@ def test_interval_variant_default(family, expected_cls): ('DP L2', 'equispaced', FIAT_DiscontinuousLagrange), ('DP L2', 'spectral', FIAT.GaussLegendre)]) def test_interval_variant(family, variant, expected_cls): - ufl_element = ufl.FiniteElement(family, ufl.interval, 3, variant=variant) + ufl_element = finat.ufl.FiniteElement(family, ufl.interval, 3, variant=variant) assert isinstance(create_element(ufl_element), expected_cls) def test_triangle_variant_spectral_fail(): - ufl_element = ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') + ufl_element = finat.ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') with pytest.raises(ValueError): create_element(ufl_element) def test_triangle_variant_spectral_fail_l2(): - ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') + ufl_element = finat.ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') with pytest.raises(ValueError): create_element(ufl_element) def test_quadrilateral_variant_spectral_q(): - element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) + element = create_element(finat.ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) assert isinstance(element.element.A, FIAT.GaussLobattoLegendre) assert isinstance(element.element.B, FIAT.GaussLobattoLegendre) def test_quadrilateral_variant_spectral_dq(): - element = create_element(ufl.FiniteElement('DQ', ufl.quadrilateral, 1, variant='spectral')) + element = create_element(finat.ufl.FiniteElement('DQ', ufl.quadrilateral, 1, variant='spectral')) assert isinstance(element.element.A, FIAT.GaussLegendre) assert isinstance(element.element.B, FIAT.GaussLegendre) def test_quadrilateral_variant_spectral_dq_l2(): - element = create_element(ufl.FiniteElement('DQ L2', ufl.quadrilateral, 1, variant='spectral')) + element = create_element(finat.ufl.FiniteElement('DQ L2', ufl.quadrilateral, 1, variant='spectral')) assert isinstance(element.element.A, FIAT.GaussLegendre) assert isinstance(element.element.B, FIAT.GaussLegendre) def test_quadrilateral_variant_spectral_rtcf(): - element = create_element(ufl.FiniteElement('RTCF', ufl.quadrilateral, 2, variant='spectral')) + element = create_element(finat.ufl.FiniteElement('RTCF', ufl.quadrilateral, 2, variant='spectral')) assert isinstance(element.element._elements[0].A, FIAT.GaussLobattoLegendre) assert isinstance(element.element._elements[0].B, FIAT.GaussLegendre) assert isinstance(element.element._elements[1].A, FIAT.GaussLegendre) diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index 2f937a8677..9300106532 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -1,6 +1,7 @@ import pytest import ufl +import finat.ufl import finat from tsfc.finatinterface import create_element, supported_elements @@ -18,7 +19,7 @@ def triangle_names(request): @pytest.fixture def ufl_element(triangle_names): - return ufl.FiniteElement(triangle_names, ufl.triangle, 2) + return finat.ufl.FiniteElement(triangle_names, ufl.triangle, 2) def test_triangle_basic(ufl_element): @@ -28,7 +29,7 @@ def test_triangle_basic(ufl_element): @pytest.fixture def ufl_vector_element(triangle_names): - return ufl.VectorElement(triangle_names, ufl.triangle, 2) + return finat.ufl.VectorElement(triangle_names, ufl.triangle, 2) def test_triangle_vector(ufl_element, ufl_vector_element): @@ -48,16 +49,16 @@ def tensor_name(request): ufl.quadrilateral], ids=lambda x: x.cellname()) def ufl_A(request, tensor_name): - return ufl.FiniteElement(tensor_name, request.param, 1) + return finat.ufl.FiniteElement(tensor_name, request.param, 1) @pytest.fixture def ufl_B(tensor_name): - return ufl.FiniteElement(tensor_name, ufl.interval, 1) + return finat.ufl.FiniteElement(tensor_name, ufl.interval, 1) def test_tensor_prod_simple(ufl_A, ufl_B): - tensor_ufl = ufl.TensorProductElement(ufl_A, ufl_B) + tensor_ufl = finat.ufl.TensorProductElement(ufl_A, ufl_B) tensor = create_element(tensor_ufl) A = create_element(ufl_A) @@ -73,7 +74,7 @@ def test_tensor_prod_simple(ufl_A, ufl_B): ('DP', finat.GaussLegendre), ('DP L2', finat.GaussLegendre)]) def test_interval_variant_default(family, expected_cls): - ufl_element = ufl.FiniteElement(family, ufl.interval, 3) + ufl_element = finat.ufl.FiniteElement(family, ufl.interval, 3) assert isinstance(create_element(ufl_element), expected_cls) @@ -85,36 +86,36 @@ def test_interval_variant_default(family, expected_cls): ('DP L2', 'equispaced', finat.DiscontinuousLagrange), ('DP L2', 'spectral', finat.GaussLegendre)]) def test_interval_variant(family, variant, expected_cls): - ufl_element = ufl.FiniteElement(family, ufl.interval, 3, variant=variant) + ufl_element = finat.ufl.FiniteElement(family, ufl.interval, 3, variant=variant) assert isinstance(create_element(ufl_element), expected_cls) def test_triangle_variant_spectral_fail(): - ufl_element = ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') + ufl_element = finat.ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') with pytest.raises(ValueError): create_element(ufl_element) def test_triangle_variant_spectral_fail_l2(): - ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') + ufl_element = finat.ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') with pytest.raises(ValueError): create_element(ufl_element) def test_quadrilateral_variant_spectral_q(): - element = create_element(ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) + element = create_element(finat.ufl.FiniteElement('Q', ufl.quadrilateral, 3, variant='spectral')) assert isinstance(element.product.factors[0], finat.GaussLobattoLegendre) assert isinstance(element.product.factors[1], finat.GaussLobattoLegendre) def test_quadrilateral_variant_spectral_dq(): - element = create_element(ufl.FiniteElement('DQ', ufl.quadrilateral, 1, variant='spectral')) + element = create_element(finat.ufl.FiniteElement('DQ', ufl.quadrilateral, 1, variant='spectral')) assert isinstance(element.product.factors[0], finat.GaussLegendre) assert isinstance(element.product.factors[1], finat.GaussLegendre) def test_quadrilateral_variant_spectral_dq_l2(): - element = create_element(ufl.FiniteElement('DQ L2', ufl.quadrilateral, 1, variant='spectral')) + element = create_element(finat.ufl.FiniteElement('DQ L2', ufl.quadrilateral, 1, variant='spectral')) assert isinstance(element.product.factors[0], finat.GaussLegendre) assert isinstance(element.product.factors[1], finat.GaussLegendre) diff --git a/tests/test_dual_evaluation.py b/tests/test_dual_evaluation.py index 461226c253..b4f6e9770a 100644 --- a/tests/test_dual_evaluation.py +++ b/tests/test_dual_evaluation.py @@ -1,12 +1,13 @@ import pytest import ufl +import finat.ufl from tsfc.finatinterface import create_element from tsfc import compile_expression_dual_evaluation def test_ufl_only_simple(): - mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) - V = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) + mesh = ufl.Mesh(finat.ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, finat.ufl.FiniteElement("P", ufl.triangle, 2)) v = ufl.Coefficient(V) expr = ufl.inner(v, v) W = V @@ -16,8 +17,8 @@ def test_ufl_only_simple(): def test_ufl_only_spatialcoordinate(): - mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) - V = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) + mesh = ufl.Mesh(finat.ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, finat.ufl.FiniteElement("P", ufl.triangle, 2)) x, y = ufl.SpatialCoordinate(mesh) expr = x*y - y**2 + x W = V @@ -27,30 +28,30 @@ def test_ufl_only_spatialcoordinate(): def test_ufl_only_from_contravariant_piola(): - mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) - V = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) + mesh = ufl.Mesh(finat.ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, finat.ufl.FiniteElement("RT", ufl.triangle, 1)) v = ufl.Coefficient(V) expr = ufl.inner(v, v) - W = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) + W = ufl.FunctionSpace(mesh, finat.ufl.FiniteElement("P", ufl.triangle, 2)) to_element = create_element(W.ufl_element()) kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) assert kernel.needs_external_coords is True def test_ufl_only_to_contravariant_piola(): - mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) - V = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2)) + mesh = ufl.Mesh(finat.ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, finat.ufl.FiniteElement("P", ufl.triangle, 2)) v = ufl.Coefficient(V) expr = ufl.as_vector([v, v]) - W = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) + W = ufl.FunctionSpace(mesh, finat.ufl.FiniteElement("RT", ufl.triangle, 1)) to_element = create_element(W.ufl_element()) kernel = compile_expression_dual_evaluation(expr, to_element, W.ufl_element()) assert kernel.needs_external_coords is True def test_ufl_only_shape_mismatch(): - mesh = ufl.Mesh(ufl.VectorElement("P", ufl.triangle, 1)) - V = ufl.FunctionSpace(mesh, ufl.FiniteElement("RT", ufl.triangle, 1)) + mesh = ufl.Mesh(finat.ufl.VectorElement("P", ufl.triangle, 1)) + V = ufl.FunctionSpace(mesh, finat.ufl.FiniteElement("RT", ufl.triangle, 1)) v = ufl.Coefficient(V) expr = ufl.inner(v, v) assert expr.ufl_shape == () diff --git a/tests/test_estimated_degree.py b/tests/test_estimated_degree.py index c3c80f02a3..e4842f2ee6 100644 --- a/tests/test_estimated_degree.py +++ b/tests/test_estimated_degree.py @@ -3,6 +3,7 @@ import pytest import ufl +import finat.ufl from tsfc import compile_form from tsfc.logging import logger @@ -14,8 +15,8 @@ def emit(self, record): def test_estimated_degree(): cell = ufl.tetrahedron - mesh = ufl.Mesh(ufl.VectorElement('P', cell, 1)) - V = ufl.FunctionSpace(mesh, ufl.FiniteElement('P', cell, 1)) + mesh = ufl.Mesh(finat.ufl.VectorElement('P', cell, 1)) + V = ufl.FunctionSpace(mesh, finat.ufl.FiniteElement('P', cell, 1)) f = ufl.Coefficient(V) u = ufl.TrialFunction(V) v = ufl.TestFunction(V) diff --git a/tests/test_firedrake_972.py b/tests/test_firedrake_972.py index 0c0cab0e2b..f9ec8d0967 100644 --- a/tests/test_firedrake_972.py +++ b/tests/test_firedrake_972.py @@ -1,8 +1,9 @@ import numpy import pytest -from ufl import (Mesh, FunctionSpace, VectorElement, TensorElement, +from ufl import (Mesh, FunctionSpace, Coefficient, TestFunction, interval, indices, dx) +from finat.ufl import VectorElement, TensorElement from ufl.classes import IndexSum, Product, MultiIndex from tsfc import compile_form diff --git a/tests/test_gem_failure.py b/tests/test_gem_failure.py index 8bf313388f..0a79a39ab8 100644 --- a/tests/test_gem_failure.py +++ b/tests/test_gem_failure.py @@ -1,5 +1,6 @@ -from ufl import (triangle, tetrahedron, FiniteElement, +from ufl import (triangle, tetrahedron, FunctionSpace, Mesh, TrialFunction, TestFunction, inner, grad, dx, dS) +from finat.ufl import FiniteElement, VectorElement from tsfc import compile_form from FIAT.hdiv_trace import TraceError import pytest @@ -12,8 +13,10 @@ def test_cell_error(cell, degree): cell triggers `gem.Failure` to raise the TraceError exception. """ trace_element = FiniteElement("HDiv Trace", cell, degree) - lambdar = TrialFunction(trace_element) - gammar = TestFunction(trace_element) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, trace_element) + lambdar = TrialFunction(space) + gammar = TestFunction(space) with pytest.raises(TraceError): compile_form(lambdar * gammar * dx) @@ -27,8 +30,10 @@ def test_gradient_error(cell, degree): exception. """ trace_element = FiniteElement("HDiv Trace", cell, degree) - lambdar = TrialFunction(trace_element) - gammar = TestFunction(trace_element) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, trace_element) + lambdar = TrialFunction(space) + gammar = TestFunction(space) with pytest.raises(TraceError): compile_form(inner(grad(lambdar('+')), grad(gammar('+'))) * dS) diff --git a/tests/test_idempotency.py b/tests/test_idempotency.py index 9ecb0f3723..cf554f4641 100644 --- a/tests/test_idempotency.py +++ b/tests/test_idempotency.py @@ -1,4 +1,5 @@ import ufl +import finat.ufl from tsfc import compile_form import loopy import pytest @@ -21,13 +22,13 @@ def coord_degree(request): @pytest.fixture def mesh(cell, coord_degree): - c = ufl.VectorElement("CG", cell, coord_degree) + c = finat.ufl.VectorElement("CG", cell, coord_degree) return ufl.Mesh(c) -@pytest.fixture(params=[ufl.FiniteElement, - ufl.VectorElement, - ufl.TensorElement], +@pytest.fixture(params=[finat.ufl.FiniteElement, + finat.ufl.VectorElement, + finat.ufl.TensorElement], ids=["FE", "VE", "TE"]) def V(request, mesh): return ufl.FunctionSpace(mesh, request.param("CG", mesh.ufl_cell(), 2)) diff --git a/tests/test_impero_loopy_flop_counts.py b/tests/test_impero_loopy_flop_counts.py index da357cf2a5..76bdad96e3 100644 --- a/tests/test_impero_loopy_flop_counts.py +++ b/tests/test_impero_loopy_flop_counts.py @@ -5,10 +5,11 @@ import numpy import loopy from tsfc import compile_form -from ufl import (FiniteElement, FunctionSpace, Mesh, TestFunction, - TrialFunction, VectorElement, dx, grad, inner, +from ufl import (FunctionSpace, Mesh, TestFunction, + TrialFunction, dx, grad, inner, interval, triangle, quadrilateral, TensorProductCell) +from finat.ufl import FiniteElement, VectorElement from tsfc.parameters import target diff --git a/tests/test_interpolation_factorisation.py b/tests/test_interpolation_factorisation.py index b588b34d07..f2404a8490 100644 --- a/tests/test_interpolation_factorisation.py +++ b/tests/test_interpolation_factorisation.py @@ -2,9 +2,9 @@ import numpy import pytest -from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, - TensorElement, Coefficient, +from ufl import (Mesh, FunctionSpace, Coefficient, interval, quadrilateral, hexahedron) +from finat.ufl import FiniteElement, VectorElement, TensorElement from tsfc import compile_expression_dual_evaluation from tsfc.finatinterface import create_element @@ -54,11 +54,11 @@ def test_sum_factorisation_scalar_tensor(mesh, element): source = element(degree - 1) target = element(degree) tensor_flops = flop_count(mesh, source, target) - expect = numpy.prod(target.value_shape()) + expect = numpy.prod(target.value_shape) if isinstance(target, FiniteElement): scalar_flops = tensor_flops else: - target = target.sub_elements()[0] - source = source.sub_elements()[0] + target = target.sub_elements[0] + source = source.sub_elements[0] scalar_flops = flop_count(mesh, source, target) assert numpy.allclose(tensor_flops / scalar_flops, expect, rtol=1e-2) diff --git a/tests/test_sum_factorisation.py b/tests/test_sum_factorisation.py index cb6a992e0b..3e785c5b26 100644 --- a/tests/test_sum_factorisation.py +++ b/tests/test_sum_factorisation.py @@ -1,11 +1,11 @@ import numpy import pytest -from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, - TestFunction, TrialFunction, TensorProductCell, - EnrichedElement, HCurlElement, HDivElement, - TensorProductElement, dx, action, interval, triangle, +from ufl import (Mesh, FunctionSpace, TestFunction, TrialFunction, + TensorProductCell, dx, action, interval, triangle, quadrilateral, curl, dot, div, grad) +from finat.ufl import (FiniteElement, VectorElement, EnrichedElement, + TensorProductElement, HCurlElement, HDivElement) from tsfc import compile_form diff --git a/tests/test_tensor.py b/tests/test_tensor.py index 39485abb2a..9d09a467fb 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -1,9 +1,10 @@ import numpy import pytest -from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, +from ufl import (Mesh, FunctionSpace, Coefficient, TestFunction, TrialFunction, dx, div, inner, interval, triangle, tetrahedron, dot, grad) +from finat.ufl import FiniteElement, VectorElement from tsfc import compile_form diff --git a/tests/test_tsfc_182.py b/tests/test_tsfc_182.py index 477356301b..556a6bafb0 100644 --- a/tests/test_tsfc_182.py +++ b/tests/test_tsfc_182.py @@ -1,7 +1,7 @@ import pytest -from ufl import (Coefficient, FiniteElement, MixedElement, - TestFunction, VectorElement, dx, inner, tetrahedron) +from ufl import Coefficient, TestFunction, dx, inner, tetrahedron, Mesh, FunctionSpace +from finat.ufl import FiniteElement, MixedElement, VectorElement from tsfc import compile_form @@ -19,9 +19,11 @@ def test_delta_elimination(mode): dim=6, quad_scheme=scheme) element_chi_lambda = MixedElement(element_eps_p, element_lambda) + domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + space = FunctionSpace(domain, element_chi_lambda) - chi_lambda = Coefficient(element_chi_lambda) - delta_chi_lambda = TestFunction(element_chi_lambda) + chi_lambda = Coefficient(space) + delta_chi_lambda = TestFunction(space) L = inner(delta_chi_lambda, chi_lambda) * dx(degree=degree, scheme=scheme) kernel, = compile_form(L, parameters={'mode': mode}) diff --git a/tests/test_tsfc_204.py b/tests/test_tsfc_204.py index e41e9c0c89..f6108c7caf 100644 --- a/tests/test_tsfc_204.py +++ b/tests/test_tsfc_204.py @@ -1,7 +1,8 @@ from tsfc import compile_form -from ufl import (BrokenElement, Coefficient, FacetNormal, FiniteElement, - FunctionSpace, Mesh, MixedElement, VectorElement, as_matrix, +from ufl import (Coefficient, FacetNormal, + FunctionSpace, Mesh, as_matrix, dot, dS, ds, dx, facet, grad, inner, outer, split, triangle) +from finat.ufl import BrokenElement, FiniteElement, MixedElement, VectorElement def test_physically_mapped_facet(): diff --git a/tests/test_tsfc_274.py b/tests/test_tsfc_274.py index 00f7a96a95..453d8746e8 100644 --- a/tests/test_tsfc_274.py +++ b/tests/test_tsfc_274.py @@ -3,7 +3,8 @@ from finat.point_set import PointSet from gem.interpreter import evaluate from tsfc.finatinterface import create_element -from ufl import FiniteElement, RestrictedElement, quadrilateral +from ufl import quadrilateral +from finat.ufl import FiniteElement, RestrictedElement def test_issue_274(): diff --git a/tests/test_underintegration.py b/tests/test_underintegration.py index 053722615f..24221e05a7 100644 --- a/tests/test_underintegration.py +++ b/tests/test_underintegration.py @@ -3,9 +3,9 @@ import numpy import pytest -from ufl import (Mesh, FunctionSpace, FiniteElement, VectorElement, - TestFunction, TrialFunction, TensorProductCell, dx, +from ufl import (Mesh, FunctionSpace, TestFunction, TrialFunction, TensorProductCell, dx, action, interval, quadrilateral, dot, grad) +from finat.ufl import FiniteElement, VectorElement from FIAT import ufc_cell from FIAT.quadrature import GaussLobattoLegendreQuadratureLineRule, GaussLegendreQuadratureLineRule diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index fb4e9cefdd..285d0eaa5b 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -25,6 +25,7 @@ import FIAT import finat import ufl +import finat.ufl __all__ = ("as_fiat_cell", "create_base_element", @@ -110,9 +111,9 @@ def convert(element, **kwargs): # Base finite elements first -@convert.register(ufl.FiniteElement) +@convert.register(finat.ufl.FiniteElement) def convert_finiteelement(element, **kwargs): - cell = as_fiat_cell(element.cell()) + cell = as_fiat_cell(element.cell) if element.family() == "Quadrature": degree = element.degree() scheme = element.quadrature_scheme() @@ -121,14 +122,14 @@ def convert_finiteelement(element, **kwargs): return finat.make_quadrature_element(cell, degree, scheme), set() lmbda = supported_elements[element.family()] - if element.family() == "Real" and element.cell().cellname() in {"quadrilateral", "hexahedron"}: + if element.family() == "Real" and element.cell.cellname() in {"quadrilateral", "hexahedron"}: lmbda = None - element = ufl.FiniteElement("DQ", element.cell(), 0) + element = finat.ufl.FiniteElement("DQ", element.cell, 0) if lmbda is None: - if element.cell().cellname() == "quadrilateral": + if element.cell.cellname() == "quadrilateral": # Handle quadrilateral short names like RTCF and RTCE. element = element.reconstruct(cell=quadrilateral_tpc) - elif element.cell().cellname() == "hexahedron": + elif element.cell.cellname() == "hexahedron": # Handle hexahedron short names like NCF and NCE. element = element.reconstruct(cell=hexahedron_tpc) else: @@ -138,7 +139,7 @@ def convert_finiteelement(element, **kwargs): return finat.FlattenedDimensions(finat_elem), deps kind = element.variant() - is_interval = element.cell().cellname() == 'interval' + is_interval = element.cell.cellname() == 'interval' if kind is None: kind = 'spectral' if is_interval else 'equispaced' # default variant @@ -164,7 +165,7 @@ def convert_finiteelement(element, **kwargs): deps = {"shift_axes", "restriction"} return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction), deps else: - raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) + raise ValueError("Variant %r not supported on %s" % (kind, element.cell)) elif element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)"}: lmbda = partial(lmbda, variant=element.variant()) @@ -188,96 +189,96 @@ def convert_finiteelement(element, **kwargs): deps = {"shift_axes", "restriction"} return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction, continuous=False), deps else: - raise ValueError("Variant %r not supported on %s" % (kind, element.cell())) + raise ValueError("Variant %r not supported on %s" % (kind, element.cell)) elif element.family() == ["DPC", "DPC L2"]: - if element.cell().geometric_dimension() == 2: + if element.cell.geometric_dimension() == 2: element = element.reconstruct(cell=ufl.cell.hypercube(2)) - elif element.cell().geometric_dimension() == 3: + elif element.cell.geometric_dimension() == 3: element = element.reconstruct(cell=ufl.cell.hypercube(3)) elif element.family() == "S": - if element.cell().geometric_dimension() == 2: + if element.cell.geometric_dimension() == 2: element = element.reconstruct(cell=ufl.cell.hypercube(2)) - elif element.cell().geometric_dimension() == 3: + elif element.cell.geometric_dimension() == 3: element = element.reconstruct(cell=ufl.cell.hypercube(3)) return lmbda(cell, element.degree()), set() # Element modifiers and compound element types -@convert.register(ufl.BrokenElement) +@convert.register(finat.ufl.BrokenElement) def convert_brokenelement(element, **kwargs): finat_elem, deps = _create_element(element._element, **kwargs) return finat.DiscontinuousElement(finat_elem), deps -@convert.register(ufl.EnrichedElement) +@convert.register(finat.ufl.EnrichedElement) def convert_enrichedelement(element, **kwargs): elements, deps = zip(*[_create_element(elem, **kwargs) for elem in element._elements]) return finat.EnrichedElement(elements), set.union(*deps) -@convert.register(ufl.NodalEnrichedElement) +@convert.register(finat.ufl.NodalEnrichedElement) def convert_nodalenrichedelement(element, **kwargs): elements, deps = zip(*[_create_element(elem, **kwargs) for elem in element._elements]) return finat.NodalEnrichedElement(elements), set.union(*deps) -@convert.register(ufl.MixedElement) +@convert.register(finat.ufl.MixedElement) def convert_mixedelement(element, **kwargs): elements, deps = zip(*[_create_element(elem, **kwargs) - for elem in element.sub_elements()]) + for elem in element.sub_elements]) return finat.MixedElement(elements), set.union(*deps) -@convert.register(ufl.VectorElement) -@convert.register(ufl.TensorElement) +@convert.register(finat.ufl.VectorElement) +@convert.register(finat.ufl.TensorElement) def convert_tensorelement(element, **kwargs): - inner_elem, deps = _create_element(element.sub_elements()[0], **kwargs) - shape = element.reference_value_shape() + inner_elem, deps = _create_element(element.sub_elements[0], **kwargs) + shape = element.reference_value_shape shape = shape[:len(shape) - len(inner_elem.value_shape)] shape_innermost = kwargs["shape_innermost"] return (finat.TensorFiniteElement(inner_elem, shape, not shape_innermost), deps | {"shape_innermost"}) -@convert.register(ufl.TensorProductElement) +@convert.register(finat.ufl.TensorProductElement) def convert_tensorproductelement(element, **kwargs): - cell = element.cell() + cell = element.cell if type(cell) is not ufl.TensorProductCell: raise ValueError("TensorProductElement not on TensorProductCell?") shift_axes = kwargs["shift_axes"] dim_offset = 0 elements = [] deps = set() - for elem in element.sub_elements(): + for elem in element.sub_elements: kwargs["shift_axes"] = shift_axes + dim_offset - dim_offset += elem.cell().topological_dimension() + dim_offset += elem.cell.topological_dimension() finat_elem, ds = _create_element(elem, **kwargs) elements.append(finat_elem) deps.update(ds) return finat.TensorProductElement(elements), deps -@convert.register(ufl.HDivElement) +@convert.register(finat.ufl.HDivElement) def convert_hdivelement(element, **kwargs): finat_elem, deps = _create_element(element._element, **kwargs) return finat.HDivElement(finat_elem), deps -@convert.register(ufl.HCurlElement) +@convert.register(finat.ufl.HCurlElement) def convert_hcurlelement(element, **kwargs): finat_elem, deps = _create_element(element._element, **kwargs) return finat.HCurlElement(finat_elem), deps -@convert.register(ufl.WithMapping) +@convert.register(finat.ufl.WithMapping) def convert_withmapping(element, **kwargs): return _create_element(element.wrapee, **kwargs) -@convert.register(ufl.RestrictedElement) +@convert.register(finat.ufl.RestrictedElement) def convert_restrictedelement(element, **kwargs): finat_elem, deps = _create_element(element._element, **kwargs) return finat.RestrictedElement(finat_elem, element.restriction_domain()), deps @@ -323,7 +324,7 @@ def _create_element(ufl_element, **kwargs): return finat_element, set(param for param, value in key) # Convert if cache miss - if ufl_element.cell() is None: + if ufl_element.cell is None: raise ValueError("Don't know how to build element when cell is not given") finat_element, deps = convert(ufl_element, **kwargs) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index db7cf53c6a..67f7dac87e 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -467,7 +467,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): if coefficient.ufl_element().family() == 'Real': # Constant - value_size = coefficient.ufl_element().value_size() + value_size = coefficient.ufl_element().value_size expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) return expression diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index f538acfb41..e51fd239bd 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -2,7 +2,8 @@ from collections import namedtuple, OrderedDict from functools import partial -from ufl import Coefficient, MixedElement as ufl_MixedElement, FunctionSpace, FiniteElement +from ufl import Coefficient, FunctionSpace +from finat.ufl import MixedElement as ufl_MixedElement, FiniteElement import gem from gem.flop_count import count_flops @@ -321,7 +322,7 @@ def set_coefficients(self, integral_data, form_data): k += 1 else: self.coefficient_split[coefficient] = [] - for j, element in enumerate(coefficient.ufl_element().sub_elements()): + for j, element in enumerate(coefficient.ufl_element().sub_elements): c = Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) self.coefficient_split[coefficient].append(c) self._coefficient(c, f"w_{k}") diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 21f94d3745..0cec4dbf26 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -22,9 +22,10 @@ from ufl.geometry import Jacobian, JacobianDeterminant, JacobianInverse from ufl.classes import (Abs, Argument, CellOrientation, Coefficient, ComponentTensor, Expr, FloatValue, Division, - MixedElement, MultiIndex, Product, + MultiIndex, Product, ScalarValue, Sqrt, Zero, CellVolume, FacetArea) from ufl.domain import extract_unique_domain +from finat.ufl import MixedElement from ufl.utils.sorting import sorted_by_count from gem.node import MemoizerArg @@ -202,7 +203,7 @@ def modified_terminal(self, o): # Apply terminal modifiers onto the subcoefficient component = construct_modified_terminal(mt, subcoeff) # Collect components of the subcoefficient - for alpha in numpy.ndindex(subcoeff.ufl_element().reference_value_shape()): + for alpha in numpy.ndindex(subcoeff.ufl_element().reference_value_shape): # New modified terminal: component[alpha + beta] components.append(component[alpha + beta]) # Repack derivative indices to shape @@ -363,7 +364,7 @@ def apply_mapping(expression, element, domain): advertise the pullback to apply. :arg domain: Optional domain to provide in case expression does not contain a domain (used for constructing geometric quantities). - :returns: A new UFL expression with shape element.reference_value_shape() + :returns: A new UFL expression with shape element.reference_value_shape :raises NotImplementedError: If we don't know how to apply the inverse of the pullback. :raises ValueError: If we get shape mismatches. @@ -411,8 +412,8 @@ def apply_mapping(expression, element, domain): mesh = domain if domain is not None and mesh != domain: raise NotImplementedError("Multiple domains not supported") - if expression.ufl_shape != element.value_shape(): - raise ValueError(f"Mismatching shapes, got {expression.ufl_shape}, expected {element.value_shape()}") + if expression.ufl_shape != element.value_shape: + raise ValueError(f"Mismatching shapes, got {expression.ufl_shape}, expected {element.value_shape}") mapping = element.mapping().lower() if mapping == "identity": rexpression = expression @@ -447,11 +448,11 @@ def apply_mapping(expression, element, domain): # We're going to apply the inverse of the physical to # reference space mapping. fcm = element.flattened_sub_element_mapping() - sub_elem = element.sub_elements()[0] + sub_elem = element.sub_elements[0] shape = expression.ufl_shape flat = ufl.as_vector([expression[i] for i in numpy.ndindex(shape)]) - vs = sub_elem.value_shape() - rvs = sub_elem.reference_value_shape() + vs = sub_elem.value_shape + rvs = sub_elem.reference_value_shape seen = set() rpieces = [] gm = int(numpy.prod(vs, dtype=int)) @@ -469,11 +470,11 @@ def apply_mapping(expression, element, domain): # Concatenate with the other pieces rpieces.extend([piece[idx] for idx in numpy.ndindex(rvs)]) # And reshape - rexpression = as_tensor(numpy.asarray(rpieces).reshape(element.reference_value_shape())) + rexpression = as_tensor(numpy.asarray(rpieces).reshape(element.reference_value_shape)) else: - raise NotImplementedError(f"Don't know how to handle mapping type {mapping} for expression of rank {element.value_shape()}") - if rexpression.ufl_shape != element.reference_value_shape(): - raise ValueError(f"Mismatching reference shapes, got {rexpression.ufl_shape} expected {element.reference_value_shape()}") + raise NotImplementedError(f"Don't know how to handle mapping type {mapping} for expression of rank {element.value_shape}") + if rexpression.ufl_shape != element.reference_value_shape: + raise ValueError(f"Mismatching reference shapes, got {rexpression.ufl_shape} expected {element.reference_value_shape}") return rexpression From 106b651a49967f2ce8867f9d5343666c0a59900d Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 15 Nov 2023 13:48:44 +0000 Subject: [PATCH 748/809] Apply suggestions from code review --- tests/test_create_fiat_element.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 9b383cfc6e..28356b0045 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -102,12 +102,12 @@ def test_interval_variant(family, variant, expected_cls): def test_triangle_variant_spectral(): - ufl_element = ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') + ufl_element = finat.ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') create_element(ufl_element) def test_triangle_variant_spectral_l2(): - ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') + ufl_element = finat.ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') create_element(ufl_element) From 362e8933217213466208a51ee095b5980fb7ff02 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 15 Nov 2023 13:49:38 +0000 Subject: [PATCH 749/809] Apply suggestions from code review --- tests/test_create_finat_element.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_create_finat_element.py b/tests/test_create_finat_element.py index d7921b6018..c0f34c292c 100644 --- a/tests/test_create_finat_element.py +++ b/tests/test_create_finat_element.py @@ -91,12 +91,12 @@ def test_interval_variant(family, variant, expected_cls): def test_triangle_variant_spectral(): - ufl_element = ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') + ufl_element = finat.ufl.FiniteElement('DP', ufl.triangle, 2, variant='spectral') create_element(ufl_element) def test_triangle_variant_spectral_l2(): - ufl_element = ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') + ufl_element = finat.ufl.FiniteElement('DP L2', ufl.triangle, 2, variant='spectral') create_element(ufl_element) From a3ebf0cca35080dd8c79982156476ac5e4a5eab9 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 23 Nov 2023 17:26:16 +0000 Subject: [PATCH 750/809] Expunge Expr.ufl_domain() --- tsfc/driver.py | 3 ++- tsfc/fem.py | 20 ++++++++++---------- tsfc/kernel_interface/firedrake_loopy.py | 3 ++- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index b38427e92f..f67423151a 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -8,6 +8,7 @@ from ufl.algorithms import extract_arguments, extract_coefficients from ufl.algorithms.analysis import has_type from ufl.classes import Form, GeometricQuantity +from ufl.domain import extract_unique_domain import gem import gem.impero_utils as impero_utils @@ -219,7 +220,7 @@ def compile_expression_dual_evaluation(expression, to_element, ufl_element, *, # Replace coordinates (if any) unless otherwise specified by kwarg if domain is None: - domain = expression.ufl_domain() + domain = extract_unique_domain(expression) assert domain is not None # Collect required coefficients and determine numbering diff --git a/tsfc/fem.py b/tsfc/fem.py index 7ef40b8b2a..de8f5ee622 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -150,7 +150,7 @@ def cell_size(self): def jacobian_at(self, point): ps = PointSingleton(point) - expr = Jacobian(self.mt.terminal.ufl_domain()) + expr = Jacobian(extract_unique_domain(self.mt.terminal)) assert ps.expression.shape == (extract_unique_domain(expr).topological_dimension(), ) if self.mt.restriction == '+': expr = PositiveRestricted(expr) @@ -163,7 +163,7 @@ def jacobian_at(self, point): return map_expr_dag(context.translator, expr) def detJ_at(self, point): - expr = JacobianDeterminant(self.mt.terminal.ufl_domain()) + expr = JacobianDeterminant(extract_unique_domain(self.mt.terminal)) if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': @@ -202,7 +202,7 @@ def physical_normals(self): return gem.ListTensor([[pts[i, 1], -1*pts[i, 0]] for i in range(3)]) def physical_edge_lengths(self): - expr = ufl.classes.CellEdgeVectors(self.mt.terminal.ufl_domain()) + expr = ufl.classes.CellEdgeVectors(extract_unique_domain(self.mt.terminal)) if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': @@ -217,13 +217,13 @@ def physical_edge_lengths(self): def physical_points(self, point_set, entity=None): """Converts point_set from reference to physical space""" - expr = SpatialCoordinate(self.mt.terminal.ufl_domain()) + expr = SpatialCoordinate(extract_unique_domain(self.mt.terminal)) point_shape, = point_set.expression.shape if entity is not None: e, _ = entity assert point_shape == e else: - assert point_shape == expr.ufl_domain().topological_dimension() + assert point_shape == extract_unique_domain(expr).topological_dimension() if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': @@ -330,7 +330,7 @@ def cell_avg(self, o): # below). raise NotImplementedError("CellAvg on non-cell integrals not yet implemented") integrand, = o.ufl_operands - domain = o.ufl_domain() + domain = extract_unique_domain(o) measure = ufl.Measure(self.context.integral_type, domain=domain) integrand, degree, argument_multiindices = entity_avg(integrand / CellVolume(domain), measure, self.context.argument_multiindices) @@ -345,7 +345,7 @@ def facet_avg(self, o): if self.context.integral_type == "cell": raise ValueError("Can't take FacetAvg in cell integral") integrand, = o.ufl_operands - domain = o.ufl_domain() + domain = extract_unique_domain(o) measure = ufl.Measure(self.context.integral_type, domain=domain) integrand, degree, argument_multiindices = entity_avg(integrand / FacetArea(domain), measure, self.context.argument_multiindices) @@ -509,7 +509,7 @@ def coefficient(self, ufl_coefficient, r): @translate.register(CellVolume) def translate_cellvolume(terminal, mt, ctx): - integrand, degree = one_times(ufl.dx(domain=terminal.ufl_domain())) + integrand, degree = one_times(ufl.dx(domain=extract_unique_domain(terminal))) interface = CellVolumeKernelInterface(ctx, mt.restriction) config = {name: getattr(ctx, name) @@ -522,7 +522,7 @@ def translate_cellvolume(terminal, mt, ctx): @translate.register(FacetArea) def translate_facetarea(terminal, mt, ctx): assert ctx.integral_type != 'cell' - domain = terminal.ufl_domain() + domain = extract_unique_domain(terminal) integrand, degree = one_times(ufl.Measure(ctx.integral_type, domain=domain)) config = {name: getattr(ctx, name) @@ -535,7 +535,7 @@ def translate_facetarea(terminal, mt, ctx): @translate.register(CellOrigin) def translate_cellorigin(terminal, mt, ctx): - domain = terminal.ufl_domain() + domain = extract_unique_domain(terminal) coords = SpatialCoordinate(domain) expression = construct_modified_terminal(mt, coords) point_set = PointSingleton((0.0,) * domain.topological_dimension()) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index e51fd239bd..c6d671b77e 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -3,6 +3,7 @@ from functools import partial from ufl import Coefficient, FunctionSpace +from ufl.domain import extract_unique_domain from finat.ufl import MixedElement as ufl_MixedElement, FiniteElement import gem @@ -323,7 +324,7 @@ def set_coefficients(self, integral_data, form_data): else: self.coefficient_split[coefficient] = [] for j, element in enumerate(coefficient.ufl_element().sub_elements): - c = Coefficient(FunctionSpace(coefficient.ufl_domain(), element)) + c = Coefficient(FunctionSpace(extract_unique_domain(coefficient), element)) self.coefficient_split[coefficient].append(c) self._coefficient(c, f"w_{k}") self.coefficient_number_index_map[c] = (n, j) From 33eeb0673d4d83eacc02b723e5e5c08f1ee43d06 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 2 Dec 2023 16:59:41 +0000 Subject: [PATCH 751/809] CG/DG integral variants on simplices --- tsfc/finatinterface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 6442e4b9ad..cdbe784ba2 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -148,7 +148,7 @@ def convert_finiteelement(element, **kwargs): lmbda = finat.Lagrange elif kind == 'spectral': lmbda = finat.GaussLobattoLegendre - elif kind == 'hierarchical' and is_interval: + elif kind == 'integral': lmbda = finat.IntegratedLegendre elif kind in ['fdm', 'fdm_ipdg'] and is_interval: lmbda = finat.FDMLagrange @@ -174,7 +174,7 @@ def convert_finiteelement(element, **kwargs): lmbda = finat.DiscontinuousLagrange elif kind == 'spectral': lmbda = finat.GaussLegendre - elif kind == 'hierarchical' and is_interval: + elif kind == 'integral': lmbda = finat.Legendre elif kind in ['fdm', 'fdm_quadrature'] and is_interval: lmbda = finat.FDMDiscontinuousLagrange From 1620ed9aa2a8158a235752d619588c99a9f84196 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 31 Dec 2023 13:37:18 -0600 Subject: [PATCH 752/809] Support IntegratedLegendre variants --- tsfc/finatinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index cdbe784ba2..b0c813ed44 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -158,6 +158,8 @@ def convert_finiteelement(element, **kwargs): lmbda = finat.FDMBrokenH1 elif kind == 'fdm_hermite' and is_interval: lmbda = finat.FDMHermite + elif kind in ['demkowicz', 'fdm']: + lmbda = partial(finat.IntegratedLegendre, variant=kind) elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] From ab84e03a0157b6d416d37de693c30d11b6b79a3a Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 22 Jan 2024 16:55:36 +0000 Subject: [PATCH 753/809] geometric -> topological --- tsfc/finatinterface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 6442e4b9ad..1e548cc1d9 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -191,14 +191,14 @@ def convert_finiteelement(element, **kwargs): else: raise ValueError("Variant %r not supported on %s" % (kind, element.cell)) elif element.family() == ["DPC", "DPC L2"]: - if element.cell.geometric_dimension() == 2: + if element.cell.topological_dimension() == 2: element = element.reconstruct(cell=ufl.cell.hypercube(2)) - elif element.cell.geometric_dimension() == 3: + elif element.cell.topological_dimension() == 3: element = element.reconstruct(cell=ufl.cell.hypercube(3)) elif element.family() == "S": - if element.cell.geometric_dimension() == 2: + if element.cell.topological_dimension() == 2: element = element.reconstruct(cell=ufl.cell.hypercube(2)) - elif element.cell.geometric_dimension() == 3: + elif element.cell.topological_dimension() == 3: element = element.reconstruct(cell=ufl.cell.hypercube(3)) return lmbda(cell, element.degree()), set() From bf8b9c40cd177395b94715e5278244ddad1f8eb1 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 22 Jan 2024 17:35:38 +0000 Subject: [PATCH 754/809] value_shape(mesh) --- tsfc/ufl_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 0cec4dbf26..25817c1d9e 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -412,8 +412,8 @@ def apply_mapping(expression, element, domain): mesh = domain if domain is not None and mesh != domain: raise NotImplementedError("Multiple domains not supported") - if expression.ufl_shape != element.value_shape: - raise ValueError(f"Mismatching shapes, got {expression.ufl_shape}, expected {element.value_shape}") + if expression.ufl_shape != element.value_shape(mesh): + raise ValueError(f"Mismatching shapes, got {expression.ufl_shape}, expected {element.value_shape(mesh)}") mapping = element.mapping().lower() if mapping == "identity": rexpression = expression @@ -451,7 +451,7 @@ def apply_mapping(expression, element, domain): sub_elem = element.sub_elements[0] shape = expression.ufl_shape flat = ufl.as_vector([expression[i] for i in numpy.ndindex(shape)]) - vs = sub_elem.value_shape + vs = sub_elem.value_shape(mesh) rvs = sub_elem.reference_value_shape seen = set() rpieces = [] @@ -472,7 +472,7 @@ def apply_mapping(expression, element, domain): # And reshape rexpression = as_tensor(numpy.asarray(rpieces).reshape(element.reference_value_shape)) else: - raise NotImplementedError(f"Don't know how to handle mapping type {mapping} for expression of rank {element.value_shape}") + raise NotImplementedError(f"Don't know how to handle mapping type {mapping} for expression of rank {element.value_shape(mesh)}") if rexpression.ufl_shape != element.reference_value_shape: raise ValueError(f"Mismatching reference shapes, got {rexpression.ufl_shape} expected {element.reference_value_shape}") return rexpression From 8a90e36f7c29dfad204cc1b327ccef3ed9259a0a Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 22 Jan 2024 17:41:41 +0000 Subject: [PATCH 755/809] fix tests --- tests/test_interpolation_factorisation.py | 2 +- tests/test_tsfc_204.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_interpolation_factorisation.py b/tests/test_interpolation_factorisation.py index f2404a8490..1c58ed2b0e 100644 --- a/tests/test_interpolation_factorisation.py +++ b/tests/test_interpolation_factorisation.py @@ -54,7 +54,7 @@ def test_sum_factorisation_scalar_tensor(mesh, element): source = element(degree - 1) target = element(degree) tensor_flops = flop_count(mesh, source, target) - expect = numpy.prod(target.value_shape) + expect = numpy.prod(target.value_shape(mesh)) if isinstance(target, FiniteElement): scalar_flops = tensor_flops else: diff --git a/tests/test_tsfc_204.py b/tests/test_tsfc_204.py index f6108c7caf..35e2caa064 100644 --- a/tests/test_tsfc_204.py +++ b/tests/test_tsfc_204.py @@ -13,8 +13,8 @@ def test_physically_mapped_facet(): V = FiniteElement("P", mesh.ufl_cell(), 1) R = FiniteElement("P", mesh.ufl_cell(), 1) Vv = VectorElement(BrokenElement(V)) - Qhat = VectorElement(BrokenElement(V[facet])) - Vhat = VectorElement(V[facet]) + Qhat = VectorElement(BrokenElement(V[facet]), dim=2) + Vhat = VectorElement(V[facet], dim=2) Z = FunctionSpace(mesh, MixedElement(U, Vv, Qhat, Vhat, R)) z = Coefficient(Z) From 21f29ccaebc372e8e327f78f44c7ded2196ccdd9 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 29 Jan 2024 16:38:41 +0000 Subject: [PATCH 756/809] move value_shape to function space --- tests/test_interpolation_factorisation.py | 2 +- tsfc/ufl_utils.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_interpolation_factorisation.py b/tests/test_interpolation_factorisation.py index 1c58ed2b0e..13fe85d39a 100644 --- a/tests/test_interpolation_factorisation.py +++ b/tests/test_interpolation_factorisation.py @@ -54,7 +54,7 @@ def test_sum_factorisation_scalar_tensor(mesh, element): source = element(degree - 1) target = element(degree) tensor_flops = flop_count(mesh, source, target) - expect = numpy.prod(target.value_shape(mesh)) + expect =FunctionSpace(mesh, target).value_size if isinstance(target, FiniteElement): scalar_flops = tensor_flops else: diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 25817c1d9e..cbc0ed4908 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -412,8 +412,9 @@ def apply_mapping(expression, element, domain): mesh = domain if domain is not None and mesh != domain: raise NotImplementedError("Multiple domains not supported") - if expression.ufl_shape != element.value_shape(mesh): - raise ValueError(f"Mismatching shapes, got {expression.ufl_shape}, expected {element.value_shape(mesh)}") + if expression.ufl_shape != ufl.FunctionSpace(mesh, element).value_shape: + raise ValueError(f"Mismatching shapes, got {expression.ufl_shape}, " + f"expected {ufl.FunctionSpace(mesh, element).value_shape}") mapping = element.mapping().lower() if mapping == "identity": rexpression = expression @@ -451,7 +452,7 @@ def apply_mapping(expression, element, domain): sub_elem = element.sub_elements[0] shape = expression.ufl_shape flat = ufl.as_vector([expression[i] for i in numpy.ndindex(shape)]) - vs = sub_elem.value_shape(mesh) + vs = ufl.FunctionSpace(mesh, sub_elem).value_shape rvs = sub_elem.reference_value_shape seen = set() rpieces = [] @@ -472,7 +473,7 @@ def apply_mapping(expression, element, domain): # And reshape rexpression = as_tensor(numpy.asarray(rpieces).reshape(element.reference_value_shape)) else: - raise NotImplementedError(f"Don't know how to handle mapping type {mapping} for expression of rank {element.value_shape(mesh)}") + raise NotImplementedError(f"Don't know how to handle mapping type {mapping} for expression of rank {ufl.FunctionSpace(mesh, element).value_shape}") if rexpression.ufl_shape != element.reference_value_shape: raise ValueError(f"Mismatching reference shapes, got {rexpression.ufl_shape} expected {element.reference_value_shape}") return rexpression From 6c5abf18be38e81f68b6b9d2a95f92a1aa04b399 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 29 Jan 2024 16:51:43 +0000 Subject: [PATCH 757/809] missing space --- tests/test_interpolation_factorisation.py | 2 +- tests/test_tsfc_204.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_interpolation_factorisation.py b/tests/test_interpolation_factorisation.py index 13fe85d39a..b3d4e3288b 100644 --- a/tests/test_interpolation_factorisation.py +++ b/tests/test_interpolation_factorisation.py @@ -54,7 +54,7 @@ def test_sum_factorisation_scalar_tensor(mesh, element): source = element(degree - 1) target = element(degree) tensor_flops = flop_count(mesh, source, target) - expect =FunctionSpace(mesh, target).value_size + expect = FunctionSpace(mesh, target).value_size if isinstance(target, FiniteElement): scalar_flops = tensor_flops else: diff --git a/tests/test_tsfc_204.py b/tests/test_tsfc_204.py index 35e2caa064..455e019aa0 100644 --- a/tests/test_tsfc_204.py +++ b/tests/test_tsfc_204.py @@ -1,3 +1,4 @@ +import pytest from tsfc import compile_form from ufl import (Coefficient, FacetNormal, FunctionSpace, Mesh, as_matrix, From bb94ad5fd6e1ecc7d7e0c8b9b85e6827da4f66c8 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 29 Jan 2024 16:53:45 +0000 Subject: [PATCH 758/809] didn't mean to commit that line --- tests/test_tsfc_204.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_tsfc_204.py b/tests/test_tsfc_204.py index 455e019aa0..35e2caa064 100644 --- a/tests/test_tsfc_204.py +++ b/tests/test_tsfc_204.py @@ -1,4 +1,3 @@ -import pytest from tsfc import compile_form from ufl import (Coefficient, FacetNormal, FunctionSpace, Mesh, as_matrix, From 1ce1d0a0cf99d5eb5fdf920d2ad314201b59898a Mon Sep 17 00:00:00 2001 From: Francis Aznaran Date: Wed, 20 Mar 2024 11:43:48 -0400 Subject: [PATCH 759/809] Implementation of Hu-Zhang elements --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index fb4e9cefdd..3d2984889f 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -48,6 +48,7 @@ "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, "Nonconforming Arnold-Winther": finat.ArnoldWintherNC, "Conforming Arnold-Winther": finat.ArnoldWinther, + "Hu-Zhang": finat.HuZhang, "Hermite": finat.Hermite, "Kong-Mulder-Veldhuizen": finat.KongMulderVeldhuizen, "Argyris": finat.Argyris, From 34b4d591023019165184aa30788a2a2561e6ebda Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 28 Mar 2024 18:23:15 -0500 Subject: [PATCH 760/809] Let FIAT handle general CG/DG variants --- tsfc/finatinterface.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index b0c813ed44..b4d18fdbb7 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -144,9 +144,7 @@ def convert_finiteelement(element, **kwargs): kind = 'spectral' # default variant if element.family() == "Lagrange": - if kind == 'equispaced': - lmbda = finat.Lagrange - elif kind == 'spectral': + if kind == 'spectral': lmbda = finat.GaussLobattoLegendre elif kind == 'integral': lmbda = finat.IntegratedLegendre @@ -167,14 +165,13 @@ def convert_finiteelement(element, **kwargs): deps = {"shift_axes", "restriction"} return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction), deps else: - raise ValueError("Variant %r not supported on %s" % (kind, element.cell)) + # Let FIAT handle the general case + lmbda = finat.Lagrange elif element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)"}: lmbda = partial(lmbda, variant=element.variant()) elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: - if kind == 'equispaced': - lmbda = finat.DiscontinuousLagrange - elif kind == 'spectral': + if kind == 'spectral': lmbda = finat.GaussLegendre elif kind == 'integral': lmbda = finat.Legendre @@ -191,7 +188,8 @@ def convert_finiteelement(element, **kwargs): deps = {"shift_axes", "restriction"} return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction, continuous=False), deps else: - raise ValueError("Variant %r not supported on %s" % (kind, element.cell)) + # Let FIAT handle the general case + lmbda = finat.DiscontinuousLagrange elif element.family() == ["DPC", "DPC L2"]: if element.cell.geometric_dimension() == 2: element = element.reconstruct(cell=ufl.cell.hypercube(2)) From 7b2fb38ed60ad062c5915087695ec60782dd3f4e Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 28 Mar 2024 18:47:47 -0500 Subject: [PATCH 761/809] pass the variant --- tsfc/finatinterface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index b4d18fdbb7..b05013562e 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -166,7 +166,7 @@ def convert_finiteelement(element, **kwargs): return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction), deps else: # Let FIAT handle the general case - lmbda = finat.Lagrange + lmbda = partial(finat.Lagrange, variant=kind) elif element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)"}: lmbda = partial(lmbda, variant=element.variant()) @@ -189,7 +189,7 @@ def convert_finiteelement(element, **kwargs): return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction, continuous=False), deps else: # Let FIAT handle the general case - lmbda = finat.DiscontinuousLagrange + lmbda = partial(finat.DiscontinuousLagrange, variant=kind) elif element.family() == ["DPC", "DPC L2"]: if element.cell.geometric_dimension() == 2: element = element.reconstruct(cell=ufl.cell.hypercube(2)) From 769e7072214f03ea0bbd77f3681a9fa1ddbd71fc Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 2 Apr 2024 13:50:27 -0500 Subject: [PATCH 762/809] fix tests --- tests/test_create_fiat_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_create_fiat_element.py b/tests/test_create_fiat_element.py index 28356b0045..f8a7d6efc4 100644 --- a/tests/test_create_fiat_element.py +++ b/tests/test_create_fiat_element.py @@ -1,7 +1,7 @@ import pytest import FIAT -from FIAT.discontinuous_lagrange import HigherOrderDiscontinuousLagrange as FIAT_DiscontinuousLagrange +from FIAT.discontinuous_lagrange import DiscontinuousLagrange as FIAT_DiscontinuousLagrange import ufl import finat.ufl From d22862d1cc546304c2ab1426cf282d0ce5e46209 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Tue, 2 Apr 2024 22:50:11 -0500 Subject: [PATCH 763/809] WIP: enable macro-quadrature --- tsfc/kernel_interface/common.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 67f7dac87e..6a1514be7c 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -1,29 +1,24 @@ import collections -import string import operator +import string from functools import reduce from itertools import chain, product +import gem +import gem.impero_utils as impero_utils import numpy -from numpy import asarray - -from ufl.utils.sequences import max_degree - from FIAT.reference_element import TensorProductCell - from finat.quadrature import AbstractQuadratureRule, make_quadrature - -import gem - from gem.node import traversal +from gem.optimise import constant_fold_zero +from gem.optimise import remove_componenttensors as prune from gem.utils import cached_property -import gem.impero_utils as impero_utils -from gem.optimise import remove_componenttensors as prune, constant_fold_zero - +from numpy import asarray from tsfc import fem, ufl_utils +from tsfc.finatinterface import as_fiat_cell, convert, create_element from tsfc.kernel_interface import KernelInterface -from tsfc.finatinterface import as_fiat_cell, create_element from tsfc.logging import logger +from ufl.utils.sequences import max_degree class KernelBuilderBase(KernelInterface): @@ -301,7 +296,8 @@ def set_quad_rule(params, cell, integral_type, functions): quadrature_degree = params["quadrature_degree"] except KeyError: quadrature_degree = params["estimated_polynomial_degree"] - function_degrees = [f.ufl_function_space().ufl_element().degree() for f in functions] + function_degrees = [f.ufl_function_space().ufl_element().degree() + for f in functions] if all((asarray(quadrature_degree) > 10 * asarray(degree)).all() for degree in function_degrees): logger.warning("Estimated quadrature degree %s more " @@ -314,9 +310,14 @@ def set_quad_rule(params, cell, integral_type, functions): quad_rule = params["quadrature_rule"] except KeyError: fiat_cell = as_fiat_cell(cell) + finat_elements = set(create_element(f.ufl_element()) for f in functions) + print(list(f.degree for f in finat_elements)) + + fiat_cells = [fiat_cell] + [finat_el.complex for finat_el in finat_elements] + integration_dim, _ = lower_integral_type(fiat_cell, integral_type) - integration_cell = fiat_cell.construct_subelement(integration_dim) - quad_rule = make_quadrature(integration_cell, quadrature_degree) + + quad_rule = make_quadrature(fiat_cells, quadrature_degree, dim=integration_dim) params["quadrature_rule"] = quad_rule if not isinstance(quad_rule, AbstractQuadratureRule): From 53228820b2caa874d3149a4d33644f75576d6e36 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Tue, 2 Apr 2024 23:28:29 -0500 Subject: [PATCH 764/809] flake8 --- tsfc/kernel_interface/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 6a1514be7c..b455516c2a 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -15,7 +15,7 @@ from gem.utils import cached_property from numpy import asarray from tsfc import fem, ufl_utils -from tsfc.finatinterface import as_fiat_cell, convert, create_element +from tsfc.finatinterface import as_fiat_cell, create_element from tsfc.kernel_interface import KernelInterface from tsfc.logging import logger from ufl.utils.sequences import max_degree From 6cc1cbbe120b568ae9172617bc50390419c0ac90 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 3 Apr 2024 13:22:59 -0500 Subject: [PATCH 765/809] Handle quadrature for macro elements --- tsfc/kernel_interface/common.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index b455516c2a..2e097efc14 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -304,20 +304,22 @@ def set_quad_rule(params, cell, integral_type, functions): "than tenfold greater than any " "argument/coefficient degree (max %s)", quadrature_degree, max_degree(function_degrees)) - if params.get("quadrature_rule") == "default": - del params["quadrature_rule"] - try: - quad_rule = params["quadrature_rule"] - except KeyError: + quad_rule = params.get("quadrature_rule", "default") + if isinstance(quad_rule, str): + scheme = quad_rule fiat_cell = as_fiat_cell(cell) - finat_elements = set(create_element(f.ufl_element()) for f in functions) - print(list(f.degree for f in finat_elements)) + integration_dim, _ = lower_integral_type(fiat_cell, integral_type) + finat_elements = set(create_element(f.ufl_element()) for f in functions) fiat_cells = [fiat_cell] + [finat_el.complex for finat_el in finat_elements] + max_cell = max(fiat_cells) + if all(max_cell >= b for b in fiat_cells): + fiat_cell = max_cell + else: + raise ValueError("Can't find a maximal complex") - integration_dim, _ = lower_integral_type(fiat_cell, integral_type) - - quad_rule = make_quadrature(fiat_cells, quadrature_degree, dim=integration_dim) + fiat_cell = fiat_cell.construct_subcomplex(integration_dim) + quad_rule = make_quadrature(fiat_cell, quadrature_degree, scheme=scheme) params["quadrature_rule"] = quad_rule if not isinstance(quad_rule, AbstractQuadratureRule): From 49a92f69e5a3ebd088ea60cbd51be2ccfb6dc9ed Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 3 Apr 2024 20:18:25 -0500 Subject: [PATCH 766/809] do not create complex on Real (Constant) spaces --- tsfc/kernel_interface/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 2e097efc14..73a76374b3 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -310,7 +310,8 @@ def set_quad_rule(params, cell, integral_type, functions): fiat_cell = as_fiat_cell(cell) integration_dim, _ = lower_integral_type(fiat_cell, integral_type) - finat_elements = set(create_element(f.ufl_element()) for f in functions) + finat_elements = set(create_element(f.ufl_element()) for f in functions + if f.ufl_element().family() != "Real") fiat_cells = [fiat_cell] + [finat_el.complex for finat_el in finat_elements] max_cell = max(fiat_cells) if all(max_cell >= b for b in fiat_cells): From 19d1357a39716ead163073afcdf7113ed51b3580 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 3 Apr 2024 21:58:43 -0500 Subject: [PATCH 767/809] handle integral variants --- tsfc/finatinterface.py | 8 ++++---- tsfc/kernel_interface/common.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index b05013562e..e570bbeaa8 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -146,8 +146,8 @@ def convert_finiteelement(element, **kwargs): if element.family() == "Lagrange": if kind == 'spectral': lmbda = finat.GaussLobattoLegendre - elif kind == 'integral': - lmbda = finat.IntegratedLegendre + elif kind.startswith('integral'): + lmbda = partial(finat.IntegratedLegendre, variant=kind) elif kind in ['fdm', 'fdm_ipdg'] and is_interval: lmbda = finat.FDMLagrange elif kind == 'fdm_quadrature' and is_interval: @@ -173,8 +173,8 @@ def convert_finiteelement(element, **kwargs): elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: if kind == 'spectral': lmbda = finat.GaussLegendre - elif kind == 'integral': - lmbda = finat.Legendre + elif kind.startswith('integral'): + lmbda = partial(finat.Legendre, variant=kind) elif kind in ['fdm', 'fdm_quadrature'] and is_interval: lmbda = finat.FDMDiscontinuousLagrange elif kind == 'fdm_ipdg' and is_interval: diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 73a76374b3..54aac54a91 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -319,7 +319,7 @@ def set_quad_rule(params, cell, integral_type, functions): else: raise ValueError("Can't find a maximal complex") - fiat_cell = fiat_cell.construct_subcomplex(integration_dim) + integration_cell = fiat_cell.construct_subcomplex(integration_dim) quad_rule = make_quadrature(fiat_cell, quadrature_degree, scheme=scheme) params["quadrature_rule"] = quad_rule From f70503468212a8de1b81a46f431dfe3527aec242 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Thu, 4 Apr 2024 18:33:18 -0500 Subject: [PATCH 768/809] use finat's max complex logic --- tsfc/kernel_interface/common.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 54aac54a91..4fe9f5432c 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -8,6 +8,7 @@ import gem.impero_utils as impero_utils import numpy from FIAT.reference_element import TensorProductCell +from finat.cell_tools import max_complex from finat.quadrature import AbstractQuadratureRule, make_quadrature from gem.node import traversal from gem.optimise import constant_fold_zero @@ -313,14 +314,10 @@ def set_quad_rule(params, cell, integral_type, functions): finat_elements = set(create_element(f.ufl_element()) for f in functions if f.ufl_element().family() != "Real") fiat_cells = [fiat_cell] + [finat_el.complex for finat_el in finat_elements] - max_cell = max(fiat_cells) - if all(max_cell >= b for b in fiat_cells): - fiat_cell = max_cell - else: - raise ValueError("Can't find a maximal complex") + max_cell = max_complex(fiat_cells) integration_cell = fiat_cell.construct_subcomplex(integration_dim) - quad_rule = make_quadrature(fiat_cell, quadrature_degree, scheme=scheme) + quad_rule = make_quadrature(integration_cell, quadrature_degree, scheme=scheme) params["quadrature_rule"] = quad_rule if not isinstance(quad_rule, AbstractQuadratureRule): From 2c04072e615d36d11d1361a84e56f63af5802f16 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Thu, 4 Apr 2024 18:36:01 -0500 Subject: [PATCH 769/809] fix an error, flake8 --- tsfc/kernel_interface/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 4fe9f5432c..4b0370e906 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -316,7 +316,7 @@ def set_quad_rule(params, cell, integral_type, functions): fiat_cells = [fiat_cell] + [finat_el.complex for finat_el in finat_elements] max_cell = max_complex(fiat_cells) - integration_cell = fiat_cell.construct_subcomplex(integration_dim) + integration_cell = max_cell.construct_subcomplex(integration_dim) quad_rule = make_quadrature(integration_cell, quadrature_degree, scheme=scheme) params["quadrature_rule"] = quad_rule From a16c5daa13944c161e9cd75a78b0354f6ac590b9 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 4 Apr 2024 18:43:55 -0500 Subject: [PATCH 770/809] lower integral type on the final cell --- tsfc/kernel_interface/common.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 4fe9f5432c..18fd363f0a 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -309,13 +309,12 @@ def set_quad_rule(params, cell, integral_type, functions): if isinstance(quad_rule, str): scheme = quad_rule fiat_cell = as_fiat_cell(cell) - integration_dim, _ = lower_integral_type(fiat_cell, integral_type) - finat_elements = set(create_element(f.ufl_element()) for f in functions if f.ufl_element().family() != "Real") fiat_cells = [fiat_cell] + [finat_el.complex for finat_el in finat_elements] - max_cell = max_complex(fiat_cells) + fiat_cell = max_complex(fiat_cells) + integration_dim, _ = lower_integral_type(fiat_cell, integral_type) integration_cell = fiat_cell.construct_subcomplex(integration_dim) quad_rule = make_quadrature(integration_cell, quadrature_degree, scheme=scheme) params["quadrature_rule"] = quad_rule From 71411c29d1548ea7dd8fd86fc313c4ac809e2ffd Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 12 Apr 2024 11:35:08 -0500 Subject: [PATCH 771/809] register HCT --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index e570bbeaa8..ac73a681fb 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -52,6 +52,7 @@ "Hermite": finat.Hermite, "Kong-Mulder-Veldhuizen": finat.KongMulderVeldhuizen, "Argyris": finat.Argyris, + "Hsieh-Clough-Tocher": finat.HsiehCloughTocher, "Mardal-Tai-Winther": finat.MardalTaiWinther, "Morley": finat.Morley, "Bell": finat.Bell, From 47653dc9478f2d9d61e6dcbe23c44b6346b422f6 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Fri, 19 Apr 2024 22:34:09 -0500 Subject: [PATCH 772/809] Add reduced element --- tsfc/finatinterface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index ac73a681fb..320b3e3218 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -19,14 +19,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with FFC. If not, see . -from functools import singledispatch, partial import weakref +from functools import partial, singledispatch import FIAT import finat -import ufl import finat.ufl - +import ufl __all__ = ("as_fiat_cell", "create_base_element", "create_element", "supported_elements") @@ -53,6 +52,7 @@ "Kong-Mulder-Veldhuizen": finat.KongMulderVeldhuizen, "Argyris": finat.Argyris, "Hsieh-Clough-Tocher": finat.HsiehCloughTocher, + "Reduced-Hsieh-Clough-Tocher": finat.ReducedHsiehCloughTocher, "Mardal-Tai-Winther": finat.MardalTaiWinther, "Morley": finat.Morley, "Bell": finat.Bell, From 9a3b9475258842f0dc4d111f74b6b2e217e30183 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 28 Apr 2024 14:42:18 +0100 Subject: [PATCH 773/809] add the Johnson-Mercier macro element --- tsfc/finatinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 320b3e3218..8e6dc70e65 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -46,6 +46,8 @@ "Gauss-Lobatto-Legendre": finat.GaussLobattoLegendre, "HDiv Trace": finat.HDivTrace, "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, + "Johnson-Mercier": finat.JohnsonMercier, + "Reduced-Johnson-Mercier": finat.ReducedJohnsonMercier, "Nonconforming Arnold-Winther": finat.ArnoldWintherNC, "Conforming Arnold-Winther": finat.ArnoldWinther, "Hermite": finat.Hermite, From c8cb7d7479c69b27e380e80c2dd29eda15cb5b2d Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 6 May 2024 10:46:24 +0100 Subject: [PATCH 774/809] Support covariant contravariant piola mapping for H(curl div) --- tsfc/ufl_utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index 0cec4dbf26..bc5069b608 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -403,6 +403,10 @@ def apply_mapping(expression, element, domain): G(X) = det(J)^2 K g(x) K^T i.e. G_il(X)=(detJ)^2 K_ij g_jk K_lk + 'covariant contravariant piola' mapping for g: + + G(X) = det(J) J^T g(x) K^T i.e. G_il(X) = det(J) J_ji g_jk(x) K_lk + If 'contravariant piola' or 'covariant piola' (or their double variants) are applied to a matrix-valued function, the appropriate mappings are applied row-by-row. @@ -442,6 +446,13 @@ def apply_mapping(expression, element, domain): *k, i, j, m, n = indices(len(expression.ufl_shape) + 2) kmn = (*k, m, n) rexpression = as_tensor(detJ**2 * K[i, m] * expression[kmn] * K[j, n], (*k, i, j)) + elif mapping == "covariant contravariant piola": + J = Jacobian(mesh) + K = JacobianInverse(mesh) + detJ = JacobianDeterminant(mesh) + *k, i, j, m, n = indices(len(expression.ufl_shape) + 2) + kmn = (*k, m, n) + rexpression = as_tensor(detJ * J[m, i] * expression[kmn] * K[j, n], (*k, i, j)) elif mapping == "symmetries": # This tells us how to get from the pieces of the reference # space expression to the physical space one. From 9a5cc7bb009723ad0c81c66eaeb6ab8b2496a73a Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 6 May 2024 11:09:57 +0100 Subject: [PATCH 775/809] Add GLS H(curl div) element --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 320b3e3218..0e6e741ed1 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -61,6 +61,7 @@ "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, "Raviart-Thomas": finat.RaviartThomas, "Regge": finat.Regge, + "Gopalakrishnan-Lederer-Schoberl": finat.GopalakrishnanLedererSchoberl, "BDMCE": finat.BrezziDouglasMariniCubeEdge, "BDMCF": finat.BrezziDouglasMariniCubeFace, # These require special treatment below From 17776a2d8af3dadb3de297a885d39383418eea7a Mon Sep 17 00:00:00 2001 From: Jack Betteridge <43041811+JDBetteridge@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:30:55 +0100 Subject: [PATCH 776/809] JDBetteridge/numpy2 rebase (#313) * Numpy 2.0 compat * fixup --------- Co-authored-by: Connor Ward --- tsfc/loopy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index be668d5797..e90899d84d 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -109,7 +109,7 @@ def assign_dtypes(expressions, scalar_type): mapper = Memoizer(_assign_dtype) mapper.scalar_type = scalar_type mapper.real_type = numpy.finfo(scalar_type).dtype - return [(e, numpy.find_common_type(mapper(e), [])) for e in expressions] + return [(e, numpy.result_type(*mapper(e))) for e in expressions] class LoopyContext(object): From aaf18772eb22127f2821f4fa6c5a8d9dcd2b4299 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 11 Jun 2024 09:53:19 +0100 Subject: [PATCH 777/809] Remove ReducedJohnsonMercier --- tsfc/finatinterface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 8e6dc70e65..659a69dc57 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -47,7 +47,6 @@ "HDiv Trace": finat.HDivTrace, "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, "Johnson-Mercier": finat.JohnsonMercier, - "Reduced-Johnson-Mercier": finat.ReducedJohnsonMercier, "Nonconforming Arnold-Winther": finat.ArnoldWintherNC, "Conforming Arnold-Winther": finat.ArnoldWinther, "Hermite": finat.Hermite, From 1ea9a33aa68c9c0f3daa11e8ba9e81eacb65a17b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 17 Jun 2024 19:02:31 +0100 Subject: [PATCH 778/809] Zany transformations in 3D --- tsfc/fem.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index de8f5ee622..532e34d026 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -21,7 +21,6 @@ from ufl.domain import extract_unique_domain from FIAT.reference_element import make_affine_mapping -from FIAT.reference_element import UFCSimplex import gem from gem.node import traversal @@ -175,31 +174,32 @@ def detJ_at(self, point): return map_expr_dag(context.translator, expr) def reference_normals(self): - if not (isinstance(self.interface.fiat_cell, UFCSimplex) and - self.interface.fiat_cell.get_spatial_dimension() == 2): - raise NotImplementedError("Only works for triangles for now") - return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_normal(i) for i in range(3)])) + cell = self.interface.fiat_cell + sd = cell.get_spatial_dimension() + num_faces = len(cell.get_topology()[sd-1]) + + return gem.Literal(numpy.asarray([cell.compute_normal(i) for i in range(num_faces)])) def reference_edge_tangents(self): - return gem.Literal(numpy.asarray([self.interface.fiat_cell.compute_edge_tangent(i) for i in range(3)])) + cell = self.interface.fiat_cell + num_edges = len(cell.get_topology()[1]) + return gem.Literal(numpy.asarray([cell.compute_edge_tangent(i) for i in range(num_edges)])) def physical_tangents(self): - if not (isinstance(self.interface.fiat_cell, UFCSimplex) and - self.interface.fiat_cell.get_spatial_dimension() == 2): - raise NotImplementedError("Only works for triangles for now") - - rts = [self.interface.fiat_cell.compute_tangents(1, f)[0] for f in range(3)] - jac = self.jacobian_at([1/3, 1/3]) - + cell = self.interface.fiat_cell + sd = cell.get_spatial_dimension() + num_edges = len(cell.get_topology()[1]) els = self.physical_edge_lengths() + rts = gem.ListTensor([cell.compute_tangents(1, i)[0] / els[i] for i in range(num_edges)]) + jac = self.jacobian_at(cell.make_points(sd, 0, sd+1)[0]) - return gem.ListTensor([[(jac[0, 0]*rts[i][0] + jac[0, 1]*rts[i][1]) / els[i], - (jac[1, 0]*rts[i][0] + jac[1, 1]*rts[i][1]) / els[i]] - for i in range(3)]) + return rts @ jac.T def physical_normals(self): + cell = self.interface.fiat_cell + num_edges = len(cell.get_topology()[1]) pts = self.physical_tangents() - return gem.ListTensor([[pts[i, 1], -1*pts[i, 0]] for i in range(3)]) + return gem.ListTensor([[pts[i, 1], -1*pts[i, 0]] for i in range(num_edges)]) def physical_edge_lengths(self): expr = ufl.classes.CellEdgeVectors(extract_unique_domain(self.mt.terminal)) @@ -208,8 +208,11 @@ def physical_edge_lengths(self): elif self.mt.restriction == '-': expr = NegativeRestricted(expr) - expr = ufl.as_vector([ufl.sqrt(ufl.dot(expr[i, :], expr[i, :])) for i in range(3)]) - config = {"point_set": PointSingleton([1/3, 1/3])} + cell = self.interface.fiat_cell + sd = cell.get_spatial_dimension() + num_edges = len(cell.get_topology()[1]) + expr = ufl.as_vector([ufl.sqrt(ufl.dot(expr[i, :], expr[i, :])) for i in range(num_edges)]) + config = {"point_set": PointSingleton(cell.make_points(sd, 0, sd+1)[0])} config.update(self.config) context = PointSetContext(**config) expr = self.preprocess(expr, context) From dc0c5fbdeb1ef8ed14ebcbbddd742475b74e2c82 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Tue, 18 Jun 2024 13:18:44 -0500 Subject: [PATCH 779/809] make failure explicit in non-triangle physical normal --- tsfc/fem.py | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index de8f5ee622..e1f44bbf60 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -5,33 +5,30 @@ import itertools from functools import singledispatch +import gem import numpy - import ufl -from ufl.corealg.map_dag import map_expr_dag, map_expr_dags -from ufl.corealg.multifunction import MultiFunction -from ufl.classes import ( - Argument, CellCoordinate, CellEdgeVectors, CellFacetJacobian, - CellOrientation, CellOrigin, CellVertices, CellVolume, Coefficient, - FacetArea, FacetCoordinate, GeometricQuantity, Jacobian, - JacobianDeterminant, NegativeRestricted, QuadratureWeight, - PositiveRestricted, ReferenceCellVolume, ReferenceCellEdgeVectors, - ReferenceFacetVolume, ReferenceNormal, SpatialCoordinate -) -from ufl.domain import extract_unique_domain - -from FIAT.reference_element import make_affine_mapping -from FIAT.reference_element import UFCSimplex - -import gem +from FIAT.reference_element import UFCSimplex, make_affine_mapping +from finat.physically_mapped import (NeedsCoordinateMappingElement, + PhysicalGeometry) +from finat.point_set import PointSet, PointSingleton +from finat.quadrature import make_quadrature from gem.node import traversal -from gem.optimise import ffc_rounding, constant_fold_zero +from gem.optimise import constant_fold_zero, ffc_rounding from gem.unconcatenate import unconcatenate from gem.utils import cached_property - -from finat.physically_mapped import PhysicalGeometry, NeedsCoordinateMappingElement -from finat.point_set import PointSet, PointSingleton -from finat.quadrature import make_quadrature +from ufl.classes import (Argument, CellCoordinate, CellEdgeVectors, + CellFacetJacobian, CellOrientation, CellOrigin, + CellVertices, CellVolume, Coefficient, FacetArea, + FacetCoordinate, GeometricQuantity, Jacobian, + JacobianDeterminant, NegativeRestricted, + PositiveRestricted, QuadratureWeight, + ReferenceCellEdgeVectors, ReferenceCellVolume, + ReferenceFacetVolume, ReferenceNormal, + SpatialCoordinate) +from ufl.corealg.map_dag import map_expr_dag, map_expr_dags +from ufl.corealg.multifunction import MultiFunction +from ufl.domain import extract_unique_domain from tsfc import ufl2gem from tsfc.finatinterface import as_fiat_cell, create_element @@ -40,8 +37,8 @@ construct_modified_terminal) from tsfc.parameters import is_complex from tsfc.ufl_utils import (ModifiedTerminalMixin, PickRestriction, - entity_avg, one_times, simplify_abs, - preprocess_expression, TSFCConstantMixin) + TSFCConstantMixin, entity_avg, one_times, + preprocess_expression, simplify_abs) class ContextBase(ProxyKernelInterface): @@ -198,6 +195,10 @@ def physical_tangents(self): for i in range(3)]) def physical_normals(self): + if not (isinstance(self.interface.fiat_cell, UFCSimplex) and + self.interface.fiat_cell.get_spatial_dimension() == 2): + raise NotImplementedError("Only works for triangles for now") + pts = self.physical_tangents() return gem.ListTensor([[pts[i, 1], -1*pts[i, 0]] for i in range(3)]) @@ -443,7 +444,8 @@ def callback(facet_i): @translate.register(ReferenceCellEdgeVectors) def translate_reference_cell_edge_vectors(terminal, mt, ctx): - from FIAT.reference_element import TensorProductCell as fiat_TensorProductCell + from FIAT.reference_element import \ + TensorProductCell as fiat_TensorProductCell fiat_cell = ctx.fiat_cell if isinstance(fiat_cell, fiat_TensorProductCell): raise NotImplementedError("ReferenceCellEdgeVectors not implemented on TensorProductElements yet") From df877b8c1fec06109ec7eb6d29a2d5f1587498fd Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Tue, 18 Jun 2024 13:34:18 -0500 Subject: [PATCH 780/809] disable the physical normals on non-ufc-triangle --- tsfc/fem.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index 3c7a0e466f..e7b5fc8b2b 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -8,7 +8,7 @@ import gem import numpy import ufl -from FIAT.reference_element import make_affine_mapping +from FIAT.reference_element import UFCSimplex, make_affine_mapping from finat.physically_mapped import (NeedsCoordinateMappingElement, PhysicalGeometry) from finat.point_set import PointSet, PointSingleton @@ -195,6 +195,9 @@ def physical_tangents(self): def physical_normals(self): cell = self.interface.fiat_cell + if (not isinstance(cell, UFCSimplex)) and cell.get_dimension() == 2: + raise NotImplementedError("Can't do physical normals on that cell yet") + num_edges = len(cell.get_topology()[1]) pts = self.physical_tangents() return gem.ListTensor([[pts[i, 1], -1*pts[i, 0]] for i in range(num_edges)]) From a7bc5f464cd3a6da2bd240fab5263236a567db53 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Tue, 18 Jun 2024 13:35:58 -0500 Subject: [PATCH 781/809] lint --- tsfc/fem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index e7b5fc8b2b..fc0ce486aa 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -197,7 +197,7 @@ def physical_normals(self): cell = self.interface.fiat_cell if (not isinstance(cell, UFCSimplex)) and cell.get_dimension() == 2: raise NotImplementedError("Can't do physical normals on that cell yet") - + num_edges = len(cell.get_topology()[1]) pts = self.physical_tangents() return gem.ListTensor([[pts[i, 1], -1*pts[i, 0]] for i in range(num_edges)]) From 63d7203fc15349a33cae664ad1251467072a66df Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Wed, 19 Jun 2024 09:43:01 -0500 Subject: [PATCH 782/809] Update logic --- tsfc/fem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/fem.py b/tsfc/fem.py index fc0ce486aa..27439f1c6e 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -195,7 +195,7 @@ def physical_tangents(self): def physical_normals(self): cell = self.interface.fiat_cell - if (not isinstance(cell, UFCSimplex)) and cell.get_dimension() == 2: + if not (isinstance(cell, UFCSimplex) and cell.get_dimension() == 2): raise NotImplementedError("Can't do physical normals on that cell yet") num_edges = len(cell.get_topology()[1]) From 8abe8b3fc76748900c36d61a1fd2235b216c9382 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 27 Jun 2024 15:29:28 +0100 Subject: [PATCH 783/809] Enable variants for Argyris --- tsfc/finatinterface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 659a69dc57..2c14e6d1f0 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -170,7 +170,8 @@ def convert_finiteelement(element, **kwargs): # Let FIAT handle the general case lmbda = partial(finat.Lagrange, variant=kind) elif element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", - "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)"}: + "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)", + "Argyris"}: lmbda = partial(lmbda, variant=element.variant()) elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: if kind == 'spectral': From 3b0d579ceba30ad7c5a8cc1c31cdb447a7a9bee2 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 28 Jun 2024 23:01:22 +0100 Subject: [PATCH 784/809] numpy 2.0 fix --- tsfc/loopy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index be668d5797..e90899d84d 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -109,7 +109,7 @@ def assign_dtypes(expressions, scalar_type): mapper = Memoizer(_assign_dtype) mapper.scalar_type = scalar_type mapper.real_type = numpy.finfo(scalar_type).dtype - return [(e, numpy.find_common_type(mapper(e), [])) for e in expressions] + return [(e, numpy.result_type(*mapper(e))) for e in expressions] class LoopyContext(object): From 01a3ee1ccd0065d1bbdbc84cc9653ac2e219b821 Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Thu, 18 Jul 2024 10:43:39 -0500 Subject: [PATCH 785/809] Add PS6 element --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 2c14e6d1f0..0e0b4aa306 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -53,6 +53,7 @@ "Kong-Mulder-Veldhuizen": finat.KongMulderVeldhuizen, "Argyris": finat.Argyris, "Hsieh-Clough-Tocher": finat.HsiehCloughTocher, + "QuadraticPowellSabin6": finat.QuadraticPowellSabin6, "Reduced-Hsieh-Clough-Tocher": finat.ReducedHsiehCloughTocher, "Mardal-Tai-Winther": finat.MardalTaiWinther, "Morley": finat.Morley, From 162bde1703675105176d1ba0133e0c6825b286bb Mon Sep 17 00:00:00 2001 From: Rob Kirby Date: Fri, 19 Jul 2024 12:27:33 -0500 Subject: [PATCH 786/809] add PS12 to finat interface --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 0e0b4aa306..3e83281901 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -54,6 +54,7 @@ "Argyris": finat.Argyris, "Hsieh-Clough-Tocher": finat.HsiehCloughTocher, "QuadraticPowellSabin6": finat.QuadraticPowellSabin6, + "QuadraticPowellSabin12": finat.QuadraticPowellSabin12, "Reduced-Hsieh-Clough-Tocher": finat.ReducedHsiehCloughTocher, "Mardal-Tai-Winther": finat.MardalTaiWinther, "Morley": finat.Morley, From bdb8daf73d4d308f691ad65c24056755f6866413 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 19 Aug 2024 15:10:45 +0100 Subject: [PATCH 787/809] Clean up variants --- tsfc/finatinterface.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 3e83281901..d7b4f66cf9 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -143,11 +143,15 @@ def convert_finiteelement(element, **kwargs): return finat.FlattenedDimensions(finat_elem), deps kind = element.variant() - is_interval = element.cell.cellname() == 'interval' if kind is None: kind = 'spectral' # default variant + is_interval = element.cell.cellname() == 'interval' - if element.family() == "Lagrange": + if element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", + "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)", + "Argyris"}: + lmbda = partial(lmbda, variant=element.variant()) + elif element.family() == "Lagrange": if kind == 'spectral': lmbda = finat.GaussLobattoLegendre elif kind.startswith('integral'): @@ -171,10 +175,6 @@ def convert_finiteelement(element, **kwargs): else: # Let FIAT handle the general case lmbda = partial(finat.Lagrange, variant=kind) - elif element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", - "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)", - "Argyris"}: - lmbda = partial(lmbda, variant=element.variant()) elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: if kind == 'spectral': lmbda = finat.GaussLegendre @@ -186,6 +186,8 @@ def convert_finiteelement(element, **kwargs): lmbda = lambda *args: finat.DiscontinuousElement(finat.FDMLagrange(*args)) elif kind in 'fdm_broken' and is_interval: lmbda = finat.FDMBrokenL2 + elif kind in ['demkowicz', 'fdm']: + lmbda = partial(finat.Legendre, variant=kind) elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] @@ -195,16 +197,10 @@ def convert_finiteelement(element, **kwargs): else: # Let FIAT handle the general case lmbda = partial(finat.DiscontinuousLagrange, variant=kind) - elif element.family() == ["DPC", "DPC L2"]: - if element.cell.geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.cell.hypercube(2)) - elif element.cell.geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.cell.hypercube(3)) - elif element.family() == "S": - if element.cell.geometric_dimension() == 2: - element = element.reconstruct(cell=ufl.cell.hypercube(2)) - elif element.cell.geometric_dimension() == 3: - element = element.reconstruct(cell=ufl.cell.hypercube(3)) + elif element.family() == ["DPC", "DPC L2", "S"]: + dim = element.cell.geometric_dimension() + if dim > 1: + element = element.reconstruct(cell=ufl.cell.hypercube(dim)) return lmbda(cell, element.degree()), set() From d4fb7d84b4262c35d93f765c8e826bbf4399aa98 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 14 Sep 2024 10:58:04 +0100 Subject: [PATCH 788/809] Add the Alfeld-Sorokina triangular macroelement --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index d7b4f66cf9..d30f6104a0 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -57,6 +57,7 @@ "QuadraticPowellSabin12": finat.QuadraticPowellSabin12, "Reduced-Hsieh-Clough-Tocher": finat.ReducedHsiehCloughTocher, "Mardal-Tai-Winther": finat.MardalTaiWinther, + "Alfeld-Sorokina": finat.AlfeldSorokina, "Morley": finat.Morley, "Bell": finat.Bell, "Lagrange": finat.Lagrange, From 91dc72d28ffd10024bb8f706d1d4435165ab9e80 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 19 Sep 2024 23:08:28 +0100 Subject: [PATCH 789/809] Add the Arnold-Qin and Reduced Arnold-Qin macroelements --- tsfc/finatinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index d30f6104a0..70a0a1a733 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -58,6 +58,8 @@ "Reduced-Hsieh-Clough-Tocher": finat.ReducedHsiehCloughTocher, "Mardal-Tai-Winther": finat.MardalTaiWinther, "Alfeld-Sorokina": finat.AlfeldSorokina, + "Arnold-Qin": finat.ArnoldQin, + "Reduced-Arnold-Qin": finat.ReducedArnoldQin, "Morley": finat.Morley, "Bell": finat.Bell, "Lagrange": finat.Lagrange, From c419e1a545c53119517c27509e2ca8b254f33dad Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 25 Sep 2024 14:16:04 +0100 Subject: [PATCH 790/809] Add Bernardi-Raugel --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 70a0a1a733..4f63a2c194 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -34,6 +34,7 @@ supported_elements = { # These all map directly to FInAT elements "Bernstein": finat.Bernstein, + "Bernardi-Raugel": finat.BernardiRaugel, "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": finat.BrezziDouglasFortinMarini, "Bubble": finat.Bubble, From 9e08e472a45aaf5c0119d8defc9d9b4de24cf4f8 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 1 Oct 2024 14:35:27 +0100 Subject: [PATCH 791/809] add Christiansen-Hu --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 4f63a2c194..8541905e0a 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -61,6 +61,7 @@ "Alfeld-Sorokina": finat.AlfeldSorokina, "Arnold-Qin": finat.ArnoldQin, "Reduced-Arnold-Qin": finat.ReducedArnoldQin, + "Christiansen-Hu": finat.ChristiansenHu, "Morley": finat.Morley, "Bell": finat.Bell, "Lagrange": finat.Lagrange, From 6d992c52bb58b67b691db9fd78a8bd79c4b95577 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 3 Oct 2024 16:05:50 +0100 Subject: [PATCH 792/809] add Guzman-Neilan --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 8541905e0a..8087086831 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -62,6 +62,7 @@ "Arnold-Qin": finat.ArnoldQin, "Reduced-Arnold-Qin": finat.ReducedArnoldQin, "Christiansen-Hu": finat.ChristiansenHu, + "Guzman-Neilan": finat.GuzmanNeilan, "Morley": finat.Morley, "Bell": finat.Bell, "Lagrange": finat.Lagrange, From 13ea69f25f1dc70e07831a3b7fd6bf0ecc72a50f Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 3 Oct 2024 17:49:02 +0100 Subject: [PATCH 793/809] Add Guzman-Neilan Bubble --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 8087086831..3357619c49 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -63,6 +63,7 @@ "Reduced-Arnold-Qin": finat.ReducedArnoldQin, "Christiansen-Hu": finat.ChristiansenHu, "Guzman-Neilan": finat.GuzmanNeilan, + "Guzman-Neilan Bubble": finat.GuzmanNeilanBubble, "Morley": finat.Morley, "Bell": finat.Bell, "Lagrange": finat.Lagrange, From 00a180d69d27bda14207d4ccac95057587f32952 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 6 Oct 2024 17:03:18 +0100 Subject: [PATCH 794/809] add Bernardi-Raugel Bubble --- tsfc/finatinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 3357619c49..dcd90eca75 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -35,6 +35,7 @@ # These all map directly to FInAT elements "Bernstein": finat.Bernstein, "Bernardi-Raugel": finat.BernardiRaugel, + "Bernardi-Raugel Bubble": finat.BernardiRaugelBubble, "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, "Brezzi-Douglas-Fortin-Marini": finat.BrezziDouglasFortinMarini, "Bubble": finat.Bubble, From efa8e4a358fee73b0d9321eca253e9bfd34c9d23 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 16 Oct 2024 16:55:56 +0100 Subject: [PATCH 795/809] Guzman-Neilan H1(div) macroelement (#320) * Guzman-Neilan H1(div) macroelement --- tsfc/finatinterface.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index dcd90eca75..f9f461fc4f 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -63,8 +63,10 @@ "Arnold-Qin": finat.ArnoldQin, "Reduced-Arnold-Qin": finat.ReducedArnoldQin, "Christiansen-Hu": finat.ChristiansenHu, - "Guzman-Neilan": finat.GuzmanNeilan, + "Guzman-Neilan 1st kind H1": finat.GuzmanNeilanFirstKindH1, + "Guzman-Neilan 2nd kind H1": finat.GuzmanNeilanSecondKindH1, "Guzman-Neilan Bubble": finat.GuzmanNeilanBubble, + "Guzman-Neilan H1(div)": finat.GuzmanNeilanH1div, "Morley": finat.Morley, "Bell": finat.Bell, "Lagrange": finat.Lagrange, From bb08e004c683f02921edd941a6c2634b847f8106 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 21 Oct 2024 13:14:48 +0100 Subject: [PATCH 796/809] Remove old warning about COFFEE (#321) --- tsfc/driver.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index f67423151a..95fd43fb13 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -47,7 +47,7 @@ """ -def compile_form(form, prefix="form", parameters=None, interface=None, diagonal=False, log=False, **kwargs): +def compile_form(form, prefix="form", parameters=None, interface=None, diagonal=False, log=False): """Compiles a UFL form into a set of assembly kernels. :arg form: UFL form @@ -59,18 +59,6 @@ def compile_form(form, prefix="form", parameters=None, interface=None, diagonal= """ cpu_time = time.time() - if "coffee" in kwargs: - use_coffee = kwargs.pop("coffee") - if use_coffee: - raise ValueError("COFFEE support has been removed from TSFC") - else: - import warnings - warnings.warn( - "The coffee kwarg has been removed from compile_form as COFFEE " - "is no longer a supported backend.", FutureWarning) - if kwargs: - raise ValueError("compile_form called with unexpected arguments") - assert isinstance(form, Form) GREEN = "\033[1;37;32m%s\033[0m" From 88319e6883bf3bf0b31cf8d43b9a3805eb519634 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 22 Oct 2024 15:51:27 +0100 Subject: [PATCH 797/809] Use make_kernel to force entrypoint registration --------- Co-authored-by: Jack Betteridge <43041811+JDBetteridge@users.noreply.github.com> --- tsfc/loopy.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index e90899d84d..943143ffe3 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -246,9 +246,17 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name domains = create_domains(ctx.index_extent.items()) # Create loopy kernel - knl = lp.make_function(domains, instructions, data, name=kernel_name, target=target, - seq_dependencies=True, silenced_warnings=["summing_if_branches_ops"], - lang_version=(2018, 2), preambles=preamble) + knl = lp.make_kernel( + domains, + instructions, + data, + name=kernel_name, + target=target, + seq_dependencies=True, + silenced_warnings=["summing_if_branches_ops"], + lang_version=(2018, 2), + preambles=preamble + ) # Prevent loopy interchange by loopy knl = lp.prioritize_loops(knl, ",".join(ctx.index_extent.keys())) From aee878a5a0f63acdedcc8065c8dcc42debc98e3f Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 24 Oct 2024 17:58:26 +0100 Subject: [PATCH 798/809] Refactor variants --- tsfc/finatinterface.py | 75 ++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index f9f461fc4f..2e5247350a 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -20,7 +20,7 @@ # along with FFC. If not, see . import weakref -from functools import partial, singledispatch +from functools import singledispatch import FIAT import finat @@ -124,6 +124,23 @@ def convert(element, **kwargs): raise ValueError("Unsupported element type %s" % type(element)) +cg_interval_variants = { + "fdm": finat.FDMLagrange, + "fdm_ipdg": finat.FDMLagrange, + "fdm_quadrature": finat.FDMQuadrature, + "fdm_broken": finat.FDMBrokenH1, + "fdm_hermite": finat.FDMHermite, +} + + +dg_interval_variants = { + "fdm": finat.FDMDiscontinuousLagrange, + "fdm_quadrature": finat.FDMDiscontinuousLagrange, + "fdm_ipdg": lambda *args: finat.DiscontinuousElement(finat.FDMLagrange(*args)), + "fdm_broken": finat.FDMBrokenL2, +} + + # Base finite elements first @convert.register(finat.ufl.FiniteElement) def convert_finiteelement(element, **kwargs): @@ -152,30 +169,19 @@ def convert_finiteelement(element, **kwargs): finat_elem, deps = _create_element(element, **kwargs) return finat.FlattenedDimensions(finat_elem), deps + kw = {} kind = element.variant() if kind is None: kind = 'spectral' # default variant - is_interval = element.cell.cellname() == 'interval' - if element.family() in {"Raviart-Thomas", "Nedelec 1st kind H(curl)", - "Brezzi-Douglas-Marini", "Nedelec 2nd kind H(curl)", - "Argyris"}: - lmbda = partial(lmbda, variant=element.variant()) - elif element.family() == "Lagrange": + if element.family() == "Lagrange": if kind == 'spectral': lmbda = finat.GaussLobattoLegendre - elif kind.startswith('integral'): - lmbda = partial(finat.IntegratedLegendre, variant=kind) - elif kind in ['fdm', 'fdm_ipdg'] and is_interval: - lmbda = finat.FDMLagrange - elif kind == 'fdm_quadrature' and is_interval: - lmbda = finat.FDMQuadrature - elif kind == 'fdm_broken' and is_interval: - lmbda = finat.FDMBrokenH1 - elif kind == 'fdm_hermite' and is_interval: - lmbda = finat.FDMHermite - elif kind in ['demkowicz', 'fdm']: - lmbda = partial(finat.IntegratedLegendre, variant=kind) + elif element.cell.cellname() == "interval" and kind in cg_interval_variants: + lmbda = cg_interval_variants[kind] + elif kind.startswith('integral') or kind in ['demkowicz', 'fdm']: + lmbda = finat.IntegratedLegendre + kw["variant"] = kind elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] @@ -184,20 +190,17 @@ def convert_finiteelement(element, **kwargs): return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction), deps else: # Let FIAT handle the general case - lmbda = partial(finat.Lagrange, variant=kind) + lmbda = finat.Lagrange + kw["variant"] = kind + elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: if kind == 'spectral': lmbda = finat.GaussLegendre - elif kind.startswith('integral'): - lmbda = partial(finat.Legendre, variant=kind) - elif kind in ['fdm', 'fdm_quadrature'] and is_interval: - lmbda = finat.FDMDiscontinuousLagrange - elif kind == 'fdm_ipdg' and is_interval: - lmbda = lambda *args: finat.DiscontinuousElement(finat.FDMLagrange(*args)) - elif kind in 'fdm_broken' and is_interval: - lmbda = finat.FDMBrokenL2 - elif kind in ['demkowicz', 'fdm']: - lmbda = partial(finat.Legendre, variant=kind) + elif element.cell.cellname() == "interval" and kind in dg_interval_variants: + lmbda = dg_interval_variants[kind] + elif kind.startswith('integral') or kind in ['demkowicz', 'fdm']: + lmbda = finat.Legendre + kw["variant"] = kind elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] @@ -206,13 +209,13 @@ def convert_finiteelement(element, **kwargs): return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction, continuous=False), deps else: # Let FIAT handle the general case - lmbda = partial(finat.DiscontinuousLagrange, variant=kind) - elif element.family() == ["DPC", "DPC L2", "S"]: - dim = element.cell.geometric_dimension() - if dim > 1: - element = element.reconstruct(cell=ufl.cell.hypercube(dim)) + lmbda = finat.DiscontinuousLagrange + kw["variant"] = kind + + elif element.variant() is not None: + kw["variant"] = element.variant() - return lmbda(cell, element.degree()), set() + return lmbda(cell, element.degree(), **kw), set() # Element modifiers and compound element types From c9449e83642ee30d012df786d5088531ac0e67f7 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 30 Oct 2024 15:35:29 +0000 Subject: [PATCH 799/809] GLS 1st and 2nd kinds --- tsfc/finatinterface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index a8863d9dff..36d8f6d6a2 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -75,7 +75,8 @@ "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, "Raviart-Thomas": finat.RaviartThomas, "Regge": finat.Regge, - "Gopalakrishnan-Lederer-Schoberl": finat.GopalakrishnanLedererSchoberl, + "Gopalakrishnan-Lederer-Schoberl 1st kind": finat.GopalakrishnanLedererSchoberlFirstKind, + "Gopalakrishnan-Lederer-Schoberl 2nd kind": finat.GopalakrishnanLedererSchoberlSecondKind, "BDMCE": finat.BrezziDouglasMariniCubeEdge, "BDMCF": finat.BrezziDouglasMariniCubeFace, # These require special treatment below From 090e3048b0bbe5c0adfe752ba8e975130cac3b24 Mon Sep 17 00:00:00 2001 From: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:07:48 +0000 Subject: [PATCH 800/809] generalise VariableIndex and FlexiblyIndexed (#317) hex: enable interior facet integration --- tsfc/driver.py | 3 -- tsfc/fem.py | 54 ++++++++++++++++++++++-- tsfc/kernel_args.py | 8 ++++ tsfc/kernel_interface/__init__.py | 4 ++ tsfc/kernel_interface/common.py | 5 +++ tsfc/kernel_interface/firedrake_loopy.py | 22 +++++++++- tsfc/loopy.py | 33 ++++++++++++--- 7 files changed, 117 insertions(+), 12 deletions(-) diff --git a/tsfc/driver.py b/tsfc/driver.py index 95fd43fb13..6e3c3baaf3 100644 --- a/tsfc/driver.py +++ b/tsfc/driver.py @@ -92,9 +92,6 @@ def compile_integral(integral_data, form_data, prefix, parameters, interface, *, :arg log: bool if the Kernel should be profiled with Log events :returns: a kernel constructed by the kernel interface """ - if integral_data.domain.ufl_cell().cellname() == "hexahedron" and \ - integral_data.integral_type == "interior_facet": - raise NotImplementedError("interior facet integration in hex meshes not currently supported") parameters = preprocess_parameters(parameters) if interface is None: interface = firedrake_interface_loopy.KernelBuilder diff --git a/tsfc/fem.py b/tsfc/fem.py index 27439f1c6e..abc8bc7cb8 100644 --- a/tsfc/fem.py +++ b/tsfc/fem.py @@ -8,7 +8,9 @@ import gem import numpy import ufl -from FIAT.reference_element import UFCSimplex, make_affine_mapping +from FIAT.orientation_utils import Orientation as FIATOrientation +from FIAT.reference_element import UFCHexahedron, UFCSimplex, make_affine_mapping +from FIAT.reference_element import TensorProductCell from finat.physically_mapped import (NeedsCoordinateMappingElement, PhysicalGeometry) from finat.point_set import PointSet, PointSingleton @@ -108,6 +110,10 @@ def translator(self): # NOTE: reference cycle! return Translator(self) + @cached_property + def use_canonical_quadrature_point_ordering(self): + return isinstance(self.fiat_cell, UFCHexahedron) and self.integral_type in ['exterior_facet', 'interior_facet'] + class CoordinateMapping(PhysicalGeometry): """Callback class that provides physical geometry to FInAT elements. @@ -266,10 +272,13 @@ class PointSetContext(ContextBase): 'weight_expr', ) + @cached_property + def integration_cell(self): + return self.fiat_cell.construct_subelement(self.integration_dim) + @cached_property def quadrature_rule(self): - integration_cell = self.fiat_cell.construct_subelement(self.integration_dim) - return make_quadrature(integration_cell, self.quadrature_degree) + return make_quadrature(self.integration_cell, self.quadrature_degree) @cached_property def point_set(self): @@ -629,6 +638,11 @@ def callback(entity_id): # lives on after ditching FFC and switching to FInAT. return ffc_rounding(square, ctx.epsilon) table = ctx.entity_selector(callback, mt.restriction) + if ctx.use_canonical_quadrature_point_ordering: + quad_multiindex = ctx.quadrature_rule.point_set.indices + quad_multiindex_permuted = _make_quad_multiindex_permuted(mt, ctx) + mapper = gem.node.MemoizerArg(gem.optimise.filtered_replace_indices) + table = mapper(table, tuple(zip(quad_multiindex, quad_multiindex_permuted))) return gem.ComponentTensor(gem.Indexed(table, argument_multiindex + sigma), sigma) @@ -698,9 +712,43 @@ def take_singleton(xs): for node in traversal((result,)) if isinstance(node, gem.Literal)): result = gem.optimise.aggressive_unroll(result) + + if ctx.use_canonical_quadrature_point_ordering: + quad_multiindex = ctx.quadrature_rule.point_set.indices + quad_multiindex_permuted = _make_quad_multiindex_permuted(mt, ctx) + mapper = gem.node.MemoizerArg(gem.optimise.filtered_replace_indices) + result = mapper(result, tuple(zip(quad_multiindex, quad_multiindex_permuted))) return result +def _make_quad_multiindex_permuted(mt, ctx): + quad_rule = ctx.quadrature_rule + # Note that each quad index here represents quad points on a physical + # cell axis, but the table is indexed by indices representing the points + # on each reference cell axis, so we need to apply permutation based on the orientation. + cell = quad_rule.ref_el + quad_multiindex = quad_rule.point_set.indices + if isinstance(cell, TensorProductCell): + for comp in set(cell.cells): + extents = set(q.extent for c, q in zip(cell.cells, quad_multiindex) if c == comp) + if len(extents) != 1: + raise ValueError("Must have the same number of quadrature points in each symmetric axis") + quad_multiindex_permuted = [] + o = ctx.entity_orientation(mt.restriction) + if not isinstance(o, FIATOrientation): + raise ValueError(f"Expecting an instance of FIATOrientation : got {o}") + eo = cell.extract_extrinsic_orientation(o) + eo_perm_map = gem.Literal(quad_rule.extrinsic_orientation_permutation_map, dtype=gem.uint_type) + for ref_axis in range(len(quad_multiindex)): + io = cell.extract_intrinsic_orientation(o, ref_axis) + io_perm_map = gem.Literal(quad_rule.intrinsic_orientation_permutation_map_tuple[ref_axis], dtype=gem.uint_type) + # Effectively swap axes if needed. + ref_index = tuple((phys_index, gem.Indexed(eo_perm_map, (eo, ref_axis, phys_axis))) for phys_axis, phys_index in enumerate(quad_multiindex)) + quad_index_permuted = gem.VariableIndex(gem.FlexiblyIndexed(io_perm_map, ((0, ((io, 1), )), (0, ref_index)))) + quad_multiindex_permuted.append(quad_index_permuted) + return tuple(quad_multiindex_permuted) + + def compile_ufl(expression, context, interior_facet=False, point_sum=False): """Translate a UFL expression to GEM. diff --git a/tsfc/kernel_args.py b/tsfc/kernel_args.py index 1c79bdaef2..a397f0f937 100644 --- a/tsfc/kernel_args.py +++ b/tsfc/kernel_args.py @@ -52,3 +52,11 @@ class ExteriorFacetKernelArg(KernelArg): class InteriorFacetKernelArg(KernelArg): ... + + +class ExteriorFacetOrientationKernelArg(KernelArg): + ... + + +class InteriorFacetOrientationKernelArg(KernelArg): + ... diff --git a/tsfc/kernel_interface/__init__.py b/tsfc/kernel_interface/__init__.py index fc1942607e..5114263848 100644 --- a/tsfc/kernel_interface/__init__.py +++ b/tsfc/kernel_interface/__init__.py @@ -33,6 +33,10 @@ def cell_size(self, restriction): def entity_number(self, restriction): """Facet or vertex number as a GEM index.""" + @abstractmethod + def entity_orientation(self, restriction): + """Entity orientation as a GEM index.""" + @abstractmethod def create_element(self, element, **kwargs): """Create a FInAT element (suitable for tabulating with) given diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 18fd363f0a..945e367efc 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -91,6 +91,11 @@ def entity_number(self, restriction): # Assume self._entity_number dict is set up at this point. return self._entity_number[restriction] + def entity_orientation(self, restriction): + """Facet orientation as a GEM index.""" + # Assume self._entity_orientation dict is set up at this point. + return self._entity_orientation[restriction] + def apply_glue(self, prepare=None, finalise=None): """Append glue code for operations that are not handled in the GEM abstraction. diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index c6d671b77e..cc35a7c5b8 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -11,7 +11,7 @@ import loopy as lp -from tsfc import kernel_args +from tsfc import kernel_args, fem from tsfc.finatinterface import create_element from tsfc.kernel_interface.common import KernelBuilderBase as _KernelBuilderBase, KernelBuilderMixin, get_index_names, check_requirements, prepare_coefficient, prepare_arguments, prepare_constant from tsfc.loopy import generate as generate_loopy @@ -259,14 +259,26 @@ def __init__(self, integral_data_info, scalar_type, if integral_type in ['exterior_facet', 'exterior_facet_vert']: facet = gem.Variable('facet', (1,)) self._entity_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} + facet_orientation = gem.Variable('facet_orientation', (1,), dtype=gem.uint_type) + self._entity_orientation = {None: gem.OrientationVariableIndex(gem.Indexed(facet_orientation, (0,)))} elif integral_type in ['interior_facet', 'interior_facet_vert']: facet = gem.Variable('facet', (2,)) self._entity_number = { '+': gem.VariableIndex(gem.Indexed(facet, (0,))), '-': gem.VariableIndex(gem.Indexed(facet, (1,))) } + facet_orientation = gem.Variable('facet_orientation', (2,), dtype=gem.uint_type) + self._entity_orientation = { + '+': gem.OrientationVariableIndex(gem.Indexed(facet_orientation, (0,))), + '-': gem.OrientationVariableIndex(gem.Indexed(facet_orientation, (1,))) + } elif integral_type == 'interior_facet_horiz': self._entity_number = {'+': 1, '-': 0} + facet_orientation = gem.Variable('facet_orientation', (1,), dtype=gem.uint_type) # base mesh entity orientation + self._entity_orientation = { + '+': gem.OrientationVariableIndex(gem.Indexed(facet_orientation, (0,))), + '-': gem.OrientationVariableIndex(gem.Indexed(facet_orientation, (0,))) + } self.set_arguments(integral_data_info.arguments) self.integral_data_info = integral_data_info @@ -406,6 +418,14 @@ def construct_kernel(self, name, ctx, log=False): elif info.integral_type in ["interior_facet", "interior_facet_vert"]: int_loopy_arg = lp.GlobalArg("facet", numpy.uint32, shape=(2,)) args.append(kernel_args.InteriorFacetKernelArg(int_loopy_arg)) + # Will generalise this in the submesh PR. + if fem.PointSetContext(**self.fem_config()).use_canonical_quadrature_point_ordering: + if info.integral_type == "exterior_facet": + ext_ornt_loopy_arg = lp.GlobalArg("facet_orientation", gem.uint_type, shape=(1,)) + args.append(kernel_args.ExteriorFacetOrientationKernelArg(ext_ornt_loopy_arg)) + elif info.integral_type == "interior_facet": + int_ornt_loopy_arg = lp.GlobalArg("facet_orientation", gem.uint_type, shape=(2,)) + args.append(kernel_args.InteriorFacetOrientationKernelArg(int_ornt_loopy_arg)) for name_, shape in tabulations: tab_loopy_arg = lp.GlobalArg(name_, dtype=self.scalar_type, shape=shape) args.append(kernel_args.TabulationKernelArg(tab_loopy_arg)) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index 943143ffe3..d19da42317 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -2,6 +2,7 @@ This is the final stage of code generation in TSFC.""" +from numbers import Integral import numpy from functools import singledispatch from collections import defaultdict, OrderedDict @@ -49,6 +50,11 @@ def _assign_dtype_terminal(expression, self): return {self.scalar_type} +@_assign_dtype.register(gem.Variable) +def _assign_dtype_variable(expression, self): + return {expression.dtype or self.scalar_type} + + @_assign_dtype.register(gem.Zero) @_assign_dtype.register(gem.Identity) @_assign_dtype.register(gem.Delta) @@ -420,6 +426,16 @@ def _expression_division(expr, ctx): return p.Quotient(*(expression(c, ctx) for c in expr.children)) +@_expression.register(gem.FloorDiv) +def _expression_floordiv(expr, ctx): + return p.FloorDiv(*(expression(c, ctx) for c in expr.children)) + + +@_expression.register(gem.Remainder) +def _expression_remainder(expr, ctx): + return p.Remainder(*(expression(c, ctx) for c in expr.children)) + + @_expression.register(gem.Power) def _expression_power(expr, ctx): return p.Variable("pow")(*(expression(c, ctx) for c in expr.children)) @@ -538,12 +554,19 @@ def _expression_flexiblyindexed(expr, ctx): rank = [] for off, idxs in expr.dim2idxs: + rank_ = [expression(off, ctx)] for index, stride in idxs: - assert isinstance(index, gem.Index) - - rank_ = [off] - for index, stride in idxs: - rank_.append(p.Product((ctx.active_indices[index], stride))) + if isinstance(index, gem.Index): + rank_.append(p.Product((ctx.active_indices[index], expression(stride, ctx)))) + elif isinstance(index, gem.VariableIndex): + rank_.append(p.Product((expression(index.expression, ctx), expression(stride, ctx)))) + else: + raise ValueError(f"Expecting Index or VariableIndex, not {type(index)}") rank.append(p.Sum(tuple(rank_))) return p.Subscript(var, tuple(rank)) + + +@_expression.register(Integral) +def _expression_numbers_integral(expr, ctx): + return expr From 07aa702d6b257d30a6096e4f5e4c67202dd6b442 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 13 Nov 2024 12:33:49 +0000 Subject: [PATCH 801/809] Get value_value size from FunctionSpace --- tsfc/kernel_interface/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 945e367efc..df7e879f09 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -472,7 +472,7 @@ def prepare_coefficient(coefficient, name, interior_facet=False): if coefficient.ufl_element().family() == 'Real': # Constant - value_size = coefficient.ufl_element().value_size + value_size = coefficient.ufl_function_space().value_size expression = gem.reshape(gem.Variable(name, (value_size,)), coefficient.ufl_shape) return expression From dc51465f3ae6f97283c8cb7f5b498f50eda0300d Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 13 Nov 2024 17:31:03 +0000 Subject: [PATCH 802/809] get physical_value_shape from ufl.pullback --- tsfc/ufl_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsfc/ufl_utils.py b/tsfc/ufl_utils.py index cbc0ed4908..461926634d 100644 --- a/tsfc/ufl_utils.py +++ b/tsfc/ufl_utils.py @@ -412,9 +412,9 @@ def apply_mapping(expression, element, domain): mesh = domain if domain is not None and mesh != domain: raise NotImplementedError("Multiple domains not supported") - if expression.ufl_shape != ufl.FunctionSpace(mesh, element).value_shape: - raise ValueError(f"Mismatching shapes, got {expression.ufl_shape}, " - f"expected {ufl.FunctionSpace(mesh, element).value_shape}") + pvs = element.pullback.physical_value_shape(element, mesh) + if expression.ufl_shape != pvs: + raise ValueError(f"Mismatching shapes, got {expression.ufl_shape}, expected {pvs}") mapping = element.mapping().lower() if mapping == "identity": rexpression = expression @@ -452,7 +452,7 @@ def apply_mapping(expression, element, domain): sub_elem = element.sub_elements[0] shape = expression.ufl_shape flat = ufl.as_vector([expression[i] for i in numpy.ndindex(shape)]) - vs = ufl.FunctionSpace(mesh, sub_elem).value_shape + vs = sub_elem.pullback.physical_value_shape(sub_elem, mesh) rvs = sub_elem.reference_value_shape seen = set() rpieces = [] From 8e5c18c443137f10a3efd8f4682080bdf5d10598 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 20 Nov 2024 17:07:42 +0000 Subject: [PATCH 803/809] Clean up variants --- tsfc/finatinterface.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tsfc/finatinterface.py b/tsfc/finatinterface.py index 36d8f6d6a2..b7e3d0ad72 100644 --- a/tsfc/finatinterface.py +++ b/tsfc/finatinterface.py @@ -172,7 +172,7 @@ def convert_finiteelement(element, **kwargs): finat_elem, deps = _create_element(element, **kwargs) return finat.FlattenedDimensions(finat_elem), deps - kw = {} + finat_kwargs = {} kind = element.variant() if kind is None: kind = 'spectral' # default variant @@ -182,9 +182,9 @@ def convert_finiteelement(element, **kwargs): lmbda = finat.GaussLobattoLegendre elif element.cell.cellname() == "interval" and kind in cg_interval_variants: lmbda = cg_interval_variants[kind] - elif kind.startswith('integral') or kind in ['demkowicz', 'fdm']: + elif any(map(kind.startswith, ['integral', 'demkowicz', 'fdm'])): lmbda = finat.IntegratedLegendre - kw["variant"] = kind + finat_kwargs["variant"] = kind elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] @@ -194,16 +194,16 @@ def convert_finiteelement(element, **kwargs): else: # Let FIAT handle the general case lmbda = finat.Lagrange - kw["variant"] = kind + finat_kwargs["variant"] = kind elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: if kind == 'spectral': lmbda = finat.GaussLegendre elif element.cell.cellname() == "interval" and kind in dg_interval_variants: lmbda = dg_interval_variants[kind] - elif kind.startswith('integral') or kind in ['demkowicz', 'fdm']: + elif any(map(kind.startswith, ['integral', 'demkowicz', 'fdm'])): lmbda = finat.Legendre - kw["variant"] = kind + finat_kwargs["variant"] = kind elif kind in ['mgd', 'feec', 'qb', 'mse']: degree = element.degree() shift_axes = kwargs["shift_axes"] @@ -213,12 +213,12 @@ def convert_finiteelement(element, **kwargs): else: # Let FIAT handle the general case lmbda = finat.DiscontinuousLagrange - kw["variant"] = kind + finat_kwargs["variant"] = kind elif element.variant() is not None: - kw["variant"] = element.variant() + finat_kwargs["variant"] = element.variant() - return lmbda(cell, element.degree(), **kw), set() + return lmbda(cell, element.degree(), **finat_kwargs), set() # Element modifiers and compound element types From 8abfc99553c9c65b7c423d81e7bd54332c7c389a Mon Sep 17 00:00:00 2001 From: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:08:00 +0000 Subject: [PATCH 804/809] gem: attach dtype to every node (#327) --- tests/test_pickle_gem.py | 2 +- tsfc/kernel_interface/firedrake_loopy.py | 8 ++++---- tsfc/loopy.py | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_pickle_gem.py b/tests/test_pickle_gem.py index 73e39cac9f..beb101f912 100644 --- a/tests/test_pickle_gem.py +++ b/tests/test_pickle_gem.py @@ -6,7 +6,7 @@ @pytest.mark.parametrize('protocol', range(3)) def test_pickle_gem(protocol): - f = gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,)), (1,))) + f = gem.VariableIndex(gem.Indexed(gem.Variable('facet', (2,), dtype=gem.uint_type), (1,))) q = gem.Index() r = gem.Index() _1 = gem.Indexed(gem.Literal(numpy.random.rand(3, 6, 8)), (f, q, r)) diff --git a/tsfc/kernel_interface/firedrake_loopy.py b/tsfc/kernel_interface/firedrake_loopy.py index cc35a7c5b8..0969854cd3 100644 --- a/tsfc/kernel_interface/firedrake_loopy.py +++ b/tsfc/kernel_interface/firedrake_loopy.py @@ -85,11 +85,11 @@ def __init__(self, scalar_type, interior_facet=False): # Cell orientation if self.interior_facet: - cell_orientations = gem.Variable("cell_orientations", (2,)) + cell_orientations = gem.Variable("cell_orientations", (2,), dtype=gem.uint_type) self._cell_orientations = (gem.Indexed(cell_orientations, (0,)), gem.Indexed(cell_orientations, (1,))) else: - cell_orientations = gem.Variable("cell_orientations", (1,)) + cell_orientations = gem.Variable("cell_orientations", (1,), dtype=gem.uint_type) self._cell_orientations = (gem.Indexed(cell_orientations, (0,)),) def _coefficient(self, coefficient, name): @@ -257,12 +257,12 @@ def __init__(self, integral_data_info, scalar_type, # Facet number if integral_type in ['exterior_facet', 'exterior_facet_vert']: - facet = gem.Variable('facet', (1,)) + facet = gem.Variable('facet', (1,), dtype=gem.uint_type) self._entity_number = {None: gem.VariableIndex(gem.Indexed(facet, (0,)))} facet_orientation = gem.Variable('facet_orientation', (1,), dtype=gem.uint_type) self._entity_orientation = {None: gem.OrientationVariableIndex(gem.Indexed(facet_orientation, (0,)))} elif integral_type in ['interior_facet', 'interior_facet_vert']: - facet = gem.Variable('facet', (2,)) + facet = gem.Variable('facet', (2,), dtype=gem.uint_type) self._entity_number = { '+': gem.VariableIndex(gem.Indexed(facet, (0,))), '-': gem.VariableIndex(gem.Indexed(facet, (1,))) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index d19da42317..08efaedefb 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -47,7 +47,7 @@ def _assign_dtype(expression, self): @_assign_dtype.register(gem.Terminal) def _assign_dtype_terminal(expression, self): - return {self.scalar_type} + return {expression.dtype or self.scalar_type} @_assign_dtype.register(gem.Variable) @@ -59,7 +59,7 @@ def _assign_dtype_variable(expression, self): @_assign_dtype.register(gem.Identity) @_assign_dtype.register(gem.Delta) def _assign_dtype_real(expression, self): - return {self.real_type} + return {expression.dtype or self.real_type} @_assign_dtype.register(gem.Literal) @@ -70,15 +70,15 @@ def _assign_dtype_identity(expression, self): @_assign_dtype.register(gem.Power) def _assign_dtype_power(expression, self): # Conservative - return {self.scalar_type} + return {expression.dtype or self.scalar_type} @_assign_dtype.register(gem.MathFunction) def _assign_dtype_mathfunction(expression, self): if expression.name in {"abs", "real", "imag"}: - return {self.real_type} + return {expression.dtype or self.real_type} elif expression.name == "sqrt": - return {self.scalar_type} + return {expression.dtype or self.scalar_type} else: return set.union(*map(self, expression.children)) @@ -87,7 +87,7 @@ def _assign_dtype_mathfunction(expression, self): @_assign_dtype.register(gem.MaxValue) def _assign_dtype_minmax(expression, self): # UFL did correctness checking - return {self.real_type} + return {expression.dtype or self.real_type} @_assign_dtype.register(gem.Conditional) @@ -100,7 +100,7 @@ def _assign_dtype_conditional(expression, self): @_assign_dtype.register(gem.LogicalAnd) @_assign_dtype.register(gem.LogicalOr) def _assign_dtype_logical(expression, self): - return {numpy.int8} + return {expression.dtype or numpy.int8} def assign_dtypes(expressions, scalar_type): From 00b7477f52d50227678daebcd4c5577a5f40102e Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 28 Nov 2024 10:07:56 +0000 Subject: [PATCH 805/809] Redo loopy loop priorities (#333) The previous solution to avoiding loop interchanges did not account for having multiple loop nests. --- tsfc/loopy.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tsfc/loopy.py b/tsfc/loopy.py index 08efaedefb..6826f0b672 100644 --- a/tsfc/loopy.py +++ b/tsfc/loopy.py @@ -126,6 +126,7 @@ def __init__(self, target=None): self.gem_to_pymbolic = {} # gem node -> pymbolic variable self.name_gen = UniqueNameGenerator() self.target = target + self.loop_priorities = set() # used to avoid disadvantageous loop interchanges def fetch_multiindex(self, multiindex): indices = [] @@ -191,6 +192,12 @@ def active_inames(self): # Return all active indices return frozenset([i.name for i in self.active_indices.values()]) + def save_loop_ordering(self): + """Save the active loops to prevent loop reordering.""" + priority = tuple(map(str, self.active_indices.values())) + if len(priority) > 1: + self.loop_priorities.add(priority) + @contextmanager def active_indices(mapping, ctx): @@ -199,6 +206,7 @@ def active_indices(mapping, ctx): :arg ctx: code generation context. :returns: new code generation context.""" ctx.active_indices.update(mapping) + ctx.save_loop_ordering() yield ctx for key in mapping: ctx.active_indices.pop(key) @@ -261,12 +269,10 @@ def generate(impero_c, args, scalar_type, kernel_name="loopy_kernel", index_name seq_dependencies=True, silenced_warnings=["summing_if_branches_ops"], lang_version=(2018, 2), - preambles=preamble + preambles=preamble, + loop_priority=frozenset(ctx.loop_priorities), ) - # Prevent loopy interchange by loopy - knl = lp.prioritize_loops(knl, ",".join(ctx.index_extent.keys())) - return knl, event_name From 5dad27537989e147ab8b2664fcfc1188ad50a4c9 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 4 Dec 2024 12:25:48 +0000 Subject: [PATCH 806/809] Move TSFC into Firedrake repository --- .github/workflows/pyop2.yml | 26 +++++++++++-------- Makefile | 2 ++ firedrake/scripts/firedrake-zenodo | 16 +++--------- pyproject.toml | 4 +-- requirements-git.txt | 4 +-- tests/{ => tsfc}/test_codegen.py | 0 tests/{ => tsfc}/test_coffee_optimise.py | 0 tests/{ => tsfc}/test_create_fiat_element.py | 0 tests/{ => tsfc}/test_create_finat_element.py | 0 tests/{ => tsfc}/test_delta_elimination.py | 0 tests/{ => tsfc}/test_dual_evaluation.py | 0 tests/{ => tsfc}/test_estimated_degree.py | 0 tests/{ => tsfc}/test_firedrake_972.py | 10 +++---- tests/{ => tsfc}/test_flexibly_indexed.py | 0 tests/{ => tsfc}/test_flop_count.py | 0 tests/{ => tsfc}/test_gem_failure.py | 0 tests/{ => tsfc}/test_geometry.py | 0 tests/{ => tsfc}/test_idempotency.py | 0 .../test_impero_loopy_flop_counts.py | 0 .../test_interpolation_factorisation.py | 0 tests/{ => tsfc}/test_pickle_gem.py | 0 tests/{ => tsfc}/test_refactorise.py | 0 tests/{ => tsfc}/test_simplification.py | 0 tests/{ => tsfc}/test_sum_factorisation.py | 0 tests/{ => tsfc}/test_syntax_sugar.py | 0 tests/{ => tsfc}/test_tensor.py | 0 tests/{ => tsfc}/test_tsfc_182.py | 0 tests/{ => tsfc}/test_tsfc_204.py | 12 ++++----- tests/{ => tsfc}/test_tsfc_274.py | 0 tests/{ => tsfc}/test_underintegration.py | 0 30 files changed, 33 insertions(+), 41 deletions(-) rename tests/{ => tsfc}/test_codegen.py (100%) rename tests/{ => tsfc}/test_coffee_optimise.py (100%) rename tests/{ => tsfc}/test_create_fiat_element.py (100%) rename tests/{ => tsfc}/test_create_finat_element.py (100%) rename tests/{ => tsfc}/test_delta_elimination.py (100%) rename tests/{ => tsfc}/test_dual_evaluation.py (100%) rename tests/{ => tsfc}/test_estimated_degree.py (100%) rename tests/{ => tsfc}/test_firedrake_972.py (78%) rename tests/{ => tsfc}/test_flexibly_indexed.py (100%) rename tests/{ => tsfc}/test_flop_count.py (100%) rename tests/{ => tsfc}/test_gem_failure.py (100%) rename tests/{ => tsfc}/test_geometry.py (100%) rename tests/{ => tsfc}/test_idempotency.py (100%) rename tests/{ => tsfc}/test_impero_loopy_flop_counts.py (100%) rename tests/{ => tsfc}/test_interpolation_factorisation.py (100%) rename tests/{ => tsfc}/test_pickle_gem.py (100%) rename tests/{ => tsfc}/test_refactorise.py (100%) rename tests/{ => tsfc}/test_simplification.py (100%) rename tests/{ => tsfc}/test_sum_factorisation.py (100%) rename tests/{ => tsfc}/test_syntax_sugar.py (100%) rename tests/{ => tsfc}/test_tensor.py (100%) rename tests/{ => tsfc}/test_tsfc_182.py (100%) rename tests/{ => tsfc}/test_tsfc_204.py (87%) rename tests/{ => tsfc}/test_tsfc_274.py (100%) rename tests/{ => tsfc}/test_underintegration.py (100%) diff --git a/.github/workflows/pyop2.yml b/.github/workflows/pyop2.yml index 36065accf1..9047362d69 100644 --- a/.github/workflows/pyop2.yml +++ b/.github/workflows/pyop2.yml @@ -1,14 +1,10 @@ -name: PyOP2 +name: Test PyOP2 and TSFC -# Trigger the workflow on push or pull request, -# but only for the master branch on: push: branches: - master pull_request: - branches: - - master jobs: test: @@ -88,14 +84,14 @@ jobs: make make install - - name: Checkout PyOP2 + - name: Checkout Firedrake uses: actions/checkout@v4 with: - path: PyOP2 + path: firedrake - name: Install PyOP2 dependencies shell: bash - working-directory: PyOP2 + working-directory: firedrake run: | source ../venv/bin/activate python -m pip install -U pip @@ -103,14 +99,22 @@ jobs: - name: Install PyOP2 shell: bash - working-directory: PyOP2 + working-directory: firedrake run: | source ../venv/bin/activate python -m pip install -v ".[test]" - - name: Run tests + - name: Run TSFC tests + shell: bash + working-directory: firedrake + run: | + source ../venv/bin/activate + pytest --tb=native --timeout=480 --timeout-method=thread -o faulthandler_timeout=540 -v tests/tsfc + timeout-minutes: 10 + + - name: Run PyOP2 tests shell: bash - working-directory: PyOP2 + working-directory: firedrake run: | source ../venv/bin/activate # Running parallel test cases separately works around a bug in pytest-mpi diff --git a/Makefile b/Makefile index 033504d3ef..69c1a6a0a1 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,8 @@ lint: @python -m flake8 $(FLAKE8_FORMAT) pyop2 @echo " Linting PyOP2 scripts" @python -m flake8 $(FLAKE8_FORMAT) pyop2/scripts --filename=* + @echo " Linting TSFC" + @python -m flake8 $(FLAKE8_FORMAT) tsfc actionlint: @echo " Pull latest actionlint image" diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index 6194d9df1d..39cc2ce609 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -16,35 +16,25 @@ import datetime # Change this to https://sandbox.zenodo.org/api for testing ZENODO_URL = "https://zenodo.org/api" -# TODO: Remove "petsc4py" once all users have switched to "petsc/src/binding/petsc4py". -# And the same for slepc4py. descriptions = OrderedDict([ ("firedrake", "an automated finite element system"), - ("tsfc", "The Two Stage Form Compiler"), ("ufl", "The Unified Form Language"), - ("FInAT", "a smarter library of finite elements"), ("fiat", "The Finite Element Automated Tabulator"), ("petsc", "Portable, Extensible Toolkit for Scientific Computation"), - ("petsc4py", "The Python interface to PETSc"), ("loopy", "Transformation-Based Generation of High-Performance CPU/GPU Code"), - ("slepc", "Scalable Library for Eigenvalue Problem Computations"), - ("slepc4py", "The Python interface to SLEPc")]) + ("slepc", "Scalable Library for Eigenvalue Problem Computations")]) projects = dict( [("firedrake", "firedrakeproject"), - ("tsfc", "firedrakeproject"), ("ufl", "firedrakeproject"), - ("FInAT", "FInAT"), ("fiat", "firedrakeproject"), ("petsc", "firedrakeproject"), - ("petsc4py", "firedrakeproject"), ("loopy", "firedrakeproject"), - ("slepc", "firedrakeproject"), - ("slepc4py", "firedrakeproject")]) + ("slepc", "firedrakeproject")]) components = list(descriptions.keys()) -optional_components = ("slepc", "slepc4py", "petsc4py") +optional_components = ("slepc",) parser = ArgumentParser(description="""Create Zenodo DOIs for specific versions of Firedrake components. diff --git a/pyproject.toml b/pyproject.toml index 69af99a483..230ee55975 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,7 @@ dependencies = [ "scipy", "sympy", "fenics-ufl @ git+https://github.com/firedrakeproject/ufl.git", - "fenics-fiat @ git+https://github.com/firedrakeproject/fiat.git", - "finat @ git+https://github.com/FInAT/FInAT.git", - "tsfc @ git+https://github.com/firedrakeproject/tsfc.git", + "fenics-fiat @ git+https://github.com/firedrakeproject/fiat.git@connorjward/add-finat-gem", "pyadjoint-ad @ git+https://github.com/dolfin-adjoint/pyadjoint.git", "loopy @ git+https://github.com/firedrakeproject/loopy.git@main", ] diff --git a/requirements-git.txt b/requirements-git.txt index c037220ed1..e77b6b1052 100644 --- a/requirements-git.txt +++ b/requirements-git.txt @@ -1,7 +1,5 @@ git+https://github.com/firedrakeproject/ufl.git#egg=fenics-ufl -git+https://github.com/firedrakeproject/fiat.git#egg=fenics-fiat -git+https://github.com/FInAT/FInAT.git#egg=finat -git+https://github.com/firedrakeproject/tsfc.git#egg=tsfc +git+https://github.com/firedrakeproject/fiat.git@connorjward/add-finat-gem#egg=fenics-fiat git+https://github.com/dolfin-adjoint/pyadjoint.git#egg=pyadjoint-ad git+https://github.com/firedrakeproject/loopy.git@main#egg=loopy git+https://github.com/firedrakeproject/pytest-mpi.git@main#egg=pytest-mpi diff --git a/tests/test_codegen.py b/tests/tsfc/test_codegen.py similarity index 100% rename from tests/test_codegen.py rename to tests/tsfc/test_codegen.py diff --git a/tests/test_coffee_optimise.py b/tests/tsfc/test_coffee_optimise.py similarity index 100% rename from tests/test_coffee_optimise.py rename to tests/tsfc/test_coffee_optimise.py diff --git a/tests/test_create_fiat_element.py b/tests/tsfc/test_create_fiat_element.py similarity index 100% rename from tests/test_create_fiat_element.py rename to tests/tsfc/test_create_fiat_element.py diff --git a/tests/test_create_finat_element.py b/tests/tsfc/test_create_finat_element.py similarity index 100% rename from tests/test_create_finat_element.py rename to tests/tsfc/test_create_finat_element.py diff --git a/tests/test_delta_elimination.py b/tests/tsfc/test_delta_elimination.py similarity index 100% rename from tests/test_delta_elimination.py rename to tests/tsfc/test_delta_elimination.py diff --git a/tests/test_dual_evaluation.py b/tests/tsfc/test_dual_evaluation.py similarity index 100% rename from tests/test_dual_evaluation.py rename to tests/tsfc/test_dual_evaluation.py diff --git a/tests/test_estimated_degree.py b/tests/tsfc/test_estimated_degree.py similarity index 100% rename from tests/test_estimated_degree.py rename to tests/tsfc/test_estimated_degree.py diff --git a/tests/test_firedrake_972.py b/tests/tsfc/test_firedrake_972.py similarity index 78% rename from tests/test_firedrake_972.py rename to tests/tsfc/test_firedrake_972.py index f9ec8d0967..e308c7986d 100644 --- a/tests/test_firedrake_972.py +++ b/tests/tsfc/test_firedrake_972.py @@ -21,11 +21,11 @@ def count_flops(n): i, j = indices(2) nc = 42 # magic number L = ((IndexSum(IndexSum(Product(nc * phi[i, j], Product(ensemble_f[i], ensemble_f[i])), - MultiIndex((i,))), MultiIndex((j,))) * dx) + - (IndexSum(IndexSum(Product(nc * phi[i, j], Product(ensemble2_f[j], ensemble2_f[j])), - MultiIndex((i,))), MultiIndex((j,))) * dx) - - (IndexSum(IndexSum(2 * nc * Product(phi[i, j], Product(ensemble_f[i], ensemble2_f[j])), - MultiIndex((i,))), MultiIndex((j,))) * dx)) + MultiIndex((i,))), MultiIndex((j,))) * dx) + + (IndexSum(IndexSum(Product(nc * phi[i, j], Product(ensemble2_f[j], ensemble2_f[j])), + MultiIndex((i,))), MultiIndex((j,))) * dx) + - (IndexSum(IndexSum(2 * nc * Product(phi[i, j], Product(ensemble_f[i], ensemble2_f[j])), + MultiIndex((i,))), MultiIndex((j,))) * dx)) kernel, = compile_form(L, parameters=dict(mode='spectral')) return kernel.flop_count diff --git a/tests/test_flexibly_indexed.py b/tests/tsfc/test_flexibly_indexed.py similarity index 100% rename from tests/test_flexibly_indexed.py rename to tests/tsfc/test_flexibly_indexed.py diff --git a/tests/test_flop_count.py b/tests/tsfc/test_flop_count.py similarity index 100% rename from tests/test_flop_count.py rename to tests/tsfc/test_flop_count.py diff --git a/tests/test_gem_failure.py b/tests/tsfc/test_gem_failure.py similarity index 100% rename from tests/test_gem_failure.py rename to tests/tsfc/test_gem_failure.py diff --git a/tests/test_geometry.py b/tests/tsfc/test_geometry.py similarity index 100% rename from tests/test_geometry.py rename to tests/tsfc/test_geometry.py diff --git a/tests/test_idempotency.py b/tests/tsfc/test_idempotency.py similarity index 100% rename from tests/test_idempotency.py rename to tests/tsfc/test_idempotency.py diff --git a/tests/test_impero_loopy_flop_counts.py b/tests/tsfc/test_impero_loopy_flop_counts.py similarity index 100% rename from tests/test_impero_loopy_flop_counts.py rename to tests/tsfc/test_impero_loopy_flop_counts.py diff --git a/tests/test_interpolation_factorisation.py b/tests/tsfc/test_interpolation_factorisation.py similarity index 100% rename from tests/test_interpolation_factorisation.py rename to tests/tsfc/test_interpolation_factorisation.py diff --git a/tests/test_pickle_gem.py b/tests/tsfc/test_pickle_gem.py similarity index 100% rename from tests/test_pickle_gem.py rename to tests/tsfc/test_pickle_gem.py diff --git a/tests/test_refactorise.py b/tests/tsfc/test_refactorise.py similarity index 100% rename from tests/test_refactorise.py rename to tests/tsfc/test_refactorise.py diff --git a/tests/test_simplification.py b/tests/tsfc/test_simplification.py similarity index 100% rename from tests/test_simplification.py rename to tests/tsfc/test_simplification.py diff --git a/tests/test_sum_factorisation.py b/tests/tsfc/test_sum_factorisation.py similarity index 100% rename from tests/test_sum_factorisation.py rename to tests/tsfc/test_sum_factorisation.py diff --git a/tests/test_syntax_sugar.py b/tests/tsfc/test_syntax_sugar.py similarity index 100% rename from tests/test_syntax_sugar.py rename to tests/tsfc/test_syntax_sugar.py diff --git a/tests/test_tensor.py b/tests/tsfc/test_tensor.py similarity index 100% rename from tests/test_tensor.py rename to tests/tsfc/test_tensor.py diff --git a/tests/test_tsfc_182.py b/tests/tsfc/test_tsfc_182.py similarity index 100% rename from tests/test_tsfc_182.py rename to tests/tsfc/test_tsfc_182.py diff --git a/tests/test_tsfc_204.py b/tests/tsfc/test_tsfc_204.py similarity index 87% rename from tests/test_tsfc_204.py rename to tests/tsfc/test_tsfc_204.py index 35e2caa064..89f1481590 100644 --- a/tests/test_tsfc_204.py +++ b/tests/tsfc/test_tsfc_204.py @@ -23,12 +23,12 @@ def test_physically_mapped_facet(): s = FacetNormal(mesh) trans = as_matrix([[1, 0], [0, 1]]) mat = trans*grad(grad(u))*trans + outer(d, d) * u - J = (u**2*dx + - u**3*dx + - u**4*dx + - inner(mat, mat)*dx + - inner(grad(d), grad(d))*dx + - dot(s, d)**2*ds) + J = (u**2*dx + + u**3*dx + + u**4*dx + + inner(mat, mat)*dx + + inner(grad(d), grad(d))*dx + + dot(s, d)**2*ds) L_match = inner(qhat, dhat - d) L = J + inner(lam, inner(d, d)-1)*dx + (L_match('+') + L_match('-'))*dS + L_match*ds compile_form(L) diff --git a/tests/test_tsfc_274.py b/tests/tsfc/test_tsfc_274.py similarity index 100% rename from tests/test_tsfc_274.py rename to tests/tsfc/test_tsfc_274.py diff --git a/tests/test_underintegration.py b/tests/tsfc/test_underintegration.py similarity index 100% rename from tests/test_underintegration.py rename to tests/tsfc/test_underintegration.py From d952afc134e7b9fe8036893039855b25dcb0cc67 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 5 Dec 2024 16:24:40 +0000 Subject: [PATCH 807/809] Fixup firedrake-zenodo --- firedrake/scripts/firedrake-zenodo | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index 39cc2ce609..d609a81326 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -22,19 +22,29 @@ descriptions = OrderedDict([ ("fiat", "The Finite Element Automated Tabulator"), ("petsc", "Portable, Extensible Toolkit for Scientific Computation"), ("loopy", "Transformation-Based Generation of High-Performance CPU/GPU Code"), - ("slepc", "Scalable Library for Eigenvalue Problem Computations")]) - -projects = dict( - [("firedrake", "firedrakeproject"), - ("ufl", "firedrakeproject"), - ("fiat", "firedrakeproject"), - ("petsc", "firedrakeproject"), - ("loopy", "firedrakeproject"), - ("slepc", "firedrakeproject")]) + ("slepc", "Scalable Library for Eigenvalue Problem Computations"), + # removed components, left so old Firedrake versions are archivable + ("PyOP2", "Framework for performance-portable parallel computations on unstructured meshes"), + ("tsfc", "The Two Stage Form Compiler"), + ("FInAT", "a smarter library of finite elements"), +]) + +projects = dict([ + ("firedrake", "firedrakeproject"), + ("ufl", "firedrakeproject"), + ("fiat", "firedrakeproject"), + ("petsc", "firedrakeproject"), + ("loopy", "firedrakeproject"), + ("slepc", "firedrakeproject"), + # removed components, left so old Firedrake versions are archivable + ("PyOP2", "OP2"), + ("tsfc", "firedrakeproject"), + ("FInAT", "FInAT"), +]) components = list(descriptions.keys()) -optional_components = ("slepc",) +optional_components = ("slepc", "PyOP2", "tsfc", "FInAT") parser = ArgumentParser(description="""Create Zenodo DOIs for specific versions of Firedrake components. From bb0d8fd97c033866daa4aacdfc0aa9b5c5910dae Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 6 Dec 2024 13:07:53 +0000 Subject: [PATCH 808/809] Make firedrake-update handle archived repos --- scripts/firedrake-install | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/firedrake-install b/scripts/firedrake-install index a83b04a451..1637971d28 100755 --- a/scripts/firedrake-install +++ b/scripts/firedrake-install @@ -1941,6 +1941,19 @@ else: if args.rebuild: pip_uninstall("petsc4py") + # Handle archived repositories + archived_repos = [("PyOP2", "firedrake", "PyOP2"), + ("tsfc", "firedrake+fiat", "tsfc"), + ("FInAT", "fiat", "FInAT")] + for src_repo, dest_repo, pip_pkg_name in archived_repos: + archived_path = os.path.join(firedrake_env, "src", src_repo) + if os.path.exists(archived_path): + log.warning("%s has been moved into %s, renaming to %s_old and pip uninstalling.\n" + % (src_repo, dest_repo, src_repo)) + pip_uninstall(pip_pkg_name) + new_path = os.path.join(firedrake_env, "src", "%s_old" % src_repo) + os.rename(archived_path, new_path) + # If there is a petsc package to remove, then we're an old installation which will need to rebuild PETSc. update_petsc = pip_uninstall("petsc") or update_petsc From c9f4d322a3a07b09ceda7cec6c29a7835cd26a90 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 6 Dec 2024 13:36:34 +0000 Subject: [PATCH 809/809] Point at the correct FIAT branch --- pyproject.toml | 2 +- requirements-git.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 230ee55975..240772b7c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "scipy", "sympy", "fenics-ufl @ git+https://github.com/firedrakeproject/ufl.git", - "fenics-fiat @ git+https://github.com/firedrakeproject/fiat.git@connorjward/add-finat-gem", + "fenics-fiat @ git+https://github.com/firedrakeproject/fiat.git", "pyadjoint-ad @ git+https://github.com/dolfin-adjoint/pyadjoint.git", "loopy @ git+https://github.com/firedrakeproject/loopy.git@main", ] diff --git a/requirements-git.txt b/requirements-git.txt index e77b6b1052..93e6df0f47 100644 --- a/requirements-git.txt +++ b/requirements-git.txt @@ -1,5 +1,5 @@ git+https://github.com/firedrakeproject/ufl.git#egg=fenics-ufl -git+https://github.com/firedrakeproject/fiat.git@connorjward/add-finat-gem#egg=fenics-fiat +git+https://github.com/firedrakeproject/fiat.git#egg=fenics-fiat git+https://github.com/dolfin-adjoint/pyadjoint.git#egg=pyadjoint-ad git+https://github.com/firedrakeproject/loopy.git@main#egg=loopy git+https://github.com/firedrakeproject/pytest-mpi.git@main#egg=pytest-mpi