From 3f686befbcf99c83bceded68391426025ad211fc Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 19 Jan 2021 15:52:08 +0000 Subject: [PATCH] Transition to basix library for finite element definitions (#296) * add some debug * Comment out some stuff * Start on libtab_interface * Add simple libtab_interface * flake8 * Add return value to make code compile * Provide more alternatives to fiat_element * Provide more alternatives to fiat_element * tidy and more print debug * Reshape for tensor elements * Removing fiat, adding libtab * libtab * eigen * -dev * Replaceing fiat with libtab * flake * more flake * remove fiat requirement * remove fiat from tests * use libtabbaseelement * Add value_shape TODO * Working on updating dof permutations * Reimplementing more functionality using libtab * return more features * turn off tests for element not implemented yet in libtab * update more tests * get permutation data to pass around correctly * implementing dof permutations * flake * comment out import * entity dofs * vertex quadrature * celltype -> cellname * fixing entity dofs and quadrature * flake * Reimplement more stuff * Get tabulate_reference_basis_derivatives working * remove evaluate_* * flake * remove unused import * contiguous * use fstrings * more fstrings * even more fstrings * missing ) * family names * Remove no longer used code * mt_averaged -> mt.averaged * Reintroduce more functionality * Run demos using pytest * use gcc compilation in demo test * block size corrections * Replace ndofs with dim * Replace ndofs with dim * Added tests that tabulate tensor is correct for Lagrange spaces * install sympy for testing * Put table data from before permutations were applied into conditionals * add interpolation to generated code * correct interpolation and interpolation test * flake * libtab branch * update to match libtab interpolation renaming * add needs_permutation_data to element * correct indices * back to master libtab * interpolation * currect use of subelements * block sizes * Correct interpolation for vectorelement * fix vectorfunctionspace interpolation and evaluation * Add mappings * Add alternative element names * update test to match scalar values * name mapping * skip demos for elements not yes in libtab * fix num_reference_componenets for mixed elements * flake * correct data for mixed and blocked elements * flake * Added permite_dofs_coordinates function * add apply_permutations function * simplify dos permutation application * reflections first * blocking (so mixed elements get correct data * always do reflections first * correct value shapes and sizes of blocked elements * correct blocked tabulation * remove blocksize from value shapes * value shapes * Speed up test * Add curl-curl to get more coverage * back to libtab master * flake8 * correct facet normals for quads * block sizes * libtab -> basix * basix branch * basix branch * ignore test c and h files * update ufc_geometry * remove some unused ufc_geometry * LibtabElement -> BasixElement * add interpolation_is_identity flag * replace reference to fiat with libtab * pass rule into quadrature Correct some geometry * basix branch * correct basix branch name * Remove ufc tabulate_reference_dof_coordinates * switch to main branch * correct quad facet normals * implement cell_vertices * add more to cmap * add mroe info to cmap * swap permutation order for integral tables * reimplement oriented jacobian * use make_perm_data function instead of copy/pasted code * Flake8 fixes * Remove unused code. * Doc fix * tidy up permutations * remove permutations from transform_reference_basis_d * Add docs to ufc.h * remove interpolation functions from finite_element * remove reverse permutation. add permutation of scalar_t data * remove interpolation test (as interpolation is now removed from generated code) * remove tabulate_dof_coordinates * Don't use conditionals * remove interpolation matrix and points from ir * remove physical and reference offsets * Remove transform_values * Remove some unnecessary const in ufc * restore facet_edge_vectors * Tidying up * use scalar_t type inside apply_dof_transformation * remove apply_dof_permutation from coordinate mapping * Revert "remove apply_dof_permutation from coordinate mapping" This reverts commit ea7cbf78d578fa05d03b0766baa8f159cd9e2245. * Tidy coordinate mapping (#297) * add unpermute * un * revert * remove element factory name Co-authored-by: Chris Richardson Co-authored-by: Garth N. Wells --- .github/workflows/pythonapp.yml | 10 +- .gitignore | 11 + demo/test_demos.py | 31 + ffcx/analysis.py | 16 +- ffcx/basix_interface.py | 441 +++++++++ ffcx/codegeneration/C/cnodes.py | 6 +- ffcx/codegeneration/C/ufl_to_cnodes.py | 4 +- ffcx/codegeneration/access.py | 74 +- ffcx/codegeneration/coordinate_mapping.py | 38 +- .../coordinate_mapping_template.py | 17 +- ffcx/codegeneration/definitions.py | 13 +- ffcx/codegeneration/dofmap.py | 22 +- ffcx/codegeneration/dofmap_template.py | 3 - ffcx/codegeneration/evalderivs.py | 387 -------- ffcx/codegeneration/evaluatebasis.py | 631 ------------- ffcx/codegeneration/evaluatedof.py | 353 ------- ffcx/codegeneration/expressions.py | 12 +- ffcx/codegeneration/finite_element.py | 285 ++---- .../codegeneration/finite_element_template.py | 42 +- ffcx/codegeneration/form.py | 20 +- ffcx/codegeneration/integrals.py | 194 ++-- ffcx/codegeneration/jit.py | 6 +- ffcx/codegeneration/symbols.py | 14 +- ffcx/codegeneration/ufc.h | 104 ++- ffcx/codegeneration/ufc_geometry.h | 880 ++---------------- ffcx/codegeneration/utils.py | 128 +++ ffcx/fiatinterface.py | 311 ------- ffcx/ir/analysis/modified_terminals.py | 15 +- ffcx/ir/analysis/visualise.py | 2 +- ffcx/ir/dof_permutations.py | 552 ----------- ffcx/ir/elementtables.py | 124 ++- ffcx/ir/integral.py | 25 +- ffcx/ir/representation.py | 642 +++---------- ffcx/ir/representationutils.py | 25 +- ffcx/main.py | 8 +- ffcx/naming.py | 2 +- requirements.txt | 2 +- setup.py | 6 +- test/Poisson.ufl | 5 +- test/conftest.py | 14 +- test/test_blocked_elements.py | 42 - test/test_dof_permutations.py | 44 - test/test_elements.py | 102 +- test/test_jit_cmaps.py | 33 - test/test_jit_elements.py | 90 -- test/test_jit_forms.py | 235 ++++- test/xtest_evaluate.py | 312 ------- 47 files changed, 1538 insertions(+), 4795 deletions(-) create mode 100644 demo/test_demos.py create mode 100644 ffcx/basix_interface.py delete mode 100644 ffcx/codegeneration/evalderivs.py delete mode 100644 ffcx/codegeneration/evaluatebasis.py delete mode 100644 ffcx/codegeneration/evaluatedof.py delete mode 100644 ffcx/fiatinterface.py delete mode 100644 ffcx/ir/dof_permutations.py delete mode 100644 test/test_dof_permutations.py delete mode 100644 test/test_jit_elements.py delete mode 100644 test/xtest_evaluate.py diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index b19bce686..4c45956b9 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -24,15 +24,15 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y graphviz libgraphviz-dev pkg-config + sudo apt-get install -y graphviz libgraphviz-dev pkg-config libeigen3-dev - name: Install dependencies (non-Python, macOS) if: runner.os == 'macOS' - run: brew install graphviz pkg-config + run: brew install graphviz pkg-config eigen - name: Install dependencies (Python) run: | pip install --upgrade pip pip install pygraphviz - pip install git+https://github.com/FEniCS/fiat.git --user + pip install git+https://github.com/FEniCS/basix.git --user pip install git+https://github.com/FEniCS/ufl.git --user - name: Lint with flake8 run: | @@ -47,7 +47,7 @@ jobs: pip install . - name: Run units tests run: | - pip install coveralls coverage pytest pytest-cov pytest-xdist + pip install coveralls coverage pytest pytest-cov pytest-xdist sympy pytest -n auto --cov=ffcx/ --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml test/ - name: Upload to Coveralls if: ${{ github.repository == 'FEniCS/ffcx' && github.head_ref == '' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' }} @@ -65,7 +65,7 @@ jobs: if: always() - name: Runs demos run: | - ffcx demo/*.ufl + python${{ matrix.python-version }} -m pytest demo/test_demos.py - name: Build documentation run: | diff --git a/.gitignore b/.gitignore index 8552b2785..9978a7f5c 100644 --- a/.gitignore +++ b/.gitignore @@ -46,9 +46,20 @@ install_manifest.txt # Visual Studio Code project config .vscode +# c, h and so files due to running demos +demo/*.c +demo/*.h +demo/*.so +test/*.c +test/*.h +test/*.so + # Tests **/.cache/ __pycache__ +test/compile-cache/ +test/libffcx_* +test/*.pdf # setuptools temps *.egg-info/ diff --git a/demo/test_demos.py b/demo/test_demos.py new file mode 100644 index 000000000..80e91f1be --- /dev/null +++ b/demo/test_demos.py @@ -0,0 +1,31 @@ +import os +import sys +import pytest + +demo_dir = os.path.dirname(os.path.realpath(__file__)) + +ufl_files = [] +for file in os.listdir(demo_dir): + if file.endswith(".ufl") and not file.startswith("."): + ufl_files.append(file[:-4]) + + +@pytest.mark.parametrize("file", ufl_files) +def test_demo(file): + if file in [ + "MixedPoissonDual", # Discontinuous Raviart-Thomas + "BiharmonicHHJ", # Hellan-Herrmann-Johnson + "NodalMini", # NodalEnrichedElement + "Mini", # EnrichedElement + "MixedGradient", "TraceElement", # HDiv Trace + "MassHdiv_2D_1", "MassHdiv_2D_3", "MixedPoisson", "MassHdiv_2D_2", # Brezzi-Douglas-Marini + "QuadratureElement" # Quadrature + ]: + # Skip demos that use elements not yet implemented in basix + pytest.skip() + + assert os.system(f"cd {demo_dir} && ffcx {file}.ufl") == 0 + assert os.system(f"cd {demo_dir} && " + "CPATH=../ffcx/codegeneration/ " + f"gcc -I/usr/include/python{sys.version_info.major}.{sys.version_info.minor} -fPIC " + f"-shared {file}.c -o {file}.so") == 0 diff --git a/ffcx/analysis.py b/ffcx/analysis.py index 813f54dc4..409f1ac83 100644 --- a/ffcx/analysis.py +++ b/ffcx/analysis.py @@ -148,9 +148,9 @@ def _analyze_form(form: ufl.form.Form, parameters: typing.Dict) -> ufl.algorithm """ if form.empty(): - raise RuntimeError("Form ({}) seems to be zero: cannot compile it.".format(str(form))) + raise RuntimeError(f"Form ({form}) seems to be zero: cannot compile it.") if _has_custom_integrals(form): - raise RuntimeError("Form ({}) contains unsupported custom integrals.".format(str(form))) + raise RuntimeError(f"Form ({form}) contains unsupported custom integrals.") # Check for complex mode complex_mode = "complex" in parameters.get("scalar_type", "double") @@ -213,16 +213,16 @@ def _analyze_form(form: ufl.form.Form, parameters: typing.Dict) -> ufl.algorithm num_points = ((qd + 1 + 1) // 2)**tdim if num_points >= 100: warnings.warn( - "Number of integration points per cell is: {}. Consider using 'quadrature_degree' " - "to reduce number.".format(num_points)) + f"Number of integration points per cell is: {num_points}. Consider using 'quadrature_degree' " + "to reduce number.") # Extract quadrature rule qr = integral.metadata().get("quadrature_rule", qr_default) - logger.info("Integral {}, integral group {}:".format(i, id)) - logger.info("--- quadrature rule: {}".format(qr)) - logger.info("--- quadrature degree: {}".format(qd)) - logger.info("--- precision: {}".format(p)) + logger.info(f"Integral {i}, integral group {id}:") + logger.info(f"--- quadrature rule: {qr}") + logger.info(f"--- quadrature degree: {qd}") + logger.info(f"--- precision: {p}") # Update the old metadata metadata = integral.metadata() diff --git a/ffcx/basix_interface.py b/ffcx/basix_interface.py new file mode 100644 index 000000000..5d2670ca4 --- /dev/null +++ b/ffcx/basix_interface.py @@ -0,0 +1,441 @@ +import numpy +import ufl +import basix + + +basix_cells = { + "interval": basix.CellType.interval, + "triangle": basix.CellType.triangle, + "tetrahedron": basix.CellType.tetrahedron, + "quadrilateral": basix.CellType.quadrilateral, + "hexahedron": basix.CellType.hexahedron +} + +# This dictionary can be used to map ufl element names to basix element names. +# Currently all the names agree but this will not necessarily remian true. +ufl_to_basix_names = { + "DQ": "Discontinuous Lagrange" +} + + +def create_basix_element(ufl_element): + # TODO: EnrichedElement + # TODO: Short/alternative names for elements + + if isinstance(ufl_element, ufl.VectorElement): + return BlockedElement(create_basix_element(ufl_element.sub_elements()[0]), + ufl_element.num_sub_elements()) + if isinstance(ufl_element, ufl.TensorElement): + return BlockedElement(create_basix_element(ufl_element.sub_elements()[0]), + ufl_element.num_sub_elements(), None) # TODO: block shape + + if isinstance(ufl_element, ufl.MixedElement): + return MixedElement([create_basix_element(e) for e in ufl_element.sub_elements()]) + + if ufl_element.family() in ufl_to_basix_names: + return BasixElement(basix.create_element( + ufl_to_basix_names[ufl_element.family()], ufl_element.cell().cellname(), ufl_element.degree())) + + return BasixElement(basix.create_element( + ufl_element.family(), ufl_element.cell().cellname(), ufl_element.degree())) + + +def basix_index(*args): + return basix.index(*args) + + +def create_quadrature(cellname, degree, rule): + if cellname == "vertex": + return [[]], [1] + return basix.make_quadrature(rule, basix_cells[cellname], degree) + + +def reference_cell_vertices(cellname): + return basix.geometry(basix_cells[cellname]) + + +def map_facet_points(points, facet, cellname): + geom = basix.geometry(basix_cells[cellname]) + facet_vertices = [geom[i] for i in basix.topology(basix_cells[cellname])[-2][facet]] + + return [facet_vertices[0] + sum((i - facet_vertices[0]) * j for i, j in zip(facet_vertices[1:], p)) + for p in points] + + +class BasixBaseElement: + def tabulate(self, nderivs, points): + raise NotImplementedError + + @property + def base_permutations(self): + raise NotImplementedError + + @property + def interpolation_matrix(self): + raise NotImplementedError + + @property + def points(self): + raise NotImplementedError + + @property + def dim(self): + raise NotImplementedError + + @property + def value_size(self): + raise NotImplementedError + + @property + def value_shape(self): + raise NotImplementedError + + @property + def entity_dofs(self): + raise NotImplementedError + + @property + def entity_dof_numbers(self): + raise NotImplementedError + + @property + def coeffs(self): + raise NotImplementedError + + @property + def num_global_support_dofs(self): + raise NotImplementedError + + @property + def family_name(self): + raise NotImplementedError + + @property + def reference_topology(self): + raise NotImplementedError + + @property + def reference_geometry(self): + raise NotImplementedError + + @property + def dof_mappings(self): + raise NotImplementedError + + @property + def num_reference_components(self): + raise NotImplementedError + + +class BasixElement(BasixBaseElement): + def __init__(self, element): + self.element = element + + def tabulate(self, nderivs, points): + return self.element.tabulate(nderivs, points) + + @property + def base_permutations(self): + return self.element.base_permutations + + @property + def interpolation_matrix(self): + return self.element.interpolation_matrix + + @property + def points(self): + return self.element.points + + @property + def dim(self): + return self.element.dim + + @property + def value_size(self): + return self.element.value_size + + @property + def value_shape(self): + return self.element.value_shape + + @property + def entity_dofs(self): + return self.element.entity_dofs + + @property + def entity_dof_numbers(self): + # TODO: move this to basix, then remove this wrapper class + start_dof = 0 + entity_dofs = [] + for i in self.entity_dofs: + dofs_list = [] + for j in i: + dofs_list.append([start_dof + k for k in range(j)]) + start_dof += j + entity_dofs.append(dofs_list) + return entity_dofs + + @property + def coeffs(self): + return self.element.coeffs + + @property + def num_global_support_dofs(self): + # TODO + return 0 + + @property + def family_name(self): + return self.element.family_name + + @property + def reference_topology(self): + return basix.topology(self.element.cell_type) + + @property + def reference_geometry(self): + return basix.geometry(self.element.cell_type) + + @property + def dof_mappings(self): + return [self.element.mapping_name for i in range(self.dim)] + + @property + def num_reference_components(self): + return {self.element.mapping_name: self.value_size} + + +class MixedElement(BasixBaseElement): + def __init__(self, sub_elements): + assert len(sub_elements) > 0 + self.sub_elements = sub_elements + + def tabulate(self, nderivs, points): + tables = [] + results = [e.tabulate(nderivs, points) for e in self.sub_elements] + for deriv_tables in zip(*results): + new_table = numpy.zeros((len(points), self.value_size * self.dim)) + start = 0 + for e, t in zip(self.sub_elements, deriv_tables): + for i in range(0, e.dim, e.value_size): + new_table[:, start: start + e.value_size] = t[:, i: i + e.value_size] + start += self.value_size + tables.append(new_table) + return tables + + @property + def base_permutations(self): + for e in self.sub_elements[1:]: + assert len(e.base_permutations) == len(self.sub_elements[0].base_permutations) + perms = [[] for i in self.sub_elements[0].base_permutations] + for e in self.sub_elements: + for i, b in enumerate(e.base_permutations): + perms[i].append(b) + + output = [] + for p in perms: + new_perm = numpy.zeros((sum(i.shape[0] for i in p), sum(i.shape[1] for i in p))) + row_start = 0 + col_start = 0 + for i in p: + new_perm[row_start: row_start + i.shape[0], col_start: col_start + i.shape[1]] = i + row_start += i.shape[0] + col_start += i.shape[1] + output.append(new_perm) + return output + + @property + def interpolation_matrix(self): + try: + matrix = numpy.zeros((self.dim, len(self.points) * self.value_size)) + start_row = 0 + start_col = 0 + for e in self.sub_elements: + m = e.interpolation_matrix + matrix[start_row: start_row + m.shape[0], start_col: start_col + m.shape[1]] = m + start_row += m.shape[0] + start_col += m.shape[1] + return matrix + except ValueError: + return numpy.zeros((0, 0)) + + @property + def points(self): + try: + return numpy.vstack([e.points for e in self.sub_elements]) + except ValueError: + return numpy.zeros(0) + + @property + def dim(self): + return sum(e.dim for e in self.sub_elements) + + @property + def value_size(self): + return sum(e.value_size for e in self.sub_elements) + + @property + def value_shape(self): + return (sum(e.value_size for e in self.sub_elements), ) + + @property + def entity_dofs(self): + data = [e.entity_dofs for e in self.sub_elements] + return [[sum(d[tdim][entity_n] for d in data) for entity_n, _ in enumerate(entities)] + for tdim, entities in enumerate(data[0])] + + @property + def entity_dof_numbers(self): + dofs = [[[] for i in entities] for entities in self.sub_elements[0].entity_dof_numbers] + start_dof = 0 + for e in self.sub_elements: + for tdim, entities in enumerate(e.entity_dof_numbers): + for entity_n, entity_dofs in enumerate(entities): + dofs[tdim][entity_n] += [start_dof + i for i in entity_dofs] + start_dof += e.dim + return dofs + + @property + def coeffs(self): + # Return empty matrix to disable interpolation into mixed elements + return numpy.zeros(0, 0) + + @property + def num_global_support_dofs(self): + return sum(e.num_global_support_dofs for e in self.sub_elements) + + @property + def family_name(self): + return "mixed element" + + @property + def reference_topology(self): + return self.sub_elements[0].reference_topology + + @property + def reference_geometry(self): + return self.sub_elements[0].reference_geometry + + @property + def dof_mappings(self): + out = [] + for e in self.sub_elements: + out += e.dof_mappings + return out + + @property + def num_reference_components(self): + out = {} + for e in self.sub_elements: + for i, j in e.num_reference_components.items(): + if i in out: + assert out[i] == j + else: + out[i] = j + return out + + +class BlockedElement(BasixBaseElement): + def __init__(self, sub_element, block_size, block_shape=None): + assert block_size > 0 + self.sub_element = sub_element + self.block_size = block_size + if block_shape is None: + self.block_shape = (block_size, ) + else: + self.block_shape = block_shape + + def tabulate(self, nderivs, points): + assert len(self.block_shape) == 1 # TODO: block shape + assert self.value_size == self.block_size # TODO: remove this assumption + + output = [] + for table in self.sub_element.tabulate(nderivs, points): + new_table = numpy.zeros((table.shape[0], table.shape[1] * self.block_size**2)) + for block in range(self.block_size): + col = block * (self.block_size + 1) + new_table[:, col: col + table.shape[1] * self.block_size**2: self.block_size**2] = table + output.append(new_table) + return output + + @property + def base_permutations(self): + assert len(self.block_shape) == 1 # TODO: block shape + + output = [] + for perm in self.sub_element.base_permutations: + new_perm = numpy.zeros((perm.shape[0] * self.block_size, perm.shape[1] * self.block_size)) + for i in range(self.block_size): + new_perm[i::self.block_size, i::self.block_size] = perm + output.append(new_perm) + return output + + @property + def interpolation_matrix(self): + sub_mat = self.sub_element.interpolation_matrix + assert self.value_size == self.block_size # TODO: remove this assumption + mat = numpy.zeros((sub_mat.shape[0] * self.block_size, sub_mat.shape[1] * self.value_size)) + for i, row in enumerate(sub_mat): + for j, entry in enumerate(row): + mat[i * self.block_size: (i + 1) * self.block_size, + j::sub_mat.shape[1]] = entry * numpy.identity(self.block_size) + return mat + + @property + def points(self): + return self.sub_element.points + + @property + def dim(self): + return self.sub_element.dim * self.block_size + + @property + def sub_elements(self): + return [self.sub_element] * self.block_size + + @property + def value_size(self): + return self.block_size * self.sub_element.value_size + + @property + def value_shape(self): + return (self.value_size, ) + + @property + def entity_dofs(self): + return [[j * self.block_size for j in i] for i in self.sub_element.entity_dofs] + + @property + def entity_dof_numbers(self): + # TODO: should this return this, or should it take blocks into account? + return [[[k * self.block_size + b for k in j for b in range(self.block_size)] + for j in i] for i in self.sub_element.entity_dof_numbers] + + @property + def coeffs(self): + # TODO: should this return this, or should it take blocks into account? + return self.sub_element.coeffs + + @property + def num_global_support_dofs(self): + return self.sub_element.num_global_support_dofs * self.block_size + + @property + def family_name(self): + return self.sub_element.family_name + + @property + def reference_topology(self): + return self.sub_element.reference_topology + + @property + def reference_geometry(self): + return self.sub_element.reference_geometry + + @property + def dof_mappings(self): + return self.sub_element.dof_mappings * self.block_size + + @property + def num_reference_components(self): + return self.sub_element.num_reference_components diff --git a/ffcx/codegeneration/C/cnodes.py b/ffcx/codegeneration/C/cnodes.py index 7f3c5804e..13ca9c5cc 100644 --- a/ffcx/codegeneration/C/cnodes.py +++ b/ffcx/codegeneration/C/cnodes.py @@ -63,7 +63,7 @@ def float_product(factors): def MemCopy(src, dst, size, type): src = as_cexpr_or_string_symbol(src) dst = as_cexpr_or_string_symbol(dst) - size = as_cexpr_or_string_symbol("{}*sizeof({})".format(size, type)) + size = as_cexpr_or_string_symbol(f"{size}*sizeof({type})") return Call("memcpy", (dst, src, size)) @@ -1330,7 +1330,9 @@ def cs_format(self, precision=None): # Zero initial values # (NB! C style zero initialization, not sure about other target languages) nb = len(sizes) - return decl + " = {lbr} 0 {rbr};".format(lbr="{" * nb, rbr="}" * nb) + lbr = "{" * nb + rbr = "}" * nb + return f"{decl} = {lbr} 0 {rbr};" else: # Construct initializer lists for arbitrary multidimensional array values if self.values.dtype.kind == "f": diff --git a/ffcx/codegeneration/C/ufl_to_cnodes.py b/ffcx/codegeneration/C/ufl_to_cnodes.py index 9c1663034..385cefbd6 100644 --- a/ffcx/codegeneration/C/ufl_to_cnodes.py +++ b/ffcx/codegeneration/C/ufl_to_cnodes.py @@ -185,11 +185,11 @@ def get(self, o, *args): if otype in self.call_lookup: return self.call_lookup[otype](o, *args) else: - raise RuntimeError("Missing C formatting rule for expr type {0}.".format(otype)) + raise RuntimeError(f"Missing C formatting rule for expr type {otype}.") def expr(self, o, *args): """Generic fallback with error message for missing rules.""" - raise RuntimeError("Missing C formatting rule for expr type {0}.".format(o._ufl_class_)) + raise RuntimeError(f"Missing C formatting rule for expr type {o._ufl_class_}.") # === Formatting rules for scalar literals === diff --git a/ffcx/codegeneration/access.py b/ffcx/codegeneration/access.py index 940e1a6c7..f8f9d5c69 100644 --- a/ffcx/codegeneration/access.py +++ b/ffcx/codegeneration/access.py @@ -9,8 +9,8 @@ import warnings import ufl -from ffcx.fiatinterface import create_element from ufl.finiteelement import MixedElement +from ffcx.basix_interface import create_basix_element logger = logging.getLogger("ffcx") @@ -61,7 +61,7 @@ def get(self, e, mt, tabledata, num_points): if handler: return handler(e, mt, tabledata, num_points) else: - raise RuntimeError("Not handled: {}".format(type(e))) + raise RuntimeError(f"Not handled: {type(e)}") def coefficient(self, e, mt, tabledata, num_points): ttype = tabledata.ttype @@ -180,56 +180,56 @@ def reference_cell_volume(self, e, mt, tabledata, access): L = self.language cellname = mt.terminal.ufl_domain().ufl_cell().cellname() if cellname in ("interval", "triangle", "tetrahedron", "quadrilateral", "hexahedron"): - return L.Symbol("{0}_reference_cell_volume".format(cellname)) + return L.Symbol(f"{cellname}_reference_cell_volume") else: - raise RuntimeError("Unhandled cell types {0}.".format(cellname)) + raise RuntimeError(f"Unhandled cell types {cellname}.") def reference_facet_volume(self, e, mt, tabledata, access): L = self.language cellname = mt.terminal.ufl_domain().ufl_cell().cellname() if cellname in ("interval", "triangle", "tetrahedron", "quadrilateral", "hexahedron"): - return L.Symbol("{0}_reference_facet_volume".format(cellname)) + return L.Symbol(f"{cellname}_reference_facet_volume") else: - raise RuntimeError("Unhandled cell types {0}.".format(cellname)) + raise RuntimeError(f"Unhandled cell types {cellname}.") def reference_normal(self, e, mt, tabledata, access): L = self.language cellname = mt.terminal.ufl_domain().ufl_cell().cellname() if cellname in ("interval", "triangle", "tetrahedron", "quadrilateral", "hexahedron"): - table = L.Symbol("{0}_reference_facet_normals".format(cellname)) + table = L.Symbol(f"{cellname}_reference_facet_normals") facet = self.symbols.entity("facet", mt.restriction) return table[facet][mt.component[0]] else: - raise RuntimeError("Unhandled cell types {0}.".format(cellname)) + raise RuntimeError(f"Unhandled cell types {cellname}.") def cell_facet_jacobian(self, e, mt, tabledata, num_points): L = self.language cellname = mt.terminal.ufl_domain().ufl_cell().cellname() if cellname in ("triangle", "tetrahedron", "quadrilateral", "hexahedron"): - table = L.Symbol("{0}_reference_facet_jacobian".format(cellname)) + table = L.Symbol(f"{cellname}_reference_facet_jacobian") facet = self.symbols.entity("facet", mt.restriction) return table[facet][mt.component[0]][mt.component[1]] elif cellname == "interval": raise RuntimeError("The reference facet jacobian doesn't make sense for interval cell.") else: - raise RuntimeError("Unhandled cell types {0}.".format(cellname)) + raise RuntimeError(f"Unhandled cell types {cellname}.") def reference_cell_edge_vectors(self, e, mt, tabledata, num_points): L = self.language cellname = mt.terminal.ufl_domain().ufl_cell().cellname() if cellname in ("triangle", "tetrahedron", "quadrilateral", "hexahedron"): - table = L.Symbol("{0}_reference_edge_vectors".format(cellname)) + table = L.Symbol(f"{cellname}_reference_edge_vectors") return table[mt.component[0]][mt.component[1]] elif cellname == "interval": raise RuntimeError("The reference cell edge vectors doesn't make sense for interval cell.") else: - raise RuntimeError("Unhandled cell types {0}.".format(cellname)) + raise RuntimeError(f"Unhandled cell types {cellname}.") def reference_facet_edge_vectors(self, e, mt, tabledata, num_points): L = self.language cellname = mt.terminal.ufl_domain().ufl_cell().cellname() if cellname in ("tetrahedron", "hexahedron"): - table = L.Symbol("{0}_reference_edge_vectors".format(cellname)) + table = L.Symbol(f"{cellname}_reference_edge_vectors") facet = self.symbols.entity("facet", mt.restriction) return table[facet][mt.component[0]][mt.component[1]] elif cellname in ("interval", "triangle", "quadrilateral"): @@ -237,15 +237,15 @@ def reference_facet_edge_vectors(self, e, mt, tabledata, num_points): "The reference cell facet edge vectors doesn't make sense for interval or triangle cell." ) else: - raise RuntimeError("Unhandled cell types {0}.".format(cellname)) + raise RuntimeError(f"Unhandled cell types {cellname}.") def facet_orientation(self, e, mt, tabledata, num_points): L = self.language cellname = mt.terminal.ufl_domain().ufl_cell().cellname() if cellname not in ("interval", "triangle", "tetrahedron"): - raise RuntimeError("Unhandled cell types {0}.".format(cellname)) + raise RuntimeError(f"Unhandled cell types {cellname}.") - table = L.Symbol("{0}_facet_orientations".format(cellname)) + table = L.Symbol(f"{cellname}_facet_orientations") facet = self.symbols.entity("facet", mt.restriction) return table[facet] @@ -260,9 +260,10 @@ def cell_vertices(self, e, mt, tabledata, num_points): assert coordinate_element.value_shape() == (gdim, ) ufl_scalar_element, = set(coordinate_element.sub_elements()) assert ufl_scalar_element.family() in ("Lagrange", "Q", "S") - fiat_scalar_element = create_element(ufl_scalar_element) - vertex_scalar_dofs = fiat_scalar_element.entity_dofs()[0] - num_scalar_dofs = fiat_scalar_element.space_dimension() + + basix_scalar_element = create_basix_element(ufl_scalar_element) + vertex_scalar_dofs = basix_scalar_element.entity_dof_numbers[0] + num_scalar_dofs = basix_scalar_element.dim # Get dof and component dof, = vertex_scalar_dofs[mt.component[0]] @@ -283,34 +284,32 @@ def cell_edge_vectors(self, e, mt, tabledata, num_points): elif cellname == "interval": raise RuntimeError("The physical cell edge vectors doesn't make sense for interval cell.") else: - raise RuntimeError("Unhandled cell types {0}.".format(cellname)) + raise RuntimeError(f"Unhandled cell types {cellname}.") # Get dimension and dofmap of scalar element assert isinstance(coordinate_element, MixedElement) assert coordinate_element.value_shape() == (gdim, ) ufl_scalar_element, = set(coordinate_element.sub_elements()) assert ufl_scalar_element.family() in ("Lagrange", "Q", "S") - fiat_scalar_element = create_element(ufl_scalar_element) - vertex_scalar_dofs = fiat_scalar_element.entity_dofs()[0] - num_scalar_dofs = fiat_scalar_element.space_dimension() + + basix_scalar_element = create_basix_element(ufl_scalar_element) + vertex_scalar_dofs = basix_scalar_element.entity_dof_numbers[0] + num_scalar_dofs = basix_scalar_element.dim # Get edge vertices edge = mt.component[0] - edge_vertices = fiat_scalar_element.get_reference_element().get_topology()[1][edge] - vertex0, vertex1 = edge_vertices + vertex0, vertex1 = basix_scalar_element.reference_topology[1][edge] # Get dofs and component dof0, = vertex_scalar_dofs[vertex0] dof1, = vertex_scalar_dofs[vertex1] component = mt.component[1] - expr = (self.symbols.domain_dof_access(dof0, component, - gdim, num_scalar_dofs, - mt.restriction) - - self.symbols.domain_dof_access(dof1, component, - gdim, num_scalar_dofs, - mt.restriction)) - return expr + return self.symbols.domain_dof_access( + dof0, component, gdim, num_scalar_dofs, mt.restriction + ) - self.symbols.domain_dof_access( + dof1, component, gdim, num_scalar_dofs, mt.restriction + ) def facet_edge_vectors(self, e, mt, tabledata, num_points): L = self.language @@ -325,22 +324,23 @@ def facet_edge_vectors(self, e, mt, tabledata, num_points): pass elif cellname in ("interval", "triangle", "quadrilateral"): raise RuntimeError( - "The physical facet edge vectors doesn't make sense for {0} cell.".format(cellname)) + f"The physical facet edge vectors doesn't make sense for {cellname} cell.") else: - raise RuntimeError("Unhandled cell types {0}.".format(cellname)) + raise RuntimeError(f"Unhandled cell types {cellname}.") # Get dimension and dofmap of scalar element assert isinstance(coordinate_element, MixedElement) assert coordinate_element.value_shape() == (gdim, ) ufl_scalar_element, = set(coordinate_element.sub_elements()) assert ufl_scalar_element.family() in ("Lagrange", "Q", "S") - fiat_scalar_element = create_element(ufl_scalar_element) - num_scalar_dofs = fiat_scalar_element.space_dimension() + + basix_scalar_element = create_basix_element(ufl_scalar_element) + num_scalar_dofs = basix_scalar_element.dim # Get edge vertices facet = self.symbols.entity("facet", mt.restriction) facet_edge = mt.component[0] - facet_edge_vertices = L.Symbol("{0}_facet_edge_vertices".format(cellname)) + facet_edge_vertices = L.Symbol(f"{cellname}_facet_edge_vertices") vertex0 = facet_edge_vertices[facet][facet_edge][0] vertex1 = facet_edge_vertices[facet][facet_edge][1] diff --git a/ffcx/codegeneration/coordinate_mapping.py b/ffcx/codegeneration/coordinate_mapping.py index 772f8fd75..22849e341 100644 --- a/ffcx/codegeneration/coordinate_mapping.py +++ b/ffcx/codegeneration/coordinate_mapping.py @@ -7,6 +7,8 @@ import logging import ffcx.codegeneration.coordinate_mapping_template as ufc_coordinate_mapping +from ffcx.codegeneration.utils import apply_permutations_to_data +import ffcx.codegeneration.C.cnodes as L logger = logging.getLogger("ffcx") @@ -15,24 +17,34 @@ def generator(ir, parameters): """Generate UFC code for a coordinate mapping.""" logger.info("Generating code for coordinate mapping:") - logger.info("--- cell shape: {}".format(ir.cell_shape)) - logger.info("--- gdim: {}".format(ir.geometric_dimension)) - logger.info("--- tdim: {}".format(ir.topological_dimension)) - logger.info("--- name: {}".format(ir.name)) - logger.info("--- scalar dofmap name: {}".format(ir.scalar_dofmap_name)) + logger.info(f"--- cell shape: {ir.cell_shape}") + logger.info(f"--- gdim: {ir.geometric_dimension}") + logger.info(f"--- tdim: {ir.topological_dimension}") + logger.info(f"--- name: {ir.name}") + logger.info(f"--- scalar dofmap name: {ir.scalar_dofmap_name}") d = {} # Attributes d["factory_name"] = ir.name d["prefix"] = ir.prefix - d["signature"] = "\"{}\"".format(ir.signature) + d["signature"] = f"\"{ir.signature}\"" d["geometric_dimension"] = ir.geometric_dimension d["topological_dimension"] = ir.topological_dimension d["is_affine"] = 1 if ir.is_affine else 0 d["cell_shape"] = ir.cell_shape d["scalar_dofmap_name"] = ir.scalar_dofmap_name - d["coord_element_factory_name"] = ir.scalar_coordinate_finite_element_classname + + d["needs_permutation_data"] = ir.needs_permutation_data + + statements = permute_dofs(L, ir.base_permutations, ir.cell_shape) + d["permute_dofs"] = L.StatementList(statements) + + statements = unpermute_dofs(L, ir.base_permutations, ir.cell_shape) + d["unpermute_dofs"] = L.StatementList(statements) + + d["family"] = f"\"{ir.coordinate_element_family}\"" + d["degree"] = ir.coordinate_element_degree # Check that no keys are redundant or have been missed from string import Formatter @@ -49,3 +61,15 @@ def generator(ir, parameters): declaration = ufc_coordinate_mapping.declaration.format(factory_name=ir.name, prefix=ir.prefix) return declaration, implementation + + +def permute_dofs(L, base_permutations, cell_shape): + data = L.Symbol("dof_list") + return apply_permutations_to_data(L, base_permutations, cell_shape, data, + dtype="int") + [L.Return(0)] + + +def unpermute_dofs(L, base_permutations, cell_shape): + data = L.Symbol("dof_list") + return apply_permutations_to_data(L, base_permutations, cell_shape, data, + reverse=True, dtype="int") + [L.Return(0)] diff --git a/ffcx/codegeneration/coordinate_mapping_template.py b/ffcx/codegeneration/coordinate_mapping_template.py index 10feb324d..89c81427e 100644 --- a/ffcx/codegeneration/coordinate_mapping_template.py +++ b/ffcx/codegeneration/coordinate_mapping_template.py @@ -20,17 +20,31 @@ factory = """ // Code for coordinate mapping {factory_name} +int permute_dofs_{factory_name}(int* dof_list, const uint32_t cell_permutation) +{{ + {permute_dofs} +}} + +int unpermute_dofs_{factory_name}(int* dof_list, const uint32_t cell_permutation) +{{ + {unpermute_dofs} +}} + ufc_coordinate_mapping* create_{factory_name}(void) {{ ufc_coordinate_mapping* cmap = (ufc_coordinate_mapping*)malloc(sizeof(*cmap)); cmap->signature = {signature}; + cmap->element_family = {family}; + cmap->element_degree = {degree}; cmap->create = create_{factory_name}; cmap->geometric_dimension = {geometric_dimension}; cmap->topological_dimension = {topological_dimension}; cmap->is_affine = {is_affine}; + cmap->needs_permutation_data = {needs_permutation_data}; + cmap->permute_dofs = permute_dofs_{factory_name}; + cmap->unpermute_dofs = unpermute_dofs_{factory_name}; cmap->cell_shape = {cell_shape}; cmap->create_scalar_dofmap = create_{scalar_dofmap_name}; - cmap->evaluate_basis_derivatives = evaluate_reference_basis_derivatives_{coord_element_factory_name}; return cmap; }} @@ -39,6 +53,5 @@ return create_{factory_name}(); }} - // End of code for coordinate mapping {factory_name} """ diff --git a/ffcx/codegeneration/definitions.py b/ffcx/codegeneration/definitions.py index e7d01df4c..b2e7d9702 100644 --- a/ffcx/codegeneration/definitions.py +++ b/ffcx/codegeneration/definitions.py @@ -8,21 +8,14 @@ import logging import ufl - -from ffcx.fiatinterface import create_element +from ffcx.basix_interface import create_basix_element logger = logging.getLogger("ffcx") def num_coordinate_component_dofs(coordinate_element): - """Get the number of dofs for a coordinate component for this degree. - - """ - fiat_elements = create_element(coordinate_element).elements() - # Extracting only first component degrees of freedom from FIAT - fiat_element = fiat_elements[0] - assert(all(isinstance(element, type(fiat_element)) for element in fiat_elements)) - return fiat_element.space_dimension() + """Get the number of dofs for a coordinate component for this degree.""" + return create_basix_element(coordinate_element).sub_element.dim class FFCXBackendDefinitions(object): diff --git a/ffcx/codegeneration/dofmap.py b/ffcx/codegeneration/dofmap.py index 3a11523c3..e49f3c9f8 100644 --- a/ffcx/codegeneration/dofmap.py +++ b/ffcx/codegeneration/dofmap.py @@ -63,7 +63,7 @@ def sub_dofmap_declaration(L, ir): classnames = set(ir.create_sub_dofmap) code = "" for name in classnames: - code += "ufc_dofmap* create_{name}(void);\n".format(name=name) + code += f"ufc_dofmap* create_{name}(void);\n" return code @@ -71,34 +71,20 @@ def generator(ir, parameters): """Generate UFC code for a dofmap.""" logger.info("Generating code for dofmap:") - logger.info("--- num element support dofs: {}".format(ir.num_element_support_dofs)) - logger.info("--- name: {}".format(ir.name)) + logger.info(f"--- num element support dofs: {ir.num_element_support_dofs}") + logger.info(f"--- name: {ir.name}") d = {} # Attributes d["factory_name"] = ir.name - d["signature"] = "\"{}\"".format(ir.signature) + d["signature"] = f"\"{ir.signature}\"" d["num_global_support_dofs"] = ir.num_global_support_dofs d["num_element_support_dofs"] = ir.num_element_support_dofs d["num_sub_dofmaps"] = ir.num_sub_dofmaps d["num_entity_dofs"] = ir.num_entity_dofs + [0, 0, 0, 0] d["block_size"] = ir.block_size - num_perms = len(ir.base_permutations) - if num_perms == 0: - num_dofs = 0 - else: - num_dofs = len(ir.base_permutations[0]) - - bp = [] - for i, perm in enumerate(ir.base_permutations): - for j, val in enumerate(perm): - bp.append(str(val)) - d["base_permutations"] = ("static const int bp[" + str(num_perms * num_dofs) + "] = {" - + ",".join(bp) + "};\n dofmap->base_permutations = bp;\n") - d["size_base_permutations"] = num_perms * num_dofs - import ffcx.codegeneration.C.cnodes as L # Functions diff --git a/ffcx/codegeneration/dofmap_template.py b/ffcx/codegeneration/dofmap_template.py index 952129854..95297c892 100644 --- a/ffcx/codegeneration/dofmap_template.py +++ b/ffcx/codegeneration/dofmap_template.py @@ -37,9 +37,6 @@ dofmap->create_sub_dofmap = create_sub_dofmap_{factory_name}; dofmap->create = create_{factory_name}; - dofmap->size_base_permutations = {size_base_permutations}; - {base_permutations} - return dofmap; }} diff --git a/ffcx/codegeneration/evalderivs.py b/ffcx/codegeneration/evalderivs.py deleted file mode 100644 index e93a6f9bb..000000000 --- a/ffcx/codegeneration/evalderivs.py +++ /dev/null @@ -1,387 +0,0 @@ -"""Work in progress translation of FFC evaluatebasis code to CNodes format.""" - -import logging - -import numpy - -from ffcx.codegeneration.evaluatebasis import (generate_compute_basisvalues, - generate_expansion_coefficients) - -logger = logging.getLogger("ffcx") - - -# Used for various indices and arrays in this file -index_type = "int" - - -def generate_evaluate_reference_basis_derivatives(L, data, classname, parameters): - # Cutoff for feature to disable generation of this code - # (consider removing after benchmarking final result) - if isinstance(data, str): - # Return an error code - return [L.Return(-1)] - - # Get some known dimensions - element_cellname = data["cellname"] - tdim = data["topological_dimension"] - max_degree = data["max_degree"] - reference_value_size = data["reference_value_size"] - num_dofs = len(data["dofs_data"]) - - # Output argument - reference_values = L.Symbol("reference_values") - - # Input arguments - order = L.Symbol("order") - num_points = L.Symbol("num_points") - X = L.Symbol("X") - - # Loop indices - ip = L.Symbol("ip") # point - idof = L.Symbol("i") # dof - c = L.Symbol("c") # component - r = L.Symbol("r") # derivative number - - l0 = L.Symbol("l0") # zeroing arrays - l1 = L.Symbol("l1") # zeroing arrays - - # Return statement (value indicates that function is implemented) - ret = L.Return(0) - - # Define symbol for number of derivatives of given order - num_derivatives = L.Symbol("num_derivatives") - reference_values_size = num_points * num_dofs * \ - num_derivatives * reference_value_size - - # FIXME: validate these dimensions - ref_values = L.FlattenedArray( - reference_values, dims=(num_points, num_dofs, num_derivatives, reference_value_size)) - # From evaluatebasis.py: - # ref_values = L.FlattenedArray(reference_values, dims=(num_points, num_dofs, reference_value_size)) - - # Initialization (zeroing) and cutoffs outside valid range of orders - setup_code = [ - # Cutoff to evaluate_basis for order 0 - - # FIXME: (GNW) This has been changed to avoid a function calling - # another function, since with the change to C we don't know to - # full name of the other function. - # L.If(L.EQ(order, 0), [L.Return(-1)]), - # L.If( - # L.EQ(order, 0), [ - # L.Call("evaluate_reference_basis", - # (reference_values, num_points, X)), ret - # ]), - L.If( - L.EQ(order, 0), [ - L.Return( - L.Call("evaluate_reference_basis_{}".format(classname), - (reference_values, num_points, X))) - ]), - # Compute number of derivatives of this order - L.VariableDecl("const " + index_type, num_derivatives, value=L.Call("pow", (tdim, order))), - # Reset output values to zero - L.ForRange( - l0, - 0, - reference_values_size, - index_type=index_type, - body=L.Assign(reference_values[l0], 0.0)), - - # Cutoff for higher order than we have - L.If(L.GT(order, max_degree), ret), - ] - - # If max_degree is zero, we don't need to generate any more code - if max_degree == 0: - return setup_code + [ret] - - # Tabulate dmats tables for all dofs and all derivative directions - dmats_names, dmats_code = generate_tabulate_dmats(L, data["dofs_data"]) - - # Generate code with static tables of expansion coefficients - tables_code, coefficients_for_dof = generate_expansion_coefficients(L, data["dofs_data"]) - - # Generate code to compute tables of basisvalues - basisvalues_code, basisvalues_for_degree = \ - generate_compute_basisvalues(L, data["dofs_data"], element_cellname, X, ip) - - # Generate all possible combinations of derivatives. - combinations_code, combinations = _generate_combinations(L, tdim, max_degree, order, - num_derivatives) - - # Define symbols for variables populated inside dof switch - derivatives = L.Symbol("derivatives") - reference_offset = L.Symbol("reference_offset") - num_components = L.Symbol("num_components") - - # Get number of components of each basis function (>1 for dofs of piola mapped subelements) - num_components_values = [dof_data["num_components"] for dof_data in data["dofs_data"]] - - # Offset into parent mixed element to first component of each basis function - reference_offset_values = [dof_data["reference_offset"] for dof_data in data["dofs_data"]] - - # Max dimensions for the reference derivatives for each dof - max_num_derivatives = tdim**max_degree - max_num_components = max(num_components_values) - - # Add constant tables of these numbers - tables_code += [ - L.ArrayDecl( - "const " + index_type, reference_offset, num_dofs, values=reference_offset_values), - L.ArrayDecl("const " + index_type, num_components, num_dofs, values=num_components_values), - ] - - # Access reference derivatives compactly - derivs = L.FlattenedArray(derivatives, dims=(num_components[idof], num_derivatives)) - - # Create code for all basis values (dofs). - dof_cases = [] - for i_dof, dof_data in enumerate(data["dofs_data"]): - - embedded_degree = dof_data["embedded_degree"] - basisvalues = basisvalues_for_degree[embedded_degree] - - shape_dmats = numpy.shape(dof_data["dmats"][0]) - if shape_dmats[0] != shape_dmats[1]: - logging.exception("Something is wrong with the dmats: {}".format(dof_data["dmats"])) - - aux = L.Symbol("aux") - dmats = L.Symbol("dmats") - dmats_old = L.Symbol("dmats_old") - dmats_name = dmats_names[i_dof] - - # Create dmats matrix by multiplication - comb = L.Symbol("comb") - s = L.Symbol("s") - t = L.Symbol("t") - u = L.Symbol("u") - tu = L.Symbol("tu") - aux_computation_code = [ - L.ArrayDecl("double", aux, shape_dmats[0], values=0), - L.Comment("Declare derivative matrix (of polynomial basis)."), - L.ArrayDecl("double", dmats, shape_dmats, values=0), - L.Comment("Initialize dmats."), - L.VariableDecl(index_type, comb, combinations[r, 0]), - L.MemCopy( - L.AddressOf(dmats_name[comb][0][0]), L.AddressOf(dmats[0][0]), - shape_dmats[0] * shape_dmats[1], "double"), - L.Comment("Looping derivative order to generate dmats."), - L.ForRange( - s, - 1, - order, - index_type=index_type, - body=[ - L.Comment("Store previous dmats matrix."), - L.ArrayDecl("double", dmats_old, shape_dmats), - L.MemCopy( - L.AddressOf(dmats[0][0]), L.AddressOf(dmats_old[0][0]), - shape_dmats[0] * shape_dmats[1], "double"), - L.Comment("Resetting dmats."), - L.ForRange( - l0, - 0, - shape_dmats[0], - index_type=index_type, - body=L.ForRange( - l1, - 0, - shape_dmats[1], - index_type=index_type, - body=L.Assign(dmats[l0][l1], 0.0))), - L.Comment("Update dmats using an inner product."), - L.Assign(comb, combinations[r, s]), - L.ForRange( - t, - 0, - shape_dmats[0], - index_type=index_type, - body=L.ForRange( - u, - 0, - shape_dmats[1], - index_type=index_type, - body=L.ForRange( - tu, - 0, - shape_dmats[0], - index_type=index_type, - body=L.AssignAdd(dmats[t, u], - dmats_name[comb, t, tu] * dmats_old[tu, u])))) - ]), - L.ForRange( - s, - 0, - shape_dmats[0], - index_type=index_type, - body=L.ForRange( - t, - 0, - shape_dmats[1], - index_type=index_type, - body=L.AssignAdd(aux[s], dmats[s, t] * basisvalues[t]))) - ] - - # Unrolled loop over components of basis function - n = dof_data["num_components"] - compute_ref_derivs_code = [L.Assign(derivs[cc][r], 0.0) for cc in range(n)] - - compute_ref_derivs_code += [ - L.ForRange( - s, - 0, - shape_dmats[0], - index_type=index_type, - body=[ - L.AssignAdd(derivs[cc][r], coefficients_for_dof[i_dof][cc][s] * aux[s]) - for cc in range(n) - ]) - ] - - embedded_degree = dof_data["embedded_degree"] - basisvalues = basisvalues_for_degree[embedded_degree] - - # Compute the derivatives of the basisfunctions on the reference (FIAT) element, - # as the dot product of the new coefficients and basisvalues. - - case_code = [ - L.Comment("Compute reference derivatives for dof %d." % i_dof), - # Accumulate sum_s coefficients[s] * aux[s] - L.ForRange( - r, - 0, - num_derivatives, - index_type=index_type, - body=[aux_computation_code, compute_ref_derivs_code]) - ] - - dof_cases.append((i_dof, case_code)) - - # Loop over all dofs, entering a different switch case in each iteration. - # This is a legacy from the previous implementation where the loop - # was in a separate function and all the setup above was also repeated - # in a call for each dof. - # TODO: Further refactoring is needed to improve on this situation, - # but at least it's better than before. There's probably more code and - # computations that can be shared between dofs, and this would probably - # be easier to fix if mixed elements were handled separately! - dof_loop_code = [ - L.Comment("Loop over all dofs"), - L.ForRange( - idof, - 0, - num_dofs, - index_type=index_type, - body=[ - L.ArrayDecl("double", derivatives, max_num_components * max_num_derivatives, 0.0), - L.Switch(idof, dof_cases), - L.ForRange( - r, - 0, - num_derivatives, - index_type=index_type, - body=[ - L.ForRange( - c, - 0, - num_components[idof], - index_type=index_type, - body=[ - # FIXME: validate ref_values dims - L.Assign(ref_values[ip][idof][r][reference_offset[idof] + c], - derivs[c][r]), - ]), - ]) - ]), - ] - - # Define loop over points - final_loop_code = [ - L.ForRange(ip, 0, num_points, index_type=index_type, body=basisvalues_code + dof_loop_code) - ] - - # Stitch it all together - code = (setup_code + dmats_code + tables_code + combinations_code + final_loop_code + [ret]) - - return code - - -def generate_tabulate_dmats(L, dofs_data): - """Tabulate the derivatives of the polynomial base.""" - - # Emit code for the dmats we've actually used - dmats_code = [L.Comment("Tables of derivatives of the polynomial base (transpose).")] - - dmats_names = [] - - all_matrices = [] - - for idof, dof_data in enumerate(dofs_data): - # Get derivative matrices (coefficients) of basis functions, computed by FIAT at compile time. - derivative_matrices = dof_data["dmats"] - num_mats = len(derivative_matrices) - num_members = dof_data["num_expansion_members"] - - # Generate tables for each spatial direction. - matrix = numpy.zeros((num_mats, num_members, num_members)) - for i, dmat in enumerate(derivative_matrices): - # Extract derivatives for current direction - # (take transpose, FIAT_NEW PolynomialSet.tabulate()). - matrix[i, ...] = numpy.transpose(dmat) - - # TODO: Use precision from parameters here - from ffcx.ir.elementtables import clamp_table_small_numbers - matrix = clamp_table_small_numbers(matrix) - - # O(n^2) matrix matching... - name = None - for oldname, oldmatrix in all_matrices: - if matrix.shape == oldmatrix.shape and numpy.allclose(matrix, oldmatrix): - name = oldname - break - - if name is None: - # Define variable name for coefficients for this dof - name = L.Symbol("dmats%d" % (idof, )) - all_matrices.append((name, matrix)) - - # Declare new dmats table with unique values - decl = L.ArrayDecl( - "static const double", - name, (num_mats, num_members, num_members), - values=matrix) - dmats_code.append(decl) - - # Append name for each dof - dmats_names.append(name) - - return dmats_names, dmats_code - - -def _generate_combinations(L, tdim, max_degree, order, num_derivatives, suffix=""): - max_num_derivatives = tdim**max_degree - combinations = L.Symbol("combinations" + suffix) - - # This precomputes the combinations for each order and stores in code as table - # Python equivalent precomputed for each valid order: - combinations_shape = (max_degree, max_num_derivatives, max_degree) - all_combinations = numpy.zeros(combinations_shape, dtype=int) - for q in range(1, max_degree + 1): - for row in range(1, max_num_derivatives): - for num in range(0, row): - for col in range(q - 1, -1, -1): - if all_combinations[q - 1][row][col] > tdim - 2: - all_combinations[q - 1][row][col] = 0 - else: - all_combinations[q - 1][row][col] += 1 - break - code = [ - L.Comment("Precomputed combinations"), - L.ArrayDecl( - "const " + index_type, combinations, combinations_shape, values=all_combinations), - ] - # Select the right order for further access - combinations = combinations[order - 1] - - return code, combinations diff --git a/ffcx/codegeneration/evaluatebasis.py b/ffcx/codegeneration/evaluatebasis.py deleted file mode 100644 index 9e9c7e806..000000000 --- a/ffcx/codegeneration/evaluatebasis.py +++ /dev/null @@ -1,631 +0,0 @@ -import logging - -import numpy - -logger = logging.getLogger("ffcx") - - -# Used for various indices and arrays in this file -index_type = "int" - - -def generate_evaluate_reference_basis(L, data, parameters): - """Evaluate basis functions on the reference element. - - Generate code to evaluate element basisfunctions at an arbitrary - point on the reference element. - - The value(s) of the basisfunction is/are computed as in FIAT as - the dot product of the coefficients (computed at compile time) and - basisvalues which are dependent on the coordinate and thus have to - be computed at run time. - - The function should work for all elements supported by FIAT, but - it remains untested for tensor valued elements. - - The FFC code has a comment "From FIAT_NEW.polynomial_set.tabulate()". - - """ - # Cutoff for feature to disable generation of this code (consider - # removing after benchmarking final result) - if isinstance(data, str): - # Return error code - return [L.Return(-1)] - - # Get some known dimensions - element_cellname = data["cellname"] - reference_value_size = data["reference_value_size"] - num_dofs = len(data["dofs_data"]) - - # Input geometry - num_points = L.Symbol("num_points") - X = L.Symbol("X") - - # Output values - reference_values = L.Symbol("reference_values") - ref_values = L.FlattenedArray( - reference_values, dims=(num_points, num_dofs, reference_value_size)) - - # Loop indices - ip = L.Symbol("ip") - k = L.Symbol("k") - c = L.Symbol("c") - r = L.Symbol("r") - - # Return statement (value indicates that function is implemented) - ret = L.Return(0) - - # Generate code with static tables of expansion coefficients - tables_code, coefficients_for_dof = generate_expansion_coefficients( - L, data["dofs_data"]) - - # Reset reference_values[:] to 0 - reset_values_code = [ - L.ForRange( - k, - 0, - num_points * num_dofs * reference_value_size, - index_type=index_type, - body=L.Assign(reference_values[k], 0.0)) - ] - setup_code = tables_code + reset_values_code - - # Generate code to compute tables of basisvalues - basisvalues_code, basisvalues_for_degree = \ - generate_compute_basisvalues( - L, data["dofs_data"], element_cellname, X, ip) - - # Accumulate products of basisvalues and coefficients into values - accumulation_code = [ - L.Comment("Accumulate products of coefficients and basisvalues"), - ] - for idof, dof_data in enumerate(data["dofs_data"]): - embedded_degree = dof_data["embedded_degree"] - num_components = dof_data["num_components"] - num_members = dof_data["num_expansion_members"] - - # In ffcx representation, this is extracted per dof - # (but will coincide for some dofs of piola mapped elements): - reference_offset = dof_data["reference_offset"] - - # Select the right basisvalues for this dof - basisvalues = basisvalues_for_degree[embedded_degree] - - # Select the right coefficients for this dof - coefficients = coefficients_for_dof[idof] - - # Generate basis accumulation loop - if num_components > 1: - # Could just simplify by using this generic code - # and dropping the below two special cases - accumulation_code += [ - L.ForRange( - c, - 0, - num_components, - index_type=index_type, - body=L.ForRange( - r, - 0, - num_members, - index_type=index_type, - body=L.AssignAdd( - ref_values[ip, idof, reference_offset + c], - coefficients[c, r] * basisvalues[r]))) - ] - elif num_members > 1: - accumulation_code += [ - L.ForRange( - r, - 0, - num_members, - index_type=index_type, - body=L.AssignAdd(ref_values[ip, idof, reference_offset], - coefficients[0, r] * basisvalues[r])) - ] - else: - accumulation_code += [ - L.AssignAdd(ref_values[ip, idof, reference_offset], - coefficients[0, 0] * basisvalues[0]) - ] - - # TODO: Move this mapping to its own ufc function - # e.g. finite_element::apply_element_mapping(reference_values, - # J, K) - # code += _generate_apply_mapping_to_computed_values(L, dof_data) # Only works for affine (no-op) - - # Stitch it all together - code = [ - setup_code, - L.ForRange( - ip, - 0, - num_points, - index_type=index_type, - body=basisvalues_code + accumulation_code), ret - ] - return code - - -def generate_expansion_coefficients(L, dofs_data): - # TODO: Use precision parameter to format coefficients to match - # legacy implementation and make regression tests more robust - - all_tables = [] - tables_code = [] - coefficients_for_dof = [] - for idof, dof_data in enumerate(dofs_data): - num_components = dof_data["num_components"] - num_members = dof_data["num_expansion_members"] - fiat_coefficients = dof_data["coeffs"] - - # Check if any fiat_coefficients tables in expansion_coefficients - # are equal and reuse instead of declaring new. - coefficients = None - # NB: O(n^2) loop over tables - for symbol, table in all_tables: - if table.shape == fiat_coefficients.shape and numpy.allclose( - table, fiat_coefficients): - coefficients = symbol - break - - # Create separate variable name for coefficients table for each dof - if coefficients is None: - coefficients = L.Symbol("coefficients%d" % idof) - all_tables.append((coefficients, fiat_coefficients)) - - # Create static table with expansion coefficients computed by FIAT compile time. - tables_code += [ - L.ArrayDecl( - "static const double", - coefficients, (num_components, num_members), - values=fiat_coefficients) - ] - - # Store symbol reference for this dof - coefficients_for_dof.append(coefficients) - - return tables_code, coefficients_for_dof - - -def generate_compute_basisvalues(L, dofs_data, element_cellname, X, ip): - basisvalues_code = [ - L.Comment("Compute basisvalues for each relevant embedded degree"), - ] - basisvalues_for_degree = {} - for idof, dof_data in enumerate(dofs_data): - embedded_degree = dof_data["embedded_degree"] - - if embedded_degree not in basisvalues_for_degree: - num_members = dof_data["num_expansion_members"] - - basisvalues = L.Symbol("basisvalues%d" % embedded_degree) - bfcode = _generate_compute_basisvalues( - L, basisvalues, X, ip, element_cellname, embedded_degree, - num_members) - basisvalues_code += [L.StatementList(bfcode)] - - # Store symbol reference for this degree - basisvalues_for_degree[embedded_degree] = basisvalues - - return basisvalues_code, basisvalues_for_degree - - -def _generate_compute_basisvalues(L, basisvalues, X, ip, element_cellname, - embedded_degree, num_members): - """From FIAT_NEW.expansions.""" - - # Branch off to cell specific implementations - if element_cellname == "interval": - code = _generate_compute_interval_basisvalues( - L, basisvalues, X, ip, embedded_degree, num_members) - elif element_cellname == "triangle": - code = _generate_compute_triangle_basisvalues( - L, basisvalues, X, ip, embedded_degree, num_members) - elif element_cellname == "tetrahedron": - code = _generate_compute_tetrahedron_basisvalues( - L, basisvalues, X, ip, embedded_degree, num_members) - elif element_cellname == "quadrilateral": - code = _generate_compute_quad_basisvalues( - L, basisvalues, X, ip, embedded_degree, num_members) - elif element_cellname == "hexahedron": - code = _generate_compute_hex_basisvalues( - L, basisvalues, X, ip, embedded_degree, num_members) - else: - raise RuntimeError("Not supported:" + element_cellname) - - return code - - -def _jrc(a, b, n): - an = float((2 * n + 1 + a + b) * (2 * n + 2 + a + b)) / float(2 * (n + 1) * (n + 1 + a + b)) - bn = float((a * a - b * b) * (2 * n + 1 + a + b)) / float(2 * (n + 1) * (2 * n + a + b) * (n + 1 + a + b)) - cn = float((n + a) * (n + b) * (2 * n + 2 + a + b)) / float((n + 1) * (n + 1 + a + b) * (2 * n + a + b)) - return (an, bn, cn) - - -def _generate_compute_interval_basisvalues(L, basisvalues, X, ip, embedded_degree, - num_members): - # FIAT_NEW.expansions.LineExpansionSet. - - # Create zero-initialized array for with basisvalues - code = [L.ArrayDecl("double", basisvalues, (num_members, ), values=0)] - - code += [L.Assign(basisvalues[0], 1.0)] - - # FIAT coordinates - Y0 = 2 * X[ip] - 1 - - if embedded_degree > 0: - code += [L.Assign(basisvalues[1], Y0)] - - # Only active if embedded_degree > 1. - for r in range(2, embedded_degree + 1): - a1 = float(2 * r * r * (2 * r - 2)) - a3 = ((2 * r - 2) * (2 * r - 1) * (2 * r)) / a1 - a4 = (2 * (r - 1) * (r - 1) * (2 * r)) / a1 - value = Y0 * a3 * basisvalues[r - 1] - a4 * basisvalues[r - 2] - code += [L.Assign(basisvalues[r], value)] - - # Scale values - p = L.Symbol("p") - code += [ - L.ForRange( - p, - 0, - embedded_degree + 1, - index_type=index_type, - body=L.AssignMul(basisvalues[p], L.Sqrt(0.5 + p))) - ] - return code - - -def _generate_compute_quad_basisvalues(L, basisvalues, X, ip, embedded_degree, - num_members): - - # Create zero-initialized array for with basisvalues - code = [L.ArrayDecl("double", basisvalues, (num_members, ), values=0)] - - p = embedded_degree + 1 - assert p * p == num_members - - bx = [1.0] - by = [1.0] - - # FIAT coordinates - Y0 = 2 * X[ip * 2] - 1 - Y1 = 2 * X[ip * 2 + 1] - 1 - - if embedded_degree > 0: - bx += [Y0] - by += [Y1] - - # Only active if embedded_degree > 1. - for r in range(2, p): - a3 = (2 * r - 1) / r - a4 = (r - 1) / r - bx += [a3 * Y0 * bx[r - 1] - a4 * bx[r - 2]] - by += [a3 * Y1 * by[r - 1] - a4 * by[r - 2]] - - for r in range(p): - bx[r] *= numpy.sqrt(r + 0.5) - by[r] *= numpy.sqrt(r + 0.5) - - for r in range(p * p): - code += [L.Assign(basisvalues[r], bx[r // p] * by[r % p])] - - return code - - -def _generate_compute_hex_basisvalues(L, basisvalues, X, ip, embedded_degree, - num_members): - - # Create zero-initialized array for with basisvalues - code = [L.ArrayDecl("double", basisvalues, (num_members, ), values=0)] - - p = embedded_degree + 1 - assert p * p * p == num_members - - bx = [1.0] - by = [1.0] - bz = [1.0] - - # FIAT coordinates - Y0 = 2 * X[ip * 3] - 1 - Y1 = 2 * X[ip * 3 + 1] - 1 - Y2 = 2 * X[ip * 3 + 2] - 1 - - if embedded_degree > 0: - bx += [Y0] - by += [Y1] - bz += [Y2] - - # Only active if embedded_degree > 1. - for r in range(2, p): - a3 = (2 * r - 1) / r - a4 = (r - 1) / r - bx += [a3 * Y0 * bx[r - 1] - a4 * bx[r - 2]] - by += [a3 * Y1 * by[r - 1] - a4 * by[r - 2]] - bz += [a3 * Y2 * bz[r - 1] - a4 * bz[r - 2]] - - for r in range(p): - bx[r] *= numpy.sqrt(r + 0.5) - by[r] *= numpy.sqrt(r + 0.5) - bz[r] *= numpy.sqrt(r + 0.5) - - for r in range(p * p * p): - code += [L.Assign(basisvalues[r], bx[r // (p * p)] * by[(r // p) % p] * bz[r % p])] - - return code - - -def _generate_compute_triangle_basisvalues(L, basisvalues, X, ip, embedded_degree, - num_members): - # FIAT_NEW.expansions.TriangleExpansionSet. - - def _idx2d(p, q): - return (p + q) * (p + q + 1) // 2 + q - - # Create zero-initialized array for with basisvalues - code = [L.ArrayDecl("double", basisvalues, (num_members, ), values=0)] - - # FIAT coordinates - Y0 = 2 * X[ip * 2] - 1 - Y1 = 2 * X[ip * 2 + 1] - 1 - - # Compute helper factors - # FIAT_NEW code - # f1 = (1.0+2*x+y)/2.0 - # f2 = (1.0 - y) / 2.0 - # f3 = f2**2 - - # The initial value basisvalues 0 is always 1.0. - # FIAT_NEW code - # for ii in range( results.shape[1] ): - # results[0,ii] = 1.0 + apts[ii,0]-apts[ii,0]+apts[ii,1]-apts[ii,1] - code += [L.Assign(basisvalues[0], 1.0)] - - # Only continue if the embedded degree is larger than zero. - if embedded_degree == 0: - return code - - # The initial value of basisfunction 1 is equal to f1. - # FIAT_NEW code - # results[idx(1,0),:] = f1 - f1 = L.Symbol("tmp1_%d" % embedded_degree) - code += [ - L.VariableDecl("const double", f1, (1.0 + 2.0 * Y0 + Y1) / 2.0) - ] - code += [L.Assign(basisvalues[1], f1)] - - # NOTE: KBO: The order of the loops is VERY IMPORTANT!! - - # FIAT_NEW code (loop 1 in FIAT) - # for p in range(1,n): - # a = (2.0*p+1)/(1.0+p) - # b = p / (p+1.0) - # results[idx(p+1,0)] = a * f1 * results[idx(p,0),:] \ - # - b * f3 *results[idx(p-1,0),:] - # Only active if embedded_degree > 1. - if embedded_degree > 1: - f2 = L.Symbol("tmp2_%d" % embedded_degree) - f3 = L.Symbol("tmp3_%d" % embedded_degree) - code += [L.VariableDecl("const double", f2, (1.0 - Y1) / 2.0)] - code += [L.VariableDecl("const double", f3, f2 * f2)] - for r in range(1, embedded_degree): - rr = _idx2d((r + 1), 0) - ss = _idx2d(r, 0) - tt = _idx2d((r - 1), 0) - A = 1 + r / (r + 1) - B = r / (r + 1) - v1 = A * f1 * basisvalues[ss] - v2 = B * f3 * basisvalues[tt] - value = v1 - v2 - code += [L.Assign(basisvalues[rr], value)] - - # FIAT_NEW code (loop 2 in FIAT). - # for p in range(n): - # results[idx(p,1),:] = 0.5 * (1+2.0*p+(3.0+2.0*p)*y) \ - # * results[idx(p,0)] - for r in range(0, embedded_degree): - # (p+q)*(p+q+1)//2 + q - rr = _idx2d(r, 1) - ss = _idx2d(r, 0) - A = r + 0.5 - B = r + 1.5 - C = A + (B * Y1) - value = C * basisvalues[ss] - code += [L.Assign(basisvalues[rr], value)] - - # Only active if embedded_degree > 1. - # FIAT_NEW code (loop 3 in FIAT). - # for p in range(n-1): - # for q in range(1,n-p): - # (a1,a2,a3) = jrc(2*p+1,0,q) - # results[idx(p,q+1),:] \ - # = ( a1 * y + a2 ) * results[idx(p,q)] \ - # - a3 * results[idx(p,q-1)] - # Only active if embedded_degree > 1. - for r in range(0, embedded_degree - 1): - for s in range(1, embedded_degree - r): - rr = _idx2d(r, (s + 1)) - ss = _idx2d(r, s) - tt = _idx2d(r, s - 1) - A, B, C = _jrc(2 * r + 1, 0, s) - value = (B + A * Y1) * basisvalues[ss] - C * basisvalues[tt] - code += [L.Assign(basisvalues[rr], value)] - - # FIAT_NEW code (loop 4 in FIAT). - # for p in range(n+1): - # for q in range(n-p+1): - # results[idx(p,q),:] *= math.sqrt((p+0.5)*(p+q+1.0)) - for r in range(0, embedded_degree + 1): - for s in range(0, embedded_degree + 1 - r): - rr = _idx2d(r, s) - A = (r + 0.5) * (r + s + 1) - code += [L.AssignMul(basisvalues[rr], L.Sqrt(A))] - - return code - - -def _generate_compute_tetrahedron_basisvalues(L, basisvalues, X, ip, - embedded_degree, num_members): - # FIAT_NEW.expansions.TetrahedronExpansionSet. - - # FIAT_NEW code (compute index function) TetrahedronExpansionSet. - # def idx(p,q,r): - # return (p+q+r)*(p+q+r+1)*(p+q+r+2)//6 + (q+r)*(q+r+1)//2 + r - def _idx3d(p, q, r): - return (p + q + r) * (p + q + r + 1) * (p + q + r + 2) // 6 + ( - q + r) * (q + r + 1) // 2 + r - - # Compute helper factors. - # FIAT_NEW code - # factor1 = 0.5 * ( 2.0 + 2.0*x + y + z ) - # factor2 = (0.5*(y+z))**2 - # factor3 = 0.5 * ( 1 + 2.0 * y + z ) - # factor4 = 0.5 * ( 1 - z ) - # factor5 = factor4 ** 2 - - # Create zero-initialized array for with basisvalues - code = [L.ArrayDecl("double", basisvalues, (num_members, ), values=0)] - - # The initial value basisvalues 0 is always 1.0. - # FIAT_NEW code - # for ii in range( results.shape[1] ): - # results[0,ii] = 1.0 + apts[ii,0]-apts[ii,0]+apts[ii,1]-apts[ii,1] - code += [L.Assign(basisvalues[0], 1.0)] - - # Only continue if the embedded degree is larger than zero. - if embedded_degree == 0: - return code - - # FIAT coordinates - Y0 = 2 * X[ip * 3] - 1 - Y1 = 2 * X[ip * 3 + 1] - 1 - Y2 = 2 * X[ip * 3 + 2] - 1 - - # The initial value of basisfunction 1 is equal to f1. - # FIAT_NEW code - # results[idx(1,0),:] = f1 - f1 = L.Symbol("tmp1_%d" % embedded_degree) - code += [ - L.VariableDecl("const double", f1, - 0.5 * (2.0 + 2.0 * Y0 + Y1 + Y2)) - ] - code += [L.Assign(basisvalues[1], f1)] - - # NOTE: KBO: The order of the loops is VERY IMPORTANT!! - - # FIAT_NEW code (loop 1 in FIAT). - # for p in range(1,n): - # a1 = ( 2.0 * p + 1.0 ) / ( p + 1.0 ) - # a2 = p / (p + 1.0) - # results[idx(p+1,0,0)] = a1 * factor1 * results[idx(p,0,0)] \ - # -a2 * factor2 * results[ idx(p-1,0,0) ] - # Only active if embedded_degree > 1. - if embedded_degree > 1: - f2 = L.Symbol("tmp2_%d" % embedded_degree) - code += [ - L.VariableDecl("const double", f2, - 0.25 * (Y1 + Y2) * (Y1 + Y2)) - ] - for r in range(1, embedded_degree): - rr = _idx3d((r + 1), 0, 0) - ss = _idx3d(r, 0, 0) - tt = _idx3d((r - 1), 0, 0) - A = (2 * r + 1.0) / (r + 1) - B = r / (r + 1.0) - value = (A * f1 * basisvalues[ss]) - (B * f2 * basisvalues[tt]) - code += [L.Assign(basisvalues[rr], value)] - - # FIAT_NEW code (loop 2 in FIAT). - # q = 1 - # for p in range(0,n): - # results[idx(p,1,0)] = results[idx(p,0,0)] \ - # * ( p * (1.0 + y) + ( 2.0 + 3.0 * y + z ) / 2 ) - for r in range(0, embedded_degree): - rr = _idx3d(r, 1, 0) - ss = _idx3d(r, 0, 0) - term0 = 0.5 * (2.0 + 3.0 * Y1 + Y2) - term1 = float(r) * (1.0 + Y1) - value = (term0 + term1) * basisvalues[ss] - code += [L.Assign(basisvalues[rr], value)] - - # FIAT_NEW code (loop 3 in FIAT). - # for p in range(0,n-1): - # for q in range(1,n-p): - # (aq,bq,cq) = jrc(2*p+1,0,q) - # qmcoeff = aq * factor3 + bq * factor4 - # qm1coeff = cq * factor5 - # results[idx(p,q+1,0)] = qmcoeff * results[idx(p,q,0)] \ - # - qm1coeff * results[idx(p,q-1,0)] - # Only active if embedded_degree > 1. - if embedded_degree > 1: - f3 = L.Symbol("tmp3_%d" % embedded_degree) - f4 = L.Symbol("tmp4_%d" % embedded_degree) - f5 = L.Symbol("tmp5_%d" % embedded_degree) - code += [ - L.VariableDecl("const double", f3, 0.5 * (1.0 + 2.0 * Y1 + Y2)) - ] - code += [L.VariableDecl("const double", f4, 0.5 * (1.0 - Y2))] - code += [L.VariableDecl("const double", f5, f4 * f4)] - for r in range(0, embedded_degree - 1): - for s in range(1, embedded_degree - r): - rr = _idx3d(r, (s + 1), 0) - ss = _idx3d(r, s, 0) - tt = _idx3d(r, s - 1, 0) - (A, B, C) = _jrc(2 * r + 1, 0, s) - term0 = ((A * f3) + (B * f4)) * basisvalues[ss] - term1 = C * f5 * basisvalues[tt] - value = term0 - term1 - code += [L.Assign(basisvalues[rr], value)] - - # FIAT_NEW code (loop 4 in FIAT). - # now handle r=1 - # for p in range(n): - # for q in range(n-p): - # results[idx(p,q,1)] = results[idx(p,q,0)] \ - # * ( 1.0 + p + q + ( 2.0 + q + p ) * z ) - for r in range(0, embedded_degree): - for s in range(0, embedded_degree - r): - rr = _idx3d(r, s, 1) - ss = _idx3d(r, s, 0) - A = (float(2 + r + s) * Y2) + float(1 + r + s) - value = A * basisvalues[ss] - code += [L.Assign(basisvalues[rr], value)] - - # FIAT_NEW code (loop 5 in FIAT). - # general r by recurrence - # for p in range(n-1): - # for q in range(0,n-p-1): - # for r in range(1,n-p-q): - # ar,br,cr = jrc(2*p+2*q+2,0,r) - # results[idx(p,q,r+1)] = \ - # (ar * z + br) * results[idx(p,q,r) ] \ - # - cr * results[idx(p,q,r-1) ] - # Only active if embedded_degree > 1. - for r in range(0, embedded_degree - 1): - for s in range(0, embedded_degree - r - 1): - for t in range(1, embedded_degree - r - s): - rr = _idx3d(r, s, (t + 1)) - ss = _idx3d(r, s, t) - tt = _idx3d(r, s, t - 1) - (A, B, C) = _jrc(2 * r + 2 * s + 2, 0, t) - az_b = B + A * Y2 - value = (az_b * basisvalues[ss]) - (C * basisvalues[tt]) - code += [L.Assign(basisvalues[rr], value)] - - # FIAT_NEW code (loop 6 in FIAT). - # for p in range(n+1): - # for q in range(n-p+1): - # for r in range(n-p-q+1): - # results[idx(p,q,r)] *= math.sqrt((p+0.5)*(p+q+1.0)*(p+q+r+1.5)) - for r in range(0, embedded_degree + 1): - for s in range(0, embedded_degree - r + 1): - for t in range(0, embedded_degree - r - s + 1): - rr = _idx3d(r, s, t) - A = (r + 0.5) * (r + s + 1) * (r + s + t + 1.5) - code += [L.AssignMul(basisvalues[rr], L.Sqrt(A))] - - return code diff --git a/ffcx/codegeneration/evaluatedof.py b/ffcx/codegeneration/evaluatedof.py deleted file mode 100644 index f29f24e15..000000000 --- a/ffcx/codegeneration/evaluatedof.py +++ /dev/null @@ -1,353 +0,0 @@ -# Copyright (C) 2009-2017 Anders Logg and Martin Sandve Alnæs, Chris Richardson -# -# This file is part of FFCX.(https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -# Note: Much of the code in this file is a direct translation -# from the old implementation in FFC, although some improvements -# have been made to the generated code. - -import logging - -from ffcx.codegeneration.jacobian import inverse_jacobian, jacobian -from ufl.permutation import build_component_numbering - -logger = logging.getLogger("ffcx") -index_type = "int" - - -def reference_to_physical_map(cellname): - """Returns a map from reference coordinates to physical element coordinates.""" - - if cellname == "interval": - return lambda x: (1.0 - x[0], x[0]) - elif cellname == "triangle": - return lambda x: (1.0 - x[0] - x[1], x[0], x[1]) - elif cellname == "tetrahedron": - return lambda x: (1.0 - x[0] - x[1] - x[2], x[0], x[1], x[2]) - elif cellname == "quadrilateral": - return lambda x: ((1 - x[0]) * (1 - x[1]), (1 - x[0]) * x[1], x[0] * (1 - x[1]), x[0] * x[1]) - elif cellname == "hexahedron": - return lambda x: ((1 - x[0]) * (1 - x[1]) * (1 - x[2]), (1 - x[0]) * (1 - x[1]) * x[2], (1 - x[0]) * x[1] * (1 - x[2]), (1 - x[0]) * x[1] * x[2], x[0] * (1 - x[1]) * (1 - x[2]), x[0] * (1 - x[1]) * x[2], x[0] * x[1] * (1 - x[2]), x[0] * x[1] * x[2]) # noqa: E501 - - -def _change_variables(L, mapping, gdim, tdim, offset): - """Generate code for mapping function values according to 'mapping' and offset. - - The basics of how to map a field from a physical to the reference - domain. (For the inverse approach -- see interpolatevertexvalues) - - 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 - - """ - - # meg: Various mappings must be handled both here and in - # interpolate_vertex_values. Could this be abstracted out? - - values = L.Symbol("physical_values") - - if mapping == "affine": - return [values[offset]] - elif mapping == "contravariant piola": - # Map each component from physical to reference using inverse - # contravariant piola - detJ = L.Symbol("detJ") - K = L.Symbol("K") - K = L.FlattenedArray(K, dims=(tdim, gdim)) - w = [] - for i in range(tdim): - inner = 0.0 - for j in range(gdim): - inner += values[j + offset] * K[i, j] - w.append(inner * detJ) - return w - - elif mapping == "covariant piola": - # Map each component from physical to reference using inverse - # covariant piola - detJ = L.Symbol("detJ") - J = L.Symbol("J") - J = L.FlattenedArray(J, dims=(gdim, tdim)) - w = [] - for i in range(tdim): - inner = 0.0 - for j in range(gdim): - inner += values[j + offset] * J[j, i] - w.append(inner) - return w - - elif mapping == "double covariant piola": - # physical to reference pullback as a covariant 2-tensor - w = [] - J = L.Symbol("J") - J = L.FlattenedArray(J, dims=(gdim, tdim)) - for i in range(tdim): - for l in range(tdim): - inner = 0.0 - for k in range(gdim): - for j in range(gdim): - inner += J[j, i] * values[j * tdim + k + offset] * J[k, l] - w.append(inner) - return w - - elif mapping == "double contravariant piola": - # physical to reference using double contravariant piola - w = [] - K = L.Symbol("K") - K = L.FlattenedArray(K, dims=(tdim, gdim)) - detJ = L.Symbol("detJ") - for i in range(tdim): - for l in range(tdim): - inner = 0.0 - for k in range(gdim): - for j in range(gdim): - inner += K[i, j] * values[j * tdim + k + offset] * K[l, k] - w.append(inner * detJ * detJ) - return w - - else: - raise Exception("The mapping (%s) is not allowed" % mapping) - - -def _generate_body(L, i, dof, mapping, gdim, tdim, cell_shape, offset=0): - """Generate code for a single dof.""" - - # EnrichedElement is handled by having [None, ..., None] dual basis - if not dof: - # Return error code - return ([L.Return(-1)], 0.0) - - points = list(dof.keys()) - - # Generate different code if multiple points. (Otherwise ffcx - # compile time blows up.) - if len(points) > 1: - return ([], -1) - # FIXME: this currently returns empty code as the line below returns code that fails to compile - # return _generate_multiple_points_body(L, i, dof, mapping, gdim, tdim, offset) - - # Get weights for mapping reference point to physical - x = points[0] - w = reference_to_physical_map(cell_shape)(x) - - # Map point onto physical element: y = F_K(x) - code = [] - - # Map function values to the reference element - F = _change_variables(L, mapping, gdim, tdim, offset) - - # Simple affine functions deserve special case: - if len(F) == 1: - return (code, dof[x][0][0] * F[0]) - - # Flatten multi-indices - (index_map, _) = build_component_numbering([tdim] * len(dof[x][0][1]), ()) - # Take inner product between components and weights - value = 0.0 - for (w, k) in dof[x]: - value += w * F[index_map[k]] - - # Return eval code and value - return (code, value) - - -def _generate_multiple_points_body(L, i, dof, mapping, gdim, tdim, offset=0): - """Generate c++ for-loop for multiple points (integral bodies).""" - - result = L.Symbol("result") - code = [L.Assign(result, 0.0)] - points = list(dof.keys()) - n = len(points) - - # Get number of tokens per point - tokens = [dof[x] for x in points] - len_tokens = [len(t) for t in tokens] - assert all(x == len_tokens[0] for x in len_tokens) - len_tokens = len_tokens[0] - - # Declare points - # points = format["list"]([format["list"](x) for x in points]) - X_i = L.Symbol("X_%d" % i) - code += [L.ArrayDecl("double", X_i, [n, tdim], points)] - - # Declare components - components = [[c[0] for (w, c) in token] for token in tokens] - # components = format["list"]([format["list"](c) for c in components]) - D_i = L.Symbol("D_%d" % i) - code += [L.ArrayDecl("int", D_i, [n, len_tokens], components)] - - # Declare weights - weights = [[w for (w, c) in token] for token in tokens] - # weights = format["list"]([format["list"](w) for w in weights]) - W_i = L.Symbol("W_%d" % i) - code += [L.ArrayDecl("double", W_i, [n, len_tokens], weights)] - - # Declare copy variable: - copy_i = L.Symbol("copy_%d" % i) - code += [L.ArrayDecl("double", copy_i, tdim)] - - # Add loop over points - code += [L.Comment("Loop over points")] - - # Map the points from the reference onto the physical element - r = L.Symbol("r") - w0 = L.Symbol("w0") - w1 = L.Symbol("w1") - w2 = L.Symbol("w2") - w3 = L.Symbol("w3") - y = L.Symbol("y") - - coordinate_dofs = L.Symbol("coordinate_dofs") - - if tdim == 1: - lines_r = [ - L.Comment("Evaluate basis functions for affine mapping"), - L.VariableDecl("const double", w0, 1 - X_i[r][0]), - L.VariableDecl("const double", w1, X_i[r][0]), - L.Comment("Compute affine mapping y = F(X)"), - L.ArrayDecl("double", y, [gdim]) - ] - for j in range(gdim): - lines_r += [L.Assign(y[j], w0 * coordinate_dofs[j] + w1 * coordinate_dofs[j + gdim])] - elif tdim == 2: - lines_r = [ - L.Comment("Evaluate basis functions for affine mapping"), - L.VariableDecl("const double", w0, 1 - X_i[r][0] - X_i[r][1]), - L.VariableDecl("const double", w1, X_i[r][0]), - L.VariableDecl("const double", w2, X_i[r][1]), - L.Comment("Compute affine mapping y = F(X)"), - L.ArrayDecl("double", y, [gdim]) - ] - for j in range(gdim): - lines_r += [ - L.Assign(y[j], w0 * coordinate_dofs[j] - + w1 * coordinate_dofs[j + gdim] - + w2 * coordinate_dofs[j + 2 * gdim]) - ] - elif tdim == 3: - lines_r = [ - L.Comment("Evaluate basis functions for affine mapping"), - L.VariableDecl("const double", w0, 1 - X_i[r][0] - X_i[r][1] - X_i[r][2]), - L.VariableDecl("const double", w1, X_i[r][0]), - L.VariableDecl("const double", w2, X_i[r][1]), - L.VariableDecl("const double", w3, X_i[r][2]), - L.Comment("Compute affine mapping y = F(X)"), - L.ArrayDecl("double", y, [gdim]) - ] - for j in range(gdim): - lines_r += [ - L.Assign(y[j], w0 * coordinate_dofs[j] - + w1 * coordinate_dofs[j + gdim] - + w2 * coordinate_dofs[j + 2 * gdim] - + w3 * coordinate_dofs[j + 3 * gdim]) - ] - - # Evaluate function at physical point - lines_r += [L.Comment("Evaluate function at physical point")] - # vals = L.Symbol("physical_values") - # c = L.Symbol("c") - lines_r += [L.Comment("Replace this: f.evaluate(vals, y, c)")] - - # Map function values to the reference element - lines_r += [L.Comment("Map function to reference element")] - F = _change_variables(L, mapping, gdim, tdim, offset) - lines_r += [L.Assign(copy_i[k], F_k) for (k, F_k) in enumerate(F)] - - # Add loop over directional components - lines_r += [L.Comment("Loop over directions")] - - s = L.Symbol("s") - lines_r += [ - L.ForRange( - s, - 0, - len_tokens, - index_type=index_type, - body=[L.AssignAdd(result, copy_i[D_i[r, s]] * W_i[r, s])]) - ] - - # Generate loop over r and add to code. - code += [L.ForRange(r, 0, n, index_type=index_type, body=lines_r)] - return (code, result) - - -def generate_transform_values(L, ir): - """Generate code for transform_values. - Transforms values in physical space into reference space. - These values represent evaluation of the function at dof - points (only valid for point evaluation dofs). - - """ - - gdim = ir.geometric_dimension - tdim = ir.topological_dimension - cell_shape = ir.cell_shape - - # Enriched element, no dofs defined - if not any(ir.dofs): - code = [] - else: - code = [] - # Check whether Jacobians are necessary. - needs_inverse_jacobian = any(["contravariant piola" in m for m in ir.mappings]) - needs_jacobian = any(["covariant piola" in m for m in ir.mappings]) - - # Intermediate variable needed for multiple point dofs - needs_temporary = any(dof is not None and len(dof) > 1 for dof in ir.dofs) - if needs_temporary: - logger.warning("Generating code for evaluation of multiple-point dofs not working properly.") - # result = L.Symbol("result") - # code += [L.VariableDecl("double", result)] - - if needs_jacobian or needs_inverse_jacobian: - code += jacobian(L, gdim, tdim, cell_shape) - - if needs_inverse_jacobian: - code += inverse_jacobian(L, gdim, tdim, cell_shape) - - # Extract variables - mappings = ir.mappings - offsets = ir.physical_offsets - - # Generate bodies for each degree of freedom - values = L.Symbol("reference_values") - value_size = ir.physical_value_size - for (i, dof) in enumerate(ir.dofs): - c, r = _generate_body(L, i, dof, mappings[i], gdim, tdim, cell_shape, - offsets[i] + i * value_size) - code += c - code += [L.Assign(values[i], r)] - - code += [L.Return(0)] - return code diff --git a/ffcx/codegeneration/expressions.py b/ffcx/codegeneration/expressions.py index 7234b8709..e1597ac30 100644 --- a/ffcx/codegeneration/expressions.py +++ b/ffcx/codegeneration/expressions.py @@ -20,8 +20,8 @@ def generator(ir, parameters): """Generate UFC code for an expression.""" logger.info("Generating code for expression:") - logger.info("--- points: {}".format(ir.points)) - logger.info("--- name: {}".format(ir.name)) + logger.info(f"--- points: {ir.points}") + logger.info(f"--- name: {ir.name}") factory_name = ir.name @@ -32,7 +32,7 @@ def generator(ir, parameters): eg = ExpressionGenerator(ir, backend) code = {} - code["name"] = "{}_expression".format(ir.name) + code["name"] = f"{ir.name}_expression" parts = eg.generate() body = format_indented_lines(parts.cs_format(), 1) @@ -129,7 +129,7 @@ def generate_quadrature_loop(self): # Generate varying partition body = self.generate_varying_partition() body = L.commented_code_list( - body, "Points loop body setup quadrature loop {}".format(self.quadrature_rule.id())) + body, f"Points loop body setup quadrature loop {self.quadrature_rule.id()}") # Generate dofblock parts, some of this # will be placed before or after quadloop @@ -155,10 +155,10 @@ def generate_varying_partition(self): # Get annotated graph of factorisation F = self.ir.integrand[self.quadrature_rule]["factorization"] - arraysymbol = L.Symbol("sv_{}".format(self.quadrature_rule.id())) + arraysymbol = L.Symbol(f"sv_{self.quadrature_rule.id()}") parts = self.generate_partition(arraysymbol, F, "varying") parts = L.commented_code_list( - parts, "Unstructured varying computations for quadrature rule {}".format(self.quadrature_rule.id())) + parts, f"Unstructured varying computations for quadrature rule {self.quadrature_rule.id()}") return parts def generate_piecewise_partition(self): diff --git a/ffcx/codegeneration/finite_element.py b/ffcx/codegeneration/finite_element.py index 579f8fdd9..c632fa753 100644 --- a/ffcx/codegeneration/finite_element.py +++ b/ffcx/codegeneration/finite_element.py @@ -10,22 +10,47 @@ from collections import defaultdict import logging -import warnings +import numpy import ffcx.codegeneration.finite_element_template as ufc_finite_element import ufl -from ffcx.codegeneration.evalderivs import (_generate_combinations, - generate_evaluate_reference_basis_derivatives) -from ffcx.codegeneration.evaluatebasis import generate_evaluate_reference_basis -from ffcx.codegeneration.evaluatedof import generate_transform_values from ffcx.codegeneration.utils import (generate_return_int_switch, - generate_return_new_switch) + generate_return_new_switch, + apply_permutations_to_data) logger = logging.getLogger("ffcx") index_type = "int" +def _generate_combinations(L, tdim, max_degree, order, num_derivatives, suffix=""): + max_num_derivatives = tdim**max_degree + combinations = L.Symbol("combinations" + suffix) + + # This precomputes the combinations for each order and stores in code as table + # Python equivalent precomputed for each valid order: + combinations_shape = (max_degree, max_num_derivatives, max_degree) + all_combinations = numpy.zeros(combinations_shape, dtype=int) + for q in range(1, max_degree + 1): + for row in range(1, max_num_derivatives): + for num in range(0, row): + for col in range(q - 1, -1, -1): + if all_combinations[q - 1][row][col] > tdim - 2: + all_combinations[q - 1][row][col] = 0 + else: + all_combinations[q - 1][row][col] += 1 + break + code = [ + L.Comment("Precomputed combinations"), + L.ArrayDecl( + "const " + index_type, combinations, combinations_shape, values=all_combinations), + ] + # Select the right order for further access + combinations = combinations[order - 1] + + return code, combinations + + def generate_element_mapping(mapping, i, num_reference_components, tdim, gdim, J, detJ, K): # Select transformation to apply if mapping == "affine": @@ -77,7 +102,7 @@ def sub_element_declaration(L, ir): classnames = set(ir.create_sub_element) code = "" for name in classnames: - code += "ufc_finite_element* create_{name}(void);\n".format(name=name) + code += f"ufc_finite_element* create_{name}(void);\n" return code @@ -86,92 +111,28 @@ def create_sub_element(L, ir): return generate_return_new_switch(L, "i", classnames) -def transform_values(L, ir, parameters): - """Generate code for transform_values.""" - return generate_transform_values(L, ir.evaluate_dof) - - -def tabulate_reference_dof_coordinates(L, ir, parameters): - # TODO: ensure points is a numpy array, - # get tdim from points.shape[1], - # place points in ir directly instead of the subdict - ir = ir.tabulate_dof_coordinates - - # Raise error if tabulate_reference_dof_coordinates is ill-defined - if not ir: - return [L.Return(-1)] - - # Extract coordinates and cell dimension - tdim = ir.tdim - points = ir.points - - # Output argument - reference_dof_coordinates = L.Symbol("reference_dof_coordinates") - - # Reference coordinates - dof_X = L.Symbol("dof_X") - dof_X_values = [X[jj] for X in points for jj in range(tdim)] - decl = L.ArrayDecl("static const double", dof_X, (len(points) * tdim, ), values=dof_X_values) - copy = L.MemCopy(dof_X, reference_dof_coordinates, tdim * len(points), "double") - ret = L.Return(0) - return [decl, copy, ret] - - -def evaluate_reference_basis(L, ir, parameters): - data = ir.evaluate_basis - if isinstance(data, str): - # Function has not been requested - msg = "evaluate_reference_basis: {}".format(data) - return [L.Comment(msg), L.Return(-1)] - - return generate_evaluate_reference_basis(L, data, parameters) - - -def evaluate_reference_basis_derivatives(L, ir, parameters): - data = ir.evaluate_basis - if isinstance(data, str): - # Function has not been requested - msg = "evaluate_reference_basis_derivatives: {}".format(data) - return [L.Comment(msg), L.Return(-1)] - - return generate_evaluate_reference_basis_derivatives(L, data, ir.name, parameters) +def apply_dof_transformation(L, ir, parameters, reverse=False, dtype="double"): + """Write function that applies the DOF tranformations/permutations to some data.""" + data = L.Symbol("data") + block = L.Symbol("block") + block_size = L.Symbol("dim") - -def entity_reflection(L, i, cell_shape): - """Returns the bool that says whether or not an entity has been reflected.""" - cell_info = L.Symbol("cell_permutation") - if cell_shape in ["triangle", "quadrilateral"]: - num_faces = 0 - face_bitsize = 1 - assert i[0] == 1 - if cell_shape == "tetrahedron": - num_faces = 4 - face_bitsize = 3 - if cell_shape == "hexahedron": - num_faces = 6 - face_bitsize = 3 - if i[0] == 1: - return L.NE(L.BitwiseAnd(cell_info, L.BitShiftL(1, face_bitsize * num_faces + i[1])), 0) - elif i[0] == 2: - return L.NE(L.BitwiseAnd(cell_info, L.BitShiftL(1, face_bitsize * i[1])), 0) - return L.LiteralBool(False) + apply_permutations = apply_permutations_to_data( + L, ir.base_permutations, ir.cell_shape, data, reverse=reverse, + indices=lambda dof: dof * block_size + block, ranges=[(block, 0, block_size)], + dtype=dtype) + return apply_permutations + [L.Return(0)] def transform_reference_basis_derivatives(L, ir, parameters): - data = ir.evaluate_basis - if isinstance(data, str): - # Function has not been requested - msg = "transform_reference_basis_derivatives: {}".format(data) - return [L.Comment(msg), L.Return(-1)] - # Get some known dimensions # element_cellname = data["cellname"] - gdim = data["geometric_dimension"] - tdim = data["topological_dimension"] - max_degree = data["max_degree"] - reference_value_size = data["reference_value_size"] - physical_value_size = data["physical_value_size"] - num_dofs = len(data["dofs_data"]) + gdim = ir.geometric_dimension + tdim = ir.topological_dimension + max_degree = ir.degree + reference_value_size = ufl.product(ir.reference_value_shape) // ir.block_size + physical_value_size = ufl.product(ir.value_shape) // ir.block_size + num_dofs = ir.space_dimension // ir.block_size max_g_d = gdim**max_degree max_t_d = tdim**max_degree @@ -271,58 +232,12 @@ def transform_reference_basis_derivatives(L, ir, parameters): body=L.Assign(values_symbol[iz], 0.0)), ] - # Make offsets available in generated code - reference_offsets = L.Symbol("reference_offsets") - physical_offsets = L.Symbol("physical_offsets") - dof_attributes_code = [ - L.ArrayDecl( - "const " + index_type, - reference_offsets, (num_dofs, ), - values=[dof_data["reference_offset"] for dof_data in data["dofs_data"]]), - L.ArrayDecl( - "const " + index_type, - physical_offsets, (num_dofs, ), - values=[dof_data["physical_offset"] for dof_data in data["dofs_data"]]) - ] - - # Make array of which vector dofs to reflect the direction of - contains_reflections = False - reflect_dofs = [] - c_false = L.LiteralBool(False) - - for dre in ir.dof_reflection_entities: - if dre is None: - # Dof does not need reflecting, so put false in array - reflect_dofs.append(c_false) - else: - # Loop through entities that the direction of the dof depends on to - # make a conditional - ref = c_false - for j in dre: - new_ref = entity_reflection(L, j, ir.cell_shape) - if ref == c_false: - # No condition has been added yet, so overwrite false - ref = new_ref - elif ref == new_ref: - # A != A is false - ref = c_false - else: - # This is not the first condition, so XOR - ref = L.NE(ref, new_ref) - reflect_dofs.append(ref) - if ref != c_false: - # Mark this space as needing reflections - contains_reflections = True - - # If one or more dofs need reflecting, write the array - if contains_reflections: - dof_attributes_code.append(L.ArrayDecl( - "const bool", L.Symbol("reflected_dofs"), (len(reflect_dofs), ), values=reflect_dofs)) - # Build dof lists for each mapping type mapping_dofs = defaultdict(list) - for idof, dof_data in enumerate(data["dofs_data"]): - mapping_dofs[dof_data["mapping"]].append(idof) + for idof, mapping in enumerate(ir.dof_mappings): + mapping_dofs[mapping].append(idof) + + dof_attributes_code = [] # Generate code for each mapping type d = L.Symbol("d") @@ -345,31 +260,16 @@ def transform_reference_basis_derivatives(L, ir, parameters): dofrange = (d, 0, len(idofs)) idof = idofs_symbol[d] - # NB! Array access to offsets, these are not Python integers - reference_offset = reference_offsets[idof] - physical_offset = physical_offsets[idof] - # How many components does each basis function with this mapping have? - # This should be uniform, i.e. there should be only one element in this set: - num_reference_components, = set(data["dofs_data"][i]["num_components"] for i in idofs) + num_reference_components = ir.num_reference_components[mapping] M_scale, M_row, num_physical_components = generate_element_mapping( mapping, i, num_reference_components, tdim, gdim, J[ip], detJ[ip], K[ip]) - # transform_apply_body = [ - # L.AssignAdd(values[ip, idof, r, physical_offset + k], - # transform[r, s] * reference_values[ip, idof, s, reference_offset + k]) - # for k in range(num_physical_components) - # ] - msg = "Using %s transform to map values back to the physical element." % mapping.replace( "piola", "Piola") mapped_value = L.Symbol("mapped_value") - if contains_reflections: - vec_scale = L.Conditional(L.Symbol("reflected_dofs")[idof], -1, 1) - else: - vec_scale = 1 transform_apply_code += [ L.ForRanges( @@ -380,63 +280,24 @@ def transform_reference_basis_derivatives(L, ir, parameters): body=[ # Unrolled application of mapping to one physical component, # for affine this automatically reduces to - # mapped_value = reference_values[..., reference_offset] + # mapped_value = reference_values[..., 0] L.Comment(msg), L.VariableDecl( "const double", mapped_value, - M_scale * vec_scale * sum( - M_row[jj] * reference_values[ip, idof, s, reference_offset + jj] + M_scale * sum( + M_row[jj] * reference_values[ip, idof, s, jj] for jj in range(num_reference_components))), # Apply derivative transformation, for order=0 this reduces to - # values[ip,idof,0,physical_offset+i] = transform[0,0]*mapped_value + # values[ip,idof,0,i] = transform[0,0]*mapped_value L.Comment("Mapping derivatives back to the physical element"), L.ForRanges((r, 0, num_derivatives_g), index_type=index_type, body=[ - L.AssignAdd(values[ip, idof, r, physical_offset + i], + L.AssignAdd(values[ip, idof, r, i], transform[r, s] * mapped_value)]) ]) ] - # Correct data for rotations and reflections of face tangents - face_tangents = [] - temporary_variables = 0 - for (entity_dim, entity_n), face_tangent_data in ir.dof_face_tangents.items(): - if entity_dim != 2: - warnings.warn("Face tangents an entity of dim != 2 not implemented.") - continue - - # Use temporary variables t0, t1, ... to store current data - temps = {} - for perm, ft in face_tangent_data.items(): - for combo in ft.values(): - for dof, w in combo: - if dof not in temps: - temps[dof] = L.Symbol("t" + str(len(temps))) - temporary_variables = max(temporary_variables, len(temps)) - - for perm, ft in face_tangent_data.items(): - body = [] - for dof, combo in ft.items(): - v = values[ip, dof, r, physical_offsets[dof] + i] - body.append(L.Assign(v, sum(w * temps[dof] for dof, w in combo))) - - # If cell_permutation has given value, overwrite data with linear combination of temporary data - if len(body) > 0: - entity_perm = L.BitwiseAnd(L.BitShiftR(L.Symbol("cell_permutation"), 3 * entity_n), 7) - - if perm == 0: - face_tangents += [L.If(L.EQ(entity_perm, perm), body)] - else: - face_tangents += [L.ElseIf(L.EQ(entity_perm, perm), body)] - - if len(face_tangents) > 0: - face_tangents = [ - L.ForRanges((s, 0, num_derivatives_t), (i, 0, num_physical_components), - (r, 0, num_derivatives_g), index_type=index_type, body=face_tangents)] - face_tangents = [L.VariableDecl("double", L.Symbol("t" + str(i)), 0) - for i in range(temporary_variables)] + face_tangents - # Transform for each point point_loop_code = [ L.ForRange( @@ -444,7 +305,7 @@ def transform_reference_basis_derivatives(L, ir, parameters): 0, num_points, index_type=index_type, - body=(transform_matrix_code + transform_apply_code + face_tangents)) + body=(transform_matrix_code + transform_apply_code)) ] # Join code @@ -458,14 +319,14 @@ def generator(ir, parameters): """Generate UFC code for a finite element.""" logger.info("Generating code for finite element:") - logger.info("--- family: {}".format(ir.family)) - logger.info("--- degree: {}".format(ir.degree)) - logger.info("--- value shape: {}".format(ir.value_shape)) - logger.info("--- name: {}".format(ir.name)) + logger.info(f"--- family: {ir.family}") + logger.info(f"--- degree: {ir.degree}") + logger.info(f"--- value shape: {ir.value_shape}") + logger.info(f"--- name: {ir.name}") d = {} d["factory_name"] = ir.name - d["signature"] = "\"{}\"".format(ir.signature) + d["signature"] = f"\"{ir.signature}\"" d["geometric_dimension"] = ir.geometric_dimension d["topological_dimension"] = ir.topological_dimension d["cell_shape"] = ir.cell_shape @@ -475,29 +336,25 @@ def generator(ir, parameters): d["reference_value_rank"] = len(ir.reference_value_shape) d["reference_value_size"] = ufl.product(ir.reference_value_shape) d["degree"] = ir.degree - d["family"] = "\"{}\"".format(ir.family) + d["family"] = f"\"{ir.family}\"" d["num_sub_elements"] = ir.num_sub_elements d["block_size"] = ir.block_size + d["needs_permutation_data"] = ir.needs_permutation_data + d["interpolation_is_identity"] = ir.interpolation_is_identity import ffcx.codegeneration.C.cnodes as L d["value_dimension"] = value_dimension(L, ir.value_shape) d["reference_value_dimension"] = reference_value_dimension(L, ir.reference_value_shape) - statements = evaluate_reference_basis(L, ir, parameters) - d["evaluate_reference_basis"] = L.StatementList(statements) - - statements = evaluate_reference_basis_derivatives(L, ir, parameters) - d["evaluate_reference_basis_derivatives"] = L.StatementList(statements) - statements = transform_reference_basis_derivatives(L, ir, parameters) d["transform_reference_basis_derivatives"] = L.StatementList(statements) - statements = transform_values(L, ir, parameters) - d["transform_values"] = L.StatementList(statements) + statements = apply_dof_transformation(L, ir, parameters) + d["apply_dof_transformation"] = L.StatementList(statements) - statements = tabulate_reference_dof_coordinates(L, ir, parameters) - d["tabulate_reference_dof_coordinates"] = L.StatementList(statements) + statements = apply_dof_transformation(L, ir, parameters, dtype="ufc_scalar_t") + d["apply_dof_transformation_to_scalar"] = L.StatementList(statements) statements = create_sub_element(L, ir) d["sub_element_declaration"] = sub_element_declaration(L, ir) diff --git a/ffcx/codegeneration/finite_element_template.py b/ffcx/codegeneration/finite_element_template.py index 02f0652a2..81b957b4e 100644 --- a/ffcx/codegeneration/finite_element_template.py +++ b/ffcx/codegeneration/finite_element_template.py @@ -20,42 +20,26 @@ {reference_value_dimension} }} -int evaluate_reference_basis_{factory_name}(double* restrict reference_values, - int num_points, - const double* restrict X) -{{ - {evaluate_reference_basis} -}} - -int evaluate_reference_basis_derivatives_{factory_name}(double * restrict reference_values, - int order, int num_points, - const double * restrict X) -{{ - {evaluate_reference_basis_derivatives} -}} - int transform_reference_basis_derivatives_{factory_name}( double * restrict values, int order, int num_points, const double * restrict reference_values, const double * restrict X, const double * restrict J, - const double * restrict detJ, const double * restrict K, - const uint32_t cell_permutation) + const double * restrict detJ, const double * restrict K) {{ {transform_reference_basis_derivatives} }} -int transform_values_{factory_name}( - ufc_scalar_t* restrict reference_values, - const ufc_scalar_t* restrict physical_values, - const double* restrict coordinate_dofs, - const ufc_coordinate_mapping* restrict cm) + +int apply_dof_transformation_{factory_name}( + double* restrict data, uint32_t cell_permutation, int dim) {{ - {transform_values} + {apply_dof_transformation} }} -int tabulate_reference_dof_coordinates_{factory_name}(double* restrict reference_dof_coordinates) +int apply_dof_transformation_to_scalar_{factory_name}( + ufc_scalar_t* restrict data, uint32_t cell_permutation, int dim) {{ - {tabulate_reference_dof_coordinates} + {apply_dof_transformation_to_scalar} }} {sub_element_declaration} @@ -82,11 +66,13 @@ element->degree = {degree}; element->family = {family}; element->block_size = {block_size}; - element->evaluate_reference_basis = evaluate_reference_basis_{factory_name}; - element->evaluate_reference_basis_derivatives = evaluate_reference_basis_derivatives_{factory_name}; + + element->needs_permutation_data = {needs_permutation_data}; + element->interpolation_is_identity = {interpolation_is_identity}; + element->transform_reference_basis_derivatives = transform_reference_basis_derivatives_{factory_name}; - element->transform_values = transform_values_{factory_name}; - element->tabulate_reference_dof_coordinates = tabulate_reference_dof_coordinates_{factory_name}; + element->apply_dof_transformation = apply_dof_transformation_{factory_name}; + element->apply_dof_transformation_to_scalar = apply_dof_transformation_to_scalar_{factory_name}; element->num_sub_elements = {num_sub_elements}; element->create_sub_element = create_sub_element_{factory_name}; element->create = create_{factory_name}; diff --git a/ffcx/codegeneration/form.py b/ffcx/codegeneration/form.py index 4512d4f2e..dd5260278 100644 --- a/ffcx/codegeneration/form.py +++ b/ffcx/codegeneration/form.py @@ -114,7 +114,7 @@ def create_coordinate_mapping(self, L, ir): def coordinate_mapping_declaration(self, L, ir): classname = ir.create_coordinate_mapping - code = "ufc_coordinate_mapping* create_{name}(void);\n".format(name=classname[0]) + code = f"ufc_coordinate_mapping* create_{classname[0]}(void);\n" return code def create_finite_element(self, L, ir): @@ -126,7 +126,7 @@ def finite_element_declaration(self, L, ir): classnames = set(ir.create_finite_element) code = "" for name in classnames: - code += "ufc_finite_element* create_{name}(void);\n".format(name=name) + code += f"ufc_finite_element* create_{name}(void);\n" return code def create_dofmap(self, L, ir): @@ -138,7 +138,7 @@ def dofmap_declaration(self, L, ir): classnames = set(ir.create_dofmap) code = "" for name in classnames: - code += "ufc_dofmap* create_{name}(void);\n".format(name=name) + code += f"ufc_dofmap* create_{name}(void);\n" return code def create_functionspace(self, L, ir): @@ -148,11 +148,9 @@ def create_functionspace(self, L, ir): i = 0 for (name, (element, dofmap, cmap)) in ir.function_spaces.items(): body = "ufc_function_space* space = (ufc_function_space*)malloc(sizeof(*space));\n" - body += "space->create_element = create_{finite_element_classname};\n".format( - finite_element_classname=element) - body += "space->create_dofmap = create_{dofmap_classname};\n".format(dofmap_classname=dofmap) - body += "space->create_coordinate_mapping = create_{coordinate_map_classname};\n".format( - coordinate_map_classname=cmap) + body += f"space->create_element = create_{element};\n" + body += f"space->create_dofmap = create_{dofmap};\n" + body += f"space->create_coordinate_mapping = create_{cmap};\n" body += "return space;" condition = L.EQ(L.Call("strcmp", (function_name, L.LiteralString(name))), 0) @@ -191,13 +189,13 @@ def generator(ir, parameters): """Generate UFC code for a form.""" logger.info("Generating code for form:") - logger.info("--- rank: {}".format(ir.rank)) - logger.info("--- name: {}".format(ir.name)) + logger.info(f"--- rank: {ir.rank}") + logger.info(f"--- name: {ir.name}") d = {} d["factory_name"] = ir.name d["name_from_uflfile"] = ir.name_from_uflfile - d["signature"] = "\"{}\"".format(ir.signature) + d["signature"] = f"\"{ir.signature}\"" d["rank"] = ir.rank d["num_coefficients"] = ir.num_coefficients d["num_constants"] = ir.num_constants diff --git a/ffcx/codegeneration/integrals.py b/ffcx/codegeneration/integrals.py index cc65ac353..b84733364 100644 --- a/ffcx/codegeneration/integrals.py +++ b/ffcx/codegeneration/integrals.py @@ -7,24 +7,21 @@ import collections import itertools import logging -import warnings - -import numpy import ufl from ffcx.codegeneration import integrals_template as ufc_integrals from ffcx.codegeneration.backend import FFCXBackend from ffcx.codegeneration.C.format_lines import format_indented_lines +from ffcx.codegeneration.utils import apply_permutations_to_data from ffcx.ir.elementtables import piecewise_ttypes logger = logging.getLogger("ffcx") def generator(ir, parameters): - logger.info("Generating code for integral:") - logger.info("--- type: {}".format(ir.integral_type)) - logger.info("--- name: {}".format(ir.name)) + logger.info(f"--- type: {ir.integral_type}") + logger.info(f"--- name: {ir.name}") """Generate code for an integral.""" factory_name = ir.name @@ -62,7 +59,7 @@ def generator(ir, parameters): # this will do for now: initializer_list = ", ".join("true" if enabled else "false" for enabled in ir.enabled_coefficients) if ir.enabled_coefficients: - enabled_coeffs_code = '\n'.join(["[{}] = {{ {} }};".format(len(ir.enabled_coefficients), initializer_list)]) + enabled_coeffs_code = f"[{len(ir.enabled_coefficients)}] = {{ {initializer_list} }};" else: enabled_coeffs_code = "[1] = {false}; /* No coefficients, but C does not permit zero-sized arrays */" @@ -80,7 +77,6 @@ def generator(ir, parameters): factory_name=factory_name, tabulate_tensor=code["tabulate_tensor"]) # Format implementation code - if integral_type == "custom": implementation = ufc_integrals.custom_factory.format( factory_name=factory_name, @@ -93,7 +89,6 @@ def generator(ir, parameters): enabled_coefficients=code["enabled_coefficients"], tabulate_tensor=tabulate_tensor_fn, needs_permutation_data=ir.needs_permutation_data) - return declaration, implementation @@ -189,24 +184,22 @@ def generate(self): alignment = self.ir.params['assume_aligned'] if alignment != -1: - parts += [L.VerbatimStatement("A = (ufc_scalar_t*)__builtin_assume_aligned(A, {});" - .format(alignment)), - L.VerbatimStatement("w = (const ufc_scalar_t*)__builtin_assume_aligned(w, {});" - .format(alignment)), - L.VerbatimStatement("c = (const ufc_scalar_t*)__builtin_assume_aligned(c, {});" - .format(alignment)), + parts += [L.VerbatimStatement(f"A = (ufc_scalar_t*)__builtin_assume_aligned(A, {alignment});"), + L.VerbatimStatement(f"w = (const ufc_scalar_t*)__builtin_assume_aligned(w, {alignment});"), + L.VerbatimStatement(f"c = (const ufc_scalar_t*)__builtin_assume_aligned(c, {alignment});"), L.VerbatimStatement( - "coordinate_dofs = (const double*)__builtin_assume_aligned(coordinate_dofs, {});" - .format(alignment))] + f"coordinate_dofs = (const double*)__builtin_assume_aligned(coordinate_dofs, {alignment});")] # Generate the tables of quadrature points and weights parts += self.generate_quadrature_tables() - # Generate the tables of basis function values and preintegrated blocks + # Generate the tables of basis function values and preintegrated + # blocks parts += self.generate_element_tables() - # Loop generation code will produce parts to go before quadloops, - # to define the quadloops, and to go after the quadloops + # Loop generation code will produce parts to go before + # quadloops, to define the quadloops, and to go after the + # quadloops all_preparts = [] all_quadparts = [] @@ -214,7 +207,8 @@ def generate(self): # Generate code to compute piecewise constant scalar factors all_preparts += self.generate_piecewise_partition(rule) - # Generate code to integrate reusable blocks of final element tensor + # Generate code to integrate reusable blocks of final + # element tensor preparts, quadparts = self.generate_quadrature_loop(rule) all_preparts += preparts all_quadparts += quadparts @@ -231,8 +225,8 @@ def generate_quadrature_tables(self): parts = [] - # No quadrature tables for custom (given argument) - # or point (evaluation in single vertex) + # No quadrature tables for custom (given argument) or point + # (evaluation in single vertex) skip = ufl.custom_integral_types + ufl.measure.point_integral_types if self.ir.integral_type in skip: return parts @@ -283,94 +277,30 @@ def generate_element_tables(self): ]) return parts - def get_entity_reflection_conditions(self, table, name): - """Gets an array of conditions stating when each dof is reflected.""" - L = self.backend.language - c_false = L.LiteralBool(False) - conditions = numpy.full(table.shape, c_false, dtype=L.CExpr) - - ref = self.ir.table_dof_reflection_entities[name] - dofmap = self.ir.table_dofmaps[name] - - for dof, entities in enumerate(ref): - if entities is None or dof not in dofmap: - continue - for indices in itertools.product(*[range(n) for n in table.shape[:-1]]): - indices += (dofmap.index(dof), ) - for entity in entities: - entity_ref = self.backend.symbols.entity_reflection(L, entity, self.ir.cell_shape) - if conditions[indices] == c_false: - # No condition has been added yet, so overwrite false - conditions[indices] = entity_ref - elif conditions[indices] == entity_ref: - # A != A is always false - conditions[indices] = c_false - else: - # This is not the first condition, so XOR - conditions[indices] = L.NE(entity_ref, conditions[indices]) - return conditions - def declare_table(self, name, table, padlen): """Declare a table. - If the dof dimensions of the table have dof rotations, apply these rotations.""" - L = self.backend.language - c_false = L.LiteralBool(False) - - rot = self.ir.table_dof_face_tangents[name] - ref = self.ir.table_dof_reflection_entities[name] - has_reflections = len([j for j in ref if j is not None]) > 0 - has_rotations = len(rot) > 0 - - # If the space has no vector-valued dofs, return the static table - if not has_reflections and not has_rotations: - return [L.ArrayDecl( - "static const double", name, table.shape, table, padlen=padlen)] - dofmap = self.ir.table_dofmaps[name] + If the dof dimensions of the table have dof rotations, apply + these rotations. - # Make the table have CExpr type so that conditionals can be put in it - if has_reflections or has_rotations: - table = numpy.array(table, dtype=L.CExpr) - - # Multiply dofs that whose reversed by reflecting an entity by 1 or -1 - if has_reflections: - conditions = self.get_entity_reflection_conditions(table, name) - for indices in itertools.product(*[range(n) for n in table.shape]): - if conditions[indices] != c_false: - table[indices] = L.Conditional(conditions[indices], -table[indices], table[indices]) + """ + L = self.backend.language - # If the table has no rotations, then we are done - if not has_rotations: + if not self.ir.table_needs_permutation_data[name]: return [L.ArrayDecl( - "const double", name, table.shape, table, padlen=padlen)] + "static const double", name, table.shape, table, padlen=padlen)] - # Correct data for rotations and reflections of face tangents - for (entity_dim, entity_n), face_tangent_data in rot.items(): - if entity_dim != 2: - warnings.warn("Face tangents an entity of dim != 2 not implemented.") - continue + out = [L.ArrayDecl( + "double", name, table.shape, table, padlen=padlen)] - for indices in itertools.product(*[range(n) for n in table.shape[:-1]]): - # Store current values in temps - temps = {} - for perm, ft in face_tangent_data.items(): - for combo in ft.values(): - for dof, w in combo: - if dof in dofmap: - temps[dof] = table[indices + (dofmap.index(dof), )] - # Replace values with conditionals containing linear combinations of values in temps - for perm, ft in face_tangent_data.items(): - entity_perm = self.backend.symbols.entity_permutation(L, (entity_dim, entity_n), self.ir.cell_shape) - for dof, combo in ft.items(): - if dof in dofmap: - table[indices + (dofmap.index(dof), )] = L.Conditional( - L.EQ(entity_perm, perm), - sum(w * temps[dof] for dof, w in combo), - table[indices + (dofmap.index(dof), )] - ) - - return [L.ArrayDecl( - "const double", name, table.shape, table, padlen=padlen)] + dummy_vars = tuple(0 if j == 1 else L.Symbol(f"i{i}") for i, j in enumerate(table.shape[:-1])) + ranges = tuple((dummy_vars[i], 0, j) for i, j in enumerate(table.shape[:-1]) if j != 1) + apply_perms = apply_permutations_to_data( + L, self.ir.table_dof_base_permutations[name], self.ir.cell_shape, L.Symbol(name), + indices=lambda dof: dummy_vars + (dof, ), ranges=ranges) + if len(apply_perms) > 0: + out += ["{"] + apply_perms + ["}"] + return out def generate_quadrature_loop(self, quadrature_rule): """Generate quadrature loop with for this num_points.""" @@ -379,17 +309,18 @@ def generate_quadrature_loop(self, quadrature_rule): # Generate varying partition body = self.generate_varying_partition(quadrature_rule) body = L.commented_code_list( - body, "Quadrature loop body setup for quadrature rule {}".format(quadrature_rule.id())) + body, f"Quadrature loop body setup for quadrature rule {quadrature_rule.id()}") - # Generate dofblock parts, some of this - # will be placed before or after quadloop + # Generate dofblock parts, some of this will be placed before or + # after quadloop preparts, quadparts = \ self.generate_dofblock_partition(quadrature_rule) body += quadparts # Wrap body in loop or scope if not body: - # Could happen for integral with everything zero and optimized away + # Could happen for integral with everything zero and + # optimized away quadparts = [] else: num_points = quadrature_rule.points.shape[0] @@ -418,23 +349,25 @@ def generate_runtime_quadrature_loop(self): body = self.generate_unstructured_varying_partition(num_points) body = L.commented_code_list(body, [ "Run-time quadrature loop body setup", - "(chunk_size={0}, analysis_num_points={1})".format(chunk_size, num_points) + f"(chunk_size={chunk_size}, analysis_num_points={num_points})" ]) - # Generate dofblock parts, some of this - # will be placed before or after quadloop + # Generate dofblock parts, some of this will be placed before or + # after quadloop preparts, quadparts = \ self.generate_dofblock_partition(num_points) body += quadparts # Wrap body in loop if not body: - # Could happen for integral with everything zero and optimized away + # Could happen for integral with everything zero and + # optimized away quadparts = [] else: rule_parts = [] - # Define two-level quadrature loop; over chunks then over points in chunk + # Define two-level quadrature loop; over chunks then over + # points in chunk iq_chunk = L.Symbol("iq_chunk") np = self.backend.symbols.num_custom_quadrature_points() num_point_blocks = (np + chunk_size - 1) / chunk_size @@ -488,7 +421,8 @@ def generate_runtime_quadrature_loop(self): # Preparations for element tables table_parts = [] - # Only declare non-piecewise tables, computed inside chunk loop + # Only declare non-piecewise tables, computed inside chunk + # loop non_piecewise_tables = [ name for name in sorted(tables) if table_types[name] not in piecewise_ttypes ] @@ -512,10 +446,10 @@ def generate_piecewise_partition(self, quadrature_rule): # Get annotated graph of factorisation F = self.ir.integrand[quadrature_rule]["factorization"] - arraysymbol = L.Symbol("sp_{}".format(quadrature_rule.id())) + arraysymbol = L.Symbol(f"sp_{quadrature_rule.id()}") parts = self.generate_partition(arraysymbol, F, "piecewise", None) parts = L.commented_code_list( - parts, "Quadrature loop independent computations for quadrature rule {}".format(quadrature_rule.id())) + parts, f"Quadrature loop independent computations for quadrature rule {quadrature_rule.id()}") return parts def generate_varying_partition(self, quadrature_rule): @@ -524,10 +458,10 @@ def generate_varying_partition(self, quadrature_rule): # Get annotated graph of factorisation F = self.ir.integrand[quadrature_rule]["factorization"] - arraysymbol = L.Symbol("sv_{}".format(quadrature_rule.id())) + arraysymbol = L.Symbol(f"sv_{quadrature_rule.id()}") parts = self.generate_partition(arraysymbol, F, "varying", quadrature_rule) parts = L.commented_code_list( - parts, "Varying computations for quadrature rule {}".format(quadrature_rule.id())) + parts, f"Varying computations for quadrature rule {quadrature_rule.id()}") return parts def generate_partition(self, symbol, F, mode, quadrature_rule): @@ -544,13 +478,15 @@ def generate_partition(self, symbol, F, mode, quadrature_rule): v = attr['expression'] mt = attr.get('mt') - # Generate code only if the expression is not already in cache + # Generate code only if the expression is not already in + # cache if not self.get_var(quadrature_rule, v): if v._ufl_is_literal_: vaccess = self.backend.ufl_to_language.get(v) elif mt is not None: - # All finite element based terminals have table data, as well - # as some, but not all, of the symbolic geometric terminals + # All finite element based terminals have table + # data, as well as some, but not all, of the + # symbolic geometric terminals tabledata = attr.get('tr') # Backend specific modified terminal translation @@ -583,9 +519,11 @@ def generate_partition(self, symbol, F, mode, quadrature_rule): vaccess = vexpr elif isinstance(v, ufl.classes.Condition): # Inline the conditions x < y, condition values - # This removes the need to handle boolean intermediate variables. - # With tensor-valued conditionals it may not be optimal but we - # let the compiler take responsibility for optimizing those cases. + # This removes the need to handle boolean + # intermediate variables. With tensor-valued + # conditionals it may not be optimal but we let + # the compiler take responsibility for + # optimizing those cases. vaccess = vexpr elif any(op._ufl_is_literal_ for op in v.ufl_operands): # Skip intermediates for e.g. -2.0*x, @@ -726,8 +664,8 @@ def generate_block_parts(self, quadrature_rule, blockmap, blockdata): iq = self.backend.symbols.quadrature_loop_index() - # Override dof index with quadrature loop index for arguments with - # quadrature element, to index B like B[iq*num_dofs + iq] + # Override dof index with quadrature loop index for arguments + # with quadrature element, to index B like B[iq*num_dofs + iq] arg_indices = tuple(self.backend.symbols.argument_loop_index(i) for i in range(block_rank)) B_indices = [] for i in range(block_rank): @@ -767,7 +705,8 @@ def generate_block_parts(self, quadrature_rule, blockmap, blockdata): if not defined: quadparts.append(L.VariableDecl("const ufc_scalar_t", fw, fw_rhs)) - # Naively accumulate integrand for this block in the innermost loop + # Naively accumulate integrand for this block in the innermost + # loop assert not blockdata.transposed A_shape = self.ir.tensor_shape @@ -786,7 +725,8 @@ def generate_block_parts(self, quadrature_rule, blockmap, blockdata): break if expand_loop: - # If DOFs in dofrange are not equally spaced, then expand out the for loop + # If DOFs in dofrange are not equally spaced, then expand + # out the for loop for A_indices, B_indices in zip(itertools.product(*blockmap), itertools.product(*[range(len(b)) for b in blockmap])): quadparts += [ diff --git a/ffcx/codegeneration/jit.py b/ffcx/codegeneration/jit.py index 5785e9086..4393d71aa 100644 --- a/ffcx/codegeneration/jit.py +++ b/ffcx/codegeneration/jit.py @@ -82,10 +82,10 @@ def get_cached_module(module_name, object_names, cache_dir, timeout): compiled_objects = [getattr(compiled_module.lib, "create_" + name)() for name in object_names] return compiled_objects, compiled_module - logger.info("Waiting for {} to appear.".format(str(ready_name))) + logger.info(f"Waiting for {ready_name} to appear.") time.sleep(1) - raise TimeoutError("""JIT compilation timed out, probably due to a failed previous compile. - Try cleaning cache (e.g. remove {}) or increase timeout parameter.""".format(c_filename)) + raise TimeoutError(f"""JIT compilation timed out, probably due to a failed previous compile. + Try cleaning cache (e.g. remove {c_filename}) or increase timeout parameter.""") def compile_elements(elements, parameters=None, cache_dir=None, timeout=10, cffi_extra_compile_args=None, diff --git a/ffcx/codegeneration/symbols.py b/ffcx/codegeneration/symbols.py index 788d10751..eb3209315 100644 --- a/ffcx/codegeneration/symbols.py +++ b/ffcx/codegeneration/symbols.py @@ -27,7 +27,7 @@ def format_mt_name(basename, mt): # Add averaged state to name if mt.averaged: - avg = "_a{0}".format(mt.averaged) + avg = f"_a{mt.averaged}" access += avg # Format restriction @@ -37,17 +37,17 @@ def format_mt_name(basename, mt): # Format global derivatives if mt.global_derivatives: assert basename == "J" - der = "_deriv_{0}".format(''.join(map(str, mt.global_derivatives))) + der = f"_deriv_{''.join(map(str, mt.global_derivatives))}" access += der # Format local derivatives if mt.local_derivatives: - der = "_d{0}".format(''.join(map(str, mt.local_derivatives))) + der = f"_d{''.join(map(str, mt.local_derivatives))}" access += der # Add flattened component to name if mt.component: - comp = "_c{0}".format(mt.flat_component) + comp = f"_c{mt.flat_component}" access += comp return access @@ -90,7 +90,7 @@ def entity(self, entitytype, restriction): elif entitytype == "vertex": return self.S("vertex[0]") else: - logging.exception("Unknown entitytype {}".format(entitytype)) + logging.exception(f"Unknown entitytype {entitytype}") def argument_loop_index(self, iarg): """Loop index for argument #iarg.""" @@ -180,11 +180,11 @@ def custom_points_table(self): def weights_table(self, quadrature_rule): """Table of quadrature weights.""" - return self.S("weights_{}".format(quadrature_rule.id())) + return self.S(f"weights_{quadrature_rule.id()}") def points_table(self, quadrature_rule): """Table of quadrature points (points on the reference integration entity).""" - return self.S("points_{}".format(quadrature_rule.id())) + return self.S(f"points_{quadrature_rule.id()}") def x_component(self, mt): """Physical coordinate component.""" diff --git a/ffcx/codegeneration/ufc.h b/ffcx/codegeneration/ufc.h index 5714def35..8899f3d45 100644 --- a/ffcx/codegeneration/ufc.h +++ b/ffcx/codegeneration/ufc.h @@ -51,22 +51,6 @@ extern "C" vertex = 60, } ufc_shape; - typedef enum - { - PointEval = 1, - ComponentPointEval = 2, - PointNormalDeriv = 3, - IntegralMoment = 4, - FrobeniusIntegralMoment = 5, - PointEdgeTangent = 6, - PointFaceTangent = 7, - PointScaledNormalEval = 8, - PointDeriv = 9, - IntegralMomentOfNormalDerivative = 10, - PointNormalEval = 11, - PointwiseInnerProductEval = 12, - } ufc_doftype; - /// Forward declarations typedef struct ufc_coordinate_mapping ufc_coordinate_mapping; typedef struct ufc_finite_element ufc_finite_element; @@ -120,40 +104,47 @@ extern "C" /// Return the family of the finite element function space const char* family; - int (*evaluate_reference_basis)(double* restrict reference_values, - int num_points, const double* restrict X); - - int (*evaluate_reference_basis_derivatives)( - double* restrict reference_values, int order, int num_points, - const double* restrict X); - /// Map the reference values on to the cell /// @param[in] cell_permutations An integer that says how each entity of the /// cell of dimension < tdim has been permuted relative to a /// low-to-high ordering of the cell. - int (*transform_reference_basis_derivatives)( double* restrict values, int order, int num_points, const double* restrict reference_values, const double* restrict X, const double* restrict J, const double* restrict detJ, - const double* restrict K, const uint32_t cell_permutation); - - /// Map values of field from physical to reference space which has - /// been evaluated at points given by - /// tabulate_reference_dof_coordinates. - int (*transform_values)(ufc_scalar_t* restrict reference_values, - const ufc_scalar_t* restrict physical_values, - const double* restrict coordinate_dofs, - const ufc_coordinate_mapping* restrict cm); - - // FIXME: change to 'const double* reference_dof_coordinates()' - /// Tabulate the coordinates of all dofs on a reference cell - int (*tabulate_reference_dof_coordinates)( - double* restrict reference_dof_coordinates); + const double* restrict K); + + /// Apply dof tranformations to some data + /// @param[in] data The data to be transformed + /// @param[in] cell_permutation An integer encoding the orientation of the + /// cell's entities + /// @param[in] dim The number of data items for each DOD + int (*apply_dof_transformation)(double* data, uint32_t cell_permutation, + int dim); + + /// Apply dof tranformations to some data + /// @param[in] data The data to be transformed + /// @param[in] cell_permutation An integer encoding the orientation of the + /// cell's entities + /// @param[in] dim The number of data items for each DOD + int (*apply_dof_transformation_to_scalar)(ufc_scalar_t* data, + uint32_t cell_permutation, + int dim); /// Return the number of sub elements (for a mixed element) int num_sub_elements; + /// Indicates whether permutation data needs to be passed into various + /// functions + bool needs_permutation_data; + + /// If true, the interpolation matrix is the identity + /// Interpolation matrix maps point-wise values at set of points into values + /// of degrees-of-freedom, dof_i = A_{ij} u(x_j). If this is the identity, then + /// the space is defined by a series of point evaluations, and so the interpolation + /// points are the DOF coordinates. + bool interpolation_is_identity; + /// Create a new finite element for sub element i (for a mixed /// element). Memory for the new object is obtained with malloc(), /// and the caller is reponsible for freeing it by calling free(). @@ -171,12 +162,6 @@ extern "C" /// Return a string identifying the dofmap const char* signature; - /// The base DoF permutations - const int* base_permutations; - - /// The size of the base DoF permutations array - int size_base_permutations; - /// Number of dofs with global support (i.e. global constants) int num_global_support_dofs; @@ -216,6 +201,12 @@ extern "C" /// Return coordinate_mapping signature string const char* signature; + /// The finite element family name for the mapping + const char* element_family; + + /// The finite element degree used in the mapping + int element_degree; + /// Create object of the same type. Memory for the new object is /// obtained with malloc(), and the caller is reponsible for /// freeing it by calling free(). @@ -227,9 +218,25 @@ extern "C" /// Return topological dimension of the coordinate_mapping int topological_dimension; - /// Boolean flag for affine + /// Boolean flag for affine int is_affine; + /// Indicates whether permutation data needs to be passed into various + /// functions + bool needs_permutation_data; + + /// Permutes a list of DOF numbers + /// As a coordinate mapping is always Lagrange or Q, the DOF permutation + /// will always be a rearrangement of DOF points, so this is valid in this + /// case. + int (*permute_dofs)(int* dof_list, uint32_t cell_permutation); + + /// Reverses a permutation of a list of DOF numbers + /// As a coordinate mapping is always Lagrange or Q, the DOF permutation + /// will always be a rearrangement of DOF points, so this is valid in this + /// case. + int (*unpermute_dofs)(int* dof_list, uint32_t cell_permutation); + /// Return cell shape of the coordinate_mapping ufc_shape cell_shape; @@ -238,11 +245,6 @@ extern "C" /// reponsible for freeing it by calling free(). ufc_dofmap* (*create_scalar_dofmap)(void); - /// Evaluate basis (and derivatives) of the associated element - int (*evaluate_basis_derivatives)( - double* restrict reference_values, int order, int num_points, - const double* restrict X); - } ufc_coordinate_mapping; /// Tabulate integral into tensor A with compiled quadrature rule @@ -296,7 +298,7 @@ extern "C" const ufc_scalar_t* restrict c, const double* restrict coordinate_dofs, const int* restrict entity_local_index, const uint8_t* restrict quadrature_permutation, - const uint32_t cell_permutation); + uint32_t cell_permutation); /// Tabulate integral into tensor A with runtime quadrature rule /// diff --git a/ffcx/codegeneration/ufc_geometry.h b/ffcx/codegeneration/ufc_geometry.h index 55566cd60..e671730ca 100644 --- a/ffcx/codegeneration/ufc_geometry.h +++ b/ffcx/codegeneration/ufc_geometry.h @@ -11,6 +11,8 @@ /// A note regarding data structures. All matrices are represented as /// row-major flattened raw C arrays. +/// TODO: Remove this file and get data from Basix instead + // TODO: Should signatures of compute___d match for each foo? // On one hand the snippets use different quantities, on the other // some consistency is nice to simplify the code generation. @@ -54,7 +56,7 @@ #define UFC_TDIM_2 2 #define UFC_TDIM_3 3 -/// --- Local reference cell coordinates by UFC conventions --- +/// --- Local reference cell coordinates by basix conventions --- static const double interval_vertices[UFC_NUM_VERTICES_IN_INTERVAL][UFC_TDIM_1] = {{0.0}, {1.0}}; @@ -70,19 +72,19 @@ static const double quadrilateral_vertices[UFC_NUM_VERTICES_IN_QUADRILATERAL] [UFC_TDIM_2] = { {0.0, 0.0}, - {0.0, 1.0}, {1.0, 0.0}, + {0.0, 1.0}, {1.0, 1.0}, }; static const double hexahedron_vertices[UFC_NUM_VERTICES_IN_HEXAHEDRON] [UFC_TDIM_3] = { - {0.0, 0.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 1.0, 0.0}, {0.0, 1.0, 1.0}, - {1.0, 0.0, 0.0}, {1.0, 0.0, 1.0}, {1.0, 1.0, 0.0}, {1.0, 1.0, 1.0}, + {0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 1.0, 0.0}, + {0.0, 0.0, 1.0}, {1.0, 0.0, 1.0}, {0.0, 1.0, 1.0}, {1.0, 1.0, 1.0}, }; -/// --- Local reference cell midpoint by UFC conventions --- +/// --- Local reference cell midpoint by basix conventions --- static const double interval_midpoint[UFC_TDIM_1] = {0.5}; @@ -94,7 +96,7 @@ static const double quadrilateral_midpoint[UFC_TDIM_2] = {0.5, 0.5}; static const double hexahedron_midpoint[UFC_TDIM_3] = {0.5, 0.5, 0.5}; -/// --- Local reference cell facet midpoints by UFC conventions --- +/// --- Local reference cell facet midpoints by basix conventions --- static const double interval_facet_midpoint[UFC_NUM_FACETS_IN_INTERVAL] [UFC_TDIM_1] @@ -116,20 +118,20 @@ static const double tetrahedron_facet_midpoint[UFC_NUM_FACETS_IN_TETRAHEDRON] static const double quadrilateral_facet_midpoint[UFC_NUM_FACETS_IN_QUADRILATERAL][UFC_TDIM_2] = { + {0.5, 0.0}, {0.0, 0.5}, {1.0, 0.5}, - {0.5, 0.0}, {0.5, 1.0}, }; static const double hexahedron_facet_midpoint[UFC_NUM_FACETS_IN_HEXAHEDRON] [UFC_TDIM_3] = { - {0.0, 0.5, 0.5}, {1.0, 0.5, 0.5}, {0.5, 0.0, 0.5}, - {0.5, 1.0, 0.5}, {0.5, 0.5, 0.0}, {0.5, 0.5, 1.0}, + {0.5, 0.5, 0.0}, {0.5, 0.0, 0.5}, {0.0, 0.5, 0.5}, + {1.0, 0.5, 0.5}, {0.5, 1.0, 0.5}, {0.5, 0.5, 1.0}, }; -/// --- Local reference cell facet orientations by UFC conventions --- +/// --- Local reference cell facet orientations by basix conventions --- static const double interval_facet_orientations[UFC_NUM_FACETS_IN_INTERVAL] = { -1.0, @@ -143,31 +145,25 @@ static const double tetrahedron_facet_orientations[UFC_NUM_FACETS_IN_TETRAHEDRON] = {+1.0, -1.0, +1.0, -1.0}; -// FIXME: Insert quad conventions here -/* static const double quadrilateral_facet_orientations[UFC_NUM_FACETS_IN_QUADRILATERAL] = { - +1.0, -1.0, + +1.0, -1.0, +1.0, - }; -*/ +}; -// FIXME: Insert quad conventions here -/* static const double hexahedron_facet_orientations[UFC_NUM_FACETS_IN_HEXAHEDRON] = { - +1.0, - -1.0, -1.0, +1.0, + -1.0, +1.0, -1.0, - }; -*/ + +1.0, +}; -/// --- Local reference cell entity relations by UFC conventions --- +/// --- Local reference cell entity relations by basix conventions --- static const unsigned int triangle_edge_vertices[UFC_NUM_EDGES_IN_TRIANGLE][2] = {{1, 2}, {0, 2}, {0, 1}}; @@ -180,19 +176,19 @@ static const unsigned int quadrilateral_edge_vertices[UFC_NUM_EDGES_IN_QUADRILATERAL][2] = { {0, 1}, - {2, 3}, {0, 2}, {1, 3}, + {2, 3}, }; static const unsigned int hexahedron_edge_vertices[UFC_NUM_EDGES_IN_HEXAHEDRON] [2] = { - {0, 1}, {2, 3}, {4, 5}, {6, 7}, {0, 2}, {1, 3}, - {4, 6}, {5, 7}, {0, 4}, {1, 5}, {2, 6}, {3, 7}, + {0, 1}, {0, 2}, {0, 4}, {1, 3}, {1, 5}, {2, 3}, + {2, 6}, {3, 7}, {4, 5}, {4, 7}, {5, 7}, {6, 7}, }; -/// --- Local reference cell entity relations by UFC conventions --- +/// --- Local reference cell entity relations by basix conventions --- static const unsigned int interval_facet_vertices[UFC_NUM_FACETS_IN_INTERVAL][1] = {{0}, {1}}; @@ -220,17 +216,17 @@ static const unsigned int [UFC_NUM_VERTICES_IN_INTERVAL] = { {0, 1}, - {2, 3}, {0, 2}, {1, 3}, + {2, 3}, }; static const unsigned int hexahedron_facet_vertices[UFC_NUM_FACETS_IN_HEXAHEDRON] [UFC_NUM_VERTICES_IN_QUADRILATERAL] = { - {0, 1, 2, 3}, {4, 5, 6, 7}, {0, 1, 4, 5}, - {2, 3, 6, 7}, {0, 2, 4, 6}, {1, 3, 5, 7}, + {0, 1, 2, 3}, {0, 1, 4, 5}, {0, 2, 4, 6}, + {1, 3, 5, 7}, {2, 3, 6, 7}, {4, 5, 6, 7}, }; static const unsigned int @@ -238,12 +234,12 @@ static const unsigned int [UFC_NUM_FACETS_IN_QUADRILATERAL] [UFC_NUM_VERTICES_IN_INTERVAL] = { - {{0, 1}, {2, 3}, {0, 2}, {1, 3}}, {{4, 5}, {6, 7}, {4, 6}, {5, 7}}, - {{0, 1}, {4, 5}, {0, 4}, {1, 5}}, {{2, 3}, {6, 7}, {2, 6}, {3, 7}}, - {{0, 2}, {4, 6}, {0, 4}, {2, 6}}, {{1, 3}, {5, 7}, {1, 5}, {3, 7}}, + {{0, 1}, {0, 2}, {1, 3}, {2, 3}}, {{0, 1}, {0, 4}, {1, 5}, {4, 5}}, + {{0, 2}, {0, 4}, {2, 6}, {4, 6}}, {{1, 3}, {1, 5}, {3, 7}, {5, 7}}, + {{2, 3}, {2, 6}, {3, 6}, {3, 7}}, {{4, 5}, {4, 6}, {5, 7}, {6, 7}}, }; -/// --- Reference cell edge vectors by UFC conventions (edge vertex 1 - edge +/// --- Reference cell edge vectors by basix conventions (edge vertex 1 - edge /// vertex 0 for each edge in cell) --- static const double triangle_reference_edge_vectors[UFC_NUM_EDGES_IN_TRIANGLE] @@ -295,18 +291,18 @@ static const double quadrilateral_reference_edge_vectors[UFC_NUM_EDGES_IN_QUADRILATERAL] [UFC_TDIM_2] = { + {1.0, 0.0}, {0.0, 1.0}, {0.0, 1.0}, {1.0, 0.0}, - {1.0, 0.0}, }; static const double hexahedron_reference_edge_vectors[UFC_NUM_EDGES_IN_HEXAHEDRON][UFC_TDIM_3] = { - {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, 0.0}, - {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, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 1.0, 0.0}, + {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, 1.0}, + {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, }; // Edge vectors for each quadrilateral facet of a hexahedron @@ -315,49 +311,49 @@ static const double hexahedron_facet_reference_edge_vectors = { { // facet 0 - {0.0, 0.0, 1.0}, - {0.0, 0.0, 1.0}, + {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 1.0, 0.0}, + {1.0, 0.0, 0.0}, }, { // facet 1 + {1.0, 0.0, 0.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}, + {1.0, 0.0, 0.0}, }, { // facet 2 + {0.0, 1.0, 0.0}, {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}, }, { // facet 3 + {0.0, 1.0, 0.0}, {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}, }, { // facet 4 - {0.0, 1.0, 0.0}, - {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, + {0.0, 0.0, 1.0}, + {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, }, { // facet 5 + {1.0, 0.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}, }, }; -/// --- Reference cell facet normals by UFC conventions (outwards pointing on +/// --- Reference cell facet normals by basix conventions (outwards pointing on /// reference cell) --- static const double interval_reference_facet_normals[UFC_NUM_FACETS_IN_INTERVAL] @@ -389,20 +385,20 @@ static const double quadrilateral_reference_facet_normals[UFC_NUM_FACETS_IN_QUADRILATERAL] [UFC_TDIM_2] = { + {0.0, -1.0}, {-1.0, 0.0}, {1.0, 0.0}, - {0.0, -1.0}, {0.0, 1.0}, }; static const double hexahedron_reference_facet_normals[UFC_NUM_FACETS_IN_HEXAHEDRON][UFC_TDIM_3] = { - {-1.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {0.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, 0.0, -1.0}, {0.0, -1.0, 0.0}, {-1.0, 0.0, 0.0}, + {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, }; -/// --- Reference cell volumes by UFC conventions --- +/// --- Reference cell volumes by basix conventions --- static const double interval_reference_cell_volume = 1.0; static const double triangle_reference_cell_volume = 0.5; @@ -417,7 +413,7 @@ static const double quadrilateral_reference_facet_volume = 1.0; static const double hexahedron_reference_facet_volume = 1.0; /// --- Jacobians of reference facet cell to reference cell coordinate mappings -/// by UFC conventions --- +/// by basix conventions --- static const double triangle_reference_facet_jacobian[UFC_NUM_FACETS_IN_TRIANGLE][UFC_TDIM_2] @@ -442,790 +438,20 @@ static const double quadrilateral_reference_facet_jacobian[UFC_NUM_FACETS_IN_QUADRILATERAL] [UFC_TDIM_2][UFC_TDIM_2 - 1] = { + {{1.0}, {0.0}}, {{0.0}, {1.0}}, {{0.0}, {1.0}}, {{1.0}, {0.0}}, - {{1.0}, {0.0}}, }; static const double hexahedron_reference_facet_jacobian[UFC_NUM_FACETS_IN_HEXAHEDRON] [UFC_TDIM_3][UFC_TDIM_3 - 1] = { + {{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, 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}}, - {{1.0, 0.0}, {0.0, 1.0}, {0.0, 0.0}}, {{1.0, 0.0}, {0.0, 1.0}, {0.0, 0.0}}, }; - -/// --- Coordinate mappings from reference facet cell to reference cell by UFC -/// conventions --- - -static inline void compute_reference_facet_to_reference_cell_coordinates_interval( - double Xc[UFC_TDIM_1], unsigned int facet) -{ - switch (facet) - { - case 0: - Xc[0] = 0.0; - break; - case 1: - Xc[0] = 1.0; - break; - }; -} - -static inline void compute_reference_facet_to_reference_cell_coordinates_triangle( - double Xc[UFC_TDIM_2], const double Xf[UFC_TDIM_2 - 1], unsigned int facet) -{ - switch (facet) - { - case 0: - Xc[0] = 1.0 - Xf[0]; - Xc[1] = Xf[0]; - break; - case 1: - Xc[0] = 0.0; - Xc[1] = Xf[0]; - break; - case 2: - Xc[0] = Xf[0]; - Xc[1] = 0.0; - break; - }; -} - -static inline void compute_reference_facet_to_reference_cell_coordinates_tetrahedron( - double Xc[UFC_TDIM_3], const double Xf[UFC_TDIM_3 - 1], unsigned int facet) -{ - switch (facet) - { - case 0: - Xc[0] = 1.0 - Xf[0] - Xf[1]; - Xc[1] = Xf[0]; - Xc[2] = Xf[1]; - break; - case 1: - Xc[0] = 0.0; - Xc[1] = Xf[0]; - Xc[2] = Xf[1]; - break; - case 2: - Xc[0] = Xf[0]; - Xc[1] = 0.0; - Xc[2] = Xf[1]; - break; - case 3: - Xc[0] = Xf[0]; - Xc[1] = Xf[1]; - Xc[2] = 0.0; - break; - }; -} - -///--- Computation of Jacobian matrices --- - -/// Compute Jacobian J for interval embedded in R^1 -static inline void compute_jacobian_interval_1d(double J[UFC_GDIM_1 * UFC_TDIM_1], - const double coordinate_dofs[2]) -{ - J[0] = coordinate_dofs[1] - coordinate_dofs[0]; -} - -/// Compute Jacobian J for interval embedded in R^2 -static inline void compute_jacobian_interval_2d(double J[UFC_GDIM_2 * UFC_TDIM_1], - const double coordinate_dofs[4]) -{ - J[0] = coordinate_dofs[2] - coordinate_dofs[0]; - J[1] = coordinate_dofs[3] - coordinate_dofs[1]; -} - -/// Compute Jacobian J for interval embedded in R^3 -static inline void compute_jacobian_interval_3d(double J[UFC_GDIM_3 * UFC_TDIM_1], - const double coordinate_dofs[6]) -{ - J[0] = coordinate_dofs[3] - coordinate_dofs[0]; - J[1] = coordinate_dofs[4] - coordinate_dofs[1]; - J[2] = coordinate_dofs[5] - coordinate_dofs[2]; -} - -/// Compute Jacobian J for triangle embedded in R^2 -static inline void compute_jacobian_triangle_2d(double J[UFC_GDIM_2 * UFC_TDIM_2], - const double coordinate_dofs[6]) -{ - J[0] = coordinate_dofs[2] - coordinate_dofs[0]; - J[1] = coordinate_dofs[4] - coordinate_dofs[0]; - J[2] = coordinate_dofs[3] - coordinate_dofs[1]; - J[3] = coordinate_dofs[5] - coordinate_dofs[1]; -} - -/// Compute Jacobian J for triangle embedded in R^3 -static inline void compute_jacobian_triangle_3d(double J[UFC_GDIM_3 * UFC_TDIM_2], - const double coordinate_dofs[9]) -{ - J[0] = coordinate_dofs[3] - coordinate_dofs[0]; - J[1] = coordinate_dofs[6] - coordinate_dofs[0]; - J[2] = coordinate_dofs[4] - coordinate_dofs[1]; - J[3] = coordinate_dofs[7] - coordinate_dofs[1]; - J[4] = coordinate_dofs[5] - coordinate_dofs[2]; - J[5] = coordinate_dofs[8] - coordinate_dofs[2]; -} - -/// Compute Jacobian J for tetrahedron embedded in R^3 -static inline void compute_jacobian_tetrahedron_3d(double J[UFC_GDIM_3 * UFC_TDIM_3], - const double coordinate_dofs[12]) -{ - J[0] = coordinate_dofs[3] - coordinate_dofs[0]; - J[1] = coordinate_dofs[6] - coordinate_dofs[0]; - J[2] = coordinate_dofs[9] - coordinate_dofs[0]; - J[3] = coordinate_dofs[4] - coordinate_dofs[1]; - J[4] = coordinate_dofs[7] - coordinate_dofs[1]; - J[5] = coordinate_dofs[10] - coordinate_dofs[1]; - J[6] = coordinate_dofs[5] - coordinate_dofs[2]; - J[7] = coordinate_dofs[8] - coordinate_dofs[2]; - J[8] = coordinate_dofs[11] - coordinate_dofs[2]; -} - -//--- Computation of Jacobian inverses --- // TODO: Remove this when ffcx is -// updated to use the NEW ones below - -/// Compute Jacobian inverse K for interval embedded in R^1 -static inline void compute_jacobian_inverse_interval_1d(double* K, double* det, - const double* J) -{ - *det = J[0]; - K[0] = 1.0 / *det; -} - -/// Compute Jacobian (pseudo)inverse K for interval embedded in R^2 -static inline void compute_jacobian_inverse_interval_2d(double* K, double* det, - const double* J) -{ - const double det2 = J[0] * J[0] + J[1] * J[1]; - *det = sqrt(det2); - - K[0] = J[0] / det2; - K[1] = J[1] / det2; -} - -/// Compute Jacobian (pseudo)inverse K for interval embedded in R^3 -static inline void compute_jacobian_inverse_interval_3d(double* K, double* det, - const double* J) -{ - // TODO: Move computation of det to a separate function, det is often needed - // when K is not - const double det2 = J[0] * J[0] + J[1] * J[1] + J[2] * J[2]; - *det = sqrt(det2); - - K[0] = J[0] / det2; - K[1] = J[1] / det2; - K[2] = J[2] / det2; -} - -/// Compute Jacobian inverse K for triangle embedded in R^2 -static inline void compute_jacobian_inverse_triangle_2d(double* K, double* det, - const double* J) -{ - *det = J[0] * J[3] - J[1] * J[2]; - - K[0] = J[3] / *det; - K[1] = -J[1] / *det; - K[2] = -J[2] / *det; - K[3] = J[0] / *det; -} - -/// Compute Jacobian (pseudo)inverse K for triangle embedded in R^3 -static inline void compute_jacobian_inverse_triangle_3d(double* K, double* det, - const double* J) -{ - const double d_0 = J[2] * J[5] - J[4] * J[3]; - const double d_1 = J[4] * J[1] - J[0] * J[5]; - const double d_2 = J[0] * J[3] - J[2] * J[1]; - - const double c_0 = J[0] * J[0] + J[2] * J[2] + J[4] * J[4]; - const double c_1 = J[1] * J[1] + J[3] * J[3] + J[5] * J[5]; - const double c_2 = J[0] * J[1] + J[2] * J[3] + J[4] * J[5]; - - const double den = c_0 * c_1 - c_2 * c_2; - - const double det2 = d_0 * d_0 + d_1 * d_1 + d_2 * d_2; - *det = sqrt(det2); - - K[0] = (J[0] * c_1 - J[1] * c_2) / den; - K[1] = (J[2] * c_1 - J[3] * c_2) / den; - K[2] = (J[4] * c_1 - J[5] * c_2) / den; - K[3] = (J[1] * c_0 - J[0] * c_2) / den; - K[4] = (J[3] * c_0 - J[2] * c_2) / den; - K[5] = (J[5] * c_0 - J[4] * c_2) / den; -} - -/// Compute Jacobian inverse K for tetrahedron embedded in R^3 -static inline void compute_jacobian_inverse_tetrahedron_3d(double* K, double* det, - const double* J) -{ - const double d_00 = J[4] * J[8] - J[5] * J[7]; - const double d_01 = J[5] * J[6] - J[3] * J[8]; - const double d_02 = J[3] * J[7] - J[4] * J[6]; - const double d_10 = J[2] * J[7] - J[1] * J[8]; - const double d_11 = J[0] * J[8] - J[2] * J[6]; - const double d_12 = J[1] * J[6] - J[0] * J[7]; - const double d_20 = J[1] * J[5] - J[2] * J[4]; - const double d_21 = J[2] * J[3] - J[0] * J[5]; - const double d_22 = J[0] * J[4] - J[1] * J[3]; - - *det = J[0] * d_00 + J[3] * d_10 + J[6] * d_20; - - K[0] = d_00 / *det; - K[1] = d_10 / *det; - K[2] = d_20 / *det; - K[3] = d_01 / *det; - K[4] = d_11 / *det; - K[5] = d_21 / *det; - K[6] = d_02 / *det; - K[7] = d_12 / *det; - K[8] = d_22 / *det; -} - -//--- NEW Computation of Jacobian (sub)determinants --- - -/// Compute Jacobian determinant for interval embedded in R^1 -static inline void compute_jacobian_determinants_interval_1d( - double* det, const double J[UFC_GDIM_1 * UFC_TDIM_1]) -{ - *det = J[0]; -} - -/// Compute Jacobian (pseudo)determinants for interval embedded in R^2 -static inline void compute_jacobian_determinants_interval_2d( - double* det2, double* det, const double J[UFC_GDIM_2 * UFC_TDIM_1]) -{ - *det2 = J[0] * J[0] + J[1] * J[1]; - *det = sqrt(*det2); -} - -/// Compute Jacobian (pseudo)determinants for interval embedded in R^3 -static inline void compute_jacobian_determinants_interval_3d( - double* det2, double* det, const double J[UFC_GDIM_3 * UFC_TDIM_1]) -{ - *det2 = J[0] * J[0] + J[1] * J[1] + J[2] * J[2]; - *det = sqrt(*det2); -} - -/// Compute Jacobian determinant for triangle embedded in R^2 -static inline void compute_jacobian_determinants_triangle_2d( - double* det, const double J[UFC_GDIM_2 * UFC_TDIM_2]) -{ - *det = J[0] * J[3] - J[1] * J[2]; -} - -/// Compute Jacobian (pseudo)determinants for triangle embedded in R^3 -static inline void compute_jacobian_determinants_triangle_3d( - double* den, double* det2, double* det, double c[3], - const double J[UFC_GDIM_3 * UFC_TDIM_2]) -{ - const double d_0 = J[2] * J[5] - J[4] * J[3]; - const double d_1 = J[4] * J[1] - J[0] * J[5]; - const double d_2 = J[0] * J[3] - J[2] * J[1]; - - c[0] = J[0] * J[0] + J[2] * J[2] + J[4] * J[4]; - c[1] = J[1] * J[1] + J[3] * J[3] + J[5] * J[5]; - c[2] = J[0] * J[1] + J[2] * J[3] + J[4] * J[5]; - - *den = c[0] * c[1] - c[2] * c[2]; - - *det2 = d_0 * d_0 + d_1 * d_1 + d_2 * d_2; - *det = sqrt(*det2); -} - -/// Compute Jacobian determinants for tetrahedron embedded in R^3 -static inline void compute_jacobian_determinants_tetrahedron_3d( - double* det, double d[9], const double J[UFC_GDIM_3 * UFC_TDIM_3]) -{ - d[0 * 3 + 0] = J[4] * J[8] - J[5] * J[7]; - d[0 * 3 + 1] = J[5] * J[6] - J[3] * J[8]; - d[0 * 3 + 2] = J[3] * J[7] - J[4] * J[6]; - d[1 * 3 + 0] = J[2] * J[7] - J[1] * J[8]; - d[1 * 3 + 1] = J[0] * J[8] - J[2] * J[6]; - d[1 * 3 + 2] = J[1] * J[6] - J[0] * J[7]; - d[2 * 3 + 0] = J[1] * J[5] - J[2] * J[4]; - d[2 * 3 + 1] = J[2] * J[3] - J[0] * J[5]; - d[2 * 3 + 2] = J[0] * J[4] - J[1] * J[3]; - - *det = J[0] * d[0 * 3 + 0] + J[3] * d[1 * 3 + 0] + J[6] * d[2 * 3 + 0]; -} - -//--- NEW Computation of Jacobian inverses --- - -/// Compute Jacobian inverse K for interval embedded in R^1 -static inline void -new_compute_jacobian_inverse_interval_1d(double K[UFC_TDIM_1 * UFC_GDIM_1], - double det) -{ - K[0] = 1.0 / det; -} - -/// Compute Jacobian (pseudo)inverse K for interval embedded in R^2 -static inline void new_compute_jacobian_inverse_interval_2d( - double K[UFC_TDIM_1 * UFC_GDIM_2], double det2, - const double J[UFC_GDIM_2 * UFC_TDIM_1]) -{ - K[0] = J[0] / det2; - K[1] = J[1] / det2; -} - -/// Compute Jacobian (pseudo)inverse K for interval embedded in R^3 -static inline void new_compute_jacobian_inverse_interval_3d( - double K[UFC_TDIM_1 * UFC_GDIM_3], double det2, - const double J[UFC_GDIM_3 * UFC_TDIM_1]) -{ - K[0] = J[0] / det2; - K[1] = J[1] / det2; - K[2] = J[2] / det2; -} - -/// Compute Jacobian inverse K for triangle embedded in R^2 -static inline void new_compute_jacobian_inverse_triangle_2d( - double K[UFC_TDIM_2 * UFC_GDIM_2], double det, - const double J[UFC_GDIM_2 * UFC_TDIM_2]) -{ - K[0] = J[3] / det; - K[1] = -J[1] / det; - K[2] = -J[2] / det; - K[3] = J[0] / det; -} - -/// Compute Jacobian (pseudo)inverse K for triangle embedded in R^3 -static inline void new_compute_jacobian_inverse_triangle_3d( - double K[UFC_TDIM_2 * UFC_GDIM_3], double den, const double c[3], - const double J[UFC_GDIM_3 * UFC_TDIM_2]) -{ - K[0] = (J[0] * c[1] - J[1] * c[2]) / den; - K[1] = (J[2] * c[1] - J[3] * c[2]) / den; - K[2] = (J[4] * c[1] - J[5] * c[2]) / den; - K[3] = (J[1] * c[0] - J[0] * c[2]) / den; - K[4] = (J[3] * c[0] - J[2] * c[2]) / den; - K[5] = (J[5] * c[0] - J[4] * c[2]) / den; -} - -/// Compute Jacobian inverse K for tetrahedron embedded in R^3 -static inline void -new_compute_jacobian_inverse_tetrahedron_3d(double K[UFC_TDIM_3 * UFC_GDIM_3], - double det, const double d[9]) -{ - K[0] = d[0 * 3 + 0] / det; - K[1] = d[1 * 3 + 0] / det; - K[2] = d[2 * 3 + 0] / det; - K[3] = d[0 * 3 + 1] / det; - K[4] = d[1 * 3 + 1] / det; - K[5] = d[2 * 3 + 1] / det; - K[6] = d[0 * 3 + 2] / det; - K[7] = d[1 * 3 + 2] / det; - K[8] = d[2 * 3 + 2] / det; -} - -// --- Computation of edge, face, facet scaling factors - -/// Compute edge scaling factors for triangle embedded in R^2 -static inline void compute_edge_scaling_factors_triangle_2d( - double dx[2], const double coordinate_dofs[6], int facet) -{ - // Get vertices on edge - const unsigned int v0 = triangle_facet_vertices[facet][0]; - const unsigned int v1 = triangle_facet_vertices[facet][1]; - - // Compute scale factor (length of edge scaled by length of reference - // interval) - dx[0] = coordinate_dofs[2 * v1 + 0] - coordinate_dofs[2 * v0 + 0]; - dx[1] = coordinate_dofs[2 * v1 + 1] - coordinate_dofs[2 * v0 + 1]; -} - -/// Compute facet scaling factor for triangle embedded in R^2 -static inline void compute_facet_scaling_factor_triangle_2d(double* det, - const double dx[2]) -{ - *det = sqrt(dx[0] * dx[0] + dx[1] * dx[1]); -} - -/// Compute edge scaling factors for triangle embedded in R^3 -static inline void compute_edge_scaling_factors_triangle_3d( - double dx[3], const double coordinate_dofs[9], int facet) -{ - // Get vertices on edge - const unsigned int v0 = triangle_facet_vertices[facet][0]; - const unsigned int v1 = triangle_facet_vertices[facet][1]; - - // Compute scale factor (length of edge scaled by length of reference - // interval) - dx[0] = coordinate_dofs[3 * v1 + 0] - coordinate_dofs[3 * v0 + 0]; - dx[1] = coordinate_dofs[3 * v1 + 1] - coordinate_dofs[3 * v0 + 1]; - dx[2] = coordinate_dofs[3 * v1 + 2] - coordinate_dofs[3 * v0 + 2]; -} - -/// Compute facet scaling factor for triangle embedded in R^3 -static inline void compute_facet_scaling_factor_triangle_3d(double* det, - const double dx[3]) -{ - *det = sqrt(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]); -} - -/// Compute face scaling factors for tetrahedron embedded in R^3 -static inline void compute_face_scaling_factors_tetrahedron_3d( - double a[3], const double coordinate_dofs[12], int facet) -{ - // Get vertices on face - const unsigned int v0 = tetrahedron_facet_vertices[facet][0]; - const unsigned int v1 = tetrahedron_facet_vertices[facet][1]; - const unsigned int v2 = tetrahedron_facet_vertices[facet][2]; - - // Compute scale factor (area of face scaled by area of reference triangle) - a[0] = (coordinate_dofs[3 * v0 + 1] * coordinate_dofs[3 * v1 + 2] - + coordinate_dofs[3 * v0 + 2] * coordinate_dofs[3 * v2 + 1] - + coordinate_dofs[3 * v1 + 1] * coordinate_dofs[3 * v2 + 2]) - - (coordinate_dofs[3 * v2 + 1] * coordinate_dofs[3 * v1 + 2] - + coordinate_dofs[3 * v2 + 2] * coordinate_dofs[3 * v0 + 1] - + coordinate_dofs[3 * v1 + 1] * coordinate_dofs[3 * v0 + 2]); - - a[1] = (coordinate_dofs[3 * v0 + 2] * coordinate_dofs[3 * v1 + 0] - + coordinate_dofs[3 * v0 + 0] * coordinate_dofs[3 * v2 + 2] - + coordinate_dofs[3 * v1 + 2] * coordinate_dofs[3 * v2 + 0]) - - (coordinate_dofs[3 * v2 + 2] * coordinate_dofs[3 * v1 + 0] - + coordinate_dofs[3 * v2 + 0] * coordinate_dofs[3 * v0 + 2] - + coordinate_dofs[3 * v1 + 2] * coordinate_dofs[3 * v0 + 0]); - - a[2] = (coordinate_dofs[3 * v0 + 0] * coordinate_dofs[3 * v1 + 1] - + coordinate_dofs[3 * v0 + 1] * coordinate_dofs[3 * v2 + 0] - + coordinate_dofs[3 * v1 + 0] * coordinate_dofs[3 * v2 + 1]) - - (coordinate_dofs[3 * v2 + 0] * coordinate_dofs[3 * v1 + 1] - + coordinate_dofs[3 * v2 + 1] * coordinate_dofs[3 * v0 + 0] - + coordinate_dofs[3 * v1 + 0] * coordinate_dofs[3 * v0 + 1]); -} - -/// Compute facet scaling factor for tetrahedron embedded in R^3 -static inline void -compute_facet_scaling_factor_tetrahedron_3d(double* det, const double a[3]) -{ - *det = sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]); -} - -///--- Compute facet normal directions --- - -/// Compute facet direction for interval embedded in R^1 -static inline void compute_facet_normal_direction_interval_1d( - bool* direction, const double coordinate_dofs[2], int facet) -{ - *direction = facet == 0 ? coordinate_dofs[0] > coordinate_dofs[1] - : coordinate_dofs[1] > coordinate_dofs[0]; -} - -/// Compute facet direction for triangle embedded in R^2 -static inline void -compute_facet_normal_direction_triangle_2d(bool* direction, - const double coordinate_dofs[6], - const double dx[2], int facet) -{ - const unsigned int v0 = triangle_facet_vertices[facet][0]; - *direction = dx[1] * (coordinate_dofs[2 * facet] - coordinate_dofs[2 * v0]) - - dx[0] - * (coordinate_dofs[2 * facet + 1] - - coordinate_dofs[2 * v0 + 1]) - < 0; -} - -/// Compute facet direction for tetrahedron embedded in R^3 -static inline void -compute_facet_normal_direction_tetrahedron_3d(bool* direction, - const double coordinate_dofs[9], - const double a[3], int facet) -{ - const unsigned int v0 = tetrahedron_facet_vertices[facet][0]; - *direction = a[0] * (coordinate_dofs[3 * facet] - coordinate_dofs[3 * v0]) - + a[1] - * (coordinate_dofs[3 * facet + 1] - - coordinate_dofs[3 * v0 + 1]) - + a[2] - * (coordinate_dofs[3 * facet + 2] - - coordinate_dofs[3 * v0 + 2]) - < 0; -} - -///--- Compute facet normal vectors --- - -/// Compute facet normal for interval embedded in R^1 -static inline void compute_facet_normal_interval_1d(double n[UFC_GDIM_1], - bool direction) -{ - // Facet normals are 1.0 or -1.0: (-1.0) <-- X------X --> (1.0) - n[0] = direction ? 1.0 : -1.0; -} - -/// Compute facet normal for interval embedded in R^2 -static inline void -compute_facet_normal_interval_2d(double n[UFC_GDIM_2], - const double coordinate_dofs[4], int facet) -{ - if (facet == 0) - { - n[0] = coordinate_dofs[0] - coordinate_dofs[2]; - n[1] = coordinate_dofs[1] - coordinate_dofs[3]; - } - else - { - n[0] = coordinate_dofs[2] - coordinate_dofs[0]; - n[1] = coordinate_dofs[3] - coordinate_dofs[1]; - } - const double n_length = sqrt(n[0] * n[0] + n[1] * n[1]); - n[0] /= n_length; - n[1] /= n_length; -} - -/// Compute facet normal for interval embedded in R^3 -static inline void -compute_facet_normal_interval_3d(double n[UFC_GDIM_3], - const double coordinate_dofs[6], int facet) -{ - if (facet == 0) - { - n[0] = coordinate_dofs[0] - coordinate_dofs[3]; - n[1] = coordinate_dofs[1] - coordinate_dofs[4]; - n[1] = coordinate_dofs[2] - coordinate_dofs[5]; - } - else - { - n[0] = coordinate_dofs[3] - coordinate_dofs[0]; - n[1] = coordinate_dofs[4] - coordinate_dofs[1]; - n[1] = coordinate_dofs[5] - coordinate_dofs[2]; - } - const double n_length = sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); - n[0] /= n_length; - n[1] /= n_length; - n[2] /= n_length; -} - -/// Compute facet normal for triangle embedded in R^2 -static inline void compute_facet_normal_triangle_2d(double n[UFC_GDIM_2], - const double dx[2], - const double det, - bool direction) -{ - // Compute facet normals from the facet scale factor constants - n[0] = direction ? dx[1] / det : -dx[1] / det; - n[1] = direction ? -dx[0] / det : dx[0] / det; -} - -/// Compute facet normal for triangle embedded in R^3 -static inline void -compute_facet_normal_triangle_3d(double n[UFC_GDIM_3], - const double coordinate_dofs[6], int facet) -{ - // Compute facet normal for triangles in 3D - const unsigned int vertex0 = facet; - - // Get coordinates corresponding the vertex opposite this - const unsigned int vertex1 = triangle_facet_vertices[facet][0]; - const unsigned int vertex2 = triangle_facet_vertices[facet][1]; - - // Define vectors n = (p2 - p0) and t = normalized (p2 - p1) - n[0] = coordinate_dofs[3 * vertex2 + 0] - coordinate_dofs[3 * vertex0 + 0]; - n[1] = coordinate_dofs[3 * vertex2 + 1] - coordinate_dofs[3 * vertex0 + 1]; - n[2] = coordinate_dofs[3 * vertex2 + 2] - coordinate_dofs[3 * vertex0 + 2]; - - double t0 - = coordinate_dofs[3 * vertex2 + 0] - coordinate_dofs[3 * vertex1 + 0]; - double t1 - = coordinate_dofs[3 * vertex2 + 1] - coordinate_dofs[3 * vertex1 + 1]; - double t2 - = coordinate_dofs[3 * vertex2 + 2] - coordinate_dofs[3 * vertex1 + 2]; - const double t_length = sqrt(t0 * t0 + t1 * t1 + t2 * t2); - t0 /= t_length; - t1 /= t_length; - t2 /= t_length; - - // Subtract, the projection of (p2 - p0) onto (p2 - p1), from (p2 - p0) - const double ndott = t0 * n[0] + t1 * n[1] + t2 * n[2]; - n[0] -= ndott * t0; - n[1] -= ndott * t1; - n[2] -= ndott * t2; - const double n_length = sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); - - // Normalize - n[0] /= n_length; - n[1] /= n_length; - n[2] /= n_length; -} - -/// Compute facet normal for tetrahedron embedded in R^3 -inline void compute_facet_normal_tetrahedron_3d(double n[UFC_GDIM_3], - const double a[3], - const double det, - bool direction) -{ - // Compute facet normals from the facet scale factor constants - n[0] = direction ? a[0] / det : -a[0] / det; - n[1] = direction ? a[1] / det : -a[1] / det; - n[2] = direction ? a[2] / det : -a[2] / det; -} - -///--- Compute circumradius --- - -/// Compute circumradius for interval embedded in R^1 -inline void compute_circumradius_interval_1d(double* circumradius, - double volume) -{ - // Compute circumradius; in 1D it is equal to half the cell length - *circumradius = volume / 2.0; -} - -/// Compute circumradius for interval embedded in R^2 -static inline void compute_circumradius_interval_2d(double* circumradius, - double volume) -{ - // Compute circumradius of interval in 2D (1/2 volume) - *circumradius = volume / 2.0; -} - -/// Compute circumradius for interval embedded in R^3 -static inline void compute_circumradius_interval_3d(double* circumradius, - double volume) -{ - // Compute circumradius of interval in 3D (1/2 volume) - *circumradius = volume / 2.0; -} - -/// Compute circumradius for triangle embedded in R^2 -static inline void compute_circumradius_triangle_2d( - double* circumradius, const double coordinate_dofs[6], - const double J[UFC_GDIM_2 * UFC_TDIM_2], double volume) -{ - // Compute circumradius of triangle in 2D - const double v1v2 = sqrt((coordinate_dofs[4] - coordinate_dofs[2]) - * (coordinate_dofs[4] - coordinate_dofs[2]) - + (coordinate_dofs[5] - coordinate_dofs[3]) - * (coordinate_dofs[5] - coordinate_dofs[3])); - const double v0v2 = sqrt(J[3] * J[3] + J[1] * J[1]); - const double v0v1 = sqrt(J[0] * J[0] + J[2] * J[2]); - - *circumradius = 0.25 * (v1v2 * v0v2 * v0v1) / volume; -} - -/// Compute circumradius for triangle embedded in R^3 -static inline void compute_circumradius_triangle_3d( - double* circumradius, const double coordinate_dofs[9], - const double J[UFC_GDIM_3 * UFC_TDIM_2], double volume) -{ - // Compute circumradius of triangle in 3D - const double v1v2 = sqrt((coordinate_dofs[6] - coordinate_dofs[3]) - * (coordinate_dofs[6] - coordinate_dofs[3]) - + (coordinate_dofs[7] - coordinate_dofs[4]) - * (coordinate_dofs[7] - coordinate_dofs[4]) - + (coordinate_dofs[8] - coordinate_dofs[5]) - * (coordinate_dofs[8] - coordinate_dofs[5])); - const double v0v2 = sqrt(J[3] * J[3] + J[1] * J[1] + J[5] * J[5]); - const double v0v1 = sqrt(J[0] * J[0] + J[2] * J[2] + J[4] * J[4]); - - *circumradius = 0.25 * (v1v2 * v0v2 * v0v1) / volume; -} - -/// Compute circumradius for tetrahedron embedded in R^3 -static inline void compute_circumradius_tetrahedron_3d( - double* circumradius, const double coordinate_dofs[12], - const double J[UFC_GDIM_3 * UFC_TDIM_3], double volume) -{ - // Compute circumradius - const double v1v2 = sqrt((coordinate_dofs[6] - coordinate_dofs[3]) - * (coordinate_dofs[6] - coordinate_dofs[3]) - + (coordinate_dofs[7] - coordinate_dofs[4]) - * (coordinate_dofs[7] - coordinate_dofs[4]) - + (coordinate_dofs[8] - coordinate_dofs[5]) - * (coordinate_dofs[8] - coordinate_dofs[5])); - const double v0v2 = sqrt(J[1] * J[1] + J[4] * J[4] + J[7] * J[7]); - const double v0v1 = sqrt(J[0] * J[0] + J[3] * J[3] + J[6] * J[6]); - const double v0v3 = sqrt(J[2] * J[2] + J[5] * J[5] + J[8] * J[8]); - const double v1v3 = sqrt((coordinate_dofs[9] - coordinate_dofs[3]) - * (coordinate_dofs[9] - coordinate_dofs[3]) - + (coordinate_dofs[10] - coordinate_dofs[4]) - * (coordinate_dofs[10] - coordinate_dofs[4]) - + (coordinate_dofs[11] - coordinate_dofs[5]) - * (coordinate_dofs[11] - coordinate_dofs[5])); - const double v2v3 = sqrt((coordinate_dofs[9] - coordinate_dofs[6]) - * (coordinate_dofs[9] - coordinate_dofs[6]) - + (coordinate_dofs[10] - coordinate_dofs[7]) - * (coordinate_dofs[10] - coordinate_dofs[7]) - + (coordinate_dofs[11] - coordinate_dofs[8]) - * (coordinate_dofs[11] - coordinate_dofs[8])); - const double la = v1v2 * v0v3; - const double lb = v0v2 * v1v3; - const double lc = v0v1 * v2v3; - const double s = 0.5 * (la + lb + lc); - const double area = sqrt(s * (s - la) * (s - lb) * (s - lc)); - - *circumradius = area / (6.0 * volume); -} - -///--- Compute max facet edge lengths --- - -/// Compute min edge length in facet of tetrahedron embedded in R^3 -static inline void compute_min_facet_edge_length_tetrahedron_3d( - double* min_edge_length, unsigned int facet, - const double coordinate_dofs[3 * 4]) -{ - // TODO: Extract compute_facet_edge_lengths_tetrahedron_3d(), reuse between - // min/max functions - double edge_lengths_sqr[3]; - for (unsigned int edge = 0; edge < 3; ++edge) - { - const unsigned int vertex0 - = tetrahedron_facet_edge_vertices[facet][edge][0]; - const unsigned int vertex1 - = tetrahedron_facet_edge_vertices[facet][edge][1]; - edge_lengths_sqr[edge] - = (coordinate_dofs[3 * vertex1 + 0] - coordinate_dofs[3 * vertex0 + 0]) - * (coordinate_dofs[3 * vertex1 + 0] - - coordinate_dofs[3 * vertex0 + 0]) - + (coordinate_dofs[3 * vertex1 + 1] - - coordinate_dofs[3 * vertex0 + 1]) - * (coordinate_dofs[3 * vertex1 + 1] - - coordinate_dofs[3 * vertex0 + 1]) - + (coordinate_dofs[3 * vertex1 + 2] - - coordinate_dofs[3 * vertex0 + 2]) - * (coordinate_dofs[3 * vertex1 + 2] - - coordinate_dofs[3 * vertex0 + 2]); - } - *min_edge_length = sqrt(fmin(fmin(edge_lengths_sqr[1], edge_lengths_sqr[1]), - edge_lengths_sqr[2])); -} - -///--- Compute max facet edge lengths --- - -/// Compute max edge length in facet of tetrahedron embedded in R^3 -static inline void -compute_max_facet_edge_length_tetrahedron_3d(double* max_edge_length, - unsigned int facet, - const double coordinate_dofs[12]) -{ - // TODO: Extract compute_facet_edge_lengths_tetrahedron_3d(), reuse between - // min/max functions - double edge_lengths_sqr[3]; - for (unsigned int edge = 0; edge < 3; ++edge) - { - const unsigned int vertex0 - = tetrahedron_facet_edge_vertices[facet][edge][0]; - const unsigned int vertex1 - = tetrahedron_facet_edge_vertices[facet][edge][1]; - edge_lengths_sqr[edge] - = (coordinate_dofs[3 * vertex1 + 0] - coordinate_dofs[3 * vertex0 + 0]) - * (coordinate_dofs[3 * vertex1 + 0] - - coordinate_dofs[3 * vertex0 + 0]) - + (coordinate_dofs[3 * vertex1 + 1] - - coordinate_dofs[3 * vertex0 + 1]) - * (coordinate_dofs[3 * vertex1 + 1] - - coordinate_dofs[3 * vertex0 + 1]) - + (coordinate_dofs[3 * vertex1 + 2] - - coordinate_dofs[3 * vertex0 + 2]) - * (coordinate_dofs[3 * vertex1 + 2] - - coordinate_dofs[3 * vertex0 + 2]); - } - *max_edge_length = sqrt(fmax(fmax(edge_lengths_sqr[0], edge_lengths_sqr[1]), - edge_lengths_sqr[2])); -} diff --git a/ffcx/codegeneration/utils.py b/ffcx/codegeneration/utils.py index 237d2c1ad..480d38326 100644 --- a/ffcx/codegeneration/utils.py +++ b/ffcx/codegeneration/utils.py @@ -1,10 +1,13 @@ # Copyright (C) 2015-2017 Martin Sandve Alnæs +# Modified by Matthew Scroggs, 2020-2021 # # This file is part of FFCX.(https://www.fenicsproject.org) # # SPDX-License-Identifier: LGPL-3.0-or-later # TODO: Move these to ffcx.language utils? +import numpy +index_type = "int" def generate_return_new(L, classname): @@ -65,3 +68,128 @@ def generate_return_literal_switch(L, def generate_return_int_switch(L, i, values, default): return generate_return_literal_switch(L, i, values, default, L.LiteralInt, "int") + + +def make_perm_data(L, base_perms, cell_shape, reverse=False): + if cell_shape == "interval": + entities = {} + elif cell_shape == "triangle": + entities = {1: 3} + elif cell_shape == "quadrilateral": + entities = {1: 4} + elif cell_shape == "tetrahedron": + entities = {1: 6, 2: 4} + face_rotation_order = 3 + elif cell_shape == "hexahedron": + entities = {1: 12, 2: 6} + face_rotation_order = 4 + else: + raise NotImplementedError + + perm_n = 0 + perm_data = [] + if 1 in entities: + for edge in range(entities[1]): + perm_data.append(( + entity_reflection(L, (1, edge), cell_shape), + None, + base_perms[perm_n] + )) + perm_n += 1 + if 2 in entities: + for face in range(entities[2]): + reflection = ( + entity_reflection(L, (2, face), cell_shape), + None, + base_perms[perm_n + 1] + ) + if not reverse: + perm_data.append(reflection) + for rot in range(1, face_rotation_order): + if reverse: + rot = face_rotation_order - rot + perm_data.append(( + entity_rotations(L, (2, face), cell_shape), + rot, + numpy.linalg.matrix_power(base_perms[perm_n], rot) + )) + if reverse: + perm_data.append(reflection) + perm_n += 2 + + assert perm_n == len(base_perms) + + return perm_data + + +def apply_permutations_to_data(L, base_permutations, cell_shape, data, reverse=False, + indices=lambda dof: dof, ranges=None, dtype="double"): + perm_data = make_perm_data(L, base_permutations, cell_shape, reverse=reverse) + + # Apply entity permutations + apply_permutations = [] + temporary_variables = 0 + for entity_perm, value, perm in perm_data: + body = [] + + # Use temporary variables t0, t1, ... to store current data + temps = {} + for index, row in enumerate(perm): + if not numpy.allclose(row, [1 if i == index else 0 for i, j in enumerate(row)]): + for dof, w in enumerate(row): + if not numpy.isclose(w, 0) and dof not in temps: + temps[dof] = L.Symbol("t" + str(len(temps))) + body.append(L.Assign(data[indices(index)], + sum(temps[dof] if numpy.isclose(w, 1) else w * temps[dof] + for dof, w in enumerate(row) if not numpy.isclose(w, 0)))) + temporary_variables = max(temporary_variables, len(temps)) + + # If no changes would be made, continue to next entity + if len(body) == 0: + continue + + if value is None: + condition = entity_perm + else: + condition = L.EQ(entity_perm, value) + + body = [L.Assign(t, data[indices(dof)]) for dof, t in temps.items()] + body + if ranges is None: + apply_permutations.append(L.If(condition, body)) + else: + apply_permutations.append(L.If(condition, + L.ForRanges(*ranges, index_type=index_type, body=body))) + + if len(apply_permutations) > 0: + apply_permutations = [L.VariableDecl(dtype, L.Symbol("t" + str(i)), 0) + for i in range(temporary_variables)] + apply_permutations + + return apply_permutations + + +def entity_reflection(L, i, cell_shape): + """Returns the bool that says whether or not an entity has been reflected.""" + cell_info = L.Symbol("cell_permutation") + if cell_shape in ["triangle", "quadrilateral"]: + num_faces = 0 + face_bitsize = 1 + assert i[0] == 1 + if cell_shape == "tetrahedron": + num_faces = 4 + face_bitsize = 3 + if cell_shape == "hexahedron": + num_faces = 6 + face_bitsize = 3 + if i[0] == 1: + return L.NE(L.BitwiseAnd(cell_info, L.BitShiftL(1, face_bitsize * num_faces + i[1])), 0) + elif i[0] == 2: + return L.NE(L.BitwiseAnd(cell_info, L.BitShiftL(1, face_bitsize * i[1])), 0) + return L.LiteralBool(False) + + +def entity_rotations(L, i, cell_shape): + """Returns number of times an entity has been rotated.""" + cell_info = L.Symbol("cell_permutation") + assert cell_shape in ["tetrahedron", "hexahedron"] + assert i[0] == 2 + return L.BitwiseAnd(L.BitShiftR(cell_info, 3 * i[1] + 1), 3) diff --git a/ffcx/fiatinterface.py b/ffcx/fiatinterface.py deleted file mode 100644 index e08d9d1c2..000000000 --- a/ffcx/fiatinterface.py +++ /dev/null @@ -1,311 +0,0 @@ -# Copyright (C) 2009-2020 Kristian B. Oelgaard, Anders Logg and Garth N. Wells -# -# This file is part of FFCX.(https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -import functools -import logging -import types - -import numpy - -import FIAT -import ufl - -logger = logging.getLogger("ffcx") - -# Element families supported by FFCX -supported_families = ("Brezzi-Douglas-Marini", "Brezzi-Douglas-Fortin-Marini", "Crouzeix-Raviart", - "Discontinuous Lagrange", "Discontinuous Raviart-Thomas", "HDiv Trace", - "Lagrange", "Lobatto", "Nedelec 1st kind H(curl)", "Nedelec 2nd kind H(curl)", - "Radau", "Raviart-Thomas", "Real", "Bubble", "Quadrature", "Regge", - "Hellan-Herrmann-Johnson", "Q", "DQ", "TensorProductElement", "Gauss-Lobatto-Legendre", - "RTCF", "NCF", - "RTCE", "NCE") -# The following elements are not supported in FIAT yet, but will be supported here once they are -# "BDMCF", "BDMCE" - -# Cache for computed elements -_cache = {} - -_tpc_quadrilateral = ufl.TensorProductCell(ufl.interval, ufl.interval) -_tpc_hexahedron = ufl.TensorProductCell(ufl.quadrilateral, ufl.interval) - - -class SpaceOfReals(object): - """Constant over the entire domain, rather than just cellwise.""" - - -def reference_cell_vertices(cellname): - """Return dict of coordinates of reference cell vertices for this 'cellname'.""" - return FIAT.ufc_cell(cellname).get_vertices() - - -@functools.singledispatch -def _create_element(element): - typename = type(element).__module__ + "." + type(element).__name__ - raise ValueError("Element type " + typename + " is not supported.") - - -@_create_element.register(ufl.FiniteElement) -def _create_finiteelement(element: ufl.FiniteElement) -> FIAT.FiniteElement: - """Create FIAT element for UFL base type ufl.FiniteElement.""" - if element.family() == "Real": - e = create_element(ufl.FiniteElement("DG", element.cell(), 0)) - e.__class__ = type('SpaceOfReals', (type(e), SpaceOfReals), {}) - return e - - if element.family() == "Quadrature": - assert element.degree() is not None - assert element.quadrature_scheme() is not None - - # Create quadrature (only interested in points) - # TODO: KBO: What should we do about quadrature functions that live on ds, dS? - # Get cell and facet names. - points, weights = create_quadrature(element.cell().cellname(), element.degree(), - element.quadrature_scheme()) - return FIAT.QuadratureElement(FIAT.ufc_cell(element.cell().cellname()), points) - - # Handle tensor-product structured elements - if element.cell().cellname() == "quadrilateral": - return _flatten_quad(element) - elif element.cell().cellname() == "hexahedron": - return _flatten_hex(element) - - if element.family() not in FIAT.supported_elements: - raise ValueError("Finite element of type \"{}\" is not supported by FIAT.".format(element.family())) - - # Handle Lagrange variants - if element.family() == "Lagrange" and element.variant() == "spectral": - assert element.cell().cellname() == "interval" - element_class = FIAT.GaussLobattoLegendre - else: - element_class = FIAT.supported_elements[element.family()] - - assert element.degree() is not None - return element_class(FIAT.ufc_cell(element.cell().cellname()), element.degree()) - - -@_create_element.register(ufl.MixedElement) -def _create_mixed_finiteelement(element: ufl.MixedElement) -> FIAT.MixedElement: - elements = [] - - def rextract(els): - for e in els: - if isinstance(e, ufl.MixedElement) \ - and not isinstance(e, ufl.VectorElement) \ - and not isinstance(e, ufl.TensorElement): - rextract(e.sub_elements()) - else: - elements.append(e) - - rextract(element.sub_elements()) - return FIAT.MixedElement(map(_create_element, elements)) - - -@_create_element.register(ufl.VectorElement) -def _create_vector_finiteelement(element: ufl.VectorElement) -> FIAT.MixedElement: - fiat_element = FIAT.MixedElement(map(_create_element, element.sub_elements())) - - def reorder_for_vector_element(item, block_size): - """Reorder the elements in item from XXYYZZ ordering to XYZXYZ.""" - space_dim = len(item) // block_size - return [item[i] for block in range(space_dim) - for i in range(block, len(item), space_dim)] - - def calculate_entity_dofs_of_vector_element(entity_dofs, block_size): - """Get the entity DOFs of a VectorElement with XYZXYZ ordering.""" - return { - dim: { - entity: [block_size * i + j for i in e_dofs for j in range(block_size)] - for entity, e_dofs in dofs.items() - } for dim, dofs in entity_dofs.items() - } - - # Reorder from XXYYZZ to XYZXYZ - block_size = fiat_element.num_sub_elements() - fiat_element.mapping = types.MethodType( - lambda self: [m for m in self._elements[0].mapping() for e in self._elements], - fiat_element) - fiat_element.dual.nodes = reorder_for_vector_element(fiat_element.dual.nodes, block_size) - fiat_element.dual.entity_ids = calculate_entity_dofs_of_vector_element( - fiat_element.elements()[0].dual.entity_ids, block_size) - fiat_element.dual.entity_closure_ids = calculate_entity_dofs_of_vector_element( - fiat_element.elements()[0].dual.entity_closure_ids, block_size) - fiat_element.old_tabulate = fiat_element.tabulate - - def tabulate(self, order, points, entity=None): - block_size = self.num_sub_elements() - scalar_dofs = len(self.dual.nodes) // block_size - return { - i: numpy.array([item[j] for dim in range(scalar_dofs) - for j in range(dim, len(item), scalar_dofs)]) - for i, item in self.old_tabulate(order, points, entity=entity).items() - } - - fiat_element.tabulate = types.MethodType(tabulate, fiat_element) - - return fiat_element - - -@_create_element.register(ufl.HDivElement) -def _create_hdiv_finiteelement(element: ufl.HDivElement) -> FIAT.TensorProductElement: - tp = _create_element(element._element) - return FIAT.Hdiv(tp) - - -@_create_element.register(ufl.HCurlElement) -def _create_hcurl_finiteelement(element: ufl.HCurlElement) -> FIAT.TensorProductElement: - tp = _create_element(element._element) - return FIAT.Hcurl(tp) - - -@_create_element.register(ufl.TensorElement) -def _create_tensor_finiteelement(element: ufl.TensorElement) -> FIAT.MixedElement: - return _create_vector_finiteelement(element) - - -@_create_element.register(ufl.EnrichedElement) -def _create_enriched_finiteelement(element: ufl.EnrichedElement) -> FIAT.EnrichedElement: - elements = [create_element(e) for e in element._elements] - return FIAT.EnrichedElement(*elements) - - -@_create_element.register(ufl.NodalEnrichedElement) -def _create_nodalenriched_finiteelement(element: ufl.NodalEnrichedElement) -> FIAT.NodalEnrichedElement: - elements = [create_element(e) for e in element._elements] - return FIAT.NodalEnrichedElement(*elements) - - -@_create_element.register(ufl.RestrictedElement) -def _create_restricted_finiteelement(element: ufl.RestrictedElement): - # element = _create_restricted_element(element) - raise RuntimeError("Cannot handle this element type: {}".format(element)) - - -@_create_element.register(ufl.TensorProductElement) -def _create_tp_finiteelement(element) -> FIAT.TensorProductElement: - e0, e1 = element.sub_elements() - return FIAT.TensorProductElement(_create_element(e0), _create_element(e1)) - - -def create_element(ufl_element: ufl.finiteelement) -> FIAT.FiniteElement: - """Create a FIAT finite element for a given UFL element.""" - - # Use UFL element as cache key - element_signature = ufl_element - if element_signature in _cache: - return _cache[element_signature] - - # Create element and add to cache - element = _create_element(ufl_element) - _cache[element_signature] = element - - return element - - -def create_quadrature(shape, degree: int, scheme: str = "default"): - """Generate quadrature rule. - - Quadrature rule(points, weights) for given shape that will integrate - an polynomial of order 'degree' exactly. - - """ - if isinstance(shape, int) and shape == 0: - return (numpy.zeros((1, 0)), numpy.ones((1, ))) - - if shape in ufl.cell.cellname2dim and ufl.cell.cellname2dim[shape] == 0: - return (numpy.zeros((1, 0)), numpy.ones((1, ))) - - quad_rule = FIAT.create_quadrature(FIAT.ufc_cell(shape), degree, scheme) - points = numpy.asarray(quad_rule.get_points()) - weights = numpy.asarray(quad_rule.get_weights()) - return points, weights - - -def map_facet_points(points, facet, cellname): - """Map points from a facet to a cell. - - Map points from the (UFC) reference simplex of dimension d - 1 to a - given facet on the (UFC) reference simplex of dimension d. This may - be used to transform points tabulated for example on the 2D - reference triangle to points on a given facet of the reference - tetrahedron. - - """ - - # Extract the geometric dimension of the points we want to map - dim = len(points[0]) + 1 - - # Special case, don't need to map coordinates on vertices - if dim == 1: - return [[(0.0, ), (1.0, )][facet]] - - # Get the FIAT reference cell - fiat_cell = FIAT.ufc_cell(cellname) - - # Extract vertex coordinates from cell and map of facet index to - # indicent vertex indices - coordinate_dofs = fiat_cell.get_vertices() - facet_vertices = fiat_cell.get_topology()[dim - 1] - - # coordinate_dofs = \ - # {1: ((0.,), (1.,)), - # 2: ((0., 0.), (1., 0.), (0., 1.)), - # 3: ((0., 0., 0.), (1., 0., 0.),(0., 1., 0.), (0., 0., 1))} - - # Facet vertices - # facet_vertices = \ - # {2: ((1, 2), (0, 2), (0, 1)), - # 3: ((1, 2, 3), (0, 2, 3), (0, 1, 3), (0, 1, 2))} - - # Compute coordinates and map the points - coordinates = [coordinate_dofs[v] for v in facet_vertices[facet]] - new_points = [] - for point in points: - w = (1.0 - sum(point), ) + tuple(point) - x = tuple(sum([w[i] * numpy.array(coordinates[i]) for i in range(len(w))])) - new_points += [x] - - return new_points - - -def _flatten_quad(element): - e = _create_element(element.reconstruct(cell=_tpc_quadrilateral)) - flat = FIAT.tensor_product.FlattenedDimensions(e) - - # Overwrite undefined DOF types of Hdiv and Hcurl spaces with correct types - if element.family() == "RTCF" or element.family() == "BDMCF": - for dofs in flat.entity_dofs()[1].values(): - for d in dofs: - flat.dual.nodes[d].functional_type = "PointNormalEval" - - # Overwrite undefined DOF types of Hdiv and Hcurl spaces with correct types - if element.family() == "RTCE" or element.family() == "BDMCE": - for dofs in flat.entity_dofs()[1].values(): - for d in dofs: - flat.dual.nodes[d].functional_type = "PointEdgeTangent" - - return flat - - -def _flatten_hex(element): - e = _create_element(element.reconstruct(cell=_tpc_hexahedron)) - flat = FIAT.tensor_product.FlattenedDimensions(e) - - # Overwrite undefined DOF types of Hdiv and Hcurl spaces with correct types - if element.family() == "NCF": - for dofs in flat.entity_dofs()[2].values(): - for d in dofs: - flat.dual.nodes[d].functional_type = "PointNormalEval" - - if element.family() == "NCE": - for dofs in flat.entity_dofs()[1].values(): - for d in dofs: - flat.dual.nodes[d].functional_type = "PointEdgeTangent" - for dofs in flat.entity_dofs()[2].values(): - for d in dofs: - flat.dual.nodes[d].functional_type = "PointFaceTangent" - - return flat diff --git a/ffcx/ir/analysis/modified_terminals.py b/ffcx/ir/analysis/modified_terminals.py index 05fd98d92..2005f0781 100644 --- a/ffcx/ir/analysis/modified_terminals.py +++ b/ffcx/ir/analysis/modified_terminals.py @@ -122,14 +122,13 @@ def __eq__(self, other): # return self.as_tuple() < other.as_tuple() def __str__(self): - s = [] - s += ["terminal: {0}".format(self.terminal)] - s += ["global_derivatives: {0}".format(self.global_derivatives)] - s += ["local_derivatives: {0}".format(self.local_derivatives)] - s += ["averaged: {0}".format(self.averaged)] - s += ["component: {0}".format(self.component)] - s += ["restriction: {0}".format(self.restriction)] - return '\n'.join(s) + return ( + f"terminal: {self.terminal}\n" + f"global_derivatives: {self.global_derivatives}\n" + f"local_derivatives: {self.local_derivatives}\n" + f"averaged: {self.averaged}\n" + f"component: {self.component}\n" + f"restriction: {self.restriction}") def is_modified_terminal(v): diff --git a/ffcx/ir/analysis/visualise.py b/ffcx/ir/analysis/visualise.py index af94338ac..d1aedfff4 100644 --- a/ffcx/ir/analysis/visualise.py +++ b/ffcx/ir/analysis/visualise.py @@ -49,7 +49,7 @@ def visualise_graph(Gx, filename): c = v.get('component') if c: - G.get_node(nd).attr['label'] += ', comp={}'.format(c) + G.get_node(nd).attr['label'] += f", comp={c}" for nd, eds in Gx.out_edges.items(): for ed in eds: diff --git a/ffcx/ir/dof_permutations.py b/ffcx/ir/dof_permutations.py deleted file mode 100644 index 19046b744..000000000 --- a/ffcx/ir/dof_permutations.py +++ /dev/null @@ -1,552 +0,0 @@ -# Copyright (C) 2020 Matthew W. Scroggs -# -# This file is part of FFCX.(https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -import warnings -import math -import ufl -from ffcx.fiatinterface import create_element - -# TODO: This information should be moved to FIAT instead of being reverse engineered here -# TODO: Currently none of the vector-valued stuff has been tested on quads and hexes -# TODO: currently these dof types are not handled at all: -# PointwiseInnerProductEval -# IntegralMomentOfNormalDerivative - - -def base_permutations(ufl_element): - """Returns the base permutations.""" - if ufl_element.num_sub_elements() == 0: - # If the element has no sub elements, return its permutations - return base_permutations_from_subdofmap(ufl_element) - - # If the element has sub elements, combine their permutations - perms = None - - if isinstance(ufl_element, ufl.VectorElement) or isinstance(ufl_element, ufl.TensorElement): - block_size = ufl_element.num_sub_elements() - return [ - [block_size * j + i for j in perm for i in range(block_size)] - for perm in base_permutations(ufl_element.sub_elements()[0]) - ] - - for e in ufl_element.sub_elements(): - bp = base_permutations(e) - if perms is None: - perms = [[] for i in bp] - for i, b in enumerate(bp): - perms[i] += [a + len(perms[i]) for a in b] - return perms - - -def reflection_entities(ufl_element): - """Returns the entities that the direction of vector-valued functions depend on.""" - if ufl_element.num_sub_elements() == 0: - # If the element has no sub elements, return its reflection entities - return reflection_entities_from_subdofmap(ufl_element) - - if isinstance(ufl_element, ufl.VectorElement) or isinstance(ufl_element, ufl.TensorElement): - block_size = ufl_element.num_sub_elements() - return [ - ref - for ref in reflection_entities(ufl_element.sub_elements()[0]) - for i in range(block_size) - ] - # If the element has sub elements, combine their reflections - reflections = [] - for e in ufl_element.sub_elements(): - reflections += reflection_entities(e) - return reflections - - -def face_tangents(ufl_element): - """Returns the rotations that rotate the direction of vector-valued face tangent dofs.""" - if ufl_element.num_sub_elements() == 0: - # If the element has no sub elements, return its rotations - return face_tangents_from_subdofmap(ufl_element) - - if isinstance(ufl_element, ufl.VectorElement) or isinstance(ufl_element, ufl.TensorElement): - if len(face_tangents(ufl_element.sub_elements()[0])) != 0: - raise NotImplementedError - - # If the element has sub elements, combine their rotations - rotations = {} - for e in ufl_element.sub_elements(): - if len(face_tangents(e)) != 0: - raise NotImplementedError - return rotations - - -def base_permutations_from_subdofmap(ufl_element): - """Calculate permutations and reflection entites for a root element. - Calculates the base permutations and the entities that the direction of vector-valued - functions depend on for an element with no sub elements.""" - fiat_element = create_element(ufl_element) - dual = fiat_element.dual_basis() - num_dofs = len(dual) - - tdim = ufl_element.cell().topological_dimension() - - # Get the entity counts and shape of each entity for the cell type - entity_counts = get_entity_counts(fiat_element) - entity_functions = get_entity_functions(ufl_element) - - # There is 1 permutation for a 1D entity, 2 for a 2D entity and 4 for a 3D entity - num_perms = sum([0, 1, 2, 4][i] * j for i, j in enumerate(entity_counts[:tdim])) - - dof_types = [e.functional_type for e in dual] - entity_dofs = fiat_element.entity_dofs() - - perms = identity_permutations(num_perms, num_dofs) - perm_n = 0 - - # Iterate through the entities of the reference element - for dim in range(1, tdim): - for entity_n in range(entity_counts[dim]): - dofs = entity_dofs[dim][entity_n] - types = [dof_types[i] for i in dofs] - # Find the unique dof types - unique_types = [] - for t in types: - if t not in unique_types: - unique_types.append(t) - # Permute the dofs of each entity type separately - for t in unique_types: - permuted = None - type_dofs = [i for i, j in zip(dofs, types) if j == t] - # First deal with special cases for tensor product Hdiv and Hcurl - if t == "PointFaceTangent" and dim == 2 and ufl_element.family() == "NCE": - permuted = permute_nce_face(type_dofs, 1) - elif t == "PointNormalEval" and dim == 2 and ufl_element.family() == "NCF": - permuted = permute_ncf_face(type_dofs, 1) - elif t == "PointNormalEval" and dim == 1 and ufl_element.family() == "RTCF": - permuted = permute_tp_edge(type_dofs, 1) - elif t == "PointEdgeTangent" and dim == 1 and ufl_element.family() == "RTCE": - permuted = permute_tp_edge(type_dofs, 1) - elif t in ["PointEval", "PointNormalDeriv", "PointEdgeTangent", - "PointDeriv", "PointNormalEval", "PointScaledNormalEval"]: - # Dof is a point evaluation, use sub_block_size 1 - permuted = entity_functions[dim](type_dofs, 1) - elif t in ["ComponentPointEval", "IntegralMoment"]: - # Dof sub_block_size is equal to entity dimension - permuted = entity_functions[dim](type_dofs, dim) - elif t == "PointFaceTangent": - # Dof sub_block_size is 2 - permuted = entity_functions[dim](type_dofs, 2) - elif t in ["FrobeniusIntegralMoment"] and dim == 2: - permuted = permute_frobenius_face(type_dofs, 1) - else: - if dim < tdim: - # TODO: What to do with other dof types - warnings.warn("Permutations of " + t + " dofs not yet " - "implemented. Results on unordered meshes may be incorrect") - continue - - # Apply these permutations - for n, p in enumerate(permuted): - for i, j in zip(type_dofs, p): - perms[perm_n + n][i] = j - perm_n += 2 ** (dim - 1) - return perms - - -def reflection_entities_from_subdofmap(ufl_element): - """Calculate permutations and reflection entites for a root element. - Calculates the base permutations and the entities that the direction of vector-valued - functions depend on for an element with no sub elements.""" - fiat_element = create_element(ufl_element) - dual = fiat_element.dual_basis() - num_dofs = len(dual) - - cname = ufl_element.cell().cellname() - tdim = ufl_element.cell().topological_dimension() - - # Get the entity counts for the cell type - entity_counts = get_entity_counts(fiat_element) - - dof_types = [e.functional_type for e in dual] - entity_dofs = fiat_element.entity_dofs() - - reflections = [None for i in range(num_dofs)] - # Iterate through the entities of the reference element - for dim in range(1, tdim): - for entity_n in range(entity_counts[dim]): - dofs = entity_dofs[dim][entity_n] - types = [dof_types[i] for i in dofs] - # Find the unique dof types - unique_types = [] - for t in types: - if t not in unique_types: - unique_types.append(t) - # Permute the dofs of each entity type separately - for t in unique_types: - type_dofs = [i for i, j in zip(dofs, types) if j == t] - if t in ["PointScaledNormalEval", "ComponentPointEval", "PointEdgeTangent", - "PointScaledNormalEval", "PointNormalEval", "IntegralMoment"]: - for i in type_dofs: - reflections[i] = [(dim, entity_n)] - elif t == "FrobeniusIntegralMoment" and cname in ["triangle", "tetrahedron"]: - if dim == 2: - s = get_frobenius_side_length(len(type_dofs)) - for i, b in enumerate(fiat_element.ref_el.connectivity[dim, dim - 1][entity_n]): - for a in type_dofs[i * s:(i + 1) * s]: - reflections[a] = [(dim, entity_n), (dim - 1, b)] - return reflections - - -def face_tangents_from_subdofmap(ufl_element): - """Calculate the effect of permuting a face on the DOFs tangential to that face. - - This function returns a dictionary with the format: - rotations[(entity_dim, entity_number)][face_permuation][dof] = [(d_i, a_i), ...] - This entry tells us that on the entity number entity_number of dimension entity_dim, - if the face permutation is equal to face_permutation, then the following should be - applied to the values for the DOFs on that face: - value[dof] = sum(d_i * a_i for i in (...)) - """ - fiat_element = create_element(ufl_element) - dual = fiat_element.dual_basis() - cname = ufl_element.cell().cellname() - - dof_types = [e.functional_type for e in dual] - entity_dofs = fiat_element.entity_dofs() - - face_tangents = {} - if cname == "tetrahedron" or cname == "hexahedron": - # Iterate through faces - for entity_n in range(len(entity_dofs[2])): - dofs = entity_dofs[2][entity_n] - types = [dof_types[i] for i in dofs] - if cname == "tetrahedron": - tangent_data = {i: {} for i in range(6)} - elif cname == "hexahedron": - tangent_data = {i: {} for i in range(8)} - - # PointFaceTangent dofs - if cname == "tetrahedron" and "PointFaceTangent" in types: - type_dofs = [i for i, t in zip(dofs, types) if t == "PointFaceTangent"] - for dof_pair in zip(type_dofs[::2], type_dofs[1::2]): - tangent_data[0][dof_pair[0]] = [(dof_pair[0], 1)] - tangent_data[1][dof_pair[0]] = [(dof_pair[1], 1)] - tangent_data[2][dof_pair[0]] = [(dof_pair[0], -1), (dof_pair[1], -1)] - tangent_data[3][dof_pair[0]] = [(dof_pair[0], -1), (dof_pair[1], -1)] - tangent_data[4][dof_pair[0]] = [(dof_pair[1], 1)] - tangent_data[5][dof_pair[0]] = [(dof_pair[0], 1)] - - tangent_data[0][dof_pair[1]] = [(dof_pair[1], 1)] - tangent_data[1][dof_pair[1]] = [(dof_pair[0], 1)] - tangent_data[2][dof_pair[1]] = [(dof_pair[0], 1)] - tangent_data[3][dof_pair[1]] = [(dof_pair[1], 1)] - tangent_data[4][dof_pair[1]] = [(dof_pair[0], -1), (dof_pair[1], -1)] - tangent_data[5][dof_pair[1]] = [(dof_pair[0], -1), (dof_pair[1], -1)] - - # FrobeniusIntegralMoment dofs - if cname == "tetrahedron" and "FrobeniusIntegralMoment" in types: - type_dofs = [i for i, t in zip(dofs, types) if t == "FrobeniusIntegralMoment"] - s = get_frobenius_side_length(len(type_dofs)) - for dof_pair in zip(type_dofs[3 * s::2], type_dofs[3 * s + 1::2]): - tangent_data[0][dof_pair[0]] = [(dof_pair[0], 1)] - tangent_data[1][dof_pair[0]] = [(dof_pair[1], 1)] - tangent_data[2][dof_pair[0]] = [(dof_pair[0], -1), (dof_pair[1], -1)] - tangent_data[3][dof_pair[0]] = [(dof_pair[0], -1), (dof_pair[1], -1)] - tangent_data[4][dof_pair[0]] = [(dof_pair[1], 1)] - tangent_data[5][dof_pair[0]] = [(dof_pair[0], 1)] - - tangent_data[0][dof_pair[1]] = [(dof_pair[1], 1)] - tangent_data[1][dof_pair[1]] = [(dof_pair[0], 1)] - tangent_data[2][dof_pair[1]] = [(dof_pair[0], 1)] - tangent_data[3][dof_pair[1]] = [(dof_pair[1], 1)] - tangent_data[4][dof_pair[1]] = [(dof_pair[0], -1), (dof_pair[1], -1)] - tangent_data[5][dof_pair[1]] = [(dof_pair[0], -1), (dof_pair[1], -1)] - - # PointFaceTangent dofs on a hex - if cname == "hexahedron" and "PointFaceTangent" in types: - type_dofs = [i for i, t in zip(dofs, types) if t == "PointFaceTangent"] - for dof in type_dofs[:len(type_dofs) // 2]: - tangent_data[0][dof] = [(dof, 1)] - tangent_data[1][dof] = [(dof, 1)] - tangent_data[2][dof] = [(dof, 1)] - tangent_data[3][dof] = [(dof, 1)] - tangent_data[4][dof] = [(dof, -1)] - tangent_data[5][dof] = [(dof, -1)] - tangent_data[6][dof] = [(dof, -1)] - tangent_data[7][dof] = [(dof, -1)] - for dof in type_dofs[len(type_dofs) // 2:]: - tangent_data[0][dof] = [(dof, 1)] - tangent_data[1][dof] = [(dof, 1)] - tangent_data[2][dof] = [(dof, -1)] - tangent_data[3][dof] = [(dof, -1)] - tangent_data[4][dof] = [(dof, -1)] - tangent_data[5][dof] = [(dof, -1)] - tangent_data[6][dof] = [(dof, 1)] - tangent_data[7][dof] = [(dof, 1)] - - if max(len(a) for a in tangent_data.values()) > 0: - face_tangents[(2, entity_n)] = tangent_data - - return face_tangents - - -def get_entity_counts(fiat_element): - topology = fiat_element.ref_el.topology - return [len(topology[i]) for i in range(len(topology))] - - -def get_entity_functions(ufl_element): - cname = ufl_element.cell().cellname() - if cname == 'point': - return [None] - elif cname == 'interval': - return [None, None] - elif cname == 'triangle': - return [None, permute_edge, None] - elif cname == 'tetrahedron': - return [None, permute_edge, permute_triangle, None] - elif cname == 'quadrilateral': - return [None, permute_edge, None] - elif cname == 'hexahedron': - return [None, permute_edge, permute_quadrilateral, None] - else: - raise ValueError("Unrecognised cell type") - - -def get_frobenius_side_length(n): - """Get the side length the arrangement of FrobeniusIntegralMoment dofs of a face of a N2curl space.""" - s = 0 - while 3 * s + s * (s - 1) < n: - s += 1 - assert 3 * s + s * (s - 1) == n - return s - - -def permute_ncf_face(dofs_in, sub_block_size, reverse_blocks=False): - """Permute the dofs on a quadrilateral.""" - n = len(dofs_in) // sub_block_size - s = math.floor(math.sqrt(n)) - assert s ** 2 == n - - if s == 1: - simple_dofs = [0] - elif s == 2: - simple_dofs = [0, 1, 2, 3] - else: - # TODO: fix higher order NCF spaces - raise RuntimeError("NCF spaces of order > 2 not yet supported") - simple_dofs = [] - for i in [0] + list(range(2 * s, n, s)) + [s]: - simple_dofs += [i] + list(range(i + 2, i + s)) + [i + 1] - dofs = [] - for d in simple_dofs: - dofs += [dofs_in[d * sub_block_size + k] for k in range(sub_block_size)] - assert len(dofs) == len(dofs_in) - - return [quadrilateral_rotation(dofs, sub_block_size), - quadrilateral_reflection(dofs, sub_block_size, reverse_blocks)] - - -def permute_nce_face(dofs, sub_block_size): - """Permute the dofs on the face of a NCE space.""" - n = len(dofs) // sub_block_size - order = math.floor(1 + math.sqrt(1 + 2 * n)) // 2 - assert 2 * order * (order - 1) == n - - if order > 2: - # TODO: fix higher order NCE spaces - raise RuntimeError("NCE spaces of order > 2 not yet supported") - - # Make the rotation - rot = [] - for i in range(order - 1): - for dof in range(order * (order + i) - 1, order * (order - 1 + i) - 1, -1): - rot += [dof * sub_block_size + k for k in range(sub_block_size)] - for i in range(order - 1): - for dof in range(order * (order - 2 - i), order * (order - 1 - i)): - rot += [dof * sub_block_size + k for k in range(sub_block_size)] - assert len(rot) == len(dofs) - - # Make the reflection - ref = [] - for dof in range(order * (order - 1), 2 * order * (order - 1)): - ref += [dof * sub_block_size + k for k in range(sub_block_size)] - for dof in range(order * (order - 1)): - ref += [dof * sub_block_size + k for k in range(sub_block_size)] - - return [[dofs[i] for i in rot], - [dofs[i] for i in ref]] - - -def permute_frobenius_face(dofs, sub_block_size): - """Permute the FrobeniusIntegralMoment dofs of a face of a N2curl space.""" - n = len(dofs) // sub_block_size - s = get_frobenius_side_length(n) - - # Make the rotation - rot = [] - for dof in range(2 * s, 3 * s): - rot += [dof * sub_block_size + k for k in range(sub_block_size)] - for dof in range(s - 1, -1, -1): - rot += [dof * sub_block_size + k for k in range(sub_block_size)] - for dof in range(2 * s - 1, s - 1, -1): - rot += [dof * sub_block_size + k for k in range(sub_block_size)] - rot += triangle_rotation(list(range(3 * s * sub_block_size, n)), 2 * sub_block_size) - assert len(rot) == len(dofs) - - # Make the reflection - ref = [] - for dof in range(s - 1, -1, -1): - ref += [dof * sub_block_size + k for k in range(sub_block_size)] - for dof in range(2 * s, 3 * s): - ref += [dof * sub_block_size + k for k in range(sub_block_size)] - for dof in range(s, 2 * s): - ref += [dof * sub_block_size + k for k in range(sub_block_size)] - ref += triangle_reflection(list(range(3 * s * sub_block_size, n)), 2 * sub_block_size) - assert len(ref) == len(dofs) - - return [[dofs[i] for i in rot], - [dofs[i] for i in ref]] - - -def permute_edge(dofs, sub_block_size, reverse_blocks=False): - """Permute the dofs on an edge.""" - return [edge_flip(dofs, sub_block_size, reverse_blocks)] - - -def permute_tp_edge(dofs, sub_block_size, reverse_blocks=False): - """Permute the dofs on an edge.""" - return [tp_edge_flip(dofs, sub_block_size, reverse_blocks)] - - -def permute_triangle(dofs, sub_block_size, reverse_blocks=False): - """Permute the dofs on a triangle.""" - return [triangle_rotation(dofs, sub_block_size), triangle_reflection(dofs, sub_block_size, reverse_blocks)] - - -def permute_quadrilateral(dofs, sub_block_size, reverse_blocks=False): - """Permute the dofs on a quadrilateral.""" - return [quadrilateral_rotation(dofs, sub_block_size), - quadrilateral_reflection(dofs, sub_block_size, reverse_blocks)] - - -def identity_permutations(num_perms, num_dofs): - """Return identity permutations of the given shape.""" - return [list(range(num_dofs)) for i in range(num_perms)] - - -def edge_flip(dofs, sub_block_size=1, reverse_blocks=False): - """Flip the dofs on an edge.""" - n = len(dofs) // sub_block_size - - perm = [] - for dof in range(n - 1, -1, -1): - if reverse_blocks: - # Reverse the dofs within a block - perm += [dof * sub_block_size + k for k in range(sub_block_size)][::-1] - else: - perm += [dof * sub_block_size + k for k in range(sub_block_size)] - - assert len(perm) == len(dofs) - - return [dofs[i] for i in perm] - - -def tp_edge_flip(dofs, sub_block_size=1, reverse_blocks=False): - """Flip the dofs on an edge.""" - n = len(dofs) // sub_block_size - - perm = [] - if n == 1: - ends = [0] - else: - ends = [1, 0] - for dof in ends + list(range(n - 1, 1, -1)): - if reverse_blocks: - # Reverse the dofs within a block - perm += [dof * sub_block_size + k for k in range(sub_block_size)][::-1] - else: - perm += [dof * sub_block_size + k for k in range(sub_block_size)] - - assert len(perm) == len(dofs) - - return [dofs[i] for i in perm] - - -def triangle_rotation(dofs, sub_block_size=1, reverse_blocks=False): - """Rotate the dofs in a triangle.""" - n = len(dofs) // sub_block_size - s = (math.floor(math.sqrt(1 + 8 * n)) - 1) // 2 - assert s * (s + 1) == 2 * n - - perm = [] - st = n - 1 - for i in range(1, s + 1): - dof = st - for sub in range(i, s + 1): - if reverse_blocks: - perm += [dof * sub_block_size + k for k in range(sub_block_size)][::-1] - else: - perm += [dof * sub_block_size + k for k in range(sub_block_size)] - dof -= sub + 1 - st -= i - assert len(perm) == len(dofs) - - return [dofs[i] for i in perm] - - -def triangle_reflection(dofs, sub_block_size=1, reverse_blocks=False): - """Reflect the dofs in a triangle.""" - n = len(dofs) // sub_block_size - s = (math.floor(math.sqrt(1 + 8 * n)) - 1) // 2 - assert s * (s + 1) == 2 * n - - perm = [] - for st in range(s): - dof = st - for add in range(s, st, -1): - if reverse_blocks: - perm += [dof * sub_block_size + k for k in range(sub_block_size)][::-1] - else: - perm += [dof * sub_block_size + k for k in range(sub_block_size)] - dof += add - assert len(perm) == len(dofs) - - return [dofs[i] for i in perm] - - -def quadrilateral_rotation(dofs, sub_block_size=1, reverse_blocks=False): - """Rotate the dofs in a quadrilateral.""" - n = len(dofs) // sub_block_size - s = math.floor(math.sqrt(n)) - assert s ** 2 == n - - perm = [] - for st in range(n - s, n): - for dof in range(st, -1, -s): - if reverse_blocks: - perm += [dof * sub_block_size + k for k in range(sub_block_size)][::-1] - else: - perm += [dof * sub_block_size + k for k in range(sub_block_size)] - assert len(perm) == len(dofs) - - return [dofs[i] for i in perm] - - -def quadrilateral_reflection(dofs, sub_block_size=1, reverse_blocks=False): - """Reflect the dofs in a quadrilateral.""" - n = len(dofs) // sub_block_size - s = math.floor(math.sqrt(n)) - assert s ** 2 == n - if s == 0: - return dofs - - perm = [] - for st in range(s): - for dof in range(st, n, s): - if reverse_blocks: - perm += [dof * sub_block_size + k for k in range(sub_block_size)][::-1] - else: - perm += [dof * sub_block_size + k for k in range(sub_block_size)] - assert len(perm) == len(dofs) - - return [dofs[i] for i in perm] diff --git a/ffcx/ir/elementtables.py b/ffcx/ir/elementtables.py index 10d31103b..0a7916a51 100644 --- a/ffcx/ir/elementtables.py +++ b/ffcx/ir/elementtables.py @@ -12,7 +12,7 @@ import ufl import ufl.utils.derivativetuples -from ffcx.fiatinterface import create_element +from ffcx.basix_interface import create_basix_element, basix_index from ffcx.ir.representationutils import (create_quadrature_points_and_weights, integral_type_to_entity_dim, map_integral_points) @@ -29,12 +29,13 @@ piecewise_ttypes = ("piecewise", "fixed", "ones", "zeros") uniform_ttypes = ("fixed", "ones", "zeros", "uniform") -valid_ttypes = set(("quadrature", )) | set(piecewise_ttypes) | set(uniform_ttypes) +valid_ttypes = set(("quadrature", )) | set( + piecewise_ttypes) | set(uniform_ttypes) unique_table_reference_t = collections.namedtuple( "unique_table_reference", ["name", "values", "dofrange", "dofmap", "original_dim", "ttype", "is_piecewise", "is_uniform", - "is_permuted"]) + "is_permuted", "dof_base_permutations", "needs_permutation_data"]) # TODO: Get restriction postfix from somewhere central @@ -172,7 +173,8 @@ def get_ffcx_table_values(points, cell, integral_type, ufl_element, avg, entityt entity_dim = integral_type_to_entity_dim(integral_type, tdim) num_entities = ufl.cell.num_cell_entities[cell.cellname()][entity_dim] - fiat_element = create_element(ufl_element) + numpy.set_printoptions(suppress=True, precision=2) + basix_element = create_basix_element(ufl_element) # Extract arrays for the right scalar component component_tables = [] @@ -180,43 +182,58 @@ def get_ffcx_table_values(points, cell, integral_type, ufl_element, avg, entityt if sh == (): # Scalar valued element for entity in range(num_entities): - entity_points = map_integral_points(points, integral_type, cell, entity) - tbl = fiat_element.tabulate(deriv_order, entity_points)[derivative_counts] + entity_points = map_integral_points( + points, integral_type, cell, entity) + # basix + tbl = basix_element.tabulate(deriv_order, entity_points) + index = basix_index(*derivative_counts) + tbl = tbl[index].transpose() + component_tables.append(tbl) elif len(sh) > 0 and ufl_element.num_sub_elements() == 0: # 2-tensor-valued elements, not a tensor product # mapping flat_component back to tensor component - (_, f2t) = ufl.permutation.build_component_numbering(sh, ufl_element.symmetry()) + (_, f2t) = ufl.permutation.build_component_numbering( + sh, ufl_element.symmetry()) t_comp = f2t[flat_component] for entity in range(num_entities): - entity_points = map_integral_points(points, integral_type, cell, entity) - tbl = fiat_element.tabulate(deriv_order, entity_points)[derivative_counts] + entity_points = map_integral_points( + points, integral_type, cell, entity) + tbl = basix_element.tabulate(deriv_order, entity_points) + tbl = tbl[basix_index(*derivative_counts)] + sum_sh = sum(sh) + bshape = (tbl.shape[0],) + sh + (tbl.shape[1] // sum_sh,) + tbl = tbl.reshape(bshape).transpose() + if len(sh) == 1: component_tables.append(tbl[:, t_comp[0], :]) elif len(sh) == 2: component_tables.append(tbl[:, t_comp[0], t_comp[1], :]) else: - raise RuntimeError("Cannot tabulate tensor valued element with rank > 2") + raise RuntimeError( + "Cannot tabulate tensor valued element with rank > 2") else: + # Vector-valued or mixed element - sub_dims = [0] + list(e.space_dimension() for e in fiat_element.elements()) - sub_cmps = [0] + list(numpy.prod(e.value_shape(), dtype=int) - for e in fiat_element.elements()) + sub_dims = [0] + [e.dim for e in basix_element.sub_elements] + sub_cmps = [0] + [e.value_size for e in basix_element.sub_elements] + irange = numpy.cumsum(sub_dims) crange = numpy.cumsum(sub_cmps) # Find index of sub element which corresponds to the current flat component - component_element_index = numpy.where(crange <= flat_component)[0].shape[0] - 1 + component_element_index = numpy.where( + crange <= flat_component)[0].shape[0] - 1 ir = irange[component_element_index:component_element_index + 2] cr = crange[component_element_index:component_element_index + 2] - component_element = fiat_element.elements()[component_element_index] + component_element = basix_element.sub_elements[component_element_index] # Get the block size to switch XXYYZZ ordering to XYZXYZ if isinstance(ufl_element, ufl.VectorElement) or isinstance(ufl_element, ufl.TensorElement): - block_size = fiat_element.num_sub_elements() + block_size = basix_element.block_size ir = [ir[0] * block_size // irange[-1], irange[-1], block_size] def slice_size(r): @@ -227,17 +244,18 @@ def slice_size(r): if len(r) == 3: return 1 + (r[1] - r[0] - 1) // r[2] - # Follows from FIAT's MixedElement tabulation - # Tabulating MixedElement in FIAT would result in tabulated subelements - # padded with zeros for entity in range(num_entities): - entity_points = map_integral_points(points, integral_type, cell, entity) + entity_points = map_integral_points( + points, integral_type, cell, entity) - # Tabulate subelement, this is dense nonzero table, [a, b, c] - tbl = component_element.tabulate(deriv_order, entity_points)[derivative_counts] + # basix + tbl = component_element.tabulate( + deriv_order, entity_points) + index = basix_index(*derivative_counts) + tbl = tbl[index].transpose() # Prepare a padded table with zeros - padded_shape = (fiat_element.space_dimension(),) + fiat_element.value_shape() + (len(entity_points), ) + padded_shape = (basix_element.dim,) + basix_element.value_shape + (len(entity_points), ) padded_tbl = numpy.zeros(padded_shape, dtype=tbl.dtype) tab = tbl.reshape(slice_size(ir), slice_size(cr), -1) @@ -299,7 +317,7 @@ def generate_psi_table_name(quadrature_rule, element_counter, averaged, entityty name += "_D" + "".join(str(d) for d in derivative_counts) name += {None: "", "cell": "_AC", "facet": "_AF"}[averaged] name += {"cell": "", "facet": "_F", "vertex": "_V"}[entitytype] - name += "_Q{}".format(quadrature_rule.id()) + name += f"_Q{quadrature_rule.id()}" return name @@ -310,9 +328,11 @@ def get_modified_terminal_element(mt): # Extract element from FormArguments and relevant GeometricQuantities if isinstance(mt.terminal, ufl.classes.FormArgument): if gd and mt.reference_value: - raise RuntimeError("Global derivatives of reference values not defined.") + raise RuntimeError( + "Global derivatives of reference values not defined.") elif ld and not mt.reference_value: - raise RuntimeError("Local derivatives of global values not defined.") + raise RuntimeError( + "Local derivatives of global values not defined.") element = mt.terminal.ufl_element() fc = mt.flat_component elif isinstance(mt.terminal, ufl.classes.SpatialCoordinate): @@ -350,7 +370,8 @@ def get_modified_terminal_element(mt): # Change derivatives format for table lookup tdim = mt.terminal.ufl_domain().topological_dimension() - local_derivatives = ufl.utils.derivativetuples.derivative_listing_to_counts(ld, tdim) + local_derivatives = ufl.utils.derivativetuples.derivative_listing_to_counts( + ld, tdim) return element, mt.averaged, local_derivatives, fc @@ -437,6 +458,7 @@ def add_table(res): element_number = element_numbers[element] name = generate_psi_table_name(quadrature_rule, element_number, avg, entitytype, local_derivatives, flat_component) + if name not in tables: tdim = cell.topological_dimension() if entitytype == "facet": @@ -450,7 +472,8 @@ def add_table(res): new_table = [] for ref in range(2): new_table.append(get_ffcx_table_values( - permute_quadrature_interval(quadrature_rule.points, ref), + permute_quadrature_interval( + quadrature_rule.points, ref), cell, integral_type, element, avg, entitytype, local_derivatives, flat_component)) tables[name] = numpy.array(new_table) @@ -462,7 +485,8 @@ def add_table(res): for rot in range(3): for ref in range(2): new_table.append(get_ffcx_table_values( - permute_quadrature_triangle(quadrature_rule.points, ref, rot), + permute_quadrature_triangle( + quadrature_rule.points, ref, rot), cell, integral_type, element, avg, entitytype, local_derivatives, flat_component)) tables[name] = numpy.array(new_table) @@ -472,7 +496,8 @@ def add_table(res): for rot in range(4): for ref in range(2): new_table.append(get_ffcx_table_values( - permute_quadrature_quadrilateral(quadrature_rule.points, ref, rot), + permute_quadrature_quadrilateral( + quadrature_rule.points, ref, rot), cell, integral_type, element, avg, entitytype, local_derivatives, flat_component)) tables[name] = numpy.array(new_table) @@ -558,7 +583,8 @@ def optimize_element_tables(tables, if isinstance(ufl_element, ufl.VectorElement) or isinstance(ufl_element, ufl.TensorElement): block_size = len(ufl_element.sub_elements()) - dofrange, dofmap, tbl = strip_table_zeros(tbl, block_size, rtol=rtol, atol=atol) + dofrange, dofmap, tbl = strip_table_zeros( + tbl, block_size, rtol=rtol, atol=atol) compressed_tables[name] = tbl table_ranges[name] = dofrange @@ -578,7 +604,8 @@ def optimize_element_tables(tables, ui = name_to_unique_index[name] if ui not in unique_names: unique_names[ui] = name - table_unames = {name: unique_names[name_to_unique_index[name]] for name in name_to_unique_index} + table_unames = { + name: unique_names[name_to_unique_index[name]] for name in name_to_unique_index} # Build mapping from unique table name to the table itself unique_tables = {} @@ -610,13 +637,15 @@ def is_quadrature_table(table, rtol=default_rtol, atol=default_atol): def is_permuted_table(table, rtol=default_rtol, atol=default_atol): return not all( - numpy.allclose(table[0, :, :, :], table[i, :, :, :], rtol=rtol, atol=atol) + numpy.allclose(table[0, :, :, :], + table[i, :, :, :], rtol=rtol, atol=atol) for i in range(1, table.shape[0])) def is_piecewise_table(table, rtol=default_rtol, atol=default_atol): return all( - numpy.allclose(table[0, :, 0, :], table[0, :, i, :], rtol=rtol, atol=atol) + numpy.allclose(table[0, :, 0, :], + table[0, :, i, :], rtol=rtol, atol=atol) for i in range(1, table.shape[2])) @@ -653,7 +682,8 @@ def analyse_table_type(table, rtol=default_rtol, atol=default_atol): def is_uniform_table(table, rtol=default_rtol, atol=default_atol): return all( - numpy.allclose(table[0, 0, :, :], table[0, i, :, :], rtol=rtol, atol=atol) + numpy.allclose(table[0, 0, :, :], + table[0, i, :, :], rtol=rtol, atol=atol) for i in range(1, table.shape[1])) @@ -685,13 +715,16 @@ def build_optimized_tables(quadrature_rule, # Optimize tables and get table name and dofrange for each modified terminal unique_tables, unique_table_origins, table_unames, table_ranges, table_dofmaps, table_permuted, \ - table_original_num_dofs = optimize_element_tables(tables, table_origins, rtol=rtol, atol=atol) + table_original_num_dofs = optimize_element_tables( + tables, table_origins, rtol=rtol, atol=atol) # Get num_dofs for all tables before they can be deleted later - unique_table_num_dofs = {uname: tbl.shape[-1] for uname, tbl in unique_tables.items()} + unique_table_num_dofs = {uname: tbl.shape[-1] + for uname, tbl in unique_tables.items()} # Analyze tables for properties useful for optimization - unique_table_ttypes = analyse_table_types(unique_tables, rtol=rtol, atol=atol) + unique_table_ttypes = analyse_table_types( + unique_tables, rtol=rtol, atol=atol) # Compress tables that are constant along num_entities or num_points for uname, tabletype in unique_table_ttypes.items(): @@ -759,6 +792,7 @@ def build_optimized_tables(quadrature_rule, # Some more metadata stored under the ename ttype = unique_table_ttypes[ename] + offset = 0 # Add offset to dofmap and dofrange for restricted terminals if mt.restriction and isinstance(mt.terminal, ufl.classes.FormArgument): # offset = 0 or number of dofs before table optimization @@ -767,10 +801,20 @@ def build_optimized_tables(quadrature_rule, dofrange = (b + offset, e + offset) dofmap = tuple(i + offset for i in dofmap) + base_perms = [ + [[p[i - offset][j - offset] for j in dofmap] for i in dofmap] + for p in create_basix_element(table_origins[name][0]).base_permutations] + + needs_permutation_data = False + for p in base_perms: + if not numpy.allclose(p, numpy.identity(len(p))): + needs_permutation_data = True + # Store reference to unique table for this mt mt_unique_table_reference[mt] = unique_table_reference_t( ename, unique_tables[ename], dofrange, dofmap, original_dim, ttype, - ttype in piecewise_ttypes, ttype in uniform_ttypes, is_permuted) + ttype in piecewise_ttypes, ttype in uniform_ttypes, is_permuted, base_perms, + needs_permutation_data) return (unique_tables, unique_table_ttypes, unique_table_num_dofs, - mt_unique_table_reference, table_origins, needs_permutation_data) + mt_unique_table_reference) diff --git a/ffcx/ir/integral.py b/ffcx/ir/integral.py index 88e225c53..da194a0e3 100644 --- a/ffcx/ir/integral.py +++ b/ffcx/ir/integral.py @@ -22,7 +22,6 @@ from ufl.algorithms.balancing import balance_modifiers from ufl.checks import is_cellwise_constant from ufl.classes import QuadratureWeight -from ffcx.ir import dof_permutations logger = logging.getLogger("ffcx") @@ -59,10 +58,9 @@ def compute_integral_ir(cell, integral_type, entitytype, integrands, argument_sh ir["integrand"] = {} ir["table_dofmaps"] = {} - ir["table_dof_face_tangents"] = {} - ir["table_dof_reflection_entities"] = {} - + ir["table_dof_base_permutations"] = {} ir["needs_permutation_data"] = 0 + ir["table_needs_permutation_data"] = {} for quadrature_rule, integrand in integrands.items(): @@ -88,8 +86,7 @@ def compute_integral_ir(cell, integral_type, entitytype, integrands, argument_sh if is_modified_terminal(v['expression'])} (unique_tables, unique_table_types, unique_table_num_dofs, - mt_unique_table_reference, table_origins, - needs_permutation_data) = build_optimized_tables( + mt_unique_table_reference) = build_optimized_tables( quadrature_rule, cell, integral_type, @@ -99,19 +96,11 @@ def compute_integral_ir(cell, integral_type, entitytype, integrands, argument_sh rtol=p["table_rtol"], atol=p["table_atol"]) - if needs_permutation_data: - ir["needs_permutation_data"] = 1 - - for k, v in table_origins.items(): - ir["table_dof_face_tangents"][k] = dof_permutations.face_tangents(v[0]) - ir["table_dof_reflection_entities"][k] = dof_permutations.reflection_entities(v[0]) - for j in ir["table_dof_face_tangents"][k]: - if j is not None: - ir["needs_permutation_data"] = 1 - if len(ir["table_dof_reflection_entities"][k]) > 0: - ir["needs_permutation_data"] = 1 - for td in mt_unique_table_reference.values(): + ir["table_needs_permutation_data"][td.name] = td.needs_permutation_data + if td.needs_permutation_data: + ir["needs_permutation_data"] = 1 + ir["table_dof_base_permutations"][td.name] = td.dof_base_permutations ir["table_dofmaps"][td.name] = td.dofmap S_targets = [i for i, v in S.nodes.items() if v.get('target', False)] diff --git a/ffcx/ir/representation.py b/ffcx/ir/representation.py index 26319a21c..6ad6a212a 100644 --- a/ffcx/ir/representation.py +++ b/ffcx/ir/representation.py @@ -1,5 +1,5 @@ -# Copyright (C) 2009-2017 Anders Logg, Martin Sandve Alnæs, Marie E. Rognes, -# Kristian B. Oelgaard, and others +# Copyright (C) 2009-2020 Anders Logg, Martin Sandve Alnæs, Marie E. Rognes, +# Kristian B. Oelgaard, Matthew W. Scroggs, Chris Richardson, and others # # This file is part of FFCX.(https://www.fenicsproject.org) # @@ -23,11 +23,9 @@ import numpy -import FIAT import ufl from ffcx import naming -from ffcx.fiatinterface import SpaceOfReals, create_element -from ffcx.ir import dof_permutations +from ffcx.basix_interface import create_basix_element from ffcx.ir.integral import compute_integral_ir from ffcx.ir.representationutils import (QuadratureRule, create_quadrature_points_and_weights) @@ -39,63 +37,45 @@ # List of supported integral types ufc_integral_types = ("cell", "exterior_facet", "interior_facet", "vertex", "custom") -ir_form = namedtuple('ir_form', ['id', 'prefix', 'name', 'signature', 'rank', - 'num_coefficients', 'num_constants', 'name_from_uflfile', - 'function_spaces', - 'original_coefficient_position', - 'coefficient_names', 'constant_names', - 'create_coordinate_mapping', 'create_finite_element', - 'create_dofmap', 'create_cell_integral', - 'get_cell_integral_ids', 'create_exterior_facet_integral', - 'get_exterior_facet_integral_ids', 'create_interior_facet_integral', - 'get_interior_facet_integral_ids', 'create_vertex_integral', - 'get_vertex_integral_ids', 'create_custom_integral', - 'get_custom_integral_ids']) -ir_element = namedtuple('ir_element', ['id', 'name', 'signature', 'cell_shape', - 'topological_dimension', - 'geometric_dimension', 'space_dimension', 'value_shape', - 'reference_value_shape', 'degree', 'family', 'evaluate_basis', - 'evaluate_dof', 'tabulate_dof_coordinates', 'num_sub_elements', - 'base_permutations', 'dof_reflection_entities', 'block_size', - 'dof_face_tangents', - 'create_sub_element', 'dof_types', 'entity_dofs']) -ir_dofmap = namedtuple('ir_dofmap', ['id', 'name', 'signature', 'num_global_support_dofs', - 'num_element_support_dofs', 'num_entity_dofs', - 'tabulate_entity_dofs', 'base_permutations', 'dof_reflection_entities', - 'dof_face_tangents', - 'num_sub_dofmaps', 'create_sub_dofmap', 'dof_types', - 'block_size']) -ir_coordinate_map = namedtuple('ir_coordinate_map', ['id', 'prefix', 'name', 'signature', 'cell_shape', - 'topological_dimension', - 'geometric_dimension', - 'compute_physical_coordinates', - 'compute_reference_coordinates', 'compute_jacobians', - 'compute_jacobian_determinants', - 'compute_jacobian_inverses', 'compute_geometry', 'tables', - 'coordinate_element_degree', 'num_scalar_coordinate_element_dofs', - 'coordinate_finite_element_classname', - 'scalar_coordinate_finite_element_classname', - 'scalar_dofmap_name', 'is_affine']) -ir_integral = namedtuple('ir_integral', ['integral_type', 'subdomain_id', - 'rank', 'geometric_dimension', 'topological_dimension', - 'entitytype', 'num_facets', 'num_vertices', 'needs_oriented', - 'enabled_coefficients', 'element_dimensions', 'element_ids', - 'tensor_shape', 'coefficient_numbering', - 'coefficient_offsets', 'original_constant_offsets', 'params', 'cell_shape', - 'unique_tables', 'unique_table_types', 'table_dofmaps', - 'table_dof_face_tangents', 'table_dof_reflection_entities', - 'integrand', 'name', 'precision', 'needs_permutation_data']) -ir_tabulate_dof_coordinates = namedtuple('ir_tabulate_dof_coordinates', ['tdim', 'gdim', 'points', 'cell_shape']) -ir_evaluate_dof = namedtuple('ir_evaluate_dof', ['mappings', 'reference_value_size', 'physical_value_size', - 'geometric_dimension', 'topological_dimension', 'dofs', - 'physical_offsets', 'cell_shape']) -ir_expression = namedtuple('ir_expression', ['name', 'element_dimensions', 'params', 'unique_tables', - 'unique_table_types', 'integrand', 'table_dofmaps', - 'table_dof_face_tangents', 'table_dof_reflection_entities', - 'coefficient_numbering', 'coefficient_offsets', - 'integral_type', 'entitytype', 'tensor_shape', 'expression_shape', - 'original_constant_offsets', 'original_coefficient_positions', 'points', - 'needs_permutation_data']) +ir_form = namedtuple('ir_form', [ + 'id', 'prefix', 'name', 'signature', 'rank', 'num_coefficients', 'num_constants', + 'name_from_uflfile', 'function_spaces', 'original_coefficient_position', + 'coefficient_names', 'constant_names', 'create_coordinate_mapping', 'create_finite_element', + 'create_dofmap', 'create_cell_integral', 'get_cell_integral_ids', 'create_exterior_facet_integral', + 'get_exterior_facet_integral_ids', 'create_interior_facet_integral', + 'get_interior_facet_integral_ids', 'create_vertex_integral', 'get_vertex_integral_ids', + 'create_custom_integral', 'get_custom_integral_ids']) +ir_element = namedtuple('ir_element', [ + 'id', 'name', 'signature', 'cell_shape', 'topological_dimension', + 'geometric_dimension', 'space_dimension', 'value_shape', 'reference_value_shape', 'degree', + 'family', 'num_sub_elements', 'block_size', 'create_sub_element', + 'entity_dofs', 'base_permutations', 'dof_mappings', + 'num_reference_components', 'needs_permutation_data', 'interpolation_is_identity']) +ir_dofmap = namedtuple('ir_dofmap', [ + 'id', 'name', 'signature', 'num_global_support_dofs', 'num_element_support_dofs', 'num_entity_dofs', + 'tabulate_entity_dofs', 'base_permutations', 'num_sub_dofmaps', 'create_sub_dofmap', 'block_size']) +ir_coordinate_map = namedtuple('ir_coordinate_map', [ + 'id', 'prefix', 'name', 'signature', 'cell_shape', 'topological_dimension', 'geometric_dimension', + 'compute_physical_coordinates', 'compute_reference_coordinates', 'compute_jacobians', + 'compute_jacobian_determinants', 'compute_jacobian_inverses', 'compute_geometry', 'tables', + 'coordinate_element_degree', 'num_scalar_coordinate_element_dofs', 'coordinate_element_family', + 'coordinate_finite_element_classname', 'scalar_coordinate_finite_element_classname', + 'scalar_dofmap_name', 'is_affine', 'needs_permutation_data', 'base_permutations']) +ir_integral = namedtuple('ir_integral', [ + 'integral_type', 'subdomain_id', 'rank', 'geometric_dimension', 'topological_dimension', 'entitytype', + 'num_facets', 'num_vertices', 'needs_oriented', 'enabled_coefficients', 'element_dimensions', + 'element_ids', 'tensor_shape', 'coefficient_numbering', 'coefficient_offsets', + 'original_constant_offsets', 'params', 'cell_shape', 'unique_tables', 'unique_table_types', + 'table_dofmaps', 'table_dof_base_permutations', 'integrand', 'name', 'precision', + 'table_needs_permutation_data', 'needs_permutation_data']) +ir_evaluate_dof = namedtuple('ir_evaluate_dof', [ + 'mappings', 'reference_value_size', 'physical_value_size', 'geometric_dimension', + 'topological_dimension', 'dofs', 'cell_shape']) +ir_expression = namedtuple('ir_expression', [ + 'name', 'element_dimensions', 'params', 'unique_tables', 'unique_table_types', 'integrand', + 'table_dofmaps', 'table_dof_base_permutations', 'coefficient_numbering', 'coefficient_offsets', + 'integral_type', 'entitytype', 'tensor_shape', 'expression_shape', 'original_constant_offsets', + 'original_coefficient_positions', 'points', 'table_needs_permutation_data', 'needs_permutation_data']) ir_data = namedtuple('ir_data', ['elements', 'dofmaps', 'coordinate_mappings', 'integrals', 'forms', 'expressions']) @@ -161,10 +141,10 @@ def compute_ir(analysis: namedtuple, object_names, prefix, parameters, visualise def _compute_element_ir(ufl_element, element_numbers, finite_element_names, epsilon): """Compute intermediate representation of element.""" - logger.info("Computing IR for element {}".format(ufl_element)) + logger.info(f"Computing IR for element {ufl_element}") - # Create FIAT element - fiat_element = create_element(ufl_element) + # Create basix elements + basix_element = create_basix_element(ufl_element) cell = ufl_element.cell() cellname = cell.cellname() @@ -177,32 +157,38 @@ def _compute_element_ir(ufl_element, element_numbers, finite_element_names, epsi ir["cell_shape"] = cellname ir["topological_dimension"] = cell.topological_dimension() ir["geometric_dimension"] = cell.geometric_dimension() - ir["space_dimension"] = fiat_element.space_dimension() + ir["space_dimension"] = basix_element.dim + ir["degree"] = ufl_element.degree() + ir["family"] = basix_element.family_name ir["value_shape"] = ufl_element.value_shape() ir["reference_value_shape"] = ufl_element.reference_value_shape() - ir["degree"] = ufl_element.degree() - ir["family"] = ufl_element.family() - - ir["evaluate_basis"] = _evaluate_basis(ufl_element, fiat_element, epsilon) - ir["evaluate_dof"] = _evaluate_dof(ufl_element, fiat_element) - ir["tabulate_dof_coordinates"] = _tabulate_dof_coordinates(ufl_element, fiat_element) ir["num_sub_elements"] = ufl_element.num_sub_elements() ir["create_sub_element"] = [finite_element_names[e] for e in ufl_element.sub_elements()] - if isinstance(ufl_element, ufl.VectorElement) or isinstance(ufl_element, ufl.TensorElement): - ir["block_size"] = ufl_element.num_sub_elements() + if hasattr(basix_element, "block_size"): + ir["block_size"] = basix_element.block_size ufl_element = ufl_element.sub_elements()[0] - fiat_element = create_element(ufl_element) + basix_element = create_basix_element(ufl_element) else: ir["block_size"] = 1 - ir["base_permutations"] = dof_permutations.base_permutations(ufl_element) - ir["dof_reflection_entities"] = dof_permutations.reflection_entities(ufl_element) - ir["dof_face_tangents"] = dof_permutations.face_tangents(ufl_element) + im = basix_element.interpolation_matrix + if im.shape[0] == im.shape[1] and numpy.allclose(im, numpy.identity(im.shape[0])): + ir["interpolation_is_identity"] = 1 + else: + ir["interpolation_is_identity"] = 0 + + ir["base_permutations"] = basix_element.base_permutations + ir["needs_permutation_data"] = 0 + for p in basix_element.base_permutations: + if not numpy.allclose(p, numpy.identity(len(p))): + ir["needs_permutation_data"] = 1 - ir["dof_types"] = [i.functional_type for i in fiat_element.dual_basis()] - ir["entity_dofs"] = fiat_element.entity_dofs() + ir["entity_dofs"] = basix_element.entity_dof_numbers + + ir["dof_mappings"] = basix_element.dof_mappings + ir["num_reference_components"] = basix_element.num_reference_components return ir_element(**ir) @@ -210,10 +196,10 @@ def _compute_element_ir(ufl_element, element_numbers, finite_element_names, epsi def _compute_dofmap_ir(ufl_element, element_numbers, dofmap_names): """Compute intermediate representation of dofmap.""" - logger.info("Computing IR for dofmap of {}".format(ufl_element)) + logger.info(f"Computing IR for dofmap of {ufl_element}") - # Create FIAT element - fiat_element = create_element(ufl_element) + # Create basix elements + basix_element = create_basix_element(ufl_element) # Store id ir = {"id": element_numbers[ufl_element]} @@ -224,28 +210,26 @@ def _compute_dofmap_ir(ufl_element, element_numbers, dofmap_names): ir["create_sub_dofmap"] = [dofmap_names[e] for e in ufl_element.sub_elements()] ir["num_sub_dofmaps"] = ufl_element.num_sub_elements() - if isinstance(ufl_element, ufl.VectorElement) or isinstance(ufl_element, ufl.TensorElement): - ir["block_size"] = ufl_element.num_sub_elements() - ufl_element = ufl_element.sub_elements()[0] - fiat_element = create_element(ufl_element) + if hasattr(basix_element, "block_size"): + ir["block_size"] = basix_element.block_size + basix_element = basix_element.sub_element else: ir["block_size"] = 1 - ir["base_permutations"] = dof_permutations.base_permutations(ufl_element) - ir["dof_reflection_entities"] = dof_permutations.reflection_entities(ufl_element) - ir["dof_face_tangents"] = dof_permutations.face_tangents(ufl_element) + ir["base_permutations"] = basix_element.base_permutations # Precompute repeatedly used items - num_dofs_per_entity = _num_dofs_per_entity(fiat_element) - entity_dofs = fiat_element.entity_dofs() + for i in basix_element.entity_dofs: + if max(i) != min(i): + raise RuntimeError("Elements with different numbers of DOFs on subentities of the same dimension" + " are not yet supported in FFCx.") + num_dofs_per_entity = [i[0] for i in basix_element.entity_dofs] ir["num_entity_dofs"] = num_dofs_per_entity - ir["tabulate_entity_dofs"] = (entity_dofs, num_dofs_per_entity) + ir["tabulate_entity_dofs"] = (basix_element.entity_dof_numbers, num_dofs_per_entity) - ir["num_global_support_dofs"] = _num_global_support_dofs(fiat_element) - ir["num_element_support_dofs"] = fiat_element.space_dimension() - ir["num_global_support_dofs"] - - ir["dof_types"] = [i.functional_type for i in fiat_element.dual_basis()] + ir["num_global_support_dofs"] = basix_element.num_global_support_dofs + ir["num_element_support_dofs"] = basix_element.dim - ir["num_global_support_dofs"] return ir_dofmap(**ir) @@ -271,34 +255,31 @@ def _tabulate_coordinate_mapping_basis(ufl_element): # with a VectorElement of scalar subelements selement = ufl_element.sub_elements()[0] - fiat_element = create_element(selement) + basix_element = create_basix_element(selement) cell = selement.cell() tdim = cell.topological_dimension() tables = {} # Get points - origo = (0.0, ) * tdim + origin = (0.0, ) * tdim midpoint = cell_midpoint(cell) # Tabulate basis - t0 = fiat_element.tabulate(1, [origo]) - tm = fiat_element.tabulate(1, [midpoint]) + t0 = basix_element.tabulate(1, [origin]) + tm = basix_element.tabulate(1, [midpoint]) - # Get basis values at cell origo - tables["x0"] = t0[(0, ) * tdim][:, 0] + # Get basis values at cell origin + tables["x0"] = t0[0][:, 0] # Get basis values at cell midpoint - tables["xm"] = tm[(0, ) * tdim][:, 0] - - # Single direction derivatives, e.g. [(1,0), (0,1)] in 2d - derivatives = [(0, ) * i + (1, ) + (0, ) * (tdim - 1 - i) for i in range(tdim)] + tables["xm"] = tm[0][:, 0] - # Get basis derivative values at cell origo - tables["J0"] = numpy.asarray([t0[d][:, 0] for d in derivatives]) + # Get basis derivative values at cell origin + tables["J0"] = numpy.asarray([t0[d][:, 0] for d in range(1, 1 + tdim)]) # Get basis derivative values at cell midpoint - tables["Jm"] = numpy.asarray([tm[d][:, 0] for d in derivatives]) + tables["Jm"] = numpy.asarray([tm[d][:, 0] for d in range(1, 1 + tdim)]) return tables @@ -311,14 +292,14 @@ def _compute_coordinate_mapping_ir(ufl_coordinate_element, finite_element_names): """Compute intermediate representation of coordinate mapping.""" - logger.info("Computing IR for coordinate mapping {}".format(ufl_coordinate_element)) + logger.info(f"Computing IR for coordinate mapping {ufl_coordinate_element}") cell = ufl_coordinate_element.cell() cellname = cell.cellname() assert ufl_coordinate_element.value_shape() == (cell.geometric_dimension(), ) - # Compute element values via fiat element + # Compute element values tables = _tabulate_coordinate_mapping_basis(ufl_coordinate_element) # Store id @@ -342,9 +323,18 @@ def _compute_coordinate_mapping_ir(ufl_coordinate_element, # NB! The entries below breaks the pattern of using ir keywords == code keywords, # which I personally don't find very useful anyway (martinal). + basix_element = create_basix_element(ufl_coordinate_element) + + ir["needs_permutation_data"] = 0 + for p in basix_element.base_permutations: + if not numpy.allclose(p, numpy.identity(len(p))): + ir["needs_permutation_data"] = 1 + ir["base_permutations"] = basix_element.sub_element.base_permutations + # Store tables and other coordinate element data ir["tables"] = tables ir["coordinate_element_degree"] = ufl_coordinate_element.degree() + ir["coordinate_element_family"] = basix_element.family_name ir["num_scalar_coordinate_element_dofs"] = tables["x0"].shape[0] ir["is_affine"] = ir["coordinate_element_degree"] == 1 and cellname in ("interval", "triangle", "tetrahedron") @@ -359,19 +349,6 @@ def _compute_coordinate_mapping_ir(ufl_coordinate_element, return ir_coordinate_map(**ir) -def _num_global_support_dofs(fiat_element): - """Compute number of global support dofs.""" - if not isinstance(fiat_element, FIAT.MixedElement): - if isinstance(fiat_element, SpaceOfReals): - return 1 - return 0 - num_reals = 0 - for e in fiat_element.elements(): - if isinstance(e, SpaceOfReals): - num_reals += 1 - return num_reals - - def _compute_integral_ir(form_data, form_index, prefix, element_numbers, integral_names, parameters, visualise): """Compute intermediate represention for form integrals.""" @@ -388,7 +365,7 @@ def _compute_integral_ir(form_data, form_index, prefix, element_numbers, integra irs = [] for itg_data_index, itg_data in enumerate(form_data.integral_data): - logger.info("Computing IR for integral in integral group {}".format(itg_data_index)) + logger.info(f"Computing IR for integral in integral group {itg_data_index}") # Compute representation entitytype = _entity_types[itg_data.integral_type] @@ -414,7 +391,7 @@ def _compute_integral_ir(form_data, form_index, prefix, element_numbers, integra # Get element space dimensions unique_elements = element_numbers.keys() ir["element_dimensions"] = { - ufl_element: create_element(ufl_element).space_dimension() + ufl_element: create_basix_element(ufl_element).dim for ufl_element in unique_elements } @@ -449,8 +426,8 @@ def _compute_integral_ir(form_data, form_index, prefix, element_numbers, integra points = md["quadrature_points"] weights = md["quadrature_weights"] elif scheme == "vertex": - # FIXME: Could this come from FIAT? - # + # FIXME: Could this come from basix? + # The vertex scheme, i.e., averaging the function value in the # vertices and multiplying with the simplex volume, is only of # order 1 and inferior to other generic schemes in terms of @@ -473,8 +450,8 @@ def _compute_integral_ir(form_data, form_index, prefix, element_numbers, integra # Trapezoidal rule return (numpy.array([[0.0], [1.0]]), numpy.array([1.0 / 2.0, 1.0 / 2.0])) else: - (points, weights) = create_quadrature_points_and_weights(integral_type, cell, degree, - scheme) + points, weights = create_quadrature_points_and_weights( + integral_type, cell, degree, scheme) points = numpy.asarray(points) weights = numpy.asarray(weights) @@ -548,7 +525,7 @@ def _compute_form_ir(form_data, form_id, prefix, element_numbers, finite_element dofmap_names, coordinate_mapping_names, object_names): """Compute intermediate representation of form.""" - logger.info("Computing IR for form {}".format(form_id)) + logger.info(f"Computing IR for form {form_id}") # Store id ir = {"id": form_id} @@ -595,21 +572,21 @@ def _compute_form_ir(form_data, form_id, prefix, element_numbers, finite_element form_name = object_names.get(id(form_data.original_form), form_id) ir["function_spaces"] = fs - ir["name_from_uflfile"] = "form_{}_{}".format(prefix, form_name) + ir["name_from_uflfile"] = f"form_{prefix}_{form_name}" # Create integral ids and names using form prefix (integrals are # always generated as part of form so don't get their own prefix) for integral_type in ufc_integral_types: irdata = _create_foo_integral(prefix, form_id, integral_type, form_data) - ir["create_{}_integral".format(integral_type)] = irdata - ir["get_{}_integral_ids".format(integral_type)] = irdata + ir[f"create_{integral_type}_integral"] = irdata + ir[f"get_{integral_type}_integral_ids"] = irdata return ir_form(**ir) def _compute_expression_ir(expression, index, prefix, analysis, parameters, visualise): - logger.info("Computing IR for expression {}".format(index)) + logger.info(f"Computing IR for expression {index}") # Compute representation ir = {} @@ -631,7 +608,7 @@ def _compute_expression_ir(expression, index, prefix, analysis, parameters, visu # Prepare dimensions of all unique element in expression, including # elements for arguments, coefficients and coordinate mappings ir["element_dimensions"] = { - ufl_element: create_element(ufl_element).space_dimension() + ufl_element: create_basix_element(ufl_element).dim for ufl_element in analysis.unique_elements } @@ -702,364 +679,6 @@ def _compute_expression_ir(expression, index, prefix, analysis, parameters, visu return ir_expression(**ir) -def _generate_reference_offsets(fiat_element, offset=0): - """Generate offsets. - - I.e., value offset for each basis function relative to a reference - element representation. - - """ - if isinstance(fiat_element, FIAT.MixedElement): - offsets = [] - for e in fiat_element.elements(): - offsets += _generate_reference_offsets(e, offset) - # NB! This is the fiat element and therefore value_shape - # means reference_value_shape - offset += ufl.utils.sequences.product(e.value_shape()) - return offsets - elif isinstance(fiat_element, FIAT.EnrichedElement): - offsets = [] - for e in fiat_element.elements(): - offsets += _generate_reference_offsets(e, offset) - return offsets - else: - return [offset] * fiat_element.space_dimension() - - -def _generate_physical_offsets(ufl_element, offset=0): - """Generate offsets. - - I.e., value offset for each basis function relative to a physical - element representation. - - """ - cell = ufl_element.cell() - gdim = cell.geometric_dimension() - tdim = cell.topological_dimension() - - # Refer to reference if gdim == tdim. This is a hack to support more - # stuff (in particular restricted elements) - if gdim == tdim: - return _generate_reference_offsets(create_element(ufl_element)) - - if isinstance(ufl_element, ufl.MixedElement): - offsets = [] - for e in ufl_element.sub_elements(): - offsets += _generate_physical_offsets(e, offset) - # e is a ufl element, so value_size means the physical value size - offset += e.value_size() - return offsets - elif isinstance(ufl_element, ufl.EnrichedElement): - offsets = [] - for e in ufl_element._elements: # TODO: Avoid private member access - offsets += _generate_physical_offsets(e, offset) - return offsets - elif isinstance(ufl_element, ufl.FiniteElement): - fiat_element = create_element(ufl_element) - return [offset] * fiat_element.space_dimension() - else: - raise NotImplementedError("This element combination is not implemented") - - -def _generate_offsets(ufl_element, reference_offset=0, physical_offset=0): - """Generate offsets. - - I.e., value offset for each basis function relative to a physical - element representation. - - """ - if isinstance(ufl_element, ufl.MixedElement): - offsets = [] - for e in ufl_element.sub_elements(): - offsets += _generate_offsets(e, reference_offset, physical_offset) - # e is a ufl element, so value_size means the physical value size - reference_offset += e.reference_value_size() - physical_offset += e.value_size() - return offsets - elif isinstance(ufl_element, ufl.EnrichedElement): - offsets = [] - for e in ufl_element._elements: # TODO: Avoid private member access - offsets += _generate_offsets(e, reference_offset, physical_offset) - return offsets - elif isinstance(ufl_element, ufl.FiniteElement): - fiat_element = create_element(ufl_element) - return [(reference_offset, physical_offset)] * fiat_element.space_dimension() - else: - # TODO: Support RestrictedElement, QuadratureElement, - # TensorProductElement, etc.! and replace - # _generate_{physical|reference}_offsets with this - # function. - raise NotImplementedError("This element combination is not implemented") - - -def _evaluate_dof(ufl_element, fiat_element): - """Compute intermediate representation of evaluate_dof.""" - cell = ufl_element.cell() - if fiat_element.is_nodal(): - dofs = [L.pt_dict for L in fiat_element.dual_basis()] - else: - dofs = [None] * fiat_element.space_dimension() - - return ir_evaluate_dof(mappings=fiat_element.mapping(), - reference_value_size=ufl_element.reference_value_size(), - physical_value_size=ufl_element.value_size(), - geometric_dimension=cell.geometric_dimension(), - topological_dimension=cell.topological_dimension(), - dofs=dofs, - physical_offsets=_generate_physical_offsets(ufl_element), - cell_shape=cell.cellname()) - - -def _extract_elements(fiat_element): - new_elements = [] - if isinstance(fiat_element, (FIAT.MixedElement, FIAT.EnrichedElement)): - for e in fiat_element.elements(): - new_elements += _extract_elements(e) - else: - new_elements.append(fiat_element) - return new_elements - - -def _get_basis_data_from_tp(e, family): - """Get the coeffs and dmats of a tensor product element.""" - assert isinstance(e, FIAT.tensor_product.FlattenedDimensions) - coeffs, dmat = _get_coeffs_and_dmats_from_tp(e.element) - - # Flatten the data - order = max(max(max(j) for j in i) for i in coeffs) + 1 - dim = e.ref_el.get_dimension() - - coeffs_new = numpy.zeros((len(coeffs), ) + e.value_shape() + (order ** dim, )) - for i, c in enumerate(coeffs): - for j, k in enumerate(itertools.product(range(order), repeat=dim)): - if k in c: - if len(e.value_shape()) == 0: - coeffs_new[i][j] = c[k][0] - else: - for d in c[k]: - if family in ["RTCF", "NCF"]: - # Swap the reference direction of some DOFs to make consistent - # with low-to-high ordering. - coeffs_new[i][d][j] = c[k][d] * (-1) ** d - else: - coeffs_new[i][d][j] = c[k][d] - - dmats_new = [] - for d in range(dim): - dmat_new = numpy.zeros([order ** dim, order ** dim]) - for row_n, row_indices in enumerate(itertools.product(range(order), repeat=dim)): - for col_n, col_indices in enumerate(itertools.product(range(order), repeat=dim)): - for i in range(dim): - if d != i and row_indices[i] != col_indices[i]: - break - else: - dmat_new[row_n, col_n] = dmat[(row_indices[d], col_indices[d])] - dmats_new.append(dmat_new) - - return dmats_new, coeffs_new, order ** dim - - -def _get_coeffs_and_dmats_from_tp(e): - """Get the coeffs in TP representation and scalar dmats of a tensor product element.""" - if isinstance(e, FIAT.tensor_product.FlattenedDimensions): - return _get_coeffs_and_dmats_from_tp(e.element) - - if isinstance(e, FIAT.enriched.EnrichedElement): - coeffs = [] - dmat = {} - for sub_e in e.elements(): - co, dm = _get_coeffs_and_dmats_from_tp(sub_e) - coeffs += co - for i, j in dm.items(): - if i in dmat: - assert numpy.isclose(dmat[i], j) - dmat[i] = j - return coeffs, dmat - - if isinstance(e, FIAT.tensor_product.TensorProductElement): - coeffs = [] - dmat = {} - a_co, a_dm = _get_coeffs_and_dmats_from_tp(e.A) - b_co, b_dm = _get_coeffs_and_dmats_from_tp(e.B) - for a in a_co: - for b in b_co: - value_rank = len(e.value_shape()) - if value_rank == 0: - # Scalar TP element - coeffs.append({ai + bi: {0: av[0] * bv[0]} for ai, av in a.items() for bi, bv in b.items()}) - elif value_rank == 1: - if e.mapping()[len(coeffs)] == "contravariant piola": - # Hdiv - if e.B.get_formdegree() == 0: - coeffs.append({ai + bi: {e.value_shape()[0] - 1: av[dim] * bv[0]} - for ai, av in a.items() for bi, bv in b.items() for dim in av}) - else: - coeffs.append({ai + bi: {dim: av[dim] * bv[0]} - for ai, av in a.items() for bi, bv in b.items() for dim in av}) - elif e.mapping()[len(coeffs)] == "covariant piola": - # Hcurl - if e.B.get_formdegree() == 1: - coeffs.append({ai + bi: {e.value_shape()[0] - 1: av[dim] * bv[0]} - for ai, av in a.items() for bi, bv in b.items() for dim in av}) - else: - coeffs.append({ai + bi: {dim: av[dim] * bv[0]} - for ai, av in a.items() for bi, bv in b.items() for dim in av}) - else: - raise RuntimeError("Unrecognised mapping: " + e.mapping()[len(coeffs)]) - else: - raise RuntimeError("Unsupported value rank: " + str(value_rank)) - for i, j in a_dm.items(): - if i in dmat: - assert numpy.isclose(dmat[i], j) - dmat[i] = j - for i, j in b_dm.items(): - if i in dmat: - assert numpy.isclose(dmat[i], j) - dmat[i] = j - return coeffs, dmat - - coeffs = [{(i, ): {0: j} for i, j in enumerate(co)} for co in e.get_coeffs()] - dm, = e.dmats() - dmat = {(i, j): value for i, row in enumerate(dm) for j, value in enumerate(row)} - return coeffs, dmat - - -def _evaluate_basis(ufl_element, fiat_element, epsilon): - """Compute intermediate representation for evaluate_basis.""" - cell = ufl_element.cell() - cellname = cell.cellname() - - if isinstance(ufl_element, ufl.VectorElement) or isinstance(ufl_element, ufl.TensorElement): - # If VectorElement, each element in the MixedElement is the same - return _evaluate_basis(ufl_element.sub_elements()[0], fiat_element.elements()[0], epsilon) - else: - # Handle Mixed and EnrichedElements by extracting 'sub' elements. - elements = _extract_elements(fiat_element) - physical_offsets = _generate_physical_offsets(ufl_element) - reference_offsets = _generate_reference_offsets(fiat_element) - mappings = fiat_element.mapping() - - # This function is evidently not implemented for TensorElements - for e in elements: - if (len(e.value_shape()) > 1) and (e.num_sub_elements() != 1): - return "Function not supported/implemented for TensorElements." - - # Handle QuadratureElement, not supported because the basis is only - # defined at the dof coordinates where the value is 1, so not very - # interesting. - for e in elements: - if isinstance(e, FIAT.QuadratureElement): - return "Function not supported/implemented for QuadratureElement." - if isinstance(e, FIAT.tensor_product.FlattenedDimensions) and isinstance(e.element, FIAT.QuadratureElement): - # Case for quad/hex cell - return "Function not supported/implemented for QuadratureElement." - if isinstance(e, FIAT.HDivTrace): - return "Function not supported for Trace elements" - - # Initialise data with 'global' values. - data = { - "reference_value_size": ufl_element.reference_value_size(), - "physical_value_size": ufl_element.value_size(), - "cellname": cellname, - "topological_dimension": cell.topological_dimension(), - "geometric_dimension": cell.geometric_dimension(), - "space_dimension": fiat_element.space_dimension(), - "needs_oriented": element_needs_oriented_jacobian(fiat_element), - "max_degree": max([e.degree() for e in elements]) - } - - # Loop element and space dimensions to generate dof data. - dof = 0 - dofs_data = [] - for e in elements: - num_components = ufl.utils.sequences.product(e.value_shape()) - if isinstance(e, FIAT.tensor_product.FlattenedDimensions): - # Tensor product element - dmats, coeffs, num_expansion_members = _get_basis_data_from_tp(e, ufl_element.family()) - else: - coeffs = e.get_coeffs() - dmats = e.dmats() - num_expansion_members = e.get_num_members(e.degree()) - - # Clamp dmats zeros - dmats = numpy.asarray(dmats) - dmats[numpy.where(numpy.isclose(dmats, 0.0, rtol=epsilon, atol=epsilon))] = 0.0 - - # Extracted parts of dd below that are common for the element - # here. These dict entries are added to each dof_data dict for - # each dof, because that's what the code generation - # implementation expects. If the code generation needs this - # structure to be optimized in the future, we can store this - # data for each subelement instead of for each dof. - subelement_data = { - "embedded_degree": e.degree(), - "num_components": num_components, - "dmats": dmats, - "num_expansion_members": num_expansion_members, - } - value_rank = len(e.value_shape()) - - for i in range(e.space_dimension()): - if num_components == 1: - coefficients = [coeffs[i]] - elif value_rank == 1: - # Handle coefficients for vector valued basis elements - # [Raviart-Thomas, Brezzi-Douglas-Marini (BDM)]. - coefficients = [coeffs[i][c] for c in range(num_components)] - elif value_rank == 2: - # Handle coefficients for tensor valued basis elements. - # [Regge] - coefficients = [ - coeffs[i][p][q] for p in range(e.value_shape()[0]) - for q in range(e.value_shape()[1]) - ] - else: - raise RuntimeError("Unknown situation with num_components > 1") - - # Clamp coefficient zeros - coefficients = numpy.asarray(coefficients) - coefficients[numpy.where(numpy.isclose(coefficients, 0.0, rtol=epsilon, - atol=epsilon))] = 0.0 - - dof_data = { - "coeffs": coefficients, - "mapping": mappings[dof], - "physical_offset": physical_offsets[dof], - "reference_offset": reference_offsets[dof], - } - # Still storing element data in dd to avoid rewriting dependent code - dof_data.update(subelement_data) - - # This list will hold one dd dict for each dof - dofs_data.append(dof_data) - dof += 1 - - data["dofs_data"] = dofs_data - - return data - - -def _tabulate_dof_coordinates(ufl_element, element): - """Compute intermediate representation of tabulate_dof_coordinates.""" - if uses_integral_moments(element): - return {} - - # Bail out if any dual basis member is missing (element is not - # nodal), this is strictly not necessary but simpler - if any(L is None or L.pt_dict is None for L in element.dual_basis()): - return {} - - if isinstance(ufl_element, ufl.VectorElement) or isinstance(ufl_element, ufl.TensorElement): - element = element.elements()[0] - - cell = ufl_element.cell() - return ir_tabulate_dof_coordinates( - tdim=cell.topological_dimension(), - gdim=cell.geometric_dimension(), - points=[sorted(L.pt_dict.keys())[0] for L in element.dual_basis()], - cell_shape=cell.cellname()) - - def _create_foo_integral(prefix, form_id, integral_type, form_data): """Compute intermediate representation of create_foo_integral.""" subdomain_ids = [] @@ -1077,7 +696,7 @@ def _create_foo_integral(prefix, form_id, integral_type, form_data): for itg_data in form_data.integral_data: if isinstance(itg_data.subdomain_id, int): if itg_data.subdomain_id < 0: - raise ValueError("Integral subdomain ID must be non-negative, not {}".format(itg_data.subdomain_id)) + raise ValueError(f"Integral subdomain ID must be non-negative, not {itg_data.subdomain_id}") if (itg_data.integral_type == integral_type): subdomain_ids += [itg_data.subdomain_id] classnames += [naming.integral_name(integral_type, form_data.original_form, @@ -1086,40 +705,17 @@ def _create_foo_integral(prefix, form_id, integral_type, form_data): return subdomain_ids, classnames -def all_elements(fiat_element): - if isinstance(fiat_element, FIAT.MixedElement): - return fiat_element.elements() - return [fiat_element] - - -def _num_dofs_per_entity(fiat_element): - """Compute list of the number of dofs associated with a single mesh entity. - - Example: Lagrange of degree 3 on triangle: [1, 2, 1] - - """ - entity_dofs = fiat_element.entity_dofs() - return [len(entity_dofs[e][0]) for e in sorted(entity_dofs.keys())] - - -def uses_integral_moments(fiat_element): - """True if element uses integral moments for its degrees of freedom.""" - integrals = set(["IntegralMoment", "FrobeniusIntegralMoment"]) - tags = set([L.get_type_tag() for L in fiat_element.dual_basis() if L]) - return len(integrals & tags) > 0 - - -def element_needs_oriented_jacobian(fiat_element): +def element_needs_oriented_jacobian(basix_element): # Check whether this element needs an oriented jacobian (only # contravariant piolas seem to need it) - return "contravariant piola" in fiat_element.mapping() + return "contravariant piola" in basix_element.dof_mappings def form_needs_oriented_jacobian(form_data): # Check whether this form needs an oriented jacobian (only forms # involving contravariant piola mappings seem to need it) for ufl_element in form_data.unique_elements: - element = create_element(ufl_element) - if "contravariant piola" in element.mapping(): + element = create_basix_element(ufl_element) + if element_needs_oriented_jacobian(element): return True return False diff --git a/ffcx/ir/representationutils.py b/ffcx/ir/representationutils.py index fcef5b75d..614d43bc2 100644 --- a/ffcx/ir/representationutils.py +++ b/ffcx/ir/representationutils.py @@ -9,17 +9,16 @@ import logging import numpy +from ffcx.basix_interface import create_quadrature, reference_cell_vertices, map_facet_points import ufl -from ffcx.fiatinterface import (create_quadrature, map_facet_points, - reference_cell_vertices) logger = logging.getLogger("ffcx") class QuadratureRule: def __init__(self, points, weights): - self.points = points + self.points = numpy.ascontiguousarray(points) # TODO: change basix to make this unnecessary self.weights = weights self._hash = None @@ -47,18 +46,18 @@ def id(self): def create_quadrature_points_and_weights(integral_type, cell, degree, rule): """Create quadrature rule and return points and weights.""" + # from IPython import embed; embed() if integral_type == "cell": - (points, weights) = create_quadrature(cell.cellname(), degree, rule) + return create_quadrature(cell.cellname(), degree + 1, rule) elif integral_type in ufl.measure.facet_integral_types: - (points, weights) = create_quadrature(ufl.cell.cellname2facetname[cell.cellname()], degree, - rule) + return create_quadrature(ufl.cell.cellname2facetname[cell.cellname()], degree + 1, rule) elif integral_type in ufl.measure.point_integral_types: - (points, weights) = create_quadrature("vertex", degree, rule) + return create_quadrature("vertex", degree + 1, rule) elif integral_type == "expression": - (points, weights) = (None, None) - else: - logging.exception("Unknown integral type: {}".format(integral_type)) - return (points, weights) + return (None, None) + + logging.exception(f"Unknown integral type: {integral_type}") + return (None, None) def integral_type_to_entity_dim(integral_type, tdim): @@ -75,7 +74,7 @@ def integral_type_to_entity_dim(integral_type, tdim): elif integral_type == "expression": entity_dim = tdim else: - raise RuntimeError("Unknown integral_type: {}".format(integral_type)) + raise RuntimeError(f"Unknown integral_type: {integral_type}") return entity_dim @@ -93,4 +92,4 @@ def map_integral_points(points, integral_type, cell, entity): elif entity_dim == 0: return numpy.asarray([reference_cell_vertices(cell.cellname())[entity]]) else: - raise RuntimeError("Can't map points from entity_dim=%s" % (entity_dim, )) + raise RuntimeError(f"Can't map points from entity_dim={entity_dim}") diff --git a/ffcx/main.py b/ffcx/main.py index 002869f83..96f89439f 100644 --- a/ffcx/main.py +++ b/ffcx/main.py @@ -25,15 +25,15 @@ parser = argparse.ArgumentParser( description="FEniCS Form Compiler (FFCX, https://fenicsproject.org)") parser.add_argument( - "--version", action='version', version="%(prog)s " + ("(version {})".format(FFCX_VERSION))) + "--version", action='version', version=f"%(prog)s (version {FFCX_VERSION})") parser.add_argument("-o", "--output-directory", type=str, default=".", help="output directory") parser.add_argument("--visualise", action="store_true", help="visualise the IR graph") parser.add_argument("-p", "--profile", action='store_true', help="enable profiling") # Add all parameters from FFC parameter system for param_name, (param_val, param_desc) in FFCX_DEFAULT_PARAMETERS.items(): - parser.add_argument("--{}".format(param_name), - type=type(param_val), help="{} (default={})".format(param_desc, param_val)) + parser.add_argument(f"--{param_name}", + type=type(param_val), help=f"{param_desc} (default={param_val})") parser.add_argument("ufl_file", nargs='+', help="UFL file(s) to be compiled") @@ -80,7 +80,7 @@ def main(args=None): # Turn off profiling and write status to file if xargs.profile: pr.disable() - pfn = "ffcx_{0}.profile".format(prefix) + pfn = f"ffcx_{prefix}.profile" pr.dump_stats(pfn) return 0 diff --git a/ffcx/naming.py b/ffcx/naming.py index 87ee2e6ca..ba2ae77a0 100644 --- a/ffcx/naming.py +++ b/ffcx/naming.py @@ -73,7 +73,7 @@ def compute_signature(ufl_objects, tag, coordinate_mapping=False): kind = "expression" else: - raise RuntimeError("Unknown ufl object type {}".format(ufl_object.__class__.__name__)) + raise RuntimeError(f"Unknown ufl object type {ufl_object.__class__.__name__}") # Build combined signature signatures = [object_signature, str(ffcx.__version__), ffcx.codegeneration.get_signature(), kind, tag] diff --git a/requirements.txt b/requirements.txt index 76104892e..fd913023d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ numpy cffi --e git+https://github.com/FEniCS/fiat.git#egg=fiat +-e git+https://github.com/FEniCS/basix.git#egg=basix -e git+https://github.com/FEniCS/ufl.git#egg=ufl diff --git a/setup.py b/setup.py index 1111ff5a8..b992c479a 100755 --- a/setup.py +++ b/setup.py @@ -4,8 +4,8 @@ import string import setuptools -if sys.version_info < (3, 5): - print("Python 3.5 or higher required, please upgrade.") +if sys.version_info < (3, 6): + print("Python 3.6 or higher required, please upgrade.") sys.exit(1) on_rtd = os.environ.get('READTHEDOCS') == 'True' @@ -19,7 +19,7 @@ REQUIREMENTS = [ "numpy", "cffi", - "fenics-fiat{}".format(RESTRICT_REQUIREMENTS), + "fenics-basix", "fenics-ufl{}".format(RESTRICT_REQUIREMENTS), ] diff --git a/test/Poisson.ufl b/test/Poisson.ufl index 5c65d4876..df199c91f 100644 --- a/test/Poisson.ufl +++ b/test/Poisson.ufl @@ -20,7 +20,10 @@ # # Compile this form with FFC: ffcx Poisson.ufl -element = FiniteElement("Lagrange", triangle, 1) +cell = triangle +mesh = Mesh(VectorElement('P', cell, 2)) + +element = FiniteElement("Lagrange", triangle, 2) u = TrialFunction(element) v = TestFunction(element) diff --git a/test/conftest.py b/test/conftest.py index eee8dfb6c..6ef04f28e 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -23,9 +23,9 @@ ("Lagrange", ufl.triangle, 3), ("Lagrange", ufl.tetrahedron, 3), ("Lagrange", ufl.quadrilateral, 3), - ("Lagrange", ufl.quadrilateral, 3, None, None, "spectral"), + # ("Lagrange", ufl.quadrilateral, 3, None, None, "spectral"), ("Lagrange", ufl.hexahedron, 3), - ("Lagrange", ufl.hexahedron, 3, None, None, "spectral"), + # ("Lagrange", ufl.hexahedron, 3, None, None, "spectral"), ("Brezzi-Douglas-Marini", ufl.triangle, 1), ("Brezzi-Douglas-Marini", ufl.tetrahedron, 1), ("Brezzi-Douglas-Marini", ufl.triangle, 2), @@ -50,11 +50,11 @@ ("N2curl", ufl.tetrahedron, 2), ("N2curl", ufl.triangle, 3), ("N2curl", ufl.tetrahedron, 3), - ("Quadrature", ufl.interval, 2, None, "default"), - ("Quadrature", ufl.triangle, 2, None, "default"), - ("Quadrature", ufl.tetrahedron, 2, None, "default"), - ("Quadrature", ufl.quadrilateral, 2, None, "default"), - ("Quadrature", ufl.hexahedron, 2, None, "default") + # ("Quadrature", ufl.interval, 2, None, "default"), + # ("Quadrature", ufl.triangle, 2, None, "default"), + # ("Quadrature", ufl.tetrahedron, 2, None, "default"), + # ("Quadrature", ufl.quadrilateral, 2, None, "default"), + # ("Quadrature", ufl.hexahedron, 2, None, "default") ] diff --git a/test/test_blocked_elements.py b/test/test_blocked_elements.py index 016e2df52..a6cf0eb8e 100644 --- a/test/test_blocked_elements.py +++ b/test/test_blocked_elements.py @@ -31,17 +31,6 @@ def test_finite_element(compile_args): assert ufc_element.reference_value_dimension(2) == 1 assert ufc_element.reference_value_size == 1 assert ufc_element.block_size == 1 - X = np.array([[0.0, 0.0], [0.5, 0.5]]) - npoint = X.shape[0] - X_ptr = module.ffi.cast("const double *", module.ffi.from_buffer(X)) - vals = np.zeros((npoint, 3, 1)) - vals_ptr = module.ffi.cast("double *", module.ffi.from_buffer(vals)) - ufc_element.evaluate_reference_basis(vals_ptr, npoint, X_ptr) - assert np.allclose(vals, [[[1], [0], [0]], [[0], [.5], [.5]]]) - vals = np.zeros(6) - vals_ptr = module.ffi.cast("double *", module.ffi.from_buffer(vals)) - ufc_element.tabulate_reference_dof_coordinates(vals_ptr) - assert np.allclose(vals, [0, 0, 1, 0, 0, 1]) assert ufc_element.num_sub_elements == 0 assert ufc_dofmap.block_size == 1 @@ -58,9 +47,6 @@ def test_finite_element(compile_args): ufc_dofmap.tabulate_entity_dofs(vals_ptr, 0, v) assert vals[0] == v assert ufc_dofmap.num_sub_dofmaps == 0 - assert ufc_dofmap.size_base_permutations == 9 - for i, j in enumerate([0, 1, 2, 0, 1, 2, 0, 1, 2]): - assert ufc_dofmap.base_permutations[i] == j def test_vector_element(compile_args): @@ -83,17 +69,6 @@ def test_vector_element(compile_args): assert ufc_element.reference_value_dimension(2) == 1 assert ufc_element.reference_value_size == 2 assert ufc_element.block_size == 2 - X = np.array([[0.0, 0.0], [0.5, 0.5]]) - npoint = X.shape[0] - X_ptr = module.ffi.cast("const double *", module.ffi.from_buffer(X)) - vals = np.zeros((npoint, 3, 1)) - vals_ptr = module.ffi.cast("double *", module.ffi.from_buffer(vals)) - ufc_element.evaluate_reference_basis(vals_ptr, npoint, X_ptr) - assert np.allclose(vals, [[[1], [0], [0]], [[0], [.5], [.5]]]) - vals = np.zeros(6) - vals_ptr = module.ffi.cast("double *", module.ffi.from_buffer(vals)) - ufc_element.tabulate_reference_dof_coordinates(vals_ptr) - assert np.allclose(vals, [0, 0, 1, 0, 0, 1]) assert ufc_element.num_sub_elements == 2 assert ufc_dofmap.block_size == 2 @@ -110,9 +85,6 @@ def test_vector_element(compile_args): ufc_dofmap.tabulate_entity_dofs(vals_ptr, 0, v) assert vals[0] == v assert ufc_dofmap.num_sub_dofmaps == 2 - assert ufc_dofmap.size_base_permutations == 9 - for i, j in enumerate([0, 1, 2, 0, 1, 2, 0, 1, 2]): - assert ufc_dofmap.base_permutations[i] == j def test_tensor_element(compile_args): @@ -135,17 +107,6 @@ def test_tensor_element(compile_args): assert ufc_element.reference_value_dimension(2) == 1 assert ufc_element.reference_value_size == 4 assert ufc_element.block_size == 4 - X = np.array([[0.0, 0.0], [0.5, 0.5]]) - npoint = X.shape[0] - X_ptr = module.ffi.cast("const double *", module.ffi.from_buffer(X)) - vals = np.zeros((npoint, 3, 1)) - vals_ptr = module.ffi.cast("double *", module.ffi.from_buffer(vals)) - ufc_element.evaluate_reference_basis(vals_ptr, npoint, X_ptr) - assert np.allclose(vals, [[[1], [0], [0]], [[0], [.5], [.5]]]) - vals = np.zeros(6) - vals_ptr = module.ffi.cast("double *", module.ffi.from_buffer(vals)) - ufc_element.tabulate_reference_dof_coordinates(vals_ptr) - assert np.allclose(vals, [0, 0, 1, 0, 0, 1]) assert ufc_element.num_sub_elements == 4 assert ufc_dofmap.block_size == 4 @@ -162,6 +123,3 @@ def test_tensor_element(compile_args): ufc_dofmap.tabulate_entity_dofs(vals_ptr, 0, v) assert vals[0] == v assert ufc_dofmap.num_sub_dofmaps == 4 - assert ufc_dofmap.size_base_permutations == 9 - for i, j in enumerate([0, 1, 2, 0, 1, 2, 0, 1, 2]): - assert ufc_dofmap.base_permutations[i] == j diff --git a/test/test_dof_permutations.py b/test/test_dof_permutations.py deleted file mode 100644 index d5ed81a44..000000000 --- a/test/test_dof_permutations.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (C) 2020 Matthew W. Scroggs -# -# This file is part of FFCX. -# -# 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 . -"Unit tests for FFCX" - - -import pytest - -from ffcx.ir import dof_permutations - - -@pytest.mark.parametrize("s", range(1, 5)) -@pytest.mark.parametrize("blocksize", range(1, 5)) -@pytest.mark.parametrize("perm_f, dof_f", [ - (dof_permutations.edge_flip, lambda s: s), - (dof_permutations.triangle_rotation, lambda s: s * (s + 1) // 2), - (dof_permutations.triangle_reflection, lambda s: s * (s + 1) // 2), - (dof_permutations.quadrilateral_rotation, lambda s: s ** 2), - (dof_permutations.quadrilateral_reflection, lambda s: s ** 2)]) -def test_permutations(s, perm_f, dof_f, blocksize): - """Test that permutations are valid.""" - dofs = dof_f(s) * blocksize - perms = perm_f(list(range(dofs)), blocksize) - if not isinstance(perms[0], list): - perms = [perms] - for p in perms: - for i in p: - # Each number must appear exactly once in the permutation - assert p.count(i) == 1 - # Each number must be less than the number of dofs - assert i < dofs diff --git a/test/test_elements.py b/test/test_elements.py index 46ad0cbd8..eb06198b1 100644 --- a/test/test_elements.py +++ b/test/test_elements.py @@ -23,7 +23,7 @@ import numpy import pytest -from ffcx.fiatinterface import create_element +from ffcx.basix_interface import create_basix_element from ufl import FiniteElement @@ -50,45 +50,45 @@ def random_point(shape): @pytest.mark.parametrize("degree, expected_dim", [(1, 3), (2, 6), (3, 10)]) def test_continuous_lagrange(degree, expected_dim): "Test space dimensions of continuous Lagrange elements." - P = create_element(FiniteElement("Lagrange", "triangle", degree)) - assert P.space_dimension() == expected_dim + P = create_basix_element(FiniteElement("Lagrange", "triangle", degree)) + assert P.dim == expected_dim @pytest.mark.parametrize("degree, expected_dim", [(1, 4), (2, 9), (3, 16)]) -def test_continuous_lagrange_quadrilateral(degree, expected_dim): +def xtest_continuous_lagrange_quadrilateral(degree, expected_dim): "Test space dimensions of continuous TensorProduct elements (quadrilateral)." - P = create_element(FiniteElement("Lagrange", "quadrilateral", degree)) - assert P.space_dimension() == expected_dim + P = create_basix_element(FiniteElement("Lagrange", "quadrilateral", degree)) + assert P.dim == expected_dim @pytest.mark.parametrize("degree, expected_dim", [(1, 4), (2, 9), (3, 16)]) -def test_continuous_lagrange_quadrilateral_spectral(degree, expected_dim): +def xtest_continuous_lagrange_quadrilateral_spectral(degree, expected_dim): "Test space dimensions of continuous TensorProduct elements (quadrilateral)." - P = create_element(FiniteElement("Lagrange", "quadrilateral", degree, variant="spectral")) - assert P.space_dimension() == expected_dim + P = create_basix_element(FiniteElement("Lagrange", "quadrilateral", degree, variant="spectral")) + assert P.dim == expected_dim @pytest.mark.parametrize("degree, expected_dim", [(0, 1), (1, 3), (2, 6), (3, 10)]) def test_discontinuous_lagrange(degree, expected_dim): "Test space dimensions of discontinuous Lagrange elements." - P = create_element(FiniteElement("DG", "triangle", degree)) - assert P.space_dimension() == expected_dim + P = create_basix_element(FiniteElement("DG", "triangle", degree)) + assert P.dim == expected_dim @pytest.mark.parametrize("degree, expected_dim", [(0, 3), (1, 9), (2, 18), (3, 30)]) def test_regge(degree, expected_dim): "Test space dimensions of generalized Regge element." - P = create_element(FiniteElement("Regge", "triangle", degree)) - assert P.space_dimension() == expected_dim + P = create_basix_element(FiniteElement("Regge", "triangle", degree)) + assert P.dim == expected_dim @pytest.mark.parametrize("degree, expected_dim", [(0, 3), (1, 9), (2, 18), (3, 30)]) -def test_hhj(degree, expected_dim): +def xtest_hhj(degree, expected_dim): "Test space dimensions of Hellan-Herrmann-Johnson element." - P = create_element(FiniteElement("HHJ", "triangle", degree)) - assert P.space_dimension() == expected_dim + P = create_basix_element(FiniteElement("HHJ", "triangle", degree)) + assert P.dim == expected_dim class TestFunctionValues(): @@ -108,27 +108,27 @@ class TestFunctionValues(): lambda x: (- 1 + x[0] + 3 * x[1], - 2 * x[1]), lambda x: (-x[0], -2 + 3 * x[0] + 2 * x[1]), lambda x: (2 * x[0], 1 - 3 * x[0] - x[1])] - reference_triangle_rt1 = [lambda x: (x[0], x[1]), lambda x: (1 - x[0], -x[1]), - lambda x: (x[0], x[1] - 1)] - reference_triangle_rt2 = [lambda x: (-x[0] + 3 * x[0]**2, -x[1] + 3 * x[0] * x[1]), - lambda x: (-x[0] + 3 * x[0] * x[1], -x[1] + 3 * x[1]**2), - lambda x: (2 - 5 * x[0] - 3 * x[1] + 3 * x[0] * x[1] + 3 * x[0]**2, - -2 * x[1] + 3 * x[0] * x[1] + 3 * x[1]**2), - lambda x: (-1.0 + x[0] + 3 * x[1] - 3 * x[0] * x[1], x[1] - 3 * x[1]**2), - lambda x: (2 * x[0] - 3 * x[0] * x[1] - 3 * x[0] ** 2, - -2 + 3 * x[0] + 5 * x[1] - 3 * x[0] * x[1] - 3 * x[1]**2), - lambda x: (-x[0] + 3 * x[0]**2, - 1 - 3 * x[0] - x[1] + 3 * x[0] * x[1]), - lambda x: (6 * x[0] - 3 * x[0] * x[1] - 6 * x[0]**2, - 3 * x[1] - 6 * x[0] * x[1] - 3 * x[1]**2), - lambda x: (3 * x[0] - 6 * x[0] * x[1] - 3 * x[0]**2, - 6 * x[1] - 3 * x[0] * x[1] - 6 * x[1]**2)] + reference_triangle_rt1 = [lambda x: (-x[0], -x[1]), lambda x: (x[0] - 1, x[1]), + lambda x: (-x[0], 1 - x[1])] + reference_triangle_rt2 = [lambda x: (x[0] - 3 * x[0]**2, x[1] - 3 * x[0] * x[1]), + lambda x: (x[0] - 3 * x[0] * x[1], x[1] - 3 * x[1]**2), + lambda x: (-2 + 5 * x[0] + 3 * x[1] - 3 * x[0] * x[1] - 3 * x[0]**2, + 2 * x[1] - 3 * x[0] * x[1] - 3 * x[1]**2), + lambda x: (1.0 - x[0] - 3 * x[1] + 3 * x[0] * x[1], x[1] + 3 * x[1]**2), + lambda x: (-2 * x[0] + 3 * x[0] * x[1] + 3 * x[0] ** 2, + 2 - 3 * x[0] - 5 * x[1] + 3 * x[0] * x[1] + 3 * x[1]**2), + lambda x: (x[0] - 3 * x[0]**2, + -1 + 3 * x[0] + x[1] - 3 * x[0] * x[1]), + lambda x: (-6 * x[0] + 3 * x[0] * x[1] + 6 * x[0]**2, + -3 * x[1] + 6 * x[0] * x[1] + 3 * x[1]**2), + lambda x: (-3 * x[0] + 6 * x[0] * x[1] + 3 * x[0]**2, + -6 * x[1] + 3 * x[0] * x[1] + 6 * x[1]**2)] reference_triangle_ned1 = [lambda x: (-x[1], x[0]), lambda x: (x[1], 1 - x[0]), lambda x: (1.0 - x[1], x[0])] - reference_tetrahedron_rt1 = [lambda x: (-x[0], -x[1], -x[2]), - lambda x: (-1.0 + x[0], x[1], x[2]), - lambda x: (-x[0], 1.0 - x[1], -x[2]), - lambda x: (x[0], x[1], -1.0 + x[2])] + reference_tetrahedron_rt1 = [lambda x: (2 * x[0], 2 * x[1], 2 * x[2]), + lambda x: (2 - 2 * x[0], -2 * x[1], -2 * x[2]), + lambda x: (2 * x[0], 2 * x[1] - 2, 2 * x[2]), + lambda x: (-2 * x[0], -2 * x[1], 2 - 2 * x[2])] reference_tetrahedron_bdm1 = [lambda x: (-3 * x[0], x[1], x[2]), lambda x: (x[0], -3 * x[1], x[2]), lambda x: (x[0], x[1], -3 * x[2]), @@ -164,35 +164,35 @@ class TestFunctionValues(): tests = [("Lagrange", "interval", 1, reference_interval_1), ("Lagrange", "triangle", 1, reference_triangle_1), ("Lagrange", "tetrahedron", 1, reference_tetrahedron_1), - ("Lagrange", "quadrilateral", 1, reference_quadrilateral_1), - ("Lagrange", "hexahedron", 1, reference_hexahedron_1), + # ("Lagrange", "quadrilateral", 1, reference_quadrilateral_1), + # ("Lagrange", "hexahedron", 1, reference_hexahedron_1), ("Discontinuous Lagrange", "interval", 1, reference_interval_1), ("Discontinuous Lagrange", "triangle", 1, reference_triangle_1), ("Discontinuous Lagrange", "tetrahedron", 1, reference_tetrahedron_1), - ("Brezzi-Douglas-Marini", "triangle", 1, reference_triangle_bdm1), + # ("Brezzi-Douglas-Marini", "triangle", 1, reference_triangle_bdm1), ("Raviart-Thomas", "triangle", 1, reference_triangle_rt1), - ("Raviart-Thomas", "triangle", 2, reference_triangle_rt2), - ("Discontinuous Raviart-Thomas", "triangle", 1, reference_triangle_rt1), - ("Discontinuous Raviart-Thomas", "triangle", 2, reference_triangle_rt2), + # ("Raviart-Thomas", "triangle", 2, reference_triangle_rt2), + # ("Discontinuous Raviart-Thomas", "triangle", 1, reference_triangle_rt1), + # ("Discontinuous Raviart-Thomas", "triangle", 2, reference_triangle_rt2), ("N1curl", "triangle", 1, reference_triangle_ned1), ("Raviart-Thomas", "tetrahedron", 1, reference_tetrahedron_rt1), - ("Discontinuous Raviart-Thomas", "tetrahedron", 1, reference_tetrahedron_rt1), - ("Brezzi-Douglas-Marini", "tetrahedron", 1, reference_tetrahedron_bdm1), + # ("Discontinuous Raviart-Thomas", "tetrahedron", 1, reference_tetrahedron_rt1), + # ("Brezzi-Douglas-Marini", "tetrahedron", 1, reference_tetrahedron_bdm1), ("N1curl", "tetrahedron", 1, reference_tetrahedron_ned1)] @pytest.mark.parametrize("family, cell, degree, reference", tests) def test_values(self, family, cell, degree, reference): # Create element - element = create_element(FiniteElement(family, cell, degree)) + element = create_basix_element(FiniteElement(family, cell, degree)) # Get some points and check basis function values at points points = [random_point(element_coords(cell)) for i in range(5)] for x in points: table = element.tabulate(0, (x,)) - basis = table[list(table.keys())[0]] - for i in range(len(basis)): - if not element.value_shape(): - assert round(float(basis[i]) - reference[i](x), 10) == 0.0 - else: - for k in range(element.value_shape()[0]): - assert round(basis[i][k][0] - reference[i](x)[k], 10) == 0.0 + basis = table[0] + if sum(element.value_shape) == 1: + for i, value in enumerate(basis[0]): + assert numpy.isclose(value, reference[i](x)) + else: + for i, ref in enumerate(reference): + assert numpy.allclose(basis[0][i::len(reference)], ref(x)) diff --git a/test/test_jit_cmaps.py b/test/test_jit_cmaps.py index 04f76ef9e..050a33496 100644 --- a/test/test_jit_cmaps.py +++ b/test/test_jit_cmaps.py @@ -6,7 +6,6 @@ import ffcx import ffcx.codegeneration.jit -import numpy as np import pytest import ufl @@ -25,14 +24,6 @@ def test_cmap_triangle(degree, compile_args): assert compiled_cmap[0].geometric_dimension == 2 assert compiled_cmap[0].topological_dimension == 2 - # Reference coordinates X to basis - phi = np.zeros(((degree + 2) * (degree + 1)) // 2, dtype=np.float64) - phi_ptr = module.ffi.cast("double *", module.ffi.from_buffer(phi)) - X = np.array([[1 / 3, 1 / 3]], dtype=np.float64) - X_ptr = module.ffi.cast("double *", module.ffi.from_buffer(X)) - compiled_cmap[0].evaluate_basis_derivatives(phi_ptr, 0, X.shape[0], X_ptr) - assert np.isclose(sum(phi), 1.0) - num_entity_dofs = compiled_cmap[0].create_scalar_dofmap().num_entity_dofs assert num_entity_dofs[0] == 1 @@ -60,14 +51,6 @@ def test_cmap_quads(degree, compile_args): assert compiled_cmap[0].geometric_dimension == 2 assert compiled_cmap[0].topological_dimension == 2 - # Reference coordinates X to basis - phi = np.zeros((degree + 1) ** 2, dtype=np.float64) - phi_ptr = module.ffi.cast("double *", module.ffi.from_buffer(phi)) - X = np.array([[0.5, 0.5]], dtype=np.float64) - X_ptr = module.ffi.cast("double *", module.ffi.from_buffer(X)) - compiled_cmap[0].evaluate_basis_derivatives(phi_ptr, 0, X.shape[0], X_ptr) - assert np.isclose(sum(phi), 1.0) - num_entity_dofs = compiled_cmap[0].create_scalar_dofmap().num_entity_dofs assert num_entity_dofs[0] == 1 @@ -96,14 +79,6 @@ def test_cmap_hex(degree, compile_args): assert compiled_cmap[0].geometric_dimension == 3 assert compiled_cmap[0].topological_dimension == 3 - # Reference coordinates X to basis - phi = np.zeros((degree + 1) ** 3, dtype=np.float64) - phi_ptr = module.ffi.cast("double *", module.ffi.from_buffer(phi)) - X = np.array([[0.5, 0.5, 0.5]], dtype=np.float64) - X_ptr = module.ffi.cast("double *", module.ffi.from_buffer(X)) - compiled_cmap[0].evaluate_basis_derivatives(phi_ptr, 0, X.shape[0], X_ptr) - assert np.isclose(sum(phi), 1.0) - num_entity_dofs = compiled_cmap[0].create_scalar_dofmap().num_entity_dofs assert num_entity_dofs[0] == 1 @@ -132,14 +107,6 @@ def test_cmap_tet(degree, compile_args): assert compiled_cmap[0].geometric_dimension == 3 assert compiled_cmap[0].topological_dimension == 3 - # Reference coordinates X to basis - phi = np.zeros((degree + 3) * (degree + 2) * (degree + 1) // 6, dtype=np.float64) - phi_ptr = module.ffi.cast("double *", module.ffi.from_buffer(phi)) - X = np.array([[0.25, 0.25, 0.25]], dtype=np.float64) - X_ptr = module.ffi.cast("double *", module.ffi.from_buffer(X)) - compiled_cmap[0].evaluate_basis_derivatives(phi_ptr, 0, X.shape[0], X_ptr) - assert np.isclose(sum(phi), 1.0) - num_entity_dofs = compiled_cmap[0].create_scalar_dofmap().num_entity_dofs assert num_entity_dofs[0] == 1 diff --git a/test/test_jit_elements.py b/test/test_jit_elements.py deleted file mode 100644 index ecab907f1..000000000 --- a/test/test_jit_elements.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (C) 2018-2020 Chris Richardson and Michal Habera -# -# This file is part of FFCX.(https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -import numpy as np -import pytest - -import ffcx -import ffcx.codegeneration.jit -import ufl - -families = ["Lagrange", "Brezzi-Douglas-Marini", "Raviart-Thomas", "N1curl", "N2curl"] -cells = [ufl.triangle, ufl.quadrilateral, ufl.tetrahedron, ufl.hexahedron] -degrees = [1, 2, 3] - - -@pytest.fixture(scope="module") -def reference_points(): - """Returns an example reference points in reference cells""" - points = {} - - points[ufl.interval] = np.array([[0.0], [0.3], [0.9]]) - points[ufl.triangle] = np.array([[0.0, 0.0], [0.5, 0.5], [0.25, 0.25], - [1 / 3, 1 / 3], [1.0, 0.0], [0.0, 1.0]]) - points[ufl.tetrahedron] = np.array([[0.0, 0.0, 0.0], [0.5, 0.2, 0.0], [0.0, 0.0, 1.0]]) - points[ufl.quadrilateral] = np.array([[0.0, 0.0], [0.5, 0.5], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]) - points[ufl.hexahedron] = np.array([[0.0, 0.0, 0.0], [0.5, 0.5, 0.5], [1.0, 0.0, 0.0], - [0.0, 1.0, 0.0], [1.0, 1.0, 1.0]]) - - return points - - -def test_dim_degree(compiled_element): - ufl_element, compiled_element, module = compiled_element - cell = ufl_element.cell() - assert compiled_element[0].geometric_dimension == cell.geometric_dimension() - assert compiled_element[0].topological_dimension == cell.topological_dimension() - assert ufl_element.degree() == compiled_element[0].degree - - -def test_tabulate_reference_dof_coordinates(compiled_element): - ufl_element, compiled_element, module = compiled_element - - if ufl_element.family() not in ["Lagrange", "Quadrature", "Q"]: - pytest.skip("Cannot tabulate dofs for this FE.") - - fiat_element = ffcx.fiatinterface.create_element(ufl_element) - - tdim = compiled_element[0].topological_dimension - space_dim = compiled_element[0].space_dimension - X = np.zeros([space_dim, tdim]) - X_ptr = module.ffi.cast("double *", module.ffi.from_buffer(X)) - compiled_element[0].tabulate_reference_dof_coordinates(X_ptr) - - fiat_coordinates = np.asarray(list(sorted(L.pt_dict.keys())[0] for L in fiat_element.dual_basis())) - assert (np.isclose(X, fiat_coordinates)).all() - - -def test_evaluate_reference_basis(compiled_element, reference_points): - ufl_element, compiled_element, module = compiled_element - - if ufl_element.family() in ["Quadrature"]: - pytest.skip("Cannot evaluate basis for this FE.") - - fiat_element = ffcx.fiatinterface.create_element(ufl_element) - - space_dim = compiled_element[0].space_dimension - tdim = compiled_element[0].topological_dimension - - # For vector/tensor valued basis this is not 1 - value_size = np.product(fiat_element.value_shape(), dtype=np.int) - - X = reference_points[ufl_element.cell()] - npoint = X.shape[0] - X_ptr = module.ffi.cast("const double *", module.ffi.from_buffer(X)) - vals = np.zeros([npoint, space_dim, value_size]) - vals_ptr = module.ffi.cast("double *", module.ffi.from_buffer(vals)) - - compiled_element[0].evaluate_reference_basis(vals_ptr, npoint, X_ptr) - - fiat_vals = fiat_element.tabulate(0, X) - - # FFC does some reordering and slicing wrt. FIAT - vals = np.transpose(vals, axes=[1, 2, 0]) - if value_size == 1: - vals = vals[:, 0, :] - - assert (np.isclose(vals, fiat_vals[(0,) * tdim])).all() diff --git a/test/test_jit_forms.py b/test/test_jit_forms.py index c7b253dce..0e0035cc3 100644 --- a/test/test_jit_forms.py +++ b/test/test_jit_forms.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018 Garth N. Wells +# Copyright (C) 2018-2020 Garth N. Wells & Matthew Scroggs # # This file is part of FFCX.(https://www.fenicsproject.org) # @@ -10,6 +10,7 @@ import ffcx.codegeneration.jit import ufl +import sympy def float_to_type(name): @@ -438,3 +439,235 @@ def test_custom_quadrature(compile_args): # Check that A is diagonal assert np.count_nonzero(A - np.diag(np.diagonal(A))) == 0 + + +def test_curl_curl(compile_args): + V = ufl.FiniteElement("N1curl", "triangle", 2) + u, v = ufl.TrialFunction(V), ufl.TestFunction(V) + a = ufl.inner(ufl.curl(u), ufl.curl(v)) * ufl.dx + + forms = [a] + compiled_forms, module = ffcx.codegeneration.jit.compile_forms(forms, cffi_extra_compile_args=compile_args) + + +def lagrange_triangle_symbolic(order, corners=[(1, 0), (2, 0), (0, 1)], fun=lambda i: i): + x = sympy.Symbol("x") + y = sympy.Symbol("y") + from sympy import S + poly_basis = [x**i * y**j for i in range(order + 1) for j in range(order + 1 - i)] + # vertices + eval_points = [S(c) for c in corners] + # edges + for e in [(1, 2), (0, 2), (0, 1)]: + p0 = corners[e[0]] + p1 = corners[e[1]] + eval_points += [tuple(S(a) + sympy.Rational((b - a) * i, order) for a, b in zip(p0, p1)) + for i in range(1, order)] + # face + for f in [(0, 1, 2)]: + p0 = corners[f[0]] + p1 = corners[f[1]] + p2 = corners[f[2]] + eval_points += [tuple(S(a) + sympy.Rational((b - a) * i, order) + + sympy.Rational((c - a) * j, order) for a, b, c in zip(p0, p1, p2)) + for i in range(1, order) for j in range(1, order - i)] + + dual_mat = [[f.subs(x, p[0]).subs(y, p[1]) for p in eval_points] for f in poly_basis] + dual_mat = sympy.Matrix(dual_mat) + mat = dual_mat.inv() + functions = [sum(i * j for i, j in zip(mat.row(k), poly_basis)) for k in range(mat.rows)] + results = [] + for f in functions: + integrand = fun(f) + results.append(integrand.integrate((x, 1 - y, 2 - 2 * y), (y, 0, 1))) + return results + + +@pytest.mark.parametrize("mode", ["double"]) +@pytest.mark.parametrize("sym_fun,ufl_fun", [ + (lambda i: i, lambda i: i), + (lambda i: i.diff("x"), lambda i: ufl.grad(i)[0]), + (lambda i: i.diff("y"), lambda i: ufl.grad(i)[1])]) +@pytest.mark.parametrize("order", [1, 2, 3, 4, 5]) +def test_lagrange_triangle(compile_args, order, mode, sym_fun, ufl_fun): + sym = lagrange_triangle_symbolic(order, fun=sym_fun) + cell = ufl.triangle + element = ufl.FiniteElement("Lagrange", cell, order) + v = ufl.TestFunction(element) + + a = ufl_fun(v) * ufl.dx + forms = [a] + compiled_forms, module = ffcx.codegeneration.jit.compile_forms( + forms, parameters={'scalar_type': mode}, cffi_extra_compile_args=compile_args) + + ffi = cffi.FFI() + form0 = compiled_forms[0][0] + + assert form0.num_cell_integrals == 1 + ids = np.zeros(form0.num_cell_integrals, dtype=np.intc) + form0.get_cell_integral_ids(ffi.cast('int *', ids.ctypes.data)) + assert ids[0] == -1 + + default_integral = form0.create_cell_integral(ids[0]) + + c_type, np_type = float_to_type(mode) + b = np.zeros((order + 2) * (order + 1) // 2, dtype=np_type) + w = np.array([], dtype=np_type) + + coords = np.array([1.0, 0.0, 2.0, 0.0, 0.0, 1.0], dtype=np.float64) + default_integral.tabulate_tensor( + ffi.cast('{type} *'.format(type=c_type), b.ctypes.data), + ffi.cast('{type} *'.format(type=c_type), w.ctypes.data), + ffi.NULL, + ffi.cast('double *', coords.ctypes.data), ffi.NULL, ffi.NULL, 0) + + # Check that the result is the same as for sympy + assert np.allclose(b, [float(i) for i in sym]) + + # Check that passing in permutations correctly reverses edges + for perm in range(1, 8): + perm_b = np.zeros((order + 2) * (order + 1) // 2, dtype=np_type) + default_integral.tabulate_tensor( + ffi.cast('{type} *'.format(type=c_type), perm_b.ctypes.data), + ffi.cast('{type} *'.format(type=c_type), w.ctypes.data), + ffi.NULL, + ffi.cast('double *', coords.ctypes.data), ffi.NULL, ffi.NULL, perm) + + for edge in range(3): + start = 3 + (order - 1) * edge + end = start + order - 1 + if perm >> edge & 1 == 1: + assert np.allclose(b[start: end], perm_b[end - 1: start - 1: -1]) + else: + assert np.allclose(b[start: end], perm_b[start: end]) + + +def lagrange_tetrahedron_symbolic(order, corners=[(1, 0, 0), (2, 0, 0), (0, 1, 0), (0, 0, 1)], fun=lambda i: i): + x = sympy.Symbol("x") + y = sympy.Symbol("y") + z = sympy.Symbol("z") + from sympy import S + poly_basis = [ + x**i * y**j * z**k for i in range(order + 1) for j in range(order + 1 - i) + for k in range(order + 1 - i - j)] + # vertices + eval_points = [S(c) for c in corners] + # edges + for e in [(2, 3), (1, 3), (1, 2), (0, 3), (0, 2), (0, 1)]: + p0 = corners[e[0]] + p1 = corners[e[1]] + eval_points += [tuple(S(a) + sympy.Rational((b - a) * i, order) for a, b in zip(p0, p1)) + for i in range(1, order)] + # face + for f in [(1, 2, 3), (0, 2, 3), (0, 1, 3), (0, 1, 2)]: + p0 = corners[f[0]] + p1 = corners[f[1]] + p2 = corners[f[2]] + eval_points += [tuple(S(a) + sympy.Rational((b - a) * i, order) + + sympy.Rational((c - a) * j, order) for a, b, c in zip(p0, p1, p2)) + for i in range(1, order) for j in range(1, order - i)] + # interior + for v in [(0, 1, 2, 3)]: + p0 = corners[v[0]] + p1 = corners[v[1]] + p2 = corners[v[2]] + p3 = corners[v[3]] + eval_points += [tuple(S(a) + sympy.Rational((b - a) * i, order) + sympy.Rational((c - a) * j, order) + + sympy.Rational((d - a) * k, order) for a, b, c, d in zip(p0, p1, p2, p3)) + for i in range(1, order) for j in range(1, order - i) for k in range(1, order - i - j)] + + dual_mat = [[f.subs(x, p[0]).subs(y, p[1]).subs(z, p[2]) for p in eval_points] for f in poly_basis] + dual_mat = sympy.Matrix(dual_mat) + mat = dual_mat.inv() + functions = [sum(i * j for i, j in zip(mat.row(k), poly_basis)) for k in range(mat.rows)] + results = [] + for f in functions: + integrand = fun(f) + results.append(integrand.integrate((x, 1 - y - z, 2 - 2 * y - 2 * z), (y, 0, 1 - z), (z, 0, 1))) + return results + + +@pytest.mark.parametrize("mode", ["double"]) +@pytest.mark.parametrize("sym_fun,ufl_fun", [ + (lambda i: i, lambda i: i), + (lambda i: i.diff("x"), lambda i: ufl.grad(i)[0]), + (lambda i: i.diff("y"), lambda i: ufl.grad(i)[1])]) +@pytest.mark.parametrize("order", [1, 2, 3, 4]) +def test_lagrange_tetrahedron(compile_args, order, mode, sym_fun, ufl_fun): + sym = lagrange_tetrahedron_symbolic(order, fun=sym_fun) + cell = ufl.tetrahedron + element = ufl.FiniteElement("Lagrange", cell, order) + v = ufl.TestFunction(element) + + a = ufl_fun(v) * ufl.dx + forms = [a] + compiled_forms, module = ffcx.codegeneration.jit.compile_forms( + forms, parameters={'scalar_type': mode}, cffi_extra_compile_args=compile_args) + + ffi = cffi.FFI() + form0 = compiled_forms[0][0] + + assert form0.num_cell_integrals == 1 + ids = np.zeros(form0.num_cell_integrals, dtype=np.intc) + form0.get_cell_integral_ids(ffi.cast('int *', ids.ctypes.data)) + assert ids[0] == -1 + + default_integral = form0.create_cell_integral(ids[0]) + + c_type, np_type = float_to_type(mode) + b = np.zeros((order + 3) * (order + 2) * (order + 1) // 6, dtype=np_type) + w = np.array([], dtype=np_type) + + coords = np.array([1.0, 0.0, 0.0, + 2.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0], dtype=np.float64) + default_integral.tabulate_tensor( + ffi.cast('{type} *'.format(type=c_type), b.ctypes.data), + ffi.cast('{type} *'.format(type=c_type), w.ctypes.data), + ffi.NULL, + ffi.cast('double *', coords.ctypes.data), ffi.NULL, ffi.NULL, 0) + + # Check that the result is the same as for sympy + assert np.allclose(b, [float(i) for i in sym]) + + # Check that passing in permutations correctly reverses edges + for edge in range(6): + perm_b = np.zeros((order + 3) * (order + 2) * (order + 1) // 6, dtype=np_type) + default_integral.tabulate_tensor( + ffi.cast('{type} *'.format(type=c_type), perm_b.ctypes.data), + ffi.cast('{type} *'.format(type=c_type), w.ctypes.data), + ffi.NULL, + ffi.cast('double *', coords.ctypes.data), ffi.NULL, ffi.NULL, 1 << (12 + edge)) + + for e in range(6): + start = 4 + (order - 1) * e + end = start + order - 1 + if e == edge: + assert np.allclose(b[start: end], perm_b[end - 1: start - 1: -1]) + else: + assert np.allclose(b[start: end], perm_b[start: end]) + + # Check that passing in permutation conts correctly permutes face data + start = 4 + 6 * (order - 1) + end = start + (order - 1) * (order - 2) // 2 + for rots in range(3): + for refs in range(2): + perm = (rots * 2 + refs) # << 6 + new_coords = [1., 0., 0.] + points = [[2.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] + new_coords += points[rots] + if refs: + new_coords += points[(rots - 1) % 3] + new_coords += points[(rots + 1) % 3] + else: + new_coords += points[(rots + 1) % 3] + new_coords += points[(rots - 1) % 3] + new_coords = np.array(new_coords, dtype=np.float64) + perm_b = np.zeros((order + 3) * (order + 2) * (order + 1) // 6, dtype=np_type) + default_integral.tabulate_tensor( + ffi.cast('{type} *'.format(type=c_type), perm_b.ctypes.data), + ffi.cast('{type} *'.format(type=c_type), w.ctypes.data), + ffi.NULL, + ffi.cast('double *', new_coords.ctypes.data), ffi.NULL, ffi.NULL, perm) + assert np.allclose(b[start:end], perm_b[start:end]) diff --git a/test/xtest_evaluate.py b/test/xtest_evaluate.py deleted file mode 100644 index c1f64fb7a..000000000 --- a/test/xtest_evaluate.py +++ /dev/null @@ -1,312 +0,0 @@ -# Copyright (C) 2017 Garth N. Wells -# -# This file is part of FFCX. -# -# 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 . - -import numpy as np -import pytest - -import ffcx -import ffcx_factory -from ufl import (FiniteElement, MixedElement, VectorElement, interval, - tetrahedron, triangle) - - -@pytest.fixture(scope="module") -def point_data(): - """Points are which evaluate functions are tested.""" - p = {1: [(0.114,), (0.349,), (0.986,)], - 2: [(0.114, 0.854), (0.349, 0.247), (0.986, 0.045)], - 3: [(0.114, 0.854, 0.126), (0.349, 0.247, 0.457), - (0.986, 0.045, 0.127)]} - return p - - -@pytest.fixture(scope="module") -def build_ufl_element_list(): - """Build collection of UFL elements.""" - - elements = [] - - # Lagrange elements - for cell in (interval, triangle, tetrahedron): - for p in range(1, 2): - elements.append(FiniteElement("Lagrange", cell, p)) - elements.append(VectorElement("Lagrange", cell, p)) - elements.append(FiniteElement("Discontinuous Lagrange", cell, p - 1)) - - # Vector elements - for cell in (triangle, tetrahedron): - for p in range(1, 2): - elements.append(FiniteElement("RT", cell, p)) - elements.append(FiniteElement("BDM", cell, p)) - elements.append(FiniteElement("N1curl", cell, p)) - elements.append(FiniteElement("N2curl", cell, p)) - elements.append(FiniteElement("Discontinuous Raviart-Thomas", cell, p)) - - # Mixed elements - for cell in (interval, triangle, tetrahedron): - for p in range(1, 3): - e0 = FiniteElement("Lagrange", cell, p + 1) - e1 = FiniteElement("Lagrange", cell, p) - e2 = VectorElement("Lagrange", cell, p + 1) - - elements.append(MixedElement([e0, e0])) - elements.append(MixedElement([e0, e1])) - elements.append(MixedElement([e1, e0])) - elements.append(MixedElement([e2, e1])) - elements.append(MixedElement([MixedElement([e1, e1]), e0])) - - for cell in (triangle, tetrahedron): - for p in range(1, 2): - e0 = FiniteElement("Lagrange", cell, p + 1) - e1 = FiniteElement("Lagrange", cell, p) - e2 = VectorElement("Lagrange", cell, p + 1) - e3 = FiniteElement("BDM", cell, p) - e4 = FiniteElement("RT", cell, p + 1) - e5 = FiniteElement("N1curl", cell, p) - - elements.append(MixedElement([e1, e2])) - elements.append(MixedElement([e3, MixedElement([e4, e5])])) - - # Misc elements - for cell in (triangle,): - for p in range(1, 2): - elements.append(FiniteElement("HHJ", cell, p)) - - for cell in (triangle, tetrahedron): - elements.append(FiniteElement("CR", cell, 1)) - for p in range(1, 2): - elements.append(FiniteElement("Regge", cell, p)) - - return elements - - -def element_id(e): - """Return element string signature as ID.""" - return str(e) - - -@pytest.fixture(params=build_ufl_element_list(), ids=element_id, scope="module") -def element_pair(request): - """Given a UFL element, returns UFL element and a JIT-compiled and wrapped UFC element.""" - - ufl_element = request.param - ufc_element, ufc_dofmap = ffcx.jit(ufl_element, parameters=None) - ufc_element = ffcx_factory.make_ufc_finite_element(ufc_element) - return ufl_element, ufc_element - - -def test_evaluate_reference_basis_vs_fiat(element_pair, point_data): - """Tests ufc::finite_element::evaluate_reference_basis against data from FIAT.""" - - ufl_element, ufc_element = element_pair - - # Get geometric and topological dimensions - tdim = ufc_element.topological_dimension() - - points = point_data[tdim] - - # Create FIAT element via FFCX - fiat_element = ffcx.fiatinterface.create_element(ufl_element) - - # Tabulate basis at requsted points via UFC - values_ufc = ufc_element.evaluate_reference_basis(points) - - # Tabulate basis at requsted points via FIAT - values_fiat = fiat_element.tabulate(0, points) - - # Extract and reshape FIAT output - key = (0,) * tdim - values_fiat = np.array(values_fiat[key]) - - # Shape checks - fiat_value_size = 1 - for i in range(ufc_element.reference_value_rank()): - fiat_value_size *= values_fiat.shape[i + 1] - assert values_fiat.shape[i + 1] == ufc_element.reference_value_dimension(i) - - # Reshape FIAT output to compare with UFC output - values_fiat = values_fiat.reshape((values_fiat.shape[0], - ufc_element.reference_value_size(), - values_fiat.shape[-1])) - - # Transpose - values_fiat = values_fiat.transpose((2, 0, 1)) - - # Check values - assert np.allclose(values_ufc, values_fiat) - - # FIXME: This could be fragile because it depend on the order of - # the sub-element in UFL being the same at the UFC order. - - # Test sub-elements recursively - n = ufc_element.num_sub_elements() - ufl_subelements = ufl_element.sub_elements() - assert n == len(ufl_subelements) - for i, ufl_e in enumerate(ufl_subelements): - ufc_sub_element = ufc_element.create_sub_element(i) - test_evaluate_reference_basis_vs_fiat((ufl_e, ufc_sub_element), point_data) - - -def test_evaluate_basis_vs_fiat(element_pair, point_data): - """Tests against FIAT. - Test ufc::finite_element::evaluate_basis and - ufc::finite_element::evaluate_basis_all against data from FIAT. - """ - - ufl_element, ufc_element = element_pair - - # Get geometric and topological dimensions - gdim = ufc_element.geometric_dimension() - - points = point_data[gdim] - - # Get geometric and topological dimensions - tdim = ufc_element.topological_dimension() - - # Create FIAT element via FFCX - fiat_element = ffcx.fiatinterface.create_element(ufl_element) - - # Tabulate basis at requsted points via FIAT - values_fiat = fiat_element.tabulate(0, points) - - # Extract and reshape FIAT output - key = (0,) * tdim - values_fiat = np.array(values_fiat[key]) - - # Shape checks - fiat_value_size = 1 - for i in range(ufc_element.reference_value_rank()): - fiat_value_size *= values_fiat.shape[i + 1] - assert values_fiat.shape[i + 1] == ufc_element.reference_value_dimension(i) - - # Reshape FIAT output to compare with UFC output - values_fiat = values_fiat.reshape((values_fiat.shape[0], - ufc_element.reference_value_size(), - values_fiat.shape[-1])) - - # Transpose - values_fiat = values_fiat.transpose((2, 0, 1)) - - # Get reference cell - cell = ufl_element.cell() - ref_coords = ffcx.fiatinterface.reference_cell(cell.cellname()).get_vertices() - - # Iterate over each point and test - for i, p in enumerate(points): - values_ufc = ufc_element.evaluate_basis_all(p, ref_coords, 1) - assert np.allclose(values_ufc, values_fiat[i]) - - for d in range(ufc_element.space_dimension()): - values_ufc = ufc_element.evaluate_basis(d, p, ref_coords, 1) - assert np.allclose(values_ufc, values_fiat[i][d]) - - # Test sub-elements recursively - n = ufc_element.num_sub_elements() - ufl_subelements = ufl_element.sub_elements() - assert n == len(ufl_subelements) - for i, ufl_e in enumerate(ufl_subelements): - ufc_sub_element = ufc_element.create_sub_element(i) - test_evaluate_basis_vs_fiat((ufl_e, ufc_sub_element), point_data) - - -@pytest.mark.parametrize("order", range(4)) -def test_evaluate_reference_basis_deriv_vs_fiat(order, element_pair, - point_data): - """Tests agaist FIAT. - Tests ufc::finite_element::evaluate_reference_basis_derivatives - against data from FIAT. - - """ - - ufl_element, ufc_element = element_pair - - # Get geometric and topological dimensions - # gdim = ufc_element.geometric_dimension() - tdim = ufc_element.topological_dimension() - - points = point_data[tdim] - - # Create FIAT element via FFCX - fiat_element = ffcx.fiatinterface.create_element(ufl_element) - - # Tabulate basis at requsted points via UFC - values_ufc = ufc_element.evaluate_reference_basis_derivatives(order, points) - - # Tabulate basis at requsted points via FIAT - values_fiat = fiat_element.tabulate(order, points) - - # Compute 'FFCX' style derivative indicators - deriv_order = order - geo_dim = tdim - num_derivatives = geo_dim**deriv_order - combinations = [[0] * deriv_order for n in range(num_derivatives)] - for i in range(1, num_derivatives): - for k in range(i): - j = deriv_order - 1 - while j + 1 > 0: - j -= 1 - if combinations[i][j] + 1 > geo_dim - 1: - combinations[i][j] = 0 - else: - combinations[i][j] += 1 - break - - def to_fiat_tuple(comb, gdim): - """Convert a list of combinations of derivatives to a fiat tuple of derivatives. - FIAT expects a list with the number of - derivatives in each spatial direction. E.g., in 2D: u_{xyy} - --> [0, 1, 1] in FFCX --> (1, 2) in FIAT. - - """ - new_comb = [0] * gdim - if comb == []: - return tuple(new_comb) - for i in range(gdim): - new_comb[i] = comb.count(i) - return tuple(new_comb) - - derivs = [to_fiat_tuple(c, tdim) for c in combinations] - assert len(derivs) == tdim**order - - # Iterate over derivatives - for i, d in enumerate(derivs): - values_fiat_slice = np.array(values_fiat[d]) - - # Shape checks - fiat_value_size = 1 - for j in range(ufc_element.reference_value_rank()): - fiat_value_size *= values_fiat_slice.shape[j + 1] - assert values_fiat_slice.shape[j + 1] == ufc_element.reference_value_dimension(j) - - # Reshape FIAT output to compare with UFC output - values_fiat_slice = values_fiat_slice.reshape((values_fiat_slice.shape[0], - ufc_element.reference_value_size(), - values_fiat_slice.shape[-1])) - - # Transpose - values_fiat_slice = values_fiat_slice.transpose((2, 0, 1)) - - # Compare - assert np.allclose(values_ufc[:, :, i, :], values_fiat_slice) - - # Test sub-elements recursively - n = ufc_element.num_sub_elements() - ufl_subelements = ufl_element.sub_elements() - assert n == len(ufl_subelements) - for i, ufl_e in enumerate(ufl_subelements): - ufc_sub_element = ufc_element.create_sub_element(i) - test_evaluate_reference_basis_deriv_vs_fiat(order, (ufl_e, ufc_sub_element), point_data)