From c0fa109566fdf3cfd114c29c95f8fe055f120a5e Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 23 Apr 2024 08:52:18 +0100 Subject: [PATCH 01/25] add symmetric to finite element (#684) --- ffcx/codegeneration/C/finite_element.py | 1 + ffcx/codegeneration/C/finite_element_template.py | 1 + ffcx/codegeneration/ufcx.h | 3 +++ ffcx/ir/representation.py | 4 ++++ 4 files changed, 9 insertions(+) diff --git a/ffcx/codegeneration/C/finite_element.py b/ffcx/codegeneration/C/finite_element.py index 407c535b0..7f3356562 100644 --- a/ffcx/codegeneration/C/finite_element.py +++ b/ffcx/codegeneration/C/finite_element.py @@ -41,6 +41,7 @@ def generator(ir, options): d["num_sub_elements"] = ir.num_sub_elements d["block_size"] = ir.block_size d["discontinuous"] = "true" if ir.discontinuous else "false" + d["symmetric"] = "true" if ir.symmetric else "false" if ir.lagrange_variant is None: d["lagrange_variant"] = -1 diff --git a/ffcx/codegeneration/C/finite_element_template.py b/ffcx/codegeneration/C/finite_element_template.py index ab9f5d283..6bf37a6aa 100644 --- a/ffcx/codegeneration/C/finite_element_template.py +++ b/ffcx/codegeneration/C/finite_element_template.py @@ -27,6 +27,7 @@ .reference_value_shape = {reference_value_shape}, .reference_value_size = {reference_value_size}, .degree = {degree}, + .symmetric = {symmetric}, .block_size = {block_size}, .basix_family = {basix_family}, .basix_cell = {basix_cell}, diff --git a/ffcx/codegeneration/ufcx.h b/ffcx/codegeneration/ufcx.h index 8ea62e3d8..3d867caa2 100644 --- a/ffcx/codegeneration/ufcx.h +++ b/ffcx/codegeneration/ufcx.h @@ -107,6 +107,9 @@ extern "C" /// Maximum polynomial degree of the finite element function space int degree; + /// Is the value a symmetric 2-tensor + bool symmetric; + /// Block size for a VectorElement. For a TensorElement, this is the /// product of the tensor's dimensions int block_size; diff --git a/ffcx/ir/representation.py b/ffcx/ir/representation.py index 1cc3c9d0d..bc24e04fc 100644 --- a/ffcx/ir/representation.py +++ b/ffcx/ir/representation.py @@ -97,6 +97,7 @@ class ElementIR(typing.NamedTuple): space_dimension: int reference_value_shape: tuple[int, ...] degree: int + symmetric: bool num_sub_elements: int block_size: int sub_elements: list[str] @@ -296,6 +297,9 @@ def _compute_element_ir(element, element_numbers, finite_element_names): ir["basix_cell"] = element.cell_type ir["discontinuous"] = element.discontinuous ir["degree"] = element.degree + ir["symmetric"] = isinstance(element, basix.ufl._BlockedElement) and isinstance( + element._pullback, ufl.pullback.SymmetricPullback + ) ir["reference_value_shape"] = element.reference_value_shape ir["num_sub_elements"] = element.num_sub_elements From 3975ad4dfcec341b3e51149f02530b867180abd4 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 25 Apr 2024 08:47:47 +0200 Subject: [PATCH 02/25] Kebab case build-wheels.yml (#685) --- .github/workflows/build-wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 88e15dc79..03f799f82 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -65,11 +65,11 @@ jobs: with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} - repository_url: https://upload.pypi.org/legacy/ + repository-url: https://upload.pypi.org/legacy/ - name: Publish to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 if: ${{ github.event.inputs.test_pypi_publish == 'true' }} with: user: __token__ password: ${{ secrets.PYPI_TEST_TOKEN }} - repository_url: https://test.pypi.org/legacy/ + repository-url: https://test.pypi.org/legacy/ From b92af3ae08241138536d9dedff5453ed93768dcf Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 25 Apr 2024 17:59:34 +0200 Subject: [PATCH 03/25] Bump version. (#686) --- cmake/CMakeLists.txt | 3 +-- ffcx/codegeneration/ufcx.h | 2 +- pyproject.toml | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 16ebfce92..332fa9d18 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.19) -project(ufcx VERSION 0.7.0 DESCRIPTION "UFCx interface header for finite element kernels" +project(ufcx VERSION 0.9.0 DESCRIPTION "UFCx interface header for finite element kernels" LANGUAGES C HOMEPAGE_URL https://github.com/fenics/ffcx) include(GNUInstallDirs) @@ -39,4 +39,3 @@ install(FILES ${PROJECT_SOURCE_DIR}/../ffcx/codegeneration/ufcx.h TYPE INCLUDE) # Configure and install pkgconfig file configure_file(ufcx.pc.in ufcx.pc @ONLY) install(FILES ${PROJECT_BINARY_DIR}/ufcx.pc DESTINATION ${CMAKE_INSTALL_DATADIR}/pkgconfig) - diff --git a/ffcx/codegeneration/ufcx.h b/ffcx/codegeneration/ufcx.h index 3d867caa2..7231b4b84 100644 --- a/ffcx/codegeneration/ufcx.h +++ b/ffcx/codegeneration/ufcx.h @@ -11,7 +11,7 @@ #pragma once #define UFCX_VERSION_MAJOR 0 -#define UFCX_VERSION_MINOR 8 +#define UFCX_VERSION_MINOR 9 #define UFCX_VERSION_MAINTENANCE 0 #define UFCX_VERSION_RELEASE 0 diff --git a/pyproject.toml b/pyproject.toml index 8d47af6a6..0b69bab26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "fenics-ffcx" -version = "0.8.0.dev0" +version = "0.9.0.dev0" description = "The FEniCSx Form Compiler" readme = "README.md" requires-python = ">=3.9" @@ -17,8 +17,8 @@ dependencies = [ "numpy>=1.21", "cffi", "setuptools; python_version >= '3.12'", # cffi with compilation support requires setuptools - "fenics-basix >= 0.8.0.dev0, <0.9.0", - "fenics-ufl >= 2023.3.0.dev0, <2023.4.0", + "fenics-basix >= 0.9.0.dev0, <0.10.0", + "fenics-ufl >= 2024.2.0.dev0, <2024.3.0", ] [project.urls] From 644c09bc61822ffeb723c5a8291c25794092e3ae Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Fri, 26 Apr 2024 17:41:55 +0100 Subject: [PATCH 04/25] Remove ufcx_dofmap and ufcx_element (#681) * branches * remove element and dofmap * basix main * fstrings * dolfinx main --- .../C/basix_custom_element_template.py | 39 --- ffcx/codegeneration/C/dofmap.py | 95 ------- ffcx/codegeneration/C/dofmap_template.py | 39 --- ffcx/codegeneration/C/expressions.py | 4 +- ffcx/codegeneration/C/finite_element.py | 232 ----------------- .../C/finite_element_template.py | 44 ---- ffcx/codegeneration/C/form.py | 19 +- ffcx/codegeneration/C/form_template.py | 2 - ffcx/codegeneration/C/integrals.py | 2 +- ffcx/codegeneration/codegeneration.py | 14 +- ffcx/codegeneration/jit.py | 88 ------- ffcx/codegeneration/ufcx.h | 185 +------------- ffcx/ir/analysis/graph.py | 2 +- ffcx/ir/analysis/reconstruct.py | 2 +- ffcx/ir/analysis/valuenumbering.py | 2 +- ffcx/ir/representation.py | 234 ++---------------- ffcx/naming.py | 22 +- test/test_blocked_elements.py | 122 --------- 18 files changed, 36 insertions(+), 1111 deletions(-) delete mode 100644 ffcx/codegeneration/C/basix_custom_element_template.py delete mode 100644 ffcx/codegeneration/C/dofmap.py delete mode 100644 ffcx/codegeneration/C/dofmap_template.py delete mode 100644 ffcx/codegeneration/C/finite_element.py delete mode 100644 ffcx/codegeneration/C/finite_element_template.py delete mode 100644 test/test_blocked_elements.py diff --git a/ffcx/codegeneration/C/basix_custom_element_template.py b/ffcx/codegeneration/C/basix_custom_element_template.py deleted file mode 100644 index b2e7d564f..000000000 --- a/ffcx/codegeneration/C/basix_custom_element_template.py +++ /dev/null @@ -1,39 +0,0 @@ -# Code generation format strings for UFC (Unified Form-assembly Code) -# This code is released into the public domain. -# -# The FEniCS Project (http://www.fenicsproject.org/) 2018. -"""Code generation strings for a Basix custom element.""" - -factory = """ -// Code for custom element {factory_name} - -{value_shape_init} -{wcoeffs_init} -{npts_init} -{ndofs_init} -{x_init} -{M_init} - -ufcx_basix_custom_finite_element {factory_name} = -{{ - .cell_type = {cell_type}, - .value_shape_length = {value_shape_length}, - .value_shape = {value_shape}, - .wcoeffs_rows = {wcoeffs_rows}, - .wcoeffs_cols = {wcoeffs_cols}, - .wcoeffs = {wcoeffs}, - .npts = {npts}, - .ndofs = {ndofs}, - .x = {x}, - .M = {M}, - .map_type = {map_type}, - .sobolev_space = {sobolev_space}, - .discontinuous = {discontinuous}, - .embedded_subdegree = {embedded_subdegree}, - .interpolation_nderivs = {interpolation_nderivs}, - .embedded_superdegree = {embedded_superdegree}, - .polyset_type = {polyset_type} -}}; - -// End of code for custom element {factory_name} -""" diff --git a/ffcx/codegeneration/C/dofmap.py b/ffcx/codegeneration/C/dofmap.py deleted file mode 100644 index a379f10f4..000000000 --- a/ffcx/codegeneration/C/dofmap.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright (C) 2009-2018 Anders Logg, Martin Sandve Alnæs and Garth N. Wells -# -# This file is part of FFCx.(https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Note: Most of the code in this file is a direct translation from the -# old implementation in FFC -"""Generate UFC code for a DOF map.""" - -import logging - -import ffcx.codegeneration.C.dofmap_template as ufcx_dofmap - -logger = logging.getLogger("ffcx") - - -def generator(ir, options): - """Generate UFC code for a dofmap.""" - logger.info("Generating code for dofmap:") - 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"] = 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 - - flattened_entity_dofs = [] - entity_dof_offsets = [0] - for dim in ir.entity_dofs: - for ent in dim: - for v in ent: - flattened_entity_dofs.append(v) - entity_dof_offsets.append(len(flattened_entity_dofs)) - d["entity_dofs"] = f"entity_dofs_{ir.name}" - values = ", ".join(str(i) for i in flattened_entity_dofs) - sizes = len(flattened_entity_dofs) - d["entity_dofs_init"] = f"int entity_dofs_{ir.name}[{sizes}] = {{{values}}};" - d["entity_dof_offsets"] = f"entity_dof_offsets_{ir.name}" - values = ", ".join(str(i) for i in entity_dof_offsets) - sizes = len(entity_dof_offsets) - d["entity_dof_offsets_init"] = f"int entity_dof_offsets_{ir.name}[{sizes}] = {{{values}}};" - - # Closure - flattened_entity_closure_dofs = [] - entity_closure_dof_offsets = [0] - for dim in ir.entity_closure_dofs: - for ent in dim: - for v in ent: - flattened_entity_closure_dofs.append(v) - entity_closure_dof_offsets.append(len(flattened_entity_closure_dofs)) - d["entity_closure_dofs"] = f"entity_closure_dofs_{ir.name}" - values = ", ".join(str(i) for i in flattened_entity_closure_dofs) - sizes = len(flattened_entity_closure_dofs) - d["entity_closure_dofs_init"] = f"int entity_closure_dofs_{ir.name}[{sizes}] = {{{values}}};" - d["entity_closure_dof_offsets"] = f"entity_closure_dof_offsets_{ir.name}" - values = ", ".join(str(i) for i in entity_closure_dof_offsets) - sizes = len(entity_dof_offsets) - d["entity_closure_dof_offsets_init"] = ( - f"int entity_closure_dof_offsets_{ir.name}[{sizes}] = {{{values}}};" - ) - - d["block_size"] = ir.block_size - - if len(ir.sub_dofmaps) > 0: - values = ", ".join(f"&{dofmap}" for dofmap in ir.sub_dofmaps) - sizes = len(ir.sub_dofmaps) - d["sub_dofmaps_initialization"] = ( - f"ufcx_dofmap* sub_dofmaps_{ir.name}[{sizes}] = {{{values}}};" - ) - d["sub_dofmaps"] = f"sub_dofmaps_{ir.name}" - else: - d["sub_dofmaps_initialization"] = "" - d["sub_dofmaps"] = "NULL" - - # Check that no keys are redundant or have been missed - from string import Formatter - - fields = [fname for _, fname, _, _ in Formatter().parse(ufcx_dofmap.factory) if fname] - # Remove square brackets from any field names - fields = [f.split("[")[0] for f in fields] - assert set(fields) == set(d.keys()), "Mismatch between keys in template and in formatting dict." - - # Format implementation code - implementation = ufcx_dofmap.factory.format_map(d) - - # Format declaration - declaration = ufcx_dofmap.declaration.format(factory_name=ir.name) - - return declaration, implementation diff --git a/ffcx/codegeneration/C/dofmap_template.py b/ffcx/codegeneration/C/dofmap_template.py deleted file mode 100644 index 38f54189d..000000000 --- a/ffcx/codegeneration/C/dofmap_template.py +++ /dev/null @@ -1,39 +0,0 @@ -# Code generation format strings for UFC (Unified Form-assembly Code) -# This code is released into the public domain. -# -# The FEniCS Project (http://www.fenicsproject.org/) 2018. -"""Code generation strings for a DOF map.""" - -declaration = """ -extern ufcx_dofmap {factory_name}; -""" - -factory = """ -// Code for dofmap {factory_name} - -{sub_dofmaps_initialization} - -{entity_dofs_init} - -{entity_dof_offsets_init} - -{entity_closure_dofs_init} - -{entity_closure_dof_offsets_init} - -ufcx_dofmap {factory_name} = -{{ - .signature = {signature}, - .num_global_support_dofs = {num_global_support_dofs}, - .num_element_support_dofs = {num_element_support_dofs}, - .block_size = {block_size}, - .entity_dofs = {entity_dofs}, - .entity_dof_offsets = {entity_dof_offsets}, - .entity_closure_dofs = {entity_closure_dofs}, - .entity_closure_dof_offsets = {entity_closure_dof_offsets}, - .num_sub_dofmaps = {num_sub_dofmaps}, - .sub_dofmaps = {sub_dofmaps} -}}; - -// End of code for dofmap {factory_name} -""" diff --git a/ffcx/codegeneration/C/expressions.py b/ffcx/codegeneration/C/expressions.py index ed461a0b4..60d7d07f6 100644 --- a/ffcx/codegeneration/C/expressions.py +++ b/ffcx/codegeneration/C/expressions.py @@ -108,7 +108,6 @@ def generator(ir, options): # ufcx_function_space is generated (also for ufcx_form) for name, ( element, - dofmap, cmap_family, cmap_degree, cmap_celltype, @@ -117,8 +116,7 @@ def generator(ir, options): ) in ir.function_spaces.items(): code += [f"static ufcx_function_space function_space_{name}_{ir.name_from_uflfile} ="] code += ["{"] - code += [f".finite_element = &{element},"] - code += [f".dofmap = &{dofmap},"] + code += [f".finite_element = {element},"] code += [f'.geometry_family = "{cmap_family}",'] code += [f".geometry_degree = {cmap_degree},"] code += [f".geometry_basix_cell = {int(cmap_celltype)},"] diff --git a/ffcx/codegeneration/C/finite_element.py b/ffcx/codegeneration/C/finite_element.py deleted file mode 100644 index 7f3356562..000000000 --- a/ffcx/codegeneration/C/finite_element.py +++ /dev/null @@ -1,232 +0,0 @@ -# Copyright (C) 2009-2022 Anders Logg, Martin Sandve Alnæs, Matthew Scroggs -# -# 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. -"""Generate UFC code for a finite element.""" - -import logging - -import ufl - -import ffcx.codegeneration.C.basix_custom_element_template as ufcx_basix_custom_finite_element -import ffcx.codegeneration.C.finite_element_template as ufcx_finite_element -import ffcx.codegeneration.C.quadrature_rule_template as ufcx_quadrature_rule - -logger = logging.getLogger("ffcx") -index_type = "int" - - -def generator(ir, options): - """Generate UFC code for a finite element.""" - logger.info("Generating code for finite element:") - logger.info(f"--- degree: {ir.degree}") - logger.info(f"--- value shape: {ir.reference_value_shape}") - logger.info(f"--- name: {ir.name}") - - d = {} - d["factory_name"] = ir.name - d["signature"] = f'"{ir.signature}"' - d["topological_dimension"] = ir.topological_dimension - d["cell_shape"] = ir.cell_shape - d["element_type"] = ir.element_type - d["space_dimension"] = ir.space_dimension - d["reference_value_rank"] = len(ir.reference_value_shape) - d["reference_value_size"] = ufl.product(ir.reference_value_shape) - d["degree"] = ir.degree - d["num_sub_elements"] = ir.num_sub_elements - d["block_size"] = ir.block_size - d["discontinuous"] = "true" if ir.discontinuous else "false" - d["symmetric"] = "true" if ir.symmetric else "false" - - if ir.lagrange_variant is None: - d["lagrange_variant"] = -1 - else: - d["lagrange_variant"] = int(ir.lagrange_variant) - - if ir.dpc_variant is None: - d["dpc_variant"] = -1 - else: - d["dpc_variant"] = int(ir.dpc_variant) - - if ir.basix_family is None: - d["basix_family"] = -1 - else: - d["basix_family"] = int(ir.basix_family) - if ir.basix_cell is None: - d["basix_cell"] = -1 - else: - d["basix_cell"] = int(ir.basix_cell) - - if len(ir.reference_value_shape) > 0: - d["reference_value_shape"] = f"reference_value_shape_{ir.name}" - values = ", ".join(str(i) for i in ir.reference_value_shape) - sizes = len(ir.reference_value_shape) - d["reference_value_shape_init"] = ( - f"int reference_value_shape_{ir.name}[{sizes}] = {{{values}}};" - ) - else: - d["reference_value_shape"] = "NULL" - d["reference_value_shape_init"] = "" - - if len(ir.sub_elements) > 0: - d["sub_elements"] = f"sub_elements_{ir.name}" - values = ", ".join(f"&{el}" for el in ir.sub_elements) - sizes = len(ir.sub_elements) - d["sub_elements_init"] = ( - f"ufcx_finite_element* sub_elements_{ir.name}[{sizes}] = {{{values}}};" - ) - else: - d["sub_elements"] = "NULL" - d["sub_elements_init"] = "" - - if ir.custom_element is not None: - d["custom_element"] = f"&custom_element_{ir.name}" - d["custom_element_init"] = generate_custom_element( - f"custom_element_{ir.name}", ir.custom_element - ) - else: - d["custom_element"] = "NULL" - d["custom_element_init"] = "" - - if ir.custom_quadrature is not None: - d["custom_quadrature"] = f"&custom_quadrature_{ir.name}" - d["custom_quadrature_init"] = generate_custom_quadrature( - f"custom_quadrature_{ir.name}", ir.custom_quadrature - ) - else: - d["custom_quadrature"] = "NULL" - d["custom_quadrature_init"] = "" - - # Check that no keys are redundant or have been missed - from string import Formatter - - fieldnames = [ - fname for _, fname, _, _ in Formatter().parse(ufcx_finite_element.factory) if fname - ] - assert set(fieldnames) == set( - d.keys() - ), "Mismatch between keys in template and in formatting dict" - - # Format implementation code - implementation = ufcx_finite_element.factory.format_map(d) - - # Format declaration - declaration = ufcx_finite_element.declaration.format(factory_name=ir.name) - - return declaration, implementation - - -def generate_custom_element(name, ir): - """Generate UFC code for a custom element.""" - d = {} - d["factory_name"] = name - d["cell_type"] = int(ir.cell_type) - d["polyset_type"] = int(ir.polyset_type) - d["map_type"] = int(ir.map_type) - d["sobolev_space"] = int(ir.sobolev_space) - d["embedded_subdegree"] = ir.embedded_subdegree - d["embedded_superdegree"] = ir.embedded_superdegree - d["discontinuous"] = "true" if ir.discontinuous else "false" - d["interpolation_nderivs"] = ir.interpolation_nderivs - d["value_shape_length"] = len(ir.value_shape) - if len(ir.value_shape) > 0: - d["value_shape"] = f"value_shape_{name}" - values = ", ".join(str(i) for i in ir.value_shape) - sizes = len(ir.value_shape) - d["value_shape_init"] = f"int value_shape_{name}[{sizes}] = {{{values}}};" - else: - d["value_shape"] = "NULL" - d["value_shape_init"] = "" - - d["wcoeffs_rows"] = ir.wcoeffs.shape[0] - d["wcoeffs_cols"] = ir.wcoeffs.shape[1] - d["wcoeffs"] = f"wcoeffs_{name}" - d["wcoeffs_init"] = f"double wcoeffs_{name}[{ir.wcoeffs.shape[0] * ir.wcoeffs.shape[1]}] = " - d["wcoeffs_init"] += "{" + ",".join([f" {i}" for row in ir.wcoeffs for i in row]) + "};" - - npts = [] - x = [] - for entity in ir.x: - for points in entity: - npts.append(points.shape[0]) - for row in points: - for i in row: - x.append(i) - d["npts"] = f"npts_{name}" - d["npts_init"] = f"int npts_{name}[{len(npts)}] = " - d["npts_init"] += "{" + ",".join([f" {i}" for i in npts]) + "};" - d["x"] = f"x_{name}" - d["x_init"] = f"double x_{name}[{len(x)}] = " - d["x_init"] += "{" + ",".join([f" {i}" for i in x]) + "};" - ndofs = [] - M = [] - for entity in ir.M: - for mat4d in entity: - ndofs.append(mat4d.shape[0]) - for mat3d in mat4d: - for mat2d in mat3d: - for row in mat2d: - for i in row: - M.append(i) - - d["ndofs"] = f"ndofs_{name}" - d["ndofs_init"] = f"int ndofs_{name}[{len(ndofs)}] = " - d["ndofs_init"] += "{" + ",".join([f" {i}" for i in ndofs]) + "};" - d["M"] = f"M_{name}" - d["M_init"] = f"double M_{name}[{len(M)}] = " - d["M_init"] += "{" + ",".join([f" {i}" for i in M]) + "};" - - # Check that no keys are redundant or have been missed - from string import Formatter - - fieldnames = [ - fname - for _, fname, _, _ in Formatter().parse(ufcx_basix_custom_finite_element.factory) - if fname - ] - assert set(fieldnames) == set( - d.keys() - ), "Mismatch between keys in template and in formatting dict" - - # Format implementation code - implementation = ufcx_basix_custom_finite_element.factory.format_map(d) - - return implementation - - -def generate_custom_quadrature(name, ir): - """Generate UFC code for a custom quadrature rule.""" - npts = ir.points.shape[0] - tdim = ir.points.shape[1] - - d = {} - d["factory_name"] = name - d["cell_shape"] = ir.cell_shape - d["topological_dimension"] = tdim - d["npts"] = npts - d["points"] = f"points_{name}" - d["points_init"] = f"double points_{name}[{npts * tdim}] = " - d["points_init"] += "{" + ",".join([f" {i}" for p in ir.points for i in p]) + "};" - d["weights"] = f"weights_{name}" - d["weights_init"] = f"double weights_{name}[{npts}] = " - d["weights_init"] += "{" + ",".join([f" {i}" for i in ir.weights]) + "};" - - # Check that no keys are redundant or have been missed - from string import Formatter - - fieldnames = [ - fname for _, fname, _, _ in Formatter().parse(ufcx_quadrature_rule.factory) if fname - ] - assert set(fieldnames) == set( - d.keys() - ), "Mismatch between keys in template and in formatting dict" - - # Format implementation code - implementation = ufcx_quadrature_rule.factory.format_map(d) - - return implementation diff --git a/ffcx/codegeneration/C/finite_element_template.py b/ffcx/codegeneration/C/finite_element_template.py deleted file mode 100644 index 6bf37a6aa..000000000 --- a/ffcx/codegeneration/C/finite_element_template.py +++ /dev/null @@ -1,44 +0,0 @@ -# Code generation format strings for UFC (Unified Form-assembly Code) -# This code is released into the public domain. -# -# The FEniCS Project (http://www.fenicsproject.org/) 2018. -"""Code generation strings for a finite element.""" - -declaration = """ -extern ufcx_finite_element {factory_name}; -""" - -factory = """ -// Code for element {factory_name} - -{reference_value_shape_init} -{sub_elements_init} -{custom_element_init} -{custom_quadrature_init} - -ufcx_finite_element {factory_name} = -{{ - .signature = {signature}, - .cell_shape = {cell_shape}, - .element_type = {element_type}, - .topological_dimension = {topological_dimension}, - .space_dimension = {space_dimension}, - .reference_value_rank = {reference_value_rank}, - .reference_value_shape = {reference_value_shape}, - .reference_value_size = {reference_value_size}, - .degree = {degree}, - .symmetric = {symmetric}, - .block_size = {block_size}, - .basix_family = {basix_family}, - .basix_cell = {basix_cell}, - .discontinuous = {discontinuous}, - .lagrange_variant = {lagrange_variant}, - .dpc_variant = {dpc_variant}, - .num_sub_elements = {num_sub_elements}, - .sub_elements = {sub_elements}, - .custom_element = {custom_element}, - .custom_quadrature = {custom_quadrature} -}}; - -// End of code for element {factory_name} -""" diff --git a/ffcx/codegeneration/C/form.py b/ffcx/codegeneration/C/form.py index 56747f909..6d02273b6 100644 --- a/ffcx/codegeneration/C/form.py +++ b/ffcx/codegeneration/C/form.py @@ -69,24 +69,13 @@ def generator(ir, options): if len(ir.finite_elements) > 0: d["finite_elements"] = f"finite_elements_{ir.name}" - values = ", ".join(f"&{el}" for el in ir.finite_elements) + values = ", ".join(f"{el}" for el in ir.finite_elements) sizes = len(ir.finite_elements) - d["finite_elements_init"] = ( - f"ufcx_finite_element* finite_elements_{ir.name}[{sizes}] = {{{values}}};" - ) + d["finite_elements_init"] = f"long int finite_elements_{ir.name}[{sizes}] = {{{values}}};" else: d["finite_elements"] = "NULL" d["finite_elements_init"] = "" - if len(ir.dofmaps) > 0: - d["dofmaps"] = f"dofmaps_{ir.name}" - values = ", ".join(f"&{dofmap}" for dofmap in ir.dofmaps) - sizes = len(ir.dofmaps) - d["dofmaps_init"] = f"ufcx_dofmap* dofmaps_{ir.name}[{sizes}] = {{{values}}};" - else: - d["dofmaps"] = "NULL" - d["dofmaps_init"] = "" - integrals = [] integral_ids = [] integral_offsets = [0] @@ -134,7 +123,6 @@ def generator(ir, options): # ufcx_function_space is generated for name, ( element, - dofmap, cmap_family, cmap_degree, cmap_celltype, @@ -143,8 +131,7 @@ def generator(ir, options): ) in ir.function_spaces.items(): code += [f"static ufcx_function_space functionspace_{name} ="] code += ["{"] - code += [f".finite_element = &{element},"] - code += [f".dofmap = &{dofmap},"] + code += [f".finite_element = {element},"] code += [f'.geometry_family = "{cmap_family}",'] code += [f".geometry_degree = {cmap_degree},"] code += [f".geometry_basix_cell = {int(cmap_celltype)},"] diff --git a/ffcx/codegeneration/C/form_template.py b/ffcx/codegeneration/C/form_template.py index dfce2f14a..5e9cfbd5c 100644 --- a/ffcx/codegeneration/C/form_template.py +++ b/ffcx/codegeneration/C/form_template.py @@ -23,7 +23,6 @@ // Code for form {factory_name} {original_coefficient_position_init} -{dofmaps_init} {finite_elements_init} {form_integral_offsets_init} {form_integrals_init} @@ -45,7 +44,6 @@ .constant_name_map = {constant_names}, .finite_elements = {finite_elements}, - .dofmaps = {dofmaps}, .form_integrals = {form_integrals}, .form_integral_ids = {form_integral_ids}, diff --git a/ffcx/codegeneration/C/integrals.py b/ffcx/codegeneration/C/integrals.py index 58ab1c9b7..d301eceda 100644 --- a/ffcx/codegeneration/C/integrals.py +++ b/ffcx/codegeneration/C/integrals.py @@ -75,7 +75,7 @@ def generator(ir: IntegralIR, options): needs_facet_permutations="true" if ir.needs_facet_permutations else "false", scalar_type=dtype_to_c_type(options["scalar_type"]), geom_type=dtype_to_c_type(dtype_to_scalar_dtype(options["scalar_type"])), - coordinate_element=f"&{ir.coordinate_element}", + coordinate_element=f"{ir.coordinate_element}", tabulate_tensor_float32=code["tabulate_tensor_float32"], tabulate_tensor_float64=code["tabulate_tensor_float64"], tabulate_tensor_complex64=code["tabulate_tensor_complex64"], diff --git a/ffcx/codegeneration/codegeneration.py b/ffcx/codegeneration/codegeneration.py index 9f573b703..9564c3837 100644 --- a/ffcx/codegeneration/codegeneration.py +++ b/ffcx/codegeneration/codegeneration.py @@ -17,10 +17,8 @@ import numpy.typing as npt -from ffcx.codegeneration.C.dofmap import generator as dofmap_generator from ffcx.codegeneration.C.expressions import generator as expression_generator from ffcx.codegeneration.C.file import generator as file_generator -from ffcx.codegeneration.C.finite_element import generator as finite_element_generator from ffcx.codegeneration.C.form import generator as form_generator from ffcx.codegeneration.C.integrals import generator as integral_generator from ffcx.ir.representation import DataIR @@ -31,13 +29,10 @@ class CodeBlocks(typing.NamedTuple): """Storage of code blocks of the form (declaration, implementation). - Blocks for elements, dofmaps, integrals, forms and expressions, - and start and end of file output + Blocks for integrals, forms and expressions, and start and end of file output """ file_pre: list[tuple[str, str]] - elements: list[tuple[str, str]] - dofmaps: list[tuple[str, str]] integrals: list[tuple[str, str]] forms: list[tuple[str, str]] expressions: list[tuple[str, str]] @@ -50,11 +45,6 @@ def generate_code(ir: DataIR, options: dict[str, int | float | npt.DTypeLike]) - logger.info("Compiler stage 3: Generating code") logger.info(79 * "*") - # Generate code for finite_elements - code_finite_elements = [ - finite_element_generator(element_ir, options) for element_ir in ir.elements - ] - code_dofmaps = [dofmap_generator(dofmap_ir, options) for dofmap_ir in ir.dofmaps] code_integrals = [integral_generator(integral_ir, options) for integral_ir in ir.integrals] code_forms = [form_generator(form_ir, options) for form_ir in ir.forms] code_expressions = [ @@ -63,8 +53,6 @@ def generate_code(ir: DataIR, options: dict[str, int | float | npt.DTypeLike]) - code_file_pre, code_file_post = file_generator(options) return CodeBlocks( file_pre=[code_file_pre], - elements=code_finite_elements, - dofmaps=code_dofmaps, integrals=code_integrals, forms=code_forms, expressions=code_expressions, diff --git a/ffcx/codegeneration/jit.py b/ffcx/codegeneration/jit.py index d63f92800..aa903c496 100644 --- a/ffcx/codegeneration/jit.py +++ b/ffcx/codegeneration/jit.py @@ -20,7 +20,6 @@ import cffi import numpy as np -import numpy.typing as npt import ffcx import ffcx.naming @@ -38,12 +37,6 @@ header = header.replace("{", "{{").replace("}", "}}") UFC_HEADER_DECL = header + "\n" -UFC_ELEMENT_DECL = "\n".join( - re.findall("typedef struct ufcx_finite_element.*?ufcx_finite_element;", ufcx_h, re.DOTALL) -) -UFC_DOFMAP_DECL = "\n".join( - re.findall("typedef struct ufcx_dofmap.*?ufcx_dofmap;", ufcx_h, re.DOTALL) -) UFC_FORM_DECL = "\n".join(re.findall("typedef struct ufcx_form.*?ufcx_form;", ufcx_h, re.DOTALL)) UFC_INTEGRAL_DECL = "\n".join( @@ -133,83 +126,6 @@ def _compilation_signature(cffi_extra_compile_args=None, cffi_debug=None): ) -def compile_elements( - elements, - options: dict[str, int | float | npt.DTypeLike] | None = None, - cache_dir=None, - timeout=10, - cffi_extra_compile_args=None, - cffi_verbose=False, - cffi_debug=None, - cffi_libraries=None, - visualise: bool = False, -): - """Compile a list of UFL elements and dofmaps into Python objects.""" - p = ffcx.options.get_options(options) - - # Get a signature for these elements - module_name = "libffcx_elements_" + ffcx.naming.compute_signature( - elements, - _compute_option_signature(p) + _compilation_signature(cffi_extra_compile_args, cffi_debug), - ) - - names = [] - for e in elements: - name = ffcx.naming.finite_element_name(e, module_name) - names.append(name) - name = ffcx.naming.dofmap_name(e, module_name) - names.append(name) - - if cache_dir is not None: - cache_dir = Path(cache_dir) - obj, mod = get_cached_module(module_name, names, cache_dir, timeout) - if obj is not None: - # Pair up elements with dofmaps - obj = list(zip(obj[::2], obj[1::2])) - return obj, mod, (None, None) - else: - cache_dir = Path(tempfile.mkdtemp()) - - try: - decl = ( - UFC_HEADER_DECL.format(np.dtype(p["scalar_type"]).name) # type: ignore - + UFC_ELEMENT_DECL - + UFC_DOFMAP_DECL - ) - element_template = "extern ufcx_finite_element {name};\n" - dofmap_template = "extern ufcx_dofmap {name};\n" - for i in range(len(elements)): - decl += element_template.format(name=names[i * 2]) - decl += dofmap_template.format(name=names[i * 2 + 1]) - - impl = _compile_objects( - decl, - elements, - names, - module_name, - p, - cache_dir, - cffi_extra_compile_args, - cffi_verbose, - cffi_debug, - cffi_libraries, - visualise=visualise, - ) - except Exception as e: - try: - # remove c file so that it will not timeout next time - c_filename = cache_dir.joinpath(module_name + ".c") - os.replace(c_filename, c_filename.with_suffix(".c.failed")) - except Exception: - pass - raise e - - objects, module = _load_objects(cache_dir, module_name, names) - # Pair up elements with dofmaps - objects = list(zip(objects[::2], objects[1::2])) - return objects, module, (decl, impl) - - def compile_forms( forms, options=None, @@ -243,8 +159,6 @@ def compile_forms( try: decl = ( UFC_HEADER_DECL.format(np.dtype(p["scalar_type"]).name) # type: ignore - + UFC_ELEMENT_DECL - + UFC_DOFMAP_DECL + UFC_INTEGRAL_DECL + UFC_FORM_DECL ) @@ -324,8 +238,6 @@ def compile_expressions( try: decl = ( UFC_HEADER_DECL.format(np.dtype(p["scalar_type"]).name) # type: ignore - + UFC_ELEMENT_DECL - + UFC_DOFMAP_DECL + UFC_INTEGRAL_DECL + UFC_FORM_DECL + UFC_EXPRESSION_DECL diff --git a/ffcx/codegeneration/ufcx.h b/ffcx/codegeneration/ufcx.h index 7231b4b84..1851d5c03 100644 --- a/ffcx/codegeneration/ufcx.h +++ b/ffcx/codegeneration/ufcx.h @@ -60,144 +60,12 @@ extern "C" interior_facet = 2 } ufcx_integral_type; - typedef enum - { - ufcx_basix_element = 0, - ufcx_mixed_element = 1, - ufcx_quadrature_element = 2, - ufcx_basix_custom_element = 3, - ufcx_real_element = 4, - } ufcx_element_type; - /// Forward declarations - typedef struct ufcx_finite_element ufcx_finite_element; - typedef struct ufcx_basix_custom_finite_element ufcx_basix_custom_finite_element; typedef struct ufcx_quadrature_rule ufcx_quadrature_rule; - typedef struct ufcx_dofmap ufcx_dofmap; typedef struct ufcx_function_space ufcx_function_space; // - typedef struct ufcx_finite_element - { - /// String identifying the finite element - const char* signature; - - /// Cell shape - ufcx_shape cell_shape; - - /// Element type - ufcx_element_type element_type; - - /// Topological dimension of the cell - int topological_dimension; - - /// Dimension of the finite element function space - int space_dimension; - - /// Rank of the reference value space - int reference_value_rank; - - /// Dimension of the reference value space for axis i - int* reference_value_shape; - - /// Number of components of the reference value space - int reference_value_size; - - /// Maximum polynomial degree of the finite element function space - int degree; - - /// Is the value a symmetric 2-tensor - bool symmetric; - - /// Block size for a VectorElement. For a TensorElement, this is the - /// product of the tensor's dimensions - int block_size; - - /// Basix identifier of the family of the finite element function space - int basix_family; - - /// Basix identifier of the cell shape - int basix_cell; - - /// Indicates whether or not this is the discontinuous version of the element - bool discontinuous; - - /// The Lagrange variant to be passed to Basix's create_element function - int lagrange_variant; - - /// The DPC variant to be passed to Basix's create_element function - int dpc_variant; - - /// Number of sub elements (for a mixed element) - int num_sub_elements; - - /// Get a finite element for sub element i (for a mixed - /// element). - ufcx_finite_element** sub_elements; - - /// Pointer to data to recreate the element if it is a custom Basix element - ufcx_basix_custom_finite_element* custom_element; - - /// Pointer to data to recreate the custom quadrature rule if the element has one - ufcx_quadrature_rule* custom_quadrature; - } ufcx_finite_element; - - typedef struct ufcx_basix_custom_finite_element - { - /// Basix identifier of the cell shape - int cell_type; - - /// Dimension of the value space for axis i - int value_shape_length; - - /// Dimension of the value space for axis i - int* value_shape; - - /// The number of rows in the wcoeffs matrix - int wcoeffs_rows; - - /// The number of columns in the wcoeffs matrix - int wcoeffs_cols; - - /// The coefficients that define the polynomial set of the element in terms - /// of the orthonormal polynomials on the cell - double* wcoeffs; - - /// The number of interpolation points associated with each entity - int* npts; - - /// The number of DOFs associated with each entity - int* ndofs; - - // The coordinates of the interpolation points - double* x; - - // The entries in the interpolation matrices - double* M; - - /// The map type for the element - int map_type; - - /// The Sobolev space for the element - int sobolev_space; - - /// Indicates whether or not this is the discontinuous version of the element - bool discontinuous; - - /// The highest degree full polynomial space contained in this element - int embedded_subdegree; - - /// The number of derivatives needed when interpolating - int interpolation_nderivs; - - /// The highest degree of a polynomial in the element - int embedded_superdegree; - - /// The polyset type of the element - int polyset_type; - } ufcx_basix_custom_finite_element; - typedef struct ufcx_quadrature_rule { /// Cell shape @@ -216,41 +84,6 @@ extern "C" double* weights; } ufcx_quadrature_rule; - typedef struct ufcx_dofmap - { - /// String identifying the dofmap - const char* signature; - - /// Number of dofs with global support (i.e. global constants) - int num_global_support_dofs; - - /// Dimension of the local finite element function space for a cell - /// (not including global support dofs) - int num_element_support_dofs; - - /// Return the block size for a VectorElement or TensorElement - int block_size; - - /// Flattened list of dofs associated with each entity - int *entity_dofs; - - /// Offset for dofs of each entity in entity_dofs - int *entity_dof_offsets; - - /// Flattened list of closure dofs associated with each entity - int *entity_closure_dofs; - - /// Offset for closure dofs of each entity in entity_closure_dofs - int *entity_closure_dof_offsets; - - /// Number of sub dofmaps (for a mixed element) - int num_sub_dofmaps; - - /// Get a dofmap for sub dofmap i (for a mixed element) - ufcx_dofmap** sub_dofmaps; - - } ufcx_dofmap; - /// Tabulate integral into tensor A with compiled quadrature rule /// /// @param[out] A @@ -329,7 +162,7 @@ extern "C" bool needs_facet_permutations; /// Get the coordinate element associated with the geometry of the mesh. - ufcx_finite_element* coordinate_element; + long int coordinate_element; } ufcx_integral; typedef struct ufcx_expression @@ -426,20 +259,12 @@ extern "C" /// List of names of constants const char** constant_name_map; - /// Get a finite element for the i-th argument function, where 0 <= + /// Get the hash of the finite element for the i-th argument function, where 0 <= /// i < r + n. /// /// @param i Argument number if 0 <= i < r Coefficient number j = i /// - r if r + j <= i < r + n - ufcx_finite_element** finite_elements; - - /// Get a dofmap for the i-th argument function, where 0 <= i < r + - /// n. - /// - /// @param i - /// Argument number if 0 <= i < r - /// Coefficient number j=i-r if r+j <= i < r+n - ufcx_dofmap** dofmaps; + long int* finite_elements; /// List of cell, interior facet and exterior facet integrals ufcx_integral** form_integrals; @@ -455,8 +280,8 @@ extern "C" // FIXME: Formalise a UFCX 'function space' typedef struct ufcx_function_space { - ufcx_finite_element* finite_element; - ufcx_dofmap* dofmap; + /// Hash of the finite element + long int finite_element; /// The family of the finite element for the geometry map const char* geometry_family; diff --git a/ffcx/ir/analysis/graph.py b/ffcx/ir/analysis/graph.py index 1a73e783f..0f52bf63e 100644 --- a/ffcx/ir/analysis/graph.py +++ b/ffcx/ir/analysis/graph.py @@ -169,7 +169,7 @@ def rebuild_with_scalar_subexpressions(G): if isinstance(vop, ufl.classes.MultiIndex): # TODO: Store MultiIndex in G.V and allocate a symbol to it for this to work if not isinstance(expr, ufl.classes.IndexSum): - raise RuntimeError("Not expecting a %s." % type(expr)) + raise RuntimeError(f"Not expecting a {type(expr)}.") sops.append(()) else: # TODO: Build edge datastructure and use instead? diff --git a/ffcx/ir/analysis/reconstruct.py b/ffcx/ir/analysis/reconstruct.py index 19973fa24..82e2f22ef 100644 --- a/ffcx/ir/analysis/reconstruct.py +++ b/ffcx/ir/analysis/reconstruct.py @@ -182,4 +182,4 @@ def reconstruct(o, *args): if isinstance(o, k): return _reconstruct_call_lookup[k](o, *args) # Nothing found - raise RuntimeError("Not expecting expression of type %s in here." % type(o)) + raise RuntimeError(f"Not expecting expression of type {type(o)} in here.") diff --git a/ffcx/ir/analysis/valuenumbering.py b/ffcx/ir/analysis/valuenumbering.py index 5618cec15..512e6070c 100644 --- a/ffcx/ir/analysis/valuenumbering.py +++ b/ffcx/ir/analysis/valuenumbering.py @@ -84,7 +84,7 @@ def compute_symbols(self): if symbol is None: # Nothing found - raise RuntimeError("Not expecting type %s here." % type(expr)) + raise RuntimeError(f"Not expecting type {type(expr)} here.") self.V_symbols.append(symbol) diff --git a/ffcx/ir/representation.py b/ffcx/ir/representation.py index bc24e04fc..1d2a9ed62 100644 --- a/ffcx/ir/representation.py +++ b/ffcx/ir/representation.py @@ -6,9 +6,8 @@ # SPDX-License-Identifier: LGPL-3.0-or-later """Compiler stage 2: Code representation. -Module computes intermediate representations of forms, elements and -dofmaps. For each UFC function, we extract the data needed for code -generation at a later stage. +Module computes intermediate representations of forms. For each UFC +function, we extract the data needed for code generation at a later stage. The representation should conform strictly to the naming and order of functions in UFC. Thus, for code generation of the function "foo", one @@ -55,29 +54,11 @@ class FormIR(typing.NamedTuple): original_coefficient_position: list[int] coefficient_names: list[str] constant_names: list[str] - finite_elements: list[str] - dofmaps: list[str] + finite_elements: list[int] integral_names: dict[str, list[str]] subdomain_ids: dict[str, list[int]] -class CustomElementIR(typing.NamedTuple): - """Intermediate representation of a custom element.""" - - cell_type: basix.CellType - value_shape: tuple[int, ...] - wcoeffs: npt.NDArray[np.float64] - x: list[list[npt.NDArray[np.float64]]] - M: list[list[npt.NDArray[np.float64]]] - map_type: basix.MapType - sobolev_space: basix.SobolevSpace - interpolation_nderivs: int - discontinuous: bool - embedded_subdegree: int - embedded_superdegree: int - polyset_type: basix.PolysetType - - class QuadratureIR(typing.NamedTuple): """Intermediate representation of a quadrature rule.""" @@ -86,48 +67,6 @@ class QuadratureIR(typing.NamedTuple): weights: npt.NDArray[np.float64] -class ElementIR(typing.NamedTuple): - """Intermediate representation of an element.""" - - id: int - name: str - signature: str - cell_shape: str - topological_dimension: int - space_dimension: int - reference_value_shape: tuple[int, ...] - degree: int - symmetric: bool - num_sub_elements: int - block_size: int - sub_elements: list[str] - element_type: str - entity_dofs: list[list[list[int]]] - lagrange_variant: basix.LagrangeVariant - dpc_variant: basix.DPCVariant - basix_family: basix.ElementFamily - basix_cell: basix.CellType - discontinuous: bool - custom_element: CustomElementIR - custom_quadrature: QuadratureIR - - -class DofMapIR(typing.NamedTuple): - """Intermediate representation of a DOF map.""" - - id: int - name: str - signature: str - num_global_support_dofs: int - num_element_support_dofs: int - entity_dofs: list[list[list[int]]] - entity_closure_dofs: list[list[list[int]]] - num_entity_closure_dofs: list[list[int]] - num_sub_dofmaps: int - sub_dofmaps: list[str] - block_size: int - - class IntegralIR(typing.NamedTuple): """Intermediate representation of an integral.""" @@ -144,7 +83,7 @@ class IntegralIR(typing.NamedTuple): integrand: dict[QuadratureRule, dict] name: str needs_facet_permutations: bool - coordinate_element: str + coordinate_element: int class ExpressionIR(typing.NamedTuple): @@ -176,8 +115,6 @@ class ExpressionIR(typing.NamedTuple): class DataIR(typing.NamedTuple): """Intermediate representation of data.""" - elements: list[ElementIR] - dofmaps: list[DofMapIR] integrals: list[IntegralIR] forms: list[FormIR] expressions: list[ExpressionIR] @@ -198,10 +135,7 @@ def compute_ir( # Compute object names # NOTE: This is done here for performance reasons, because repeated calls # within each IR computation would be expensive due to UFL signature computations - finite_element_names = { - e: naming.finite_element_name(e, prefix) for e in analysis.unique_elements - } - dofmap_names = {e: naming.dofmap_name(e, prefix) for e in analysis.unique_elements} + finite_element_hashes = {e: hash(e) for e in analysis.unique_elements} integral_names = {} form_names = {} for fd_index, fd in enumerate(analysis.form_data): @@ -211,23 +145,13 @@ def compute_ir( fd.original_form, itg_data.integral_type, fd_index, itg_data.subdomain_id, prefix ) - ir_elements = [ - _compute_element_ir(e, analysis.element_numbers, finite_element_names) - for e in analysis.unique_elements - ] - - ir_dofmaps = [ - _compute_dofmap_ir(e, analysis.element_numbers, dofmap_names) - for e in analysis.unique_elements - ] - irs = [ _compute_integral_ir( fd, i, analysis.element_numbers, integral_names, - finite_element_names, + finite_element_hashes, options, visualise, ) @@ -243,8 +167,7 @@ def compute_ir( form_names, integral_names, analysis.element_numbers, - finite_element_names, - dofmap_names, + finite_element_hashes, object_names, ) for (i, fd) in enumerate(analysis.form_data) @@ -259,133 +182,26 @@ def compute_ir( options, visualise, object_names, - finite_element_names, - dofmap_names, + finite_element_hashes, ) for i, expr in enumerate(analysis.expressions) ] return DataIR( - elements=ir_elements, - dofmaps=ir_dofmaps, integrals=ir_integrals, forms=ir_forms, expressions=ir_expressions, ) -def _compute_element_ir(element, element_numbers, finite_element_names): - """Compute intermediate representation of element.""" - logger.info(f"Computing IR for element {element}") - - # Create basix elements - cell = element.cell - - # Store id - ir = {"id": element_numbers[element]} - ir["name"] = finite_element_names[element] - - # Compute data for each function - ir["signature"] = repr(element) - ir["cell_shape"] = element.cell_type.name - ir["topological_dimension"] = cell.topological_dimension() - ir["space_dimension"] = element.dim + element.num_global_support_dofs - ir["element_type"] = element.ufcx_element_type - ir["lagrange_variant"] = element.lagrange_variant - ir["dpc_variant"] = element.dpc_variant - ir["basix_family"] = element.element_family - ir["basix_cell"] = element.cell_type - ir["discontinuous"] = element.discontinuous - ir["degree"] = element.degree - ir["symmetric"] = isinstance(element, basix.ufl._BlockedElement) and isinstance( - element._pullback, ufl.pullback.SymmetricPullback - ) - ir["reference_value_shape"] = element.reference_value_shape - - ir["num_sub_elements"] = element.num_sub_elements - ir["sub_elements"] = [finite_element_names[e] for e in element.sub_elements] - - ir["block_size"] = element.block_size - if element.block_size > 1: - element = element._sub_element - - ir["entity_dofs"] = element.entity_dofs - - if element.is_custom_element: - ir["custom_element"] = _compute_custom_element_ir(element._element) - else: - ir["custom_element"] = None - - if element.has_custom_quadrature: - ir["custom_quadrature"] = _compute_custom_quadrature_ir(element) - else: - ir["custom_quadrature"] = None - - return ElementIR(**ir) - - -def _compute_custom_element_ir( - basix_element: basix.finite_element.FiniteElement, -) -> CustomElementIR: - """Compute intermediate representation of a custom Basix element.""" - ir: dict[str, typing.Any] = {} - ir["cell_type"] = basix_element.cell_type - ir["value_shape"] = basix_element.value_shape - ir["wcoeffs"] = basix_element.wcoeffs - ir["x"] = basix_element.x - ir["M"] = basix_element.M - ir["map_type"] = basix_element.map_type - ir["sobolev_space"] = basix_element.sobolev_space - ir["discontinuous"] = basix_element.discontinuous - ir["interpolation_nderivs"] = basix_element.interpolation_nderivs - ir["embedded_subdegree"] = basix_element.embedded_subdegree - ir["embedded_superdegree"] = basix_element.embedded_superdegree - ir["polyset_type"] = basix_element.polyset_type - - return CustomElementIR(**ir) - - -def _compute_custom_quadrature_ir(element: basix.ufl._ElementBase) -> QuadratureIR: - """Compute intermediate representation of a custom Basix element.""" - ir: dict[str, typing.Any] = {} - ir["cell_shape"] = element.cell_type.name - ir["points"], ir["weights"] = element.custom_quadrature() - - return QuadratureIR(**ir) - - -def _compute_dofmap_ir(element, element_numbers, dofmap_names): - """Compute intermediate representation of dofmap.""" - logger.info(f"Computing IR for dofmap of {element}") - - # Store id - ir = {"id": element_numbers[element]} - ir["name"] = dofmap_names[element] - - # Compute data for each function - ir["signature"] = "FFCx dofmap for " + repr(element) - ir["sub_dofmaps"] = [dofmap_names[e] for e in element.sub_elements] - ir["num_sub_dofmaps"] = element.num_sub_elements - - ir["block_size"] = element.block_size - if element.block_size > 1: - element = element._sub_element - - ir["entity_dofs"] = element.entity_dofs - ir["entity_closure_dofs"] = element.entity_closure_dofs - - num_dofs_per_entity_closure = [i[0] for i in element.num_entity_closure_dofs] - ir["num_entity_closure_dofs"] = num_dofs_per_entity_closure - ir["entity_closure_dofs"] = element.entity_closure_dofs - - ir["num_global_support_dofs"] = element.num_global_support_dofs - ir["num_element_support_dofs"] = element.dim - - return DofMapIR(**ir) - - def _compute_integral_ir( - form_data, form_index, element_numbers, integral_names, finite_element_names, options, visualise + form_data, + form_index, + element_numbers, + integral_names, + finite_element_hashes, + options, + visualise, ) -> list[IntegralIR]: """Compute intermediate representation for form integrals.""" _entity_types = { @@ -413,7 +229,7 @@ def _compute_integral_ir( "rank": form_data.rank, "entitytype": entitytype, "enabled_coefficients": itg_data.enabled_coefficients, - "coordinate_element": finite_element_names[itg_data.domain.ufl_coordinate_element()], + "coordinate_element": finite_element_hashes[itg_data.domain.ufl_coordinate_element()], } # Get element space dimensions @@ -612,8 +428,7 @@ def _compute_form_ir( form_names, integral_names, element_numbers, - finite_element_names, - dofmap_names, + finite_element_hashes, object_names, ) -> FormIR: """Compute intermediate representation of form.""" @@ -643,14 +458,10 @@ def _compute_form_ir( ir["original_coefficient_position"] = form_data.original_coefficient_positions ir["finite_elements"] = [ - finite_element_names[e] + finite_element_hashes[e] for e in form_data.argument_elements + form_data.coefficient_elements ] - ir["dofmaps"] = [ - dofmap_names[e] for e in form_data.argument_elements + form_data.coefficient_elements - ] - fs = {} for function in form_data.original_form.arguments() + tuple(form_data.reduced_coefficients): name = object_names.get(id(function), str(function)) @@ -667,8 +478,7 @@ def _compute_form_ir( degree = cmap.degree value_shape = space.value_shape fs[name] = ( - finite_element_names[el], - dofmap_names[el], + finite_element_hashes[el], family, degree, cmap.cell_type, @@ -712,8 +522,7 @@ def _compute_expression_ir( options, visualise, object_names, - finite_element_names, - dofmap_names, + finite_element_hashes, ): """Compute intermediate representation of expression.""" logger.info(f"Computing IR for expression {index}") @@ -788,8 +597,7 @@ def _compute_expression_ir( degree = cmap.degree value_shape = space.value_shape fs[name] = ( - finite_element_names[el], - dofmap_names[el], + finite_element_hashes[el], family, degree, cmap.cell_type, diff --git a/ffcx/naming.py b/ffcx/naming.py index df6cc1203..293d0d69c 100644 --- a/ffcx/naming.py +++ b/ffcx/naming.py @@ -9,7 +9,6 @@ import hashlib -import basix.ufl import numpy as np import numpy.typing as npt import ufl @@ -19,9 +18,7 @@ def compute_signature( - ufl_objects: list[ - ufl.Form | basix.ufl._ElementBase | tuple[ufl.core.expr.Expr, npt.NDArray[np.float64]] - ], + ufl_objects: list[ufl.Form | tuple[ufl.core.expr.Expr, npt.NDArray[np.float64]]], tag: str, ) -> str: """Compute the signature hash. @@ -34,9 +31,6 @@ def compute_signature( if isinstance(ufl_object, ufl.Form): kind = "form" object_signature += ufl_object.signature() - elif isinstance(ufl_object, ufl.AbstractFiniteElement): - object_signature += repr(ufl_object) - kind = "element" elif isinstance(ufl_object, tuple) and isinstance(ufl_object[0], ufl.core.expr.Expr): expr = ufl_object[0] points = ufl_object[1] @@ -102,20 +96,6 @@ def form_name(original_form: ufl.form.Form, form_id: int, prefix: str) -> str: return f"form_{sig}" -def finite_element_name(ufl_element: basix.ufl._ElementBase, prefix: str) -> str: - """Get finite element name.""" - assert isinstance(ufl_element, basix.ufl._ElementBase) - sig = compute_signature([ufl_element], prefix) - return f"element_{sig}" - - -def dofmap_name(ufl_element: basix.ufl._ElementBase, prefix: str) -> str: - """Get DOF map name.""" - assert isinstance(ufl_element, basix.ufl._ElementBase) - sig = compute_signature([ufl_element], prefix) - return f"dofmap_{sig}" - - def expression_name( expression: tuple[ufl.core.expr.Expr, npt.NDArray[np.floating]], prefix: str ) -> str: diff --git a/test/test_blocked_elements.py b/test/test_blocked_elements.py deleted file mode 100644 index d72cb4e78..000000000 --- a/test/test_blocked_elements.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright (C) 2020 Matthew Scroggs -# -# This file is part of FFCx. (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -import basix.ufl -import numpy as np - -import ffcx -import ffcx.codegeneration.jit - - -def test_finite_element(compile_args): - ufl_element = basix.ufl.element("Lagrange", "triangle", 1) - jit_compiled_elements, module, code = ffcx.codegeneration.jit.compile_elements( - [ufl_element], cffi_extra_compile_args=compile_args - ) - ufcx_element, ufcx_dofmap = jit_compiled_elements[0] - - assert ufcx_element.topological_dimension == 2 - assert ufcx_element.space_dimension == 3 - assert ufcx_element.reference_value_rank == 0 - assert ufcx_element.reference_value_size == 1 - assert ufcx_element.block_size == 1 - assert ufcx_element.num_sub_elements == 0 - - assert ufcx_dofmap.block_size == 1 - assert ufcx_dofmap.num_global_support_dofs == 0 - assert ufcx_dofmap.num_global_support_dofs == 0 - assert ufcx_dofmap.num_element_support_dofs == 3 - off = np.array([ufcx_dofmap.entity_dof_offsets[i] for i in range(8)]) - assert np.all(np.diff(off) == [1, 1, 1, 0, 0, 0, 0]) - - for v in range(3): - assert ufcx_dofmap.entity_dofs[v] == v - assert ufcx_dofmap.num_sub_dofmaps == 0 - - -def test_vector_element(compile_args): - ufl_element = basix.ufl.element("Lagrange", "triangle", 1, shape=(2,)) - jit_compiled_elements, module, code = ffcx.codegeneration.jit.compile_elements( - [ufl_element], cffi_extra_compile_args=compile_args - ) - ufcx_element, ufcx_dofmap = jit_compiled_elements[0] - - assert ufcx_element.topological_dimension == 2 - assert ufcx_element.space_dimension == 6 - assert ufcx_element.reference_value_rank == 1 - assert ufcx_element.reference_value_shape[0] == 2 - assert ufcx_element.reference_value_size == 2 - assert ufcx_element.block_size == 2 - assert ufcx_element.num_sub_elements == 2 - - assert ufcx_dofmap.block_size == 2 - assert ufcx_dofmap.num_global_support_dofs == 0 - assert ufcx_dofmap.num_global_support_dofs == 0 - assert ufcx_dofmap.num_element_support_dofs == 3 - off = np.array([ufcx_dofmap.entity_dof_offsets[i] for i in range(8)]) - assert np.all(np.diff(off) == [1, 1, 1, 0, 0, 0, 0]) - - for v in range(3): - assert ufcx_dofmap.entity_dofs[v] == v - assert ufcx_dofmap.num_sub_dofmaps == 2 - - -def test_tensor_element(compile_args): - ufl_element = basix.ufl.element("Lagrange", "triangle", 1, shape=(2, 2)) - jit_compiled_elements, module, code = ffcx.codegeneration.jit.compile_elements( - [ufl_element], cffi_extra_compile_args=compile_args - ) - ufcx_element, ufcx_dofmap = jit_compiled_elements[0] - - assert ufcx_element.topological_dimension == 2 - assert ufcx_element.space_dimension == 12 - assert ufcx_element.reference_value_rank == 2 - assert ufcx_element.reference_value_shape[0] == 2 - assert ufcx_element.reference_value_shape[1] == 2 - assert ufcx_element.reference_value_size == 4 - assert ufcx_element.block_size == 4 - assert ufcx_element.num_sub_elements == 4 - - assert ufcx_dofmap.block_size == 4 - assert ufcx_dofmap.num_global_support_dofs == 0 - assert ufcx_dofmap.num_global_support_dofs == 0 - assert ufcx_dofmap.num_element_support_dofs == 3 - off = np.array([ufcx_dofmap.entity_dof_offsets[i] for i in range(8)]) - assert np.all(np.diff(off) == [1, 1, 1, 0, 0, 0, 0]) - - for v in range(3): - assert ufcx_dofmap.entity_dofs[v] == v - assert ufcx_dofmap.num_sub_dofmaps == 4 - - -def test_vector_quadrature_element(compile_args): - ufl_element = basix.ufl.blocked_element( - basix.ufl.quadrature_element("tetrahedron", degree=2, scheme="default"), shape=(3,) - ) - jit_compiled_elements, module, code = ffcx.codegeneration.jit.compile_elements( - [ufl_element], cffi_extra_compile_args=compile_args - ) - ufcx_element, ufcx_dofmap = jit_compiled_elements[0] - - assert ufcx_element.topological_dimension == 3 - assert ufcx_element.space_dimension == 12 - assert ufcx_element.reference_value_rank == 1 - assert ufcx_element.reference_value_shape[0] == 3 - assert ufcx_element.reference_value_size == 3 - assert ufcx_element.block_size == 3 - assert ufcx_element.num_sub_elements == 3 - - assert ufcx_dofmap.block_size == 3 - assert ufcx_dofmap.num_global_support_dofs == 0 - assert ufcx_dofmap.num_global_support_dofs == 0 - assert ufcx_dofmap.num_element_support_dofs == 4 - off = np.array([ufcx_dofmap.entity_dof_offsets[i] for i in range(16)]) - assert np.all(np.diff(off) == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4]) - - for i in range(4): - assert ufcx_dofmap.entity_dofs[i] == i - - assert ufcx_dofmap.num_sub_dofmaps == 3 From 5a220bb66894e7cc21291421c6c9b061acd1396f Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Tue, 30 Apr 2024 21:45:53 +0200 Subject: [PATCH 05/25] Remove to debug on win32 --- ffcx/codegeneration/jit.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ffcx/codegeneration/jit.py b/ffcx/codegeneration/jit.py index aa903c496..70f3f36bb 100644 --- a/ffcx/codegeneration/jit.py +++ b/ffcx/codegeneration/jit.py @@ -121,8 +121,6 @@ def _compilation_signature(cffi_extra_compile_args=None, cffi_debug=None): return ( str(cffi_extra_compile_args) + str(cffi_debug) - + sysconfig.get_config_var("CFLAGS") - + sysconfig.get_config_var("SOABI") ) From f25652a449e2079fca8c53237157d337c402fb5b Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Tue, 30 Apr 2024 21:52:37 +0200 Subject: [PATCH 06/25] Revert "Remove to debug on win32" This reverts commit 5a220bb66894e7cc21291421c6c9b061acd1396f. --- ffcx/codegeneration/jit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ffcx/codegeneration/jit.py b/ffcx/codegeneration/jit.py index 70f3f36bb..aa903c496 100644 --- a/ffcx/codegeneration/jit.py +++ b/ffcx/codegeneration/jit.py @@ -121,6 +121,8 @@ def _compilation_signature(cffi_extra_compile_args=None, cffi_debug=None): return ( str(cffi_extra_compile_args) + str(cffi_debug) + + sysconfig.get_config_var("CFLAGS") + + sysconfig.get_config_var("SOABI") ) From 8eb372f3a87bddcbb1c35c5c2100e4691b3b2086 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 1 May 2024 15:16:51 +0100 Subject: [PATCH 07/25] Remove ufcx_function_space (#690) * remove geometry_* from ufcx_function_space * remove now unused info from ir * remove ufcx_function_space (!) * remove ir for function spaces (!) --- ffcx/codegeneration/C/expressions.py | 49 ----------------- ffcx/codegeneration/C/expressions_template.py | 3 - ffcx/codegeneration/C/form.py | 40 -------------- ffcx/codegeneration/C/form_template.py | 10 ---- ffcx/codegeneration/ufcx.h | 33 ----------- ffcx/ir/representation.py | 55 ------------------- 6 files changed, 190 deletions(-) diff --git a/ffcx/codegeneration/C/expressions.py b/ffcx/codegeneration/C/expressions.py index 60d7d07f6..681dd423b 100644 --- a/ffcx/codegeneration/C/expressions.py +++ b/ffcx/codegeneration/C/expressions.py @@ -101,55 +101,6 @@ def generator(ir, options): d["constant_names_init"] = "" d["constant_names"] = "NULL" - code = [] - vs_code = [] - - # FIXME: Should be handled differently, revise how - # ufcx_function_space is generated (also for ufcx_form) - for name, ( - element, - cmap_family, - cmap_degree, - cmap_celltype, - cmap_variant, - value_shape, - ) in ir.function_spaces.items(): - code += [f"static ufcx_function_space function_space_{name}_{ir.name_from_uflfile} ="] - code += ["{"] - code += [f".finite_element = {element},"] - code += [f'.geometry_family = "{cmap_family}",'] - code += [f".geometry_degree = {cmap_degree},"] - code += [f".geometry_basix_cell = {int(cmap_celltype)},"] - code += [f".geometry_basix_variant = {int(cmap_variant)},"] - code += [f".value_rank = {len(value_shape)},"] - if len(value_shape) == 0: - code += [".value_shape = NULL"] - else: - vs_code += [ - f"int value_shape_{name}_{ir.name_from_uflfile}[{len(value_shape)}] = {{", - " " + ", ".join([f"{i}" for i in value_shape]), - "};", - ] - code += [f".value_shape = value_shape_{name}_{ir.name_from_uflfile}"] - code += ["};"] - - d["function_spaces_alloc"] = "\n".join(vs_code) + "\n" + "\n".join(code) - d["function_spaces"] = "" - - if len(ir.function_spaces) > 0: - d["function_spaces"] = f"function_spaces_{ir.name}" - values = ", ".join( - f"&function_space_{name}_{ir.name_from_uflfile}" - for (name, _) in ir.function_spaces.items() - ) - sizes = len(ir.function_spaces) - d["function_spaces_init"] = ( - f"ufcx_function_space* function_spaces_{ir.name}[{sizes}] = {{{values}}};" - ) - else: - d["function_spaces"] = "NULL" - d["function_spaces_init"] = "" - # Check that no keys are redundant or have been missed from string import Formatter diff --git a/ffcx/codegeneration/C/expressions_template.py b/ffcx/codegeneration/C/expressions_template.py index 9b25cbee2..7bb88f83a 100644 --- a/ffcx/codegeneration/C/expressions_template.py +++ b/ffcx/codegeneration/C/expressions_template.py @@ -31,8 +31,6 @@ {points_init} {value_shape_init} {original_coefficient_positions_init} -{function_spaces_alloc} -{function_spaces_init} {coefficient_names_init} {constant_names_init} @@ -51,7 +49,6 @@ .value_shape = {value_shape}, .num_components = {num_components}, .rank = {rank}, - .function_spaces = {function_spaces} }}; // Alias name diff --git a/ffcx/codegeneration/C/form.py b/ffcx/codegeneration/C/form.py index 6d02273b6..471bc9afe 100644 --- a/ffcx/codegeneration/C/form.py +++ b/ffcx/codegeneration/C/form.py @@ -116,46 +116,6 @@ def generator(ir, options): f"int form_integral_offsets_{ir.name}[{sizes}] = {{{values}}};" ) - vs_code = [] - code = [] - - # FIXME: Should be handled differently, revise how - # ufcx_function_space is generated - for name, ( - element, - cmap_family, - cmap_degree, - cmap_celltype, - cmap_variant, - value_shape, - ) in ir.function_spaces.items(): - code += [f"static ufcx_function_space functionspace_{name} ="] - code += ["{"] - code += [f".finite_element = {element},"] - code += [f'.geometry_family = "{cmap_family}",'] - code += [f".geometry_degree = {cmap_degree},"] - code += [f".geometry_basix_cell = {int(cmap_celltype)},"] - code += [f".geometry_basix_variant = {int(cmap_variant)},"] - code += [f".value_rank = {len(value_shape)},"] - if len(value_shape) == 0: - code += [".value_shape = NULL"] - else: - vs_code += [ - f"int value_shape_{ir.name}_{name}[{len(value_shape)}] = {{", - " " + ", ".join([f"{i}" for i in value_shape]), - "};", - ] - code += [f".value_shape = value_shape_{ir.name}_{name}"] - code += ["};"] - - for name in ir.function_spaces.keys(): - code += [f'if (strcmp(function_name, "{name}") == 0) return &functionspace_{name};'] - - code += ["return NULL;\n"] - - d["functionspace"] = "\n".join(code) - d["value_shape_init"] = "\n".join(vs_code) - # Check that no keys are redundant or have been missed from string import Formatter diff --git a/ffcx/codegeneration/C/form_template.py b/ffcx/codegeneration/C/form_template.py index 5e9cfbd5c..011ee583b 100644 --- a/ffcx/codegeneration/C/form_template.py +++ b/ffcx/codegeneration/C/form_template.py @@ -13,10 +13,6 @@ // extern ufcx_form* {name_from_uflfile}; -// Helper used to create function space using function name -// i.e. name of the Python variable. -// -ufcx_function_space* functionspace_{name_from_uflfile}(const char* function_name); """ factory = """ @@ -53,11 +49,5 @@ // Alias name ufcx_form* {name_from_uflfile} = &{factory_name}; -{value_shape_init} -ufcx_function_space* functionspace_{name_from_uflfile}(const char* function_name) -{{ -{functionspace} -}} - // End of code for form {factory_name} """ diff --git a/ffcx/codegeneration/ufcx.h b/ffcx/codegeneration/ufcx.h index 1851d5c03..d4a3c0b8b 100644 --- a/ffcx/codegeneration/ufcx.h +++ b/ffcx/codegeneration/ufcx.h @@ -62,7 +62,6 @@ extern "C" /// Forward declarations typedef struct ufcx_quadrature_rule ufcx_quadrature_rule; - typedef struct ufcx_function_space ufcx_function_space; // @@ -213,13 +212,6 @@ extern "C" /// Rank, i.e. number of arguments int rank; - /// Function spaces for all functions in the Expression. - /// - /// Function spaces for coefficients are followed by - /// Arguments function spaces. - /// Dimensions: function_spaces[num_coefficients + rank] - ufcx_function_space** function_spaces; - } ufcx_expression; /// This class defines the interface for the assembly of the global @@ -277,31 +269,6 @@ extern "C" } ufcx_form; - // FIXME: Formalise a UFCX 'function space' - typedef struct ufcx_function_space - { - /// Hash of the finite element - long int finite_element; - - /// The family of the finite element for the geometry map - const char* geometry_family; - - /// The degree of the finite element for the geometry map - int geometry_degree; - - /// The Basix cell of the finite element for the geometry map - int geometry_basix_cell; - - /// The Basix variant of the finite element for the geometry map - int geometry_basix_variant; - - /// Rank of the value space - int value_rank; - - /// Shape of the value space - int* value_shape; - } ufcx_function_space; - #ifdef __cplusplus #undef restrict } diff --git a/ffcx/ir/representation.py b/ffcx/ir/representation.py index 1d2a9ed62..35a7d0cad 100644 --- a/ffcx/ir/representation.py +++ b/ffcx/ir/representation.py @@ -22,8 +22,6 @@ import typing import warnings -import basix -import basix.ufl import numpy as np import numpy.typing as npt import ufl @@ -48,9 +46,6 @@ class FormIR(typing.NamedTuple): num_coefficients: int num_constants: int name_from_uflfile: str - function_spaces: dict[ - str, tuple[str, str, str, int, basix.CellType, basix.LagrangeVariant, tuple[int]] - ] original_coefficient_position: list[int] coefficient_names: list[str] constant_names: list[str] @@ -105,9 +100,6 @@ class ExpressionIR(typing.NamedTuple): coefficient_names: list[str] constant_names: list[str] needs_facet_permutations: bool - function_spaces: dict[ - str, tuple[str, str, str, int, basix.CellType, basix.LagrangeVariant, tuple[int]] - ] name_from_uflfile: str original_coefficient_positions: list[int] @@ -462,33 +454,8 @@ def _compute_form_ir( for e in form_data.argument_elements + form_data.coefficient_elements ] - fs = {} - for function in form_data.original_form.arguments() + tuple(form_data.reduced_coefficients): - name = object_names.get(id(function), str(function)) - if not str(name).isidentifier(): - raise ValueError(f'Function name "{name}" must be a valid object identifier.') - el = function.ufl_function_space().ufl_element() - space = function.ufl_function_space() - domain = space.ufl_domain() - cmap = domain.ufl_coordinate_element() - # Default point spacing for CoordinateElement is equispaced - if not isinstance(cmap, basix.ufl._ElementBase) and cmap.variant() is None: - cmap._sub_element._variant = "equispaced" - family = cmap.family_name - degree = cmap.degree - value_shape = space.value_shape - fs[name] = ( - finite_element_hashes[el], - family, - degree, - cmap.cell_type, - cmap.lagrange_variant, - value_shape, - ) - form_name = object_names.get(id(form_data.original_form), form_id) - ir["function_spaces"] = fs ir["name_from_uflfile"] = f"form_{prefix}_{form_name}" # Store names of integrals and subdomain_ids for this form, grouped @@ -584,30 +551,8 @@ def _compute_expression_ir( for j, obj in enumerate(ufl.algorithms.analysis.extract_constants(expression)) ] - fs = {} - for function in tuple(original_coefficients) + tuple(arguments): - name = object_names.get(id(function), str(function)) - if not str(name).isidentifier(): - raise ValueError(f'Function name "{name}" must be a valid object identifier.') - el = function.ufl_function_space().ufl_element() - space = function.ufl_function_space() - domain = space.ufl_domain() - cmap = domain.ufl_coordinate_element() - family = cmap.family_name - degree = cmap.degree - value_shape = space.value_shape - fs[name] = ( - finite_element_hashes[el], - family, - degree, - cmap.cell_type, - cmap.lagrange_variant, - value_shape, - ) - expression_name = object_names.get(id(original_expression), index) - ir["function_spaces"] = fs ir["name_from_uflfile"] = f"expression_{prefix}_{expression_name}" if len(argument_elements) > 1: From 958b5f0a6b6256512a7b574efa93732a68da7f6e Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 1 May 2024 19:22:22 +0100 Subject: [PATCH 08/25] Remove ufcx_shape and ufcx_quadrature_rule (#691) * remove ufcx_quadrature_rule * remove ufcx_shape --- .../C/quadrature_rule_template.py | 23 ------------- ffcx/codegeneration/ufcx.h | 33 ------------------- 2 files changed, 56 deletions(-) delete mode 100644 ffcx/codegeneration/C/quadrature_rule_template.py diff --git a/ffcx/codegeneration/C/quadrature_rule_template.py b/ffcx/codegeneration/C/quadrature_rule_template.py deleted file mode 100644 index f4e635823..000000000 --- a/ffcx/codegeneration/C/quadrature_rule_template.py +++ /dev/null @@ -1,23 +0,0 @@ -# Code generation format strings for UFC (Unified Form-assembly Code) -# This code is released into the public domain. -# -# The FEniCS Project (http://www.fenicsproject.org/) 2023. -"""Code generation strings for a quadrature rule.""" - -factory = """ -// Code for quadrature rule {factory_name} - -{points_init} -{weights_init} - -ufcx_quadrature_rule {factory_name} = -{{ - .cell_shape = {cell_shape}, - .npts = {npts}, - .topological_dimension = {topological_dimension}, - .points = {points}, - .weights = {weights} -}}; - -// End of code for quadrature rule {factory_name} -""" diff --git a/ffcx/codegeneration/ufcx.h b/ffcx/codegeneration/ufcx.h index d4a3c0b8b..f50b793e6 100644 --- a/ffcx/codegeneration/ufcx.h +++ b/ffcx/codegeneration/ufcx.h @@ -41,18 +41,6 @@ extern "C" // - typedef enum - { - interval = 10, - triangle = 20, - quadrilateral = 30, - tetrahedron = 40, - hexahedron = 50, - vertex = 60, - prism = 70, - pyramid = 80 - } ufcx_shape; - typedef enum { cell = 0, @@ -60,29 +48,8 @@ extern "C" interior_facet = 2 } ufcx_integral_type; - /// Forward declarations - typedef struct ufcx_quadrature_rule ufcx_quadrature_rule; - // - typedef struct ufcx_quadrature_rule - { - /// Cell shape - ufcx_shape cell_shape; - - /// The number of points - int npts; - - /// The topological dimension of the cell - int topological_dimension; - - /// The quadraute points - double* points; - - /// The quadraute weights - double* weights; - } ufcx_quadrature_rule; - /// Tabulate integral into tensor A with compiled quadrature rule /// /// @param[out] A From 32320ef1123030431f9a6b9cc013bd161db7a725 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Thu, 2 May 2024 16:21:09 +0100 Subject: [PATCH 09/25] Write the Basix hash into generated code (or 0 if the element is not plain C++ Basix element) (#688) * write C++ hash into code, not result of hash() * ruff * remove L * long long? * uint62_t * u * ruff format * add _hash to names * ruff * ULL * ULL -> ull --- ffcx/codegeneration/C/form.py | 16 +++++++++------- ffcx/codegeneration/C/form_template.py | 4 ++-- ffcx/codegeneration/C/integrals.py | 4 +++- ffcx/codegeneration/C/integrals_template.py | 2 +- ffcx/codegeneration/ufcx.h | 6 +++--- ffcx/ir/representation.py | 12 +++++++----- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/ffcx/codegeneration/C/form.py b/ffcx/codegeneration/C/form.py index 471bc9afe..b5cef225b 100644 --- a/ffcx/codegeneration/C/form.py +++ b/ffcx/codegeneration/C/form.py @@ -67,14 +67,16 @@ def generator(ir, options): d["constant_names_init"] = "" d["constant_names"] = "NULL" - if len(ir.finite_elements) > 0: - d["finite_elements"] = f"finite_elements_{ir.name}" - values = ", ".join(f"{el}" for el in ir.finite_elements) - sizes = len(ir.finite_elements) - d["finite_elements_init"] = f"long int finite_elements_{ir.name}[{sizes}] = {{{values}}};" + if len(ir.finite_element_hashes) > 0: + d["finite_element_hashes"] = f"finite_element_hashes_{ir.name}" + values = ", ".join(f"{0 if el is None else el}ull" for el in ir.finite_element_hashes) + sizes = len(ir.finite_element_hashes) + d["finite_element_hashes_init"] = ( + f"uint64_t finite_element_hashes_{ir.name}[{sizes}] = {{{values}}};" + ) else: - d["finite_elements"] = "NULL" - d["finite_elements_init"] = "" + d["finite_element_hashes"] = "NULL" + d["finite_element_hashes_init"] = "" integrals = [] integral_ids = [] diff --git a/ffcx/codegeneration/C/form_template.py b/ffcx/codegeneration/C/form_template.py index 011ee583b..ad8b81abf 100644 --- a/ffcx/codegeneration/C/form_template.py +++ b/ffcx/codegeneration/C/form_template.py @@ -19,7 +19,7 @@ // Code for form {factory_name} {original_coefficient_position_init} -{finite_elements_init} +{finite_element_hashes_init} {form_integral_offsets_init} {form_integrals_init} {form_integral_ids_init} @@ -39,7 +39,7 @@ .coefficient_name_map = {coefficient_names}, .constant_name_map = {constant_names}, - .finite_elements = {finite_elements}, + .finite_element_hashes = {finite_element_hashes}, .form_integrals = {form_integrals}, .form_integral_ids = {form_integral_ids}, diff --git a/ffcx/codegeneration/C/integrals.py b/ffcx/codegeneration/C/integrals.py index d301eceda..9489ce53f 100644 --- a/ffcx/codegeneration/C/integrals.py +++ b/ffcx/codegeneration/C/integrals.py @@ -67,6 +67,8 @@ def generator(ir: IntegralIR, options): np_scalar_type = np.dtype(options["scalar_type"]).name code[f"tabulate_tensor_{np_scalar_type}"] = f"tabulate_tensor_{factory_name}" + element_hash = 0 if ir.coordinate_element_hash is None else ir.coordinate_element_hash + implementation = ufcx_integrals.factory.format( factory_name=factory_name, enabled_coefficients=code["enabled_coefficients"], @@ -75,7 +77,7 @@ def generator(ir: IntegralIR, options): needs_facet_permutations="true" if ir.needs_facet_permutations else "false", scalar_type=dtype_to_c_type(options["scalar_type"]), geom_type=dtype_to_c_type(dtype_to_scalar_dtype(options["scalar_type"])), - coordinate_element=f"{ir.coordinate_element}", + coordinate_element_hash=f"{element_hash}ull", tabulate_tensor_float32=code["tabulate_tensor_float32"], tabulate_tensor_float64=code["tabulate_tensor_float64"], tabulate_tensor_complex64=code["tabulate_tensor_complex64"], diff --git a/ffcx/codegeneration/C/integrals_template.py b/ffcx/codegeneration/C/integrals_template.py index 65884e801..937f759ae 100644 --- a/ffcx/codegeneration/C/integrals_template.py +++ b/ffcx/codegeneration/C/integrals_template.py @@ -31,7 +31,7 @@ .tabulate_tensor_complex64 = {tabulate_tensor_complex64}, .tabulate_tensor_complex128 = {tabulate_tensor_complex128}, .needs_facet_permutations = {needs_facet_permutations}, - .coordinate_element = {coordinate_element}, + .coordinate_element_hash = {coordinate_element_hash}, }}; // End of code for integral {factory_name} diff --git a/ffcx/codegeneration/ufcx.h b/ffcx/codegeneration/ufcx.h index f50b793e6..38895c9b0 100644 --- a/ffcx/codegeneration/ufcx.h +++ b/ffcx/codegeneration/ufcx.h @@ -127,8 +127,8 @@ extern "C" ufcx_tabulate_tensor_complex128* tabulate_tensor_complex128; bool needs_facet_permutations; - /// Get the coordinate element associated with the geometry of the mesh. - long int coordinate_element; + /// Get the hash of the coordinate element associated with the geometry of the mesh. + uint64_t coordinate_element_hash; } ufcx_integral; typedef struct ufcx_expression @@ -223,7 +223,7 @@ extern "C" /// /// @param i Argument number if 0 <= i < r Coefficient number j = i /// - r if r + j <= i < r + n - long int* finite_elements; + uint64_t* finite_element_hashes; /// List of cell, interior facet and exterior facet integrals ufcx_integral** form_integrals; diff --git a/ffcx/ir/representation.py b/ffcx/ir/representation.py index 35a7d0cad..0f61329fc 100644 --- a/ffcx/ir/representation.py +++ b/ffcx/ir/representation.py @@ -49,7 +49,7 @@ class FormIR(typing.NamedTuple): original_coefficient_position: list[int] coefficient_names: list[str] constant_names: list[str] - finite_elements: list[int] + finite_element_hashes: list[int] integral_names: dict[str, list[str]] subdomain_ids: dict[str, list[int]] @@ -78,7 +78,7 @@ class IntegralIR(typing.NamedTuple): integrand: dict[QuadratureRule, dict] name: str needs_facet_permutations: bool - coordinate_element: int + coordinate_element_hash: int class ExpressionIR(typing.NamedTuple): @@ -127,7 +127,7 @@ def compute_ir( # Compute object names # NOTE: This is done here for performance reasons, because repeated calls # within each IR computation would be expensive due to UFL signature computations - finite_element_hashes = {e: hash(e) for e in analysis.unique_elements} + finite_element_hashes = {e: e.basix_hash() for e in analysis.unique_elements} integral_names = {} form_names = {} for fd_index, fd in enumerate(analysis.form_data): @@ -221,7 +221,9 @@ def _compute_integral_ir( "rank": form_data.rank, "entitytype": entitytype, "enabled_coefficients": itg_data.enabled_coefficients, - "coordinate_element": finite_element_hashes[itg_data.domain.ufl_coordinate_element()], + "coordinate_element_hash": finite_element_hashes[ + itg_data.domain.ufl_coordinate_element() + ], } # Get element space dimensions @@ -449,7 +451,7 @@ def _compute_form_ir( ir["original_coefficient_position"] = form_data.original_coefficient_positions - ir["finite_elements"] = [ + ir["finite_element_hashes"] = [ finite_element_hashes[e] for e in form_data.argument_elements + form_data.coefficient_elements ] From d2b269e1d3e9090d32a2bb8aa38321304af4269c Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Fri, 3 May 2024 12:04:19 +0100 Subject: [PATCH 10/25] Use UINT64_C(*) instead of *ull (#692) * use UINT64_C * ruff * ruff --- ffcx/codegeneration/C/form.py | 4 +++- ffcx/codegeneration/C/integrals.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ffcx/codegeneration/C/form.py b/ffcx/codegeneration/C/form.py index b5cef225b..1c278e8dd 100644 --- a/ffcx/codegeneration/C/form.py +++ b/ffcx/codegeneration/C/form.py @@ -69,7 +69,9 @@ def generator(ir, options): if len(ir.finite_element_hashes) > 0: d["finite_element_hashes"] = f"finite_element_hashes_{ir.name}" - values = ", ".join(f"{0 if el is None else el}ull" for el in ir.finite_element_hashes) + values = ", ".join( + f"UINT64_C({0 if el is None else el})" for el in ir.finite_element_hashes + ) sizes = len(ir.finite_element_hashes) d["finite_element_hashes_init"] = ( f"uint64_t finite_element_hashes_{ir.name}[{sizes}] = {{{values}}};" diff --git a/ffcx/codegeneration/C/integrals.py b/ffcx/codegeneration/C/integrals.py index 9489ce53f..4c79bbdfe 100644 --- a/ffcx/codegeneration/C/integrals.py +++ b/ffcx/codegeneration/C/integrals.py @@ -77,7 +77,7 @@ def generator(ir: IntegralIR, options): needs_facet_permutations="true" if ir.needs_facet_permutations else "false", scalar_type=dtype_to_c_type(options["scalar_type"]), geom_type=dtype_to_c_type(dtype_to_scalar_dtype(options["scalar_type"])), - coordinate_element_hash=f"{element_hash}ull", + coordinate_element_hash=f"UINT64_C({element_hash})", tabulate_tensor_float32=code["tabulate_tensor_float32"], tabulate_tensor_float64=code["tabulate_tensor_float64"], tabulate_tensor_complex64=code["tabulate_tensor_complex64"], From 210ae0f8a73d7c4b41ea64d3ef532b8846597fad Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Mon, 6 May 2024 10:11:36 +0200 Subject: [PATCH 11/25] Break system packages (#695) --- .github/workflows/dolfinx-tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dolfinx-tests.yml b/.github/workflows/dolfinx-tests.yml index 8cb2d9006..48e045fe8 100644 --- a/.github/workflows/dolfinx-tests.yml +++ b/.github/workflows/dolfinx-tests.yml @@ -35,16 +35,16 @@ jobs: - name: Install UFL and Basix (default branches/tags) if: github.event_name != 'workflow_dispatch' run: | - python3 -m pip install git+https://github.com/FEniCS/ufl.git - python3 -m pip install git+https://github.com/FEniCS/basix.git + python3 -m pip install --break-system-packages git+https://github.com/FEniCS/ufl.git + python3 -m pip install --break-system-packages git+https://github.com/FEniCS/basix.git - name: Install UFL and Basix (specified branches/tags) if: github.event_name == 'workflow_dispatch' run: | - python3 -m pip install git+https://github.com/FEniCS/ufl.git@${{ github.event.inputs.ufl_ref }} - python3 -m pip install git+https://github.com/FEniCS/basix.git@${{ github.event.inputs.basix_ref }} + python3 -m pip install --break-system-packages git+https://github.com/FEniCS/ufl.git@${{ github.event.inputs.ufl_ref }} + python3 -m pip install --break-system-packages git+https://github.com/FEniCS/basix.git@${{ github.event.inputs.basix_ref }} - name: Install FFCx run: | - pip3 install . + pip3 install --break-system-packages . - name: Get DOLFINx source (default branch/tag) if: github.event_name != 'workflow_dispatch' uses: actions/checkout@v4 @@ -65,7 +65,7 @@ jobs: cmake --build build cmake --install build - name: Install DOLFINx (Python) - run: python3 -m pip -v install --check-build-dependencies --no-build-isolation dolfinx/python/ + run: python3 -m pip -v install --break-system-packages --check-build-dependencies --no-build-isolation dolfinx/python/ - name: Build DOLFINx C++ unit tests run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S dolfinx/cpp/test/ From a0e62567881b3045e21229c7008f378a8d8f8f80 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Mon, 13 May 2024 07:20:17 +0100 Subject: [PATCH 12/25] Disable Spack self test (#696) Something seems to have changed in Spack. --- .github/workflows/spack.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/spack.yml b/.github/workflows/spack.yml index d30d81463..eaf12e804 100644 --- a/.github/workflows/spack.yml +++ b/.github/workflows/spack.yml @@ -55,7 +55,8 @@ jobs: spack env create ffcx-main spack env activate ffcx-main spack add py-fenics-ffcx@main - spack install --test=root + spack install + # spack install --test=root - name: Install FFCx and run tests if: github.event_name == 'workflow_dispatch' @@ -64,4 +65,5 @@ jobs: spack env create ffcx-testing spack env activate ffcx-testing spack add py-fenics-ffcx@${{ github.event.inputs.ffcx_version }} - spack install --test=root + spack install + # spack install --test=root From 27d08965ab8974f4a773ffe49cfe39f65939d686 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Sat, 18 May 2024 16:20:22 +0100 Subject: [PATCH 13/25] Re-enable mypy checks (#697) --- .github/workflows/pythonapp.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index cc1c704bf..6e5701c6b 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -44,7 +44,6 @@ jobs: run: pip install .[ci] - name: Static check with mypy run: mypy ffcx/ - if: matrix.python-version != '3.12' - name: ruff checks run: | ruff check . From b83e4a87a6ac2816bf324cd5dfc5df086deabc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Tue, 21 May 2024 13:40:30 +0200 Subject: [PATCH 14/25] Unify Expression and IntegralIR with a common base (#680) * Unify Expression and IntegralIR with a common base * Add future imports * Update ufc interface * Test with modified dolfinx * Apply suggestions from code review Co-authored-by: Matthew Scroggs * Remove unused IR * Ruff format * Revert branch --------- Co-authored-by: Matthew Scroggs --- ffcx/codegeneration/C/expressions.py | 60 +++++------ ffcx/codegeneration/C/form.py | 17 +-- ffcx/codegeneration/C/form_template.py | 2 +- ffcx/codegeneration/C/integrals.py | 12 +-- ffcx/codegeneration/access.py | 14 +-- ffcx/codegeneration/backend.py | 19 ++-- ffcx/codegeneration/definitions.py | 18 ++-- ffcx/codegeneration/expression_generator.py | 32 +++--- ffcx/codegeneration/integral_generator.py | 34 +++--- ffcx/codegeneration/symbols.py | 16 +-- ffcx/codegeneration/ufcx.h | 2 +- ffcx/ir/elementtables.py | 24 ++--- ffcx/ir/integral.py | 4 +- ffcx/ir/representation.py | 110 ++++++++++---------- 14 files changed, 194 insertions(+), 170 deletions(-) diff --git a/ffcx/codegeneration/C/expressions.py b/ffcx/codegeneration/C/expressions.py index 681dd423b..fb7270827 100644 --- a/ffcx/codegeneration/C/expressions.py +++ b/ffcx/codegeneration/C/expressions.py @@ -5,6 +5,8 @@ # SPDX-License-Identifier: LGPL-3.0-or-later """Generate UFC code for an expression.""" +from __future__ import annotations + import logging import numpy as np @@ -14,17 +16,19 @@ from ffcx.codegeneration.C.c_implementation import CFormatter from ffcx.codegeneration.expression_generator import ExpressionGenerator from ffcx.codegeneration.utils import dtype_to_c_type, dtype_to_scalar_dtype +from ffcx.ir.representation import ExpressionIR logger = logging.getLogger("ffcx") -def generator(ir, options): +def generator(ir: ExpressionIR, options): """Generate UFC code for an expression.""" logger.info("Generating code for expression:") - logger.info(f"--- points: {ir.points}") - logger.info(f"--- name: {ir.name}") - - factory_name = ir.name + assert len(ir.expression.integrand) == 1, "Expressions only support single quadrature rule" + points = next(iter(ir.expression.integrand)).points + logger.info(f"--- points: {points}") + factory_name = ir.expression.name + logger.info(f"--- name: {factory_name}") # Format declaration declaration = expressions_template.declaration.format( @@ -34,58 +38,56 @@ def generator(ir, options): backend = FFCXBackend(ir, options) eg = ExpressionGenerator(ir, backend) - d = {} + d: dict[str, str | int] = {} d["name_from_uflfile"] = ir.name_from_uflfile - d["factory_name"] = ir.name - + d["factory_name"] = factory_name parts = eg.generate() CF = CFormatter(options["scalar_type"]) d["tabulate_expression"] = CF.c_format(parts) if len(ir.original_coefficient_positions) > 0: - d["original_coefficient_positions"] = f"original_coefficient_positions_{ir.name}" + d["original_coefficient_positions"] = f"original_coefficient_positions_{factory_name}" values = ", ".join(str(i) for i in ir.original_coefficient_positions) sizes = len(ir.original_coefficient_positions) d["original_coefficient_positions_init"] = ( - f"static int original_coefficient_positions_{ir.name}[{sizes}] = {{{values}}};" + f"static int original_coefficient_positions_{factory_name}[{sizes}] = {{{values}}};" ) else: d["original_coefficient_positions"] = "NULL" d["original_coefficient_positions_init"] = "" - values = ", ".join(str(p) for p in ir.points.flatten()) - sizes = ir.points.size - d["points_init"] = f"static double points_{ir.name}[{sizes}] = {{{values}}};" - d["points"] = f"points_{ir.name}" + values = ", ".join(str(p) for p in points.flatten()) + sizes = points.size + d["points_init"] = f"static double points_{factory_name}[{sizes}] = {{{values}}};" + d["points"] = f"points_{factory_name}" - if len(ir.expression_shape) > 0: - values = ", ".join(str(i) for i in ir.expression_shape) - sizes = len(ir.expression_shape) - d["value_shape_init"] = f"static int value_shape_{ir.name}[{sizes}] = {{{values}}};" - d["value_shape"] = f"value_shape_{ir.name}" + if len(ir.expression.shape) > 0: + values = ", ".join(str(i) for i in ir.expression.shape) + sizes = len(ir.expression.shape) + d["value_shape_init"] = f"static int value_shape_{factory_name}[{sizes}] = {{{values}}};" + d["value_shape"] = f"value_shape_{factory_name}" else: d["value_shape_init"] = "" d["value_shape"] = "NULL" - - d["num_components"] = len(ir.expression_shape) - d["num_coefficients"] = len(ir.coefficient_numbering) + d["num_components"] = len(ir.expression.shape) + d["num_coefficients"] = len(ir.expression.coefficient_numbering) d["num_constants"] = len(ir.constant_names) - d["num_points"] = ir.points.shape[0] - d["entity_dimension"] = ir.points.shape[1] + d["num_points"] = points.shape[0] + d["entity_dimension"] = points.shape[1] d["scalar_type"] = dtype_to_c_type(options["scalar_type"]) d["geom_type"] = dtype_to_c_type(dtype_to_scalar_dtype(options["scalar_type"])) d["np_scalar_type"] = np.dtype(options["scalar_type"]).name - d["rank"] = len(ir.tensor_shape) + d["rank"] = len(ir.expression.tensor_shape) if len(ir.coefficient_names) > 0: values = ", ".join(f'"{name}"' for name in ir.coefficient_names) sizes = len(ir.coefficient_names) d["coefficient_names_init"] = ( - f"static const char* coefficient_names_{ir.name}[{sizes}] = {{{values}}};" + f"static const char* coefficient_names_{factory_name}[{sizes}] = {{{values}}};" ) - d["coefficient_names"] = f"coefficient_names_{ir.name}" + d["coefficient_names"] = f"coefficient_names_{factory_name}" else: d["coefficient_names_init"] = "" d["coefficient_names"] = "NULL" @@ -94,9 +96,9 @@ def generator(ir, options): values = ", ".join(f'"{name}"' for name in ir.constant_names) sizes = len(ir.constant_names) d["constant_names_init"] = ( - f"static const char* constant_names_{ir.name}[{sizes}] = {{{values}}};" + f"static const char* constant_names_{factory_name}[{sizes}] = {{{values}}};" ) - d["constant_names"] = f"constant_names_{ir.name}" + d["constant_names"] = f"constant_names_{factory_name}" else: d["constant_names_init"] = "" d["constant_names"] = "NULL" diff --git a/ffcx/codegeneration/C/form.py b/ffcx/codegeneration/C/form.py index 1c278e8dd..eab91c59d 100644 --- a/ffcx/codegeneration/C/form.py +++ b/ffcx/codegeneration/C/form.py @@ -10,22 +10,25 @@ # old implementation in FFC """Generate UFC code for a form.""" +from __future__ import annotations + import logging import numpy as np from ffcx.codegeneration.C import form_template +from ffcx.ir.representation import FormIR logger = logging.getLogger("ffcx") -def generator(ir, options): +def generator(ir: FormIR, options): """Generate UFC code for a form.""" logger.info("Generating code for form:") logger.info(f"--- rank: {ir.rank}") logger.info(f"--- name: {ir.name}") - d = {} + d: dict[str, int | str] = {} d["factory_name"] = ir.name d["name_from_uflfile"] = ir.name_from_uflfile d["signature"] = f'"{ir.signature}"' @@ -33,17 +36,17 @@ def generator(ir, options): d["num_coefficients"] = ir.num_coefficients d["num_constants"] = ir.num_constants - if len(ir.original_coefficient_position) > 0: - values = ", ".join(str(i) for i in ir.original_coefficient_position) - sizes = len(ir.original_coefficient_position) + if len(ir.original_coefficient_positions) > 0: + values = ", ".join(str(i) for i in ir.original_coefficient_positions) + sizes = len(ir.original_coefficient_positions) d["original_coefficient_position_init"] = ( f"int original_coefficient_position_{ir.name}[{sizes}] = {{{values}}};" ) - d["original_coefficient_position"] = f"original_coefficient_position_{ir.name}" + d["original_coefficient_positions"] = f"original_coefficient_position_{ir.name}" else: d["original_coefficient_position_init"] = "" - d["original_coefficient_position"] = "NULL" + d["original_coefficient_positions"] = "NULL" if len(ir.coefficient_names) > 0: values = ", ".join(f'"{name}"' for name in ir.coefficient_names) diff --git a/ffcx/codegeneration/C/form_template.py b/ffcx/codegeneration/C/form_template.py index ad8b81abf..f6eb7baa1 100644 --- a/ffcx/codegeneration/C/form_template.py +++ b/ffcx/codegeneration/C/form_template.py @@ -34,7 +34,7 @@ .rank = {rank}, .num_coefficients = {num_coefficients}, .num_constants = {num_constants}, - .original_coefficient_position = {original_coefficient_position}, + .original_coefficient_positions = {original_coefficient_positions}, .coefficient_name_map = {coefficient_names}, .constant_name_map = {constant_names}, diff --git a/ffcx/codegeneration/C/integrals.py b/ffcx/codegeneration/C/integrals.py index 4c79bbdfe..8816d06d4 100644 --- a/ffcx/codegeneration/C/integrals.py +++ b/ffcx/codegeneration/C/integrals.py @@ -22,11 +22,11 @@ def generator(ir: IntegralIR, options): """Generate C code for an integral.""" logger.info("Generating code for integral:") - logger.info(f"--- type: {ir.integral_type}") - logger.info(f"--- name: {ir.name}") + logger.info(f"--- type: {ir.expression.integral_type}") + logger.info(f"--- name: {ir.expression.name}") """Generate code for an integral.""" - factory_name = ir.name + factory_name = ir.expression.name # Format declaration declaration = ufcx_integrals.declaration.format(factory_name=factory_name) @@ -51,9 +51,9 @@ def generator(ir: IntegralIR, options): values = ", ".join("1" if i else "0" for i in ir.enabled_coefficients) sizes = len(ir.enabled_coefficients) code["enabled_coefficients_init"] = ( - f"bool enabled_coefficients_{ir.name}[{sizes}] = {{{values}}};" + f"bool enabled_coefficients_{ir.expression.name}[{sizes}] = {{{values}}};" ) - code["enabled_coefficients"] = f"enabled_coefficients_{ir.name}" + code["enabled_coefficients"] = f"enabled_coefficients_{ir.expression.name}" else: code["enabled_coefficients_init"] = "" code["enabled_coefficients"] = "NULL" @@ -74,7 +74,7 @@ def generator(ir: IntegralIR, options): enabled_coefficients=code["enabled_coefficients"], enabled_coefficients_init=code["enabled_coefficients_init"], tabulate_tensor=code["tabulate_tensor"], - needs_facet_permutations="true" if ir.needs_facet_permutations else "false", + needs_facet_permutations="true" if ir.expression.needs_facet_permutations else "false", scalar_type=dtype_to_c_type(options["scalar_type"]), geom_type=dtype_to_c_type(dtype_to_scalar_dtype(options["scalar_type"])), coordinate_element_hash=f"UINT64_C({element_hash})", diff --git a/ffcx/codegeneration/access.py b/ffcx/codegeneration/access.py index 003ea0812..18bcf9c89 100644 --- a/ffcx/codegeneration/access.py +++ b/ffcx/codegeneration/access.py @@ -23,11 +23,11 @@ class FFCXBackendAccess: """FFCx specific formatter class.""" - def __init__(self, ir, symbols, options): + def __init__(self, entity_type: str, integral_type: str, symbols, options): """Initialise.""" # Store ir and options - self.entitytype = ir.entitytype - self.integral_type = ir.integral_type + self.entity_type = entity_type + self.integral_type = integral_type self.symbols = symbols self.options = options @@ -72,7 +72,7 @@ def get( break if handler: - return handler(mt, tabledata, quadrature_rule) + return handler(mt, tabledata, quadrature_rule) # type: ignore else: raise RuntimeError(f"Not handled: {type(e)}") @@ -400,7 +400,7 @@ def _pass(self, *args, **kwargs): def table_access( self, tabledata: UniqueTableReferenceT, - entitytype: str, + entity_type: str, restriction: str, quadrature_index: L.MultiIndex, dof_index: L.MultiIndex, @@ -409,12 +409,12 @@ def table_access( Args: tabledata: Table data object - entitytype: Entity type ("cell", "facet", "vertex") + entity_type: Entity type ("cell", "facet", "vertex") restriction: Restriction ("+", "-") quadrature_index: Quadrature index dof_index: Dof index """ - entity = self.symbols.entity(entitytype, restriction) + entity = self.symbols.entity(entity_type, restriction) iq_global_index = quadrature_index.global_index ic_global_index = dof_index.global_index qp = 0 # quadrature permutation diff --git a/ffcx/codegeneration/backend.py b/ffcx/codegeneration/backend.py index b7217ff8a..ef6963987 100644 --- a/ffcx/codegeneration/backend.py +++ b/ffcx/codegeneration/backend.py @@ -5,23 +5,30 @@ # SPDX-License-Identifier: LGPL-3.0-or-later """Collection of FFCx specific pieces for the code generation phase.""" +from __future__ import annotations + from ffcx.codegeneration.access import FFCXBackendAccess from ffcx.codegeneration.definitions import FFCXBackendDefinitions from ffcx.codegeneration.symbols import FFCXBackendSymbols +from ffcx.ir.representation import ExpressionIR, IntegralIR class FFCXBackend: """Class collecting all aspects of the FFCx backend.""" - def __init__(self, ir, options): + def __init__(self, ir: IntegralIR | ExpressionIR, options): """Initialise.""" - coefficient_numbering = ir.coefficient_numbering - coefficient_offsets = ir.coefficient_offsets + coefficient_numbering = ir.expression.coefficient_numbering + coefficient_offsets = ir.expression.coefficient_offsets - original_constant_offsets = ir.original_constant_offsets + original_constant_offsets = ir.expression.original_constant_offsets self.symbols = FFCXBackendSymbols( coefficient_numbering, coefficient_offsets, original_constant_offsets ) - self.access = FFCXBackendAccess(ir, self.symbols, options) - self.definitions = FFCXBackendDefinitions(ir, self.access, options) + self.access = FFCXBackendAccess( + ir.expression.entity_type, ir.expression.integral_type, self.symbols, options + ) + self.definitions = FFCXBackendDefinitions( + ir.expression.entity_type, ir.expression.integral_type, self.access, options + ) diff --git a/ffcx/codegeneration/definitions.py b/ffcx/codegeneration/definitions.py index 8396e474e..92ea16bbb 100644 --- a/ffcx/codegeneration/definitions.py +++ b/ffcx/codegeneration/definitions.py @@ -50,17 +50,14 @@ def create_dof_index(tabledata, dof_index_symbol): class FFCXBackendDefinitions: """FFCx specific code definitions.""" - def __init__(self, ir, access, options): + def __init__(self, entity_type: str, integral_type: str, access, options): """Initialise.""" # Store ir and options - self.integral_type = ir.integral_type - self.entitytype = ir.entitytype + self.integral_type = integral_type + self.entity_type = entity_type self.access = access - self.symbols = access.symbols self.options = options - self.ir = ir - # called, depending on the first argument type. self.handler_lookup = { ufl.coefficient.Coefficient: self.coefficient, @@ -80,6 +77,11 @@ def __init__(self, ir, access, options): ufl.geometry.FacetOrientation: self.pass_through, } + @property + def symbols(self): + """Return formatter.""" + return self.access.symbols + def get( self, mt: ModifiedTerminal, @@ -141,7 +143,7 @@ def coefficient( assert begin < end # Get access to element table - FE, tables = self.access.table_access(tabledata, self.entitytype, mt.restriction, iq, ic) + FE, tables = self.access.table_access(tabledata, self.entity_type, mt.restriction, iq, ic) dof_access: L.ArrayAccess = self.symbols.coefficient_dof_access( mt.terminal, (ic.global_index) * bs + begin ) @@ -190,7 +192,7 @@ def _define_coordinate_dofs_lincomb( iq_symbol = self.symbols.quadrature_loop_index ic = create_dof_index(tabledata, ic_symbol) iq = create_quadrature_index(quadrature_rule, iq_symbol) - FE, tables = self.access.table_access(tabledata, self.entitytype, mt.restriction, iq, ic) + FE, tables = self.access.table_access(tabledata, self.entity_type, mt.restriction, iq, ic) dof_access = L.Symbol("coordinate_dofs", dtype=L.DataType.REAL) diff --git a/ffcx/codegeneration/expression_generator.py b/ffcx/codegeneration/expression_generator.py index 83197953a..a3c0c56f0 100644 --- a/ffcx/codegeneration/expression_generator.py +++ b/ffcx/codegeneration/expression_generator.py @@ -26,7 +26,7 @@ class ExpressionGenerator: def __init__(self, ir: ExpressionIR, backend: FFCXBackend): """Initialise.""" - if len(list(ir.integrand.keys())) != 1: + if len(list(ir.expression.integrand.keys())) != 1: raise RuntimeError("Only one set of points allowed for expression evaluation") self.ir = ir @@ -35,7 +35,7 @@ def __init__(self, ir: ExpressionIR, backend: FFCXBackend): self._ufl_names: set[Any] = set() self.symbol_counters: collections.defaultdict[Any, int] = collections.defaultdict(int) self.shared_symbols: dict[Any, Any] = {} - self.quadrature_rule = list(self.ir.integrand.keys())[0] + self.quadrature_rule = next(iter(self.ir.expression.integrand.keys())) def generate(self): """Generate.""" @@ -68,12 +68,12 @@ def generate_geometry_tables(self): } cells: dict[Any, set[Any]] = {t: set() for t in ufl_geometry.keys()} # type: ignore - for integrand in self.ir.integrand.values(): + for integrand in self.ir.expression.integrand.values(): for attr in integrand["factorization"].nodes.values(): mt = attr.get("mt") if mt is not None: t = type(mt.terminal) - if self.ir.entitytype == "cell" and issubclass( + if self.ir.expression.entity_type == "cell" and issubclass( t, ufl.geometry.GeometricFacetQuantity ): raise RuntimeError(f"Expressions for cells do not support {t}.") @@ -93,7 +93,7 @@ def generate_element_tables(self): """Generate tables of FE basis evaluated at specified points.""" parts = [] - tables = self.ir.unique_tables + tables = self.ir.expression.unique_tables table_names = sorted(tables) for name in table_names: @@ -142,7 +142,7 @@ def generate_quadrature_loop(self): def generate_varying_partition(self): """Generate factors of blocks which are not cellwise constant.""" # Get annotated graph of factorisation - F = self.ir.integrand[self.quadrature_rule]["factorization"] + F = self.ir.expression.integrand[self.quadrature_rule]["factorization"] arraysymbol = L.Symbol(f"sv_{self.quadrature_rule.id()}", dtype=L.DataType.SCALAR) parts = self.generate_partition(arraysymbol, F, "varying") @@ -158,7 +158,7 @@ def generate_piecewise_partition(self): I.e. do not depend on quadrature points). """ # Get annotated graph of factorisation - F = self.ir.integrand[self.quadrature_rule]["factorization"] + F = self.ir.expression.integrand[self.quadrature_rule]["factorization"] arraysymbol = L.Symbol("sp", dtype=L.DataType.SCALAR) parts = self.generate_partition(arraysymbol, F, "piecewise") @@ -167,7 +167,9 @@ def generate_piecewise_partition(self): def generate_dofblock_partition(self): """Generate assignments of blocks multiplied with their factors into final tensor A.""" - block_contributions = self.ir.integrand[self.quadrature_rule]["block_contributions"] + block_contributions = self.ir.expression.integrand[self.quadrature_rule][ + "block_contributions" + ] preparts = [] quadparts = [] @@ -205,13 +207,13 @@ def generate_block_parts(self, blockmap, blockdata): arg_indices = tuple(self.backend.symbols.argument_loop_index(i) for i in range(block_rank)) - F = self.ir.integrand[self.quadrature_rule]["factorization"] + F = self.ir.expression.integrand[self.quadrature_rule]["factorization"] assert not blockdata.transposed, "Not handled yet" - components = ufl.product(self.ir.expression_shape) + components = ufl.product(self.ir.expression.shape) num_points = self.quadrature_rule.points.shape[0] - A_shape = [num_points, components] + self.ir.tensor_shape + A_shape = [num_points, components] + self.ir.expression.tensor_shape A = self.backend.symbols.element_tensor iq = self.backend.symbols.quadrature_loop_index @@ -291,9 +293,13 @@ def get_arg_factors(self, blockdata, block_rank, indices): for i in range(block_rank): mad = blockdata.ma_data[i] td = mad.tabledata - mt = self.ir.integrand[self.quadrature_rule]["modified_arguments"][mad.ma_index] + mt = self.ir.expression.integrand[self.quadrature_rule]["modified_arguments"][ + mad.ma_index + ] - table = self.backend.symbols.element_table(td, self.ir.entitytype, mt.restriction) + table = self.backend.symbols.element_table( + td, self.ir.expression.entity_type, mt.restriction + ) assert td.ttype != "zeros" diff --git a/ffcx/codegeneration/integral_generator.py b/ffcx/codegeneration/integral_generator.py index bcd447e59..c94b2ef5a 100644 --- a/ffcx/codegeneration/integral_generator.py +++ b/ffcx/codegeneration/integral_generator.py @@ -72,7 +72,9 @@ def __init__(self, ir, backend): def init_scopes(self): """Initialize variable scope dicts.""" # Reset variables, separate sets for each quadrature rule - self.scopes = {quadrature_rule: {} for quadrature_rule in self.ir.integrand.keys()} + self.scopes = { + quadrature_rule: {} for quadrature_rule in self.ir.expression.integrand.keys() + } self.scopes[None] = {} def set_var(self, quadrature_rule, v, vaccess): @@ -157,7 +159,7 @@ def generate(self): # Pre-definitions are collected across all quadrature loops to # improve re-use and avoid name clashes - for rule in self.ir.integrand.keys(): + for rule in self.ir.expression.integrand.keys(): # Generate code to compute piecewise constant scalar factors all_preparts += self.generate_piecewise_partition(rule) @@ -178,11 +180,11 @@ def generate_quadrature_tables(self): # 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: + if self.ir.expression.integral_type in skip: return parts # Loop over quadrature rules - for quadrature_rule, integrand in self.ir.integrand.items(): + for quadrature_rule, _ in self.ir.expression.integrand.items(): # Generate quadrature weights array wsym = self.backend.symbols.weights_table(quadrature_rule) parts += [L.ArrayDecl(wsym, values=quadrature_rule.weights, const=True)] @@ -205,7 +207,7 @@ def generate_geometry_tables(self): } cells: dict[Any, set[Any]] = {t: set() for t in ufl_geometry.keys()} # type: ignore - for integrand in self.ir.integrand.values(): + for integrand in self.ir.expression.integrand.values(): for attr in integrand["factorization"].nodes.values(): mt = attr.get("mt") if mt is not None: @@ -228,9 +230,9 @@ def generate_element_tables(self): With precomputed element basis function values in quadrature points. """ parts = [] - tables = self.ir.unique_tables - table_types = self.ir.unique_table_types - if self.ir.integral_type in ufl.custom_integral_types: + tables = self.ir.expression.unique_tables + table_types = self.ir.expression.unique_table_types + if self.ir.expression.integral_type in ufl.custom_integral_types: # Define only piecewise tables table_names = [name for name in sorted(tables) if table_types[name] in piecewise_ttypes] else: @@ -298,14 +300,14 @@ def generate_quadrature_loop(self, quadrature_rule: QuadratureRule): def generate_piecewise_partition(self, quadrature_rule): """Generate a piecewise partition.""" # Get annotated graph of factorisation - F = self.ir.integrand[quadrature_rule]["factorization"] + F = self.ir.expression.integrand[quadrature_rule]["factorization"] arraysymbol = L.Symbol(f"sp_{quadrature_rule.id()}", dtype=L.DataType.SCALAR) return self.generate_partition(arraysymbol, F, "piecewise", None) def generate_varying_partition(self, quadrature_rule): """Generate a varying partition.""" # Get annotated graph of factorisation - F = self.ir.integrand[quadrature_rule]["factorization"] + F = self.ir.expression.integrand[quadrature_rule]["factorization"] arraysymbol = L.Symbol(f"sv_{quadrature_rule.id()}", dtype=L.DataType.SCALAR) return self.generate_partition(arraysymbol, F, "varying", quadrature_rule) @@ -358,7 +360,7 @@ def generate_partition(self, symbol, F, mode, quadrature_rule): def generate_dofblock_partition(self, quadrature_rule: QuadratureRule): """Generate a dofblock partition.""" - block_contributions = self.ir.integrand[quadrature_rule]["block_contributions"] + block_contributions = self.ir.expression.integrand[quadrature_rule]["block_contributions"] quadparts = [] blocks = [ (blockmap, blockdata) @@ -399,7 +401,7 @@ def get_arg_factors(self, blockdata, block_rank, quadrature_rule, iq, indices): for i in range(block_rank): mad = blockdata.ma_data[i] td = mad.tabledata - scope = self.ir.integrand[quadrature_rule]["modified_arguments"] + scope = self.ir.expression.integrand[quadrature_rule]["modified_arguments"] mt = scope[mad.ma_index] arg_tables = [] @@ -415,7 +417,7 @@ def get_arg_factors(self, blockdata, block_rank, quadrature_rule, iq, indices): else: # Assuming B sparsity follows element table sparsity arg_factor, arg_tables = self.backend.access.table_access( - td, self.ir.entitytype, mt.restriction, iq, indices[i] + td, self.ir.expression.entity_type, mt.restriction, iq, indices[i] ) tables += arg_tables @@ -468,13 +470,13 @@ def generate_block_parts( factor_index = blockdata.factor_indices_comp_indices[0][0] # Get factor expression - F = self.ir.integrand[quadrature_rule]["factorization"] + F = self.ir.expression.integrand[quadrature_rule]["factorization"] v = F.nodes[factor_index]["expression"] f = self.get_var(quadrature_rule, v) # Quadrature weight was removed in representation, add it back now - if self.ir.integral_type in ufl.custom_integral_types: + if self.ir.expression.integral_type in ufl.custom_integral_types: weights = self.backend.symbols.custom_weights_table weight = weights[iq.global_index] else: @@ -535,7 +537,7 @@ def generate_block_parts( body: list[L.LNode] = [] A = self.backend.symbols.element_tensor - A_shape = self.ir.tensor_shape + A_shape = self.ir.expression.tensor_shape for indices in keep: multi_index = L.MultiIndex(list(indices), A_shape) for expression in keep[indices]: diff --git a/ffcx/codegeneration/symbols.py b/ffcx/codegeneration/symbols.py index e0260618b..c4916db4d 100644 --- a/ffcx/codegeneration/symbols.py +++ b/ffcx/codegeneration/symbols.py @@ -95,21 +95,21 @@ def __init__(self, coefficient_numbering, coefficient_offsets, original_constant # Table for chunk of custom quadrature points (physical coordinates). self.custom_points_table = L.Symbol("points_chunk", dtype=L.DataType.REAL) - def entity(self, entitytype, restriction): + def entity(self, entity_type, restriction): """Entity index for lookup in element tables.""" - if entitytype == "cell": + if entity_type == "cell": # Always 0 for cells (even with restriction) return L.LiteralInt(0) - if entitytype == "facet": + if entity_type == "facet": if restriction == "-": return self.entity_local_index[1] else: return self.entity_local_index[0] - elif entitytype == "vertex": + elif entity_type == "vertex": return self.entity_local_index[0] else: - logging.exception(f"Unknown entitytype {entitytype}") + logging.exception(f"Unknown entity_type {entity_type}") def argument_loop_index(self, iarg): """Loop index for argument iarg.""" @@ -175,14 +175,14 @@ def constant_index_access(self, constant, index): return c[offset + index] # TODO: Remove this, use table_access instead - def element_table(self, tabledata, entitytype, restriction): + def element_table(self, tabledata, entity_type, restriction): """Get an element table.""" - entity = self.entity(entitytype, restriction) + entity = self.entity(entity_type, restriction) if tabledata.is_uniform: entity = 0 else: - entity = self.entity(entitytype, restriction) + entity = self.entity(entity_type, restriction) if tabledata.is_piecewise: iq = 0 diff --git a/ffcx/codegeneration/ufcx.h b/ffcx/codegeneration/ufcx.h index 38895c9b0..d905b5745 100644 --- a/ffcx/codegeneration/ufcx.h +++ b/ffcx/codegeneration/ufcx.h @@ -210,7 +210,7 @@ extern "C" int num_constants; /// Original coefficient position for each coefficient - int* original_coefficient_position; + int* original_coefficient_positions; /// List of names of coefficients const char** coefficient_name_map; diff --git a/ffcx/ir/elementtables.py b/ffcx/ir/elementtables.py index bd6d23456..b98d9629f 100644 --- a/ffcx/ir/elementtables.py +++ b/ffcx/ir/elementtables.py @@ -77,7 +77,7 @@ def clamp_table_small_numbers( def get_ffcx_table_values( - points, cell, integral_type, element, avg, entitytype, derivative_counts, flat_component + points, cell, integral_type, element, avg, entity_type, derivative_counts, flat_component ): """Extract values from FFCx element table. @@ -94,7 +94,7 @@ def get_ffcx_table_values( if integral_type == "expression": # FFCx tables for expression are generated as either interior cell points # or points on a facet - if entitytype == "cell": + if entity_type == "cell": integral_type = "cell" else: integral_type = "exterior_facet" @@ -162,7 +162,7 @@ def get_ffcx_table_values( def generate_psi_table_name( - quadrature_rule, element_counter, averaged: str, entitytype, derivative_counts, flat_component + quadrature_rule, element_counter, averaged: str, entity_type, derivative_counts, flat_component ): """Generate a name for the psi table. @@ -187,7 +187,7 @@ def generate_psi_table_name( if any(derivative_counts): 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 += {"cell": "", "facet": "_F", "vertex": "_V"}[entity_type] name += f"_Q{quadrature_rule.id()}" return name @@ -283,7 +283,7 @@ def build_optimized_tables( quadrature_rule, cell, integral_type, - entitytype, + entity_type, modified_terminals, existing_tables, use_sum_factorization, @@ -293,7 +293,7 @@ def build_optimized_tables( """Build the element tables needed for a list of modified terminals. Input: - entitytype - str + entity_type - str modified_terminals - ordered sequence of unique modified terminals FIXME: Document @@ -332,7 +332,7 @@ def build_optimized_tables( # Build name for this particular table element_number = element_numbers[element] name = generate_psi_table_name( - quadrature_rule, element_number, avg, entitytype, local_derivatives, flat_component + quadrature_rule, element_number, avg, entity_type, local_derivatives, flat_component ) # FIXME - currently just recalculate the tables every time, @@ -349,7 +349,7 @@ def build_optimized_tables( integral_type, element, avg, - entitytype, + entity_type, local_derivatives, flat_component, ) @@ -363,7 +363,7 @@ def build_optimized_tables( integral_type, element, avg, - entitytype, + entity_type, local_derivatives, flat_component, ) @@ -384,7 +384,7 @@ def build_optimized_tables( integral_type, element, avg, - entitytype, + entity_type, local_derivatives, flat_component, ) @@ -404,7 +404,7 @@ def build_optimized_tables( integral_type, element, avg, - entitytype, + entity_type, local_derivatives, flat_component, ) @@ -418,7 +418,7 @@ def build_optimized_tables( integral_type, element, avg, - entitytype, + entity_type, local_derivatives, flat_component, ) diff --git a/ffcx/ir/integral.py b/ffcx/ir/integral.py index 342cff58a..382ced092 100644 --- a/ffcx/ir/integral.py +++ b/ffcx/ir/integral.py @@ -46,7 +46,7 @@ class BlockDataT(typing.NamedTuple): is_permuted: bool # Do quad points on facets need to be permuted? -def compute_integral_ir(cell, integral_type, entitytype, integrands, argument_shape, p, visualise): +def compute_integral_ir(cell, integral_type, entity_type, integrands, argument_shape, p, visualise): """Compute intermediate representation for an integral.""" # The intermediate representation dict we're building and returning # here @@ -86,7 +86,7 @@ def compute_integral_ir(cell, integral_type, entitytype, integrands, argument_sh quadrature_rule, cell, integral_type, - entitytype, + entity_type, initial_terminals.values(), ir["unique_tables"], use_sum_factorization=p["sum_factorization"], diff --git a/ffcx/ir/representation.py b/ffcx/ir/representation.py index 0f61329fc..b75cc280f 100644 --- a/ffcx/ir/representation.py +++ b/ffcx/ir/representation.py @@ -46,7 +46,7 @@ class FormIR(typing.NamedTuple): num_coefficients: int num_constants: int name_from_uflfile: str - original_coefficient_position: list[int] + original_coefficient_positions: list[int] coefficient_names: list[str] constant_names: list[str] finite_element_hashes: list[int] @@ -62,13 +62,11 @@ class QuadratureIR(typing.NamedTuple): weights: npt.NDArray[np.float64] -class IntegralIR(typing.NamedTuple): - """Intermediate representation of an integral.""" +class CommonExpressionIR(typing.NamedTuple): + """Common-ground for IntegralIR and ExpressionIR.""" integral_type: str - rank: int - entitytype: str - enabled_coefficients: list[bool] + entity_type: str tensor_shape: list[int] coefficient_numbering: dict[ufl.Coefficient, int] coefficient_offsets: dict[ufl.Coefficient, int] @@ -78,30 +76,26 @@ class IntegralIR(typing.NamedTuple): integrand: dict[QuadratureRule, dict] name: str needs_facet_permutations: bool - coordinate_element_hash: int + shape: list[int] + + +class IntegralIR(typing.NamedTuple): + """Intermediate representation of an integral.""" + + expression: CommonExpressionIR + rank: int + enabled_coefficients: list[bool] + coordinate_element_hash: str class ExpressionIR(typing.NamedTuple): - """Intermediate representation of an expression.""" + """Intermediate representation of a DOLFINx Expression.""" - name: str - options: dict - unique_tables: dict[str, npt.NDArray[np.float64]] - unique_table_types: dict[str, str] - integrand: dict[QuadratureRule, dict] - coefficient_numbering: dict[ufl.Coefficient, int] - coefficient_offsets: dict[ufl.Coefficient, int] - integral_type: str - entitytype: str - tensor_shape: list[int] - expression_shape: list[int] - original_constant_offsets: dict[ufl.Constant, int] - points: npt.NDArray[np.float64] + expression: CommonExpressionIR + original_coefficient_positions: list[int] coefficient_names: list[str] constant_names: list[str] - needs_facet_permutations: bool name_from_uflfile: str - original_coefficient_positions: list[int] class DataIR(typing.NamedTuple): @@ -208,18 +202,22 @@ def _compute_integral_ir( irs = [] for itg_data_index, itg_data in enumerate(form_data.integral_data): logger.info(f"Computing IR for integral in integral group {itg_data_index}") + expression_ir = {} # Compute representation - entitytype = _entity_types[itg_data.integral_type] + entity_type = _entity_types[itg_data.integral_type] cell = itg_data.domain.ufl_cell() cellname = cell.cellname() tdim = cell.topological_dimension() assert all(tdim == itg.ufl_domain().topological_dimension() for itg in itg_data.integrals) - ir = { + expression_ir = { "integral_type": itg_data.integral_type, + "entity_type": entity_type, + "shape": (), + } + ir = { "rank": form_data.rank, - "entitytype": entitytype, "enabled_coefficients": itg_data.enabled_coefficients, "coordinate_element_hash": finite_element_hashes[ itg_data.domain.ufl_coordinate_element() @@ -239,10 +237,10 @@ def _compute_integral_ir( ] # Compute shape of element tensor - if ir["integral_type"] == "interior_facet": - ir["tensor_shape"] = [2 * dim for dim in argument_dimensions] + if expression_ir["integral_type"] == "interior_facet": + expression_ir["tensor_shape"] = [2 * dim for dim in argument_dimensions] else: - ir["tensor_shape"] = argument_dimensions + expression_ir["tensor_shape"] = argument_dimensions integral_type = itg_data.integral_type cell = itg_data.domain.ufl_cell() @@ -367,7 +365,7 @@ def _compute_integral_ir( coefficient_numbering[f] = i # Add coefficient numbering to IR - ir["coefficient_numbering"] = coefficient_numbering + expression_ir["coefficient_numbering"] = coefficient_numbering index_to_coeff = sorted([(v, k) for k, v in coefficient_numbering.items()]) offsets = {} @@ -378,7 +376,7 @@ def _compute_integral_ir( _offset += width * element_dimensions[el] # Copy offsets also into IR - ir["coefficient_offsets"] = offsets + expression_ir["coefficient_offsets"] = offsets # Build offsets for Constants original_constant_offsets = {} @@ -387,7 +385,7 @@ def _compute_integral_ir( original_constant_offsets[constant] = _offset _offset += np.prod(constant.ufl_shape, dtype=int) - ir["original_constant_offsets"] = original_constant_offsets + expression_ir["original_constant_offsets"] = original_constant_offsets # Create map from number of quadrature points -> integrand integrand_map: dict[QuadratureRule, ufl.core.expr.Expr] = { @@ -398,18 +396,18 @@ def _compute_integral_ir( integral_ir = compute_integral_ir( itg_data.domain.ufl_cell(), itg_data.integral_type, - ir["entitytype"], + expression_ir["entity_type"], integrand_map, - ir["tensor_shape"], + expression_ir["tensor_shape"], options, visualise, ) - ir.update(integral_ir) + expression_ir.update(integral_ir) # Fetch name - ir["name"] = integral_names[(form_index, itg_data_index)] - + expression_ir["name"] = integral_names[(form_index, itg_data_index)] + ir["expression"] = CommonExpressionIR(**expression_ir) irs.append(IntegralIR(**ir)) return irs @@ -449,7 +447,7 @@ def _compute_form_ir( for j, obj in enumerate(form_data.original_form.constants()) ] - ir["original_coefficient_position"] = form_data.original_coefficient_positions + ir["original_coefficient_positions"] = form_data.original_coefficient_positions ir["finite_element_hashes"] = [ finite_element_hashes[e] @@ -498,10 +496,10 @@ def _compute_expression_ir( # Compute representation ir = {} - + base_ir = {} original_expression = (expression[2], expression[1]) - ir["name"] = naming.expression_name(original_expression, prefix) + base_ir["name"] = naming.expression_name(original_expression, prefix) original_expression = expression[2] points = expression[1] @@ -527,9 +525,9 @@ def _compute_expression_ir( argument_dimensions = [element_dimensions[element] for element in argument_elements] tensor_shape = argument_dimensions - ir["tensor_shape"] = tensor_shape + base_ir["tensor_shape"] = tensor_shape - ir["expression_shape"] = list(expression.ufl_shape) + base_ir["shape"] = list(expression.ufl_shape) coefficients = ufl.algorithms.extract_coefficients(expression) coefficient_numbering = {} @@ -537,7 +535,7 @@ def _compute_expression_ir( coefficient_numbering[coeff] = i # Add coefficient numbering to IR - ir["coefficient_numbering"] = coefficient_numbering + base_ir["coefficient_numbering"] = coefficient_numbering original_coefficient_positions = [] original_coefficients = ufl.algorithms.extract_coefficients(original_expression) @@ -571,14 +569,14 @@ def _compute_expression_ir( _offset += element_dimensions[el] # Copy offsets also into IR - ir["coefficient_offsets"] = offsets + base_ir["coefficient_offsets"] = offsets - ir["integral_type"] = "expression" + base_ir["integral_type"] = "expression" if cell is not None: if (tdim := cell.topological_dimension()) == (pdim := points.shape[1]): - ir["entitytype"] = "cell" + base_ir["entity_type"] = "cell" elif tdim - 1 == pdim: - ir["entitytype"] = "facet" + base_ir["entity_type"] = "facet" else: raise ValueError( f"Expression on domain with topological dimension {tdim}" @@ -586,7 +584,7 @@ def _compute_expression_ir( ) else: # For spatially invariant expressions, all expressions are evaluated in the cell - ir["entitytype"] = "cell" + base_ir["entity_type"] = "cell" # Build offsets for Constants original_constant_offsets = {} @@ -595,9 +593,7 @@ def _compute_expression_ir( original_constant_offsets[constant] = _offset _offset += np.prod(constant.ufl_shape, dtype=int) - ir["original_constant_offsets"] = original_constant_offsets - - ir["points"] = points + base_ir["original_constant_offsets"] = original_constant_offsets weights = np.array([1.0] * points.shape[0]) rule = QuadratureRule(points, weights) @@ -610,9 +606,15 @@ def _compute_expression_ir( ) expression_ir = compute_integral_ir( - cell, ir["integral_type"], ir["entitytype"], integrands, tensor_shape, options, visualise + cell, + base_ir["integral_type"], + base_ir["entity_type"], + integrands, + tensor_shape, + options, + visualise, ) - ir["options"] = options - ir.update(expression_ir) + base_ir.update(expression_ir) + ir["expression"] = CommonExpressionIR(**base_ir) return ExpressionIR(**ir) From 86a8cc2db8b4ea1f534f77ec9803d17f5ca50240 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 22 May 2024 16:51:45 +0200 Subject: [PATCH 15/25] Add Windows CI (#689) * Whitespace. * Try Windows. * try without graphviz * Refine optional dependencies. * Verbose. * Check pygraphviz exists * Not clear why this fails on Windows. * Update conftest.py * Remove to debug on win32 * Ruff format. * Complex number support is optional part of C11 standard. * Define __restrict for MSVC. * Invert logic * Few notes. * Works, hacky. * Format. * Fix. * Adjust code generation. * Throw NotImplementedError on Windows. * Attempt jit form pass on Windows. * Ruff check fix. * More adjustments to form code gen. * Add proper restrict keyword for MSVC. * Compile in C17 mode. * Make compile args default empty list. * Make sure it enters the cache string. * Don't modify list in place. * Don't link m on windows. * Ruff check fix. * Windows needs type on empty list. * Expected fail on other complex tests. * Throw error later just before compile. * This macro doesn't exist when compiling in C++ mode. * Fix raise. * Fix bug. * Fix test. * Try running demos on Windows. * Try this. * Fixes. * Ruff format. * Use c17. * Ruff format * Disable this error. * Ruff check. * Typo. * Don't see necessity of this. * Tidy. * Make one test use numpy dtypes. * Make sure it works with numpy dtypes. * Try this logic. * Try this logic. * Accepts standard options. * Should work also on Windows. * Not true. * Does support these options, easier to remember. * Also try with clang-cl * Fix. * Update pythonapp.yml * Add replacement for SOABI on Windows. * get_config_vars potentially returns None, typing error. * Use CC environment variable or generic Unix cc symlink. * Type hints for these public facing methods. * Fix mypy errors. * Typo. * Get optional dependencies installed on Linux. * Improve test error messages. * Fix. --- .github/workflows/pythonapp.yml | 49 ++++++++- demo/test_demos.py | 54 +++++++--- ffcx/codegeneration/C/file_template.py | 7 +- ffcx/codegeneration/C/integrals.py | 17 +++- ffcx/codegeneration/C/integrals_template.py | 8 +- ffcx/codegeneration/jit.py | 106 +++++++++++++++----- ffcx/codegeneration/ufcx.h | 16 ++- pyproject.toml | 3 +- test/conftest.py | 7 +- test/test_add_mode.py | 38 ++++++- test/test_cmdline.py | 7 ++ test/test_jit_forms.py | 72 +++++++++++-- test/test_signatures.py | 40 ++++++-- test/test_submesh.py | 11 +- 14 files changed, 350 insertions(+), 85 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 6e5701c6b..6fb7dcfb0 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -22,40 +22,75 @@ jobs: matrix: os: [ubuntu-latest] python-version: ['3.9', '3.10', '3.11', '3.12'] + include: + - os: windows-latest + python-version: '3.11' + - os: macos-latest + python-version: '3.12' + steps: - name: Checkout FFCx uses: actions/checkout@v4 + - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + + - name: Export GitHub Actions cache environment variables (Windows) + if: runner.os == 'Windows' + uses: actions/github-script@v6 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + - name: Install dependencies (non-Python, Linux) if: runner.os == 'Linux' run: | sudo apt-get install -y graphviz libgraphviz-dev ninja-build pkg-config - name: Install dependencies (non-Python, macOS) if: runner.os == 'macOS' - run: brew install graphviz ninja pkg-config - - name: Install FEniCS dependencies (Python) + run: brew install ninja pkg-config + + - name: Install FEniCS dependencies (Python, Unix) + if: runner.os == 'Linux' || runner.os == 'macOS' run: | pip install git+https://github.com/FEniCS/ufl.git pip install git+https://github.com/FEniCS/basix.git - - name: Install FFCx + + - name: Install FEniCS dependencies (Python, Windows) + if: runner.os == 'Windows' + env: + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" + run: | + pip install git+https://github.com/FEniCS/ufl.git + pip install -v git+https://github.com/FEniCS/basix.git --config-settings=cmake.args=-DINSTALL_RUNTIME_DEPENDENCIES=ON --config-settings=cmake.args=-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake + + - name: Install FFCx (Linux, with optional dependencies) + if: runner.os == 'Linux' + run: pip install .[ci,optional] + - name: Install FFCx (macOS, Windows) + if: runner.os != 'Linux' run: pip install .[ci] + - name: Static check with mypy run: mypy ffcx/ - name: ruff checks run: | ruff check . ruff format --check . + - name: Run units tests run: python -m 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' }} env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} run: coveralls continue-on-error: true + - name: Upload pytest results uses: actions/upload-artifact@v4 with: @@ -64,10 +99,15 @@ jobs: # Use always() to always run this step to publish test results # when there are test failures if: always() + + - name: Setup cl.exe (Windows) + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + - name: Run FFCx demos run: | pytest demo/test_demos.py - rm -Rf ufl/ + - name: Build documentation run: | cd doc @@ -79,6 +119,7 @@ jobs: path: doc/build/html/ retention-days: 2 if-no-files-found: error + - name: Checkout FEniCS/docs if: ${{ github.repository == 'FEniCS/ffcx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.8 }} uses: actions/checkout@v4 diff --git a/demo/test_demos.py b/demo/test_demos.py index 4d62bb61a..78b18234f 100644 --- a/demo/test_demos.py +++ b/demo/test_demos.py @@ -17,6 +17,10 @@ @pytest.mark.parametrize("scalar_type", ["float64", "float32", "complex128", "complex64"]) def test_demo(file, scalar_type): """Test a demo.""" + if sys.platform.startswith("win32") and "complex" in scalar_type: + # Skip complex demos on win32 + pytest.skip(reason="_Complex not supported on Windows") + if file in [ "MixedGradient", "TraceElement", # HDiv Trace @@ -25,7 +29,7 @@ def test_demo(file, scalar_type): "_TensorProductElement", ]: # Skip demos that use elements not yet implemented in Basix - pytest.skip() + pytest.skip(reason="Element not yet implemented in Basix") if "complex" in scalar_type and file in [ "BiharmonicHHJ", @@ -33,21 +37,39 @@ def test_demo(file, scalar_type): "StabilisedStokes", ]: # Skip demos that are not implemented for complex scalars - pytest.skip() + pytest.skip(reason="Not implemented for complex types") elif "Complex" in file and scalar_type in ["float64", "float32"]: # Skip demos that are only implemented for complex scalars - pytest.skip() - - opts = f"--scalar_type {scalar_type}" - extra_flags = "-Wunused-variable -Werror -fPIC " - assert os.system(f"cd {demo_dir} && ffcx {opts} {file}.py") == 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} " - f"{extra_flags} " - f"-shared {file}.c -o {file}.so" + pytest.skip(reason="Not implemented for real types") + + if sys.platform.startswith("win32"): + opts = f"--scalar_type {scalar_type}" + extra_flags = "/std:c17" + assert os.system(f"cd {demo_dir} && ffcx {opts} {file}.py") == 0 + assert ( + os.system( + f"cd {demo_dir} && " f'cl.exe /I "../ffcx/codegeneration" {extra_flags} /c {file}.c' + ) + ) == 0 + assert ( + os.system( + f"cd {demo_dir} && " + f'clang-cl.exe /I "../ffcx/codegeneration" {extra_flags} /c {file}.c' + ) + ) == 0 + else: + cc = os.environ.get("CC", "cc") + opts = f"--scalar_type {scalar_type}" + extra_flags = ( + "-std=c17 -Wunused-variable -Werror -fPIC -Wno-error=implicit-function-declaration" + ) + assert os.system(f"cd {demo_dir} && ffcx {opts} {file}.py") == 0 + assert ( + os.system( + f"cd {demo_dir} && " + f"{cc} -I../ffcx/codegeneration " + f"{extra_flags} " + f"-c {file}.c" + ) + == 0 ) - == 0 - ) diff --git a/ffcx/codegeneration/C/file_template.py b/ffcx/codegeneration/C/file_template.py index 801b5972a..b107f8251 100644 --- a/ffcx/codegeneration/C/file_template.py +++ b/ffcx/codegeneration/C/file_template.py @@ -4,6 +4,8 @@ # The FEniCS Project (http://www.fenicsproject.org/) 2018. """Code generation strings for a file.""" +import sys + declaration_pre = """ // This code conforms with the UFC specification version {ufcx_version} // and was automatically generated by FFCx version {ffcx_version}. @@ -43,6 +45,9 @@ """ -libraries = ["m"] +if sys.platform.startswith("win32"): + libraries: list[str] = [] +else: + libraries: list[str] = ["m"] implementation_post = "" diff --git a/ffcx/codegeneration/C/integrals.py b/ffcx/codegeneration/C/integrals.py index 8816d06d4..1115e3731 100644 --- a/ffcx/codegeneration/C/integrals.py +++ b/ffcx/codegeneration/C/integrals.py @@ -6,6 +6,7 @@ """Generate UFC code for an integral.""" import logging +import sys import numpy as np @@ -60,12 +61,18 @@ def generator(ir: IntegralIR, options): code["tabulate_tensor"] = body - code["tabulate_tensor_float32"] = "NULL" - code["tabulate_tensor_float64"] = "NULL" - code["tabulate_tensor_complex64"] = "NULL" - code["tabulate_tensor_complex128"] = "NULL" + code["tabulate_tensor_float32"] = ".tabulate_tensor_float32 = NULL," + code["tabulate_tensor_float64"] = ".tabulate_tensor_float64 = NULL," + if sys.platform.startswith("win32"): + code["tabulate_tensor_complex64"] = "" + code["tabulate_tensor_complex128"] = "" + else: + code["tabulate_tensor_complex64"] = ".tabulate_tensor_complex64 = NULL," + code["tabulate_tensor_complex128"] = ".tabulate_tensor_complex128 = NULL," np_scalar_type = np.dtype(options["scalar_type"]).name - code[f"tabulate_tensor_{np_scalar_type}"] = f"tabulate_tensor_{factory_name}" + code[f"tabulate_tensor_{np_scalar_type}"] = ( + f".tabulate_tensor_{np_scalar_type} = tabulate_tensor_{factory_name}," + ) element_hash = 0 if ir.coordinate_element_hash is None else ir.coordinate_element_hash diff --git a/ffcx/codegeneration/C/integrals_template.py b/ffcx/codegeneration/C/integrals_template.py index 937f759ae..2bb1568ec 100644 --- a/ffcx/codegeneration/C/integrals_template.py +++ b/ffcx/codegeneration/C/integrals_template.py @@ -26,10 +26,10 @@ ufcx_integral {factory_name} = {{ .enabled_coefficients = {enabled_coefficients}, - .tabulate_tensor_float32 = {tabulate_tensor_float32}, - .tabulate_tensor_float64 = {tabulate_tensor_float64}, - .tabulate_tensor_complex64 = {tabulate_tensor_complex64}, - .tabulate_tensor_complex128 = {tabulate_tensor_complex128}, + {tabulate_tensor_float32} + {tabulate_tensor_float64} + {tabulate_tensor_complex64} + {tabulate_tensor_complex128} .needs_facet_permutations = {needs_facet_permutations}, .coordinate_element_hash = {coordinate_element_hash}, }}; diff --git a/ffcx/codegeneration/jit.py b/ffcx/codegeneration/jit.py index aa903c496..6eb5dbb8f 100644 --- a/ffcx/codegeneration/jit.py +++ b/ffcx/codegeneration/jit.py @@ -12,6 +12,7 @@ import logging import os import re +import sys import sysconfig import tempfile import time @@ -20,6 +21,8 @@ import cffi import numpy as np +import numpy.typing as npt +import ufl import ffcx import ffcx.naming @@ -33,6 +36,20 @@ with open(file_dir + "/ufcx.h") as f: ufcx_h = "".join(f.readlines()) +# Emulate C preprocessor on __STDC_NO_COMPLEX__ +if sys.platform.startswith("win32"): + # Remove macro statements and content + ufcx_h = re.sub( + r"\#ifndef __STDC_NO_COMPLEX__.*?\#endif // __STDC_NO_COMPLEX__", + "", + ufcx_h, + flags=re.DOTALL, + ) +else: + # Remove only macros keeping content + ufcx_h = ufcx_h.replace("#ifndef __STDC_NO_COMPLEX__", "") + ufcx_h = ufcx_h.replace("#endif // __STDC_NO_COMPLEX__", "") + header = ufcx_h.split("")[1].split("")[0].strip(" /\n") header = header.replace("{", "{{").replace("}", "}}") UFC_HEADER_DECL = header + "\n" @@ -51,13 +68,11 @@ UFC_INTEGRAL_DECL += "\n".join( re.findall(r"typedef void ?\(ufcx_tabulate_tensor_complex128\).*?\);", ufcx_h, re.DOTALL) ) -UFC_INTEGRAL_DECL += "\n".join( - re.findall(r"typedef void ?\(ufcx_tabulate_tensor_longdouble\).*?\);", ufcx_h, re.DOTALL) -) UFC_INTEGRAL_DECL += "\n".join( re.findall("typedef struct ufcx_integral.*?ufcx_integral;", ufcx_h, re.DOTALL) ) + UFC_EXPRESSION_DECL = "\n".join( re.findall("typedef struct ufcx_expression.*?ufcx_expression;", ufcx_h, re.DOTALL) ) @@ -110,7 +125,7 @@ def get_cached_module(module_name, object_names, cache_dir, timeout): ) -def _compilation_signature(cffi_extra_compile_args=None, cffi_debug=None): +def _compilation_signature(cffi_extra_compile_args, cffi_debug): """Compute the compilation-inputs part of the signature. Used to avoid cache conflicts across Python versions, architectures, installs. @@ -118,26 +133,46 @@ def _compilation_signature(cffi_extra_compile_args=None, cffi_debug=None): - SOABI includes platform, Python version, debug flags - CFLAGS includes prefixes, arch targets """ - return ( - str(cffi_extra_compile_args) - + str(cffi_debug) - + sysconfig.get_config_var("CFLAGS") - + sysconfig.get_config_var("SOABI") - ) + if sys.platform.startswith("win32"): + # NOTE: SOABI not defined on win32, EXT_SUFFIX contains e.g. '.cp312-win_amd64.pyd' + return ( + str(cffi_extra_compile_args) + + str(cffi_debug) + + str(sysconfig.get_config_var("EXT_SUFFIX")) + ) + else: + return ( + str(cffi_extra_compile_args) + + str(cffi_debug) + + str(sysconfig.get_config_var("CFLAGS")) + + str(sysconfig.get_config_var("SOABI")) + ) def compile_forms( - forms, - options=None, - cache_dir=None, - timeout=10, - cffi_extra_compile_args=None, - cffi_verbose=False, - cffi_debug=None, - cffi_libraries=None, + forms: list[ufl.Form], + options: dict = {}, + cache_dir: Path | None = None, + timeout: int = 10, + cffi_extra_compile_args: list[str] = [], + cffi_verbose: bool = False, + cffi_debug: bool = False, + cffi_libraries: list[str] = [], visualise: bool = False, ): - """Compile a list of UFL forms into UFC Python objects.""" + """Compile a list of UFL forms into UFC Python objects. + + Args: + forms: List of ufl.form to compile. + options: Options + cache_dir: Cache directory + timeout: Timeout + cffi_extra_compile_args: Extra compilation args for CFFI + cffi_verbose: Use verbose compile + cffi_debug: Use compiler debug mode + cffi_libraries: libraries to use with compiler + visualise: Toggle visualisation + """ p = ffcx.options.get_options(options) # Get a signature for these forms @@ -194,14 +229,14 @@ def compile_forms( def compile_expressions( - expressions, - options=None, - cache_dir=None, - timeout=10, - cffi_extra_compile_args=None, + expressions: list[tuple[ufl.Expr, npt.NDArray[np.floating]]], + options: dict = {}, + cache_dir: Path | None = None, + timeout: int = 10, + cffi_extra_compile_args: list[str] = [], cffi_verbose: bool = False, - cffi_debug=None, - cffi_libraries=None, + cffi_debug: bool = False, + cffi_libraries: list[str] = [], visualise: bool = False, ): """Compile a list of UFL expressions into UFC Python objects. @@ -296,13 +331,30 @@ def _compile_objects( ufl_objects, prefix=module_name, options=options, visualise=visualise ) + # Raise error immediately prior to compilation if no support for C99 + # _Complex. Doing this here allows FFCx to be used for complex codegen on + # Windows. + if sys.platform.startswith("win32"): + if np.issubdtype(options["scalar_type"], np.complexfloating): + raise NotImplementedError("win32 platform does not support C99 _Complex numbers") + elif isinstance(options["scalar_type"], str) and "complex" in options["scalar_type"]: + raise NotImplementedError("win32 platform does not support C99 _Complex numbers") + + # Compile in C17 mode + if sys.platform.startswith("win32"): + cffi_base_compile_args = ["-std:c17"] + else: + cffi_base_compile_args = ["-std=c17"] + + cffi_final_compile_args = cffi_base_compile_args + cffi_extra_compile_args + ffibuilder = cffi.FFI() ffibuilder.set_source( module_name, code_body, include_dirs=[ffcx.codegeneration.get_include_path()], - extra_compile_args=cffi_extra_compile_args, + extra_compile_args=cffi_final_compile_args, libraries=libraries, ) diff --git a/ffcx/codegeneration/ufcx.h b/ffcx/codegeneration/ufcx.h index d905b5745..e1dd838d1 100644 --- a/ffcx/codegeneration/ufcx.h +++ b/ffcx/codegeneration/ufcx.h @@ -31,9 +31,12 @@ extern "C" { #if defined(__clang__) -#define restrict -#elif defined(__GNUC__) || defined(__GNUG__) +#define restrict __restrict +#elif defined(__GNUG__) #define restrict __restrict__ +#elif defined(_MSC_VER) +#define restrict __restrict +#define __STDC_NO_COMPLEX__ #else #define restrict #endif // restrict @@ -98,6 +101,7 @@ extern "C" const int* restrict entity_local_index, const uint8_t* restrict quadrature_permutation); +#ifndef __STDC_NO_COMPLEX__ /// Tabulate integral into tensor A with compiled /// quadrature rule and complex single precision /// @@ -107,7 +111,9 @@ extern "C" const float _Complex* restrict c, const float* restrict coordinate_dofs, const int* restrict entity_local_index, const uint8_t* restrict quadrature_permutation); +#endif // __STDC_NO_COMPLEX__ +#ifndef __STDC_NO_COMPLEX__ /// Tabulate integral into tensor A with compiled /// quadrature rule and complex double precision /// @@ -117,14 +123,17 @@ extern "C" const double _Complex* restrict c, const double* restrict coordinate_dofs, const int* restrict entity_local_index, const uint8_t* restrict quadrature_permutation); +#endif // __STDC_NO_COMPLEX__ typedef struct ufcx_integral { const bool* enabled_coefficients; ufcx_tabulate_tensor_float32* tabulate_tensor_float32; ufcx_tabulate_tensor_float64* tabulate_tensor_float64; +#ifndef __STDC_NO_COMPLEX__ ufcx_tabulate_tensor_complex64* tabulate_tensor_complex64; ufcx_tabulate_tensor_complex128* tabulate_tensor_complex128; +#endif // __STDC_NO_COMPLEX__ bool needs_facet_permutations; /// Get the hash of the coordinate element associated with the geometry of the mesh. @@ -142,8 +151,10 @@ extern "C" /// ufcx_tabulate_tensor_float32* tabulate_tensor_float32; ufcx_tabulate_tensor_float64* tabulate_tensor_float64; +#ifndef __STDC_NO_COMPLEX__ ufcx_tabulate_tensor_complex64* tabulate_tensor_complex64; ufcx_tabulate_tensor_complex128* tabulate_tensor_complex128; +#endif // __STDC_NO_COMPLEX__ /// Number of coefficients int num_coefficients; @@ -238,5 +249,6 @@ extern "C" #ifdef __cplusplus #undef restrict +#undef __STDC_NO_COMPLEX__ } #endif diff --git a/pyproject.toml b/pyproject.toml index 0b69bab26..99e53dbc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ ffcx = "ffcx:__main__.main" lint = ["ruff"] docs = ["sphinx", "sphinx_rtd_theme"] optional = ["numba", "pygraphviz==1.7"] -test = ["pytest >= 6.0", "sympy"] +test = ["pytest >= 6.0", "sympy", "numba"] ci = [ "coveralls", "coverage", @@ -42,7 +42,6 @@ ci = [ "types-setuptools", "mypy", "fenics-ffcx[docs]", - "fenics-ffcx[optional]", "fenics-ffcx[lint]", "fenics-ffcx[test]", ] diff --git a/test/conftest.py b/test/conftest.py index e4b5f1dcb..1092de329 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -5,10 +5,15 @@ # SPDX-License-Identifier: LGPL-3.0-or-later """Test configuration.""" +import sys + import pytest @pytest.fixture(scope="module") def compile_args(): """Compiler arguments.""" - return ["-O1", "-Wall", "-Werror"] + if sys.platform.startswith("win32"): + return ["-Od"] + else: + return ["-O1", "-Wall", "-Werror"] diff --git a/test/test_add_mode.py b/test/test_add_mode.py index 34e91a24a..574b02946 100644 --- a/test/test_add_mode.py +++ b/test/test_add_mode.py @@ -4,6 +4,8 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +import sys + import basix.ufl import numpy as np import pytest @@ -18,8 +20,22 @@ [ "float32", "float64", - "complex64", - "complex128", + pytest.param( + "complex64", + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), + ), + pytest.param( + "complex128", + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), + ), ], ) def test_additive_facet_integral(dtype, compile_args): @@ -79,8 +95,22 @@ def test_additive_facet_integral(dtype, compile_args): [ "float32", "float64", - "complex64", - "complex128", + pytest.param( + "complex64", + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), + ), + pytest.param( + "complex128", + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), + ), ], ) def test_additive_cell_integral(dtype, compile_args): diff --git a/test/test_cmdline.py b/test/test_cmdline.py index aa80ebcbe..9f785b82c 100644 --- a/test/test_cmdline.py +++ b/test/test_cmdline.py @@ -8,6 +8,8 @@ import os.path import subprocess +import pytest + def test_cmdline_simple(): os.chdir(os.path.dirname(__file__)) @@ -15,6 +17,11 @@ def test_cmdline_simple(): def test_visualise(): + try: + import pygraphviz # noqa: F401 + except ImportError: + pytest.skip("pygraphviz not installed") + os.chdir(os.path.dirname(__file__)) subprocess.run(["ffcx", "--visualise", "Poisson.py"]) assert os.path.isfile("S.pdf") diff --git a/test/test_jit_forms.py b/test/test_jit_forms.py index 697619aa7..dd0f45451 100644 --- a/test/test_jit_forms.py +++ b/test/test_jit_forms.py @@ -4,6 +4,8 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +import sys + import basix.ufl import numpy as np import pytest @@ -22,7 +24,7 @@ "float64", np.array([[1.0, -0.5, -0.5], [-0.5, 0.5, 0.0], [-0.5, 0.0, 0.5]], dtype=np.float64), ), - ( + pytest.param( "complex128", np.array( [ @@ -32,6 +34,11 @@ ], dtype=np.complex128, ), + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), ), ], ) @@ -89,7 +96,7 @@ def test_laplace_bilinear_form_2d(dtype, expected_result, compile_args): "dtype,expected_result", [ ( - "float32", + np.float32, np.array( [ [1.0 / 12.0, 1.0 / 24.0, 1.0 / 24.0], @@ -105,7 +112,7 @@ def test_laplace_bilinear_form_2d(dtype, expected_result, compile_args): # [1.0 / 24.0, 1.0 / 24.0, 1.0 / 12.0]], # dtype=np.longdouble)), ( - "float64", + np.float64, np.array( [ [1.0 / 12.0, 1.0 / 24.0, 1.0 / 24.0], @@ -115,8 +122,8 @@ def test_laplace_bilinear_form_2d(dtype, expected_result, compile_args): dtype=np.float64, ), ), - ( - "complex128", + pytest.param( + np.complex128, np.array( [ [1.0 / 12.0, 1.0 / 24.0, 1.0 / 24.0], @@ -125,9 +132,14 @@ def test_laplace_bilinear_form_2d(dtype, expected_result, compile_args): ], dtype=np.complex128, ), + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), ), - ( - "complex64", + pytest.param( + np.complex64, np.array( [ [1.0 / 12.0, 1.0 / 24.0, 1.0 / 24.0], @@ -136,6 +148,11 @@ def test_laplace_bilinear_form_2d(dtype, expected_result, compile_args): ], dtype=np.complex64, ), + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), ), ], ) @@ -160,10 +177,15 @@ def test_mass_bilinear_form_2d(dtype, expected_result, compile_args): np.array([[1.0, -0.5, -0.5], [-0.5, 0.5, 0.0], [-0.5, 0.0, 0.5]], dtype=np.float64) - (1.0 / 24.0) * np.array([[2, 1, 1], [1, 2, 1], [1, 1, 2]], dtype=np.float64), ), - ( + pytest.param( "complex128", np.array([[1.0, -0.5, -0.5], [-0.5, 0.5, 0.0], [-0.5, 0.0, 0.5]], dtype=np.complex128) - (1.0j / 24.0) * np.array([[2, 1, 1], [1, 2, 1], [1, 1, 2]], dtype=np.complex128), + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), ), ], ) @@ -229,7 +251,7 @@ def test_helmholtz_form_2d(dtype, expected_result, compile_args): dtype=np.float64, ), ), - ( + pytest.param( "complex128", np.array( [ @@ -240,6 +262,11 @@ def test_helmholtz_form_2d(dtype, expected_result, compile_args): ], dtype=np.complex128, ), + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), ), ], ) @@ -362,7 +389,20 @@ def test_subdomains(compile_args): assert ids[0] == 0 and ids[1] == 210 -@pytest.mark.parametrize("dtype", ["float64", "complex128"]) +@pytest.mark.parametrize( + "dtype", + [ + "float64", + pytest.param( + "complex128", + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), + ), + ], +) def test_interior_facet_integral(dtype, compile_args): element = basix.ufl.element("Lagrange", "triangle", 1) domain = ufl.Mesh(basix.ufl.element("Lagrange", "triangle", 1, shape=(2,))) @@ -417,7 +457,14 @@ def test_interior_facet_integral(dtype, compile_args): "dtype", [ "float64", - "complex128", + pytest.param( + "complex128", + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), + ), ], ) def test_conditional(dtype, compile_args): @@ -808,6 +855,9 @@ def test_prism(compile_args): assert np.isclose(sum(b), 0.5) +@pytest.mark.xfail( + sys.platform.startswith("win32"), raises=NotImplementedError, reason="missing _Complex" +) def test_complex_operations(compile_args): dtype = "complex128" cell = "triangle" diff --git a/test/test_signatures.py b/test/test_signatures.py index 5cc0169d4..68391e2c2 100644 --- a/test/test_signatures.py +++ b/test/test_signatures.py @@ -4,6 +4,8 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +import sys + import basix.ufl import cffi import numpy as np @@ -16,7 +18,9 @@ def generate_kernel(forms, scalar_type, options): """Generate kernel for given forms.""" - compiled_forms, module, code = ffcx.codegeneration.jit.compile_forms(forms) + compiled_forms, module, code = ffcx.codegeneration.jit.compile_forms( + forms, options={"scalar_type": scalar_type} + ) for f, compiled_f in zip(forms, compiled_forms): assert compiled_f.rank == len(f.arguments()) @@ -33,16 +37,35 @@ def generate_kernel(forms, scalar_type, options): return kernel, code, module -@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) +@pytest.mark.parametrize( + "dtype", + [ + "float32", + "float64", + pytest.param( + "complex64", + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), + ), + pytest.param( + "complex128", + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), + ), + ], +) def test_numba_kernel_signature(dtype): try: import numba except ImportError: pytest.skip("Numba not installed") - # Convert to numpy dtype - dtype = np.dtype(dtype) - # Create a simple form mesh = ufl.Mesh(basix.ufl.element("P", "triangle", 2, shape=(2,))) e = basix.ufl.element("Lagrange", "triangle", 2) @@ -56,9 +79,12 @@ def test_numba_kernel_signature(dtype): # Generate and compile the kernel kernel, code, module = generate_kernel([a], dtype, {}) + # Convert to numpy dtype + np_dtype = np.dtype(dtype) + # Generate the Numba signature xtype = utils.dtype_to_scalar_dtype(dtype) - signature = utils.numba_ufcx_kernel_signature(dtype, xtype) + signature = utils.numba_ufcx_kernel_signature(np_dtype, xtype) assert isinstance(signature, numba.core.typing.templates.Signature) # Get the signature from the compiled kernel @@ -68,6 +94,6 @@ def test_numba_kernel_signature(dtype): # check that the signature is equivalent to the one in the generated code assert len(args) == len(signature.args) for i, (arg, sig) in enumerate(zip(args, signature.args)): - type_name = sig.name.replace(str(dtype), utils.dtype_to_c_type(dtype)) + type_name = sig.name.replace(str(np_dtype), utils.dtype_to_c_type(np_dtype)) ctypes_name = type_name.replace(" *", "*") assert ctypes_name == type_name diff --git a/test/test_submesh.py b/test/test_submesh.py index bce346dee..0379fbd13 100644 --- a/test/test_submesh.py +++ b/test/test_submesh.py @@ -6,6 +6,8 @@ from __future__ import annotations +import sys + import basix.ufl import numpy as np import pytest @@ -55,7 +57,14 @@ def compute_tensor(forms: list[ufl.form.Form], dtype: str, compile_args: list[st "dtype", [ "float64", - "complex128", + pytest.param( + "complex128", + marks=pytest.mark.xfail( + sys.platform.startswith("win32"), + raises=NotImplementedError, + reason="missing _Complex", + ), + ), ], ) def test_multiple_mesh_codim0(dtype, compile_args): From 17045ea9f8c2330bde518976175ceb95b68b7b5d Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Mon, 27 May 2024 09:02:22 +0100 Subject: [PATCH 16/25] Update for DOLFINx CI (#698) --- .github/workflows/dolfinx-tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dolfinx-tests.yml b/.github/workflows/dolfinx-tests.yml index 48e045fe8..844f1166f 100644 --- a/.github/workflows/dolfinx-tests.yml +++ b/.github/workflows/dolfinx-tests.yml @@ -65,7 +65,9 @@ jobs: cmake --build build cmake --install build - name: Install DOLFINx (Python) - run: python3 -m pip -v install --break-system-packages --check-build-dependencies --no-build-isolation dolfinx/python/ + run: | + python3 -m pip -v install --break-system-packages nanobind scikit-build-core[pyproject] + python3 -m pip -v install --break-system-packages --check-build-dependencies --no-build-isolation dolfinx/python/ - name: Build DOLFINx C++ unit tests run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S dolfinx/cpp/test/ @@ -74,6 +76,9 @@ jobs: run: | cd build/test ctest -V --output-on-failure -R unittests + + - name: Install Python demo/test dependencies + run: python3 -m pip install --break-system-packages matplotlib numba pyamg pytest pytest-xdist scipy - name: Run DOLFINx Python unit tests run: python3 -m pytest -n auto dolfinx/python/test/unit - name: Run DOLFINx Python demos From cc703a436a811087ecc6a344782f52cb0765e2ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Tue, 28 May 2024 11:15:49 +0200 Subject: [PATCH 17/25] Fix access in assert (#700) --- ffcx/ir/representation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffcx/ir/representation.py b/ffcx/ir/representation.py index b75cc280f..3c3bd378b 100644 --- a/ffcx/ir/representation.py +++ b/ffcx/ir/representation.py @@ -602,7 +602,7 @@ def _compute_expression_ir( if cell is None: assert ( len(ir["original_coefficient_positions"]) == 0 - and len(ir["original_constant_offsets"]) == 0 + and len(base_ir["original_constant_offsets"]) == 0 ) expression_ir = compute_integral_ir( From d324f3808ebb828b71cae4afec76870417b1d0e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Wed, 29 May 2024 20:56:40 +0200 Subject: [PATCH 18/25] Remove zero Lexpr check (#701) * Remove zero Lexpr check * Update lnodes.py --- ffcx/codegeneration/lnodes.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ffcx/codegeneration/lnodes.py b/ffcx/codegeneration/lnodes.py index 035f28bda..fcee212ab 100644 --- a/ffcx/codegeneration/lnodes.py +++ b/ffcx/codegeneration/lnodes.py @@ -86,7 +86,7 @@ def is_negative_one_lexpr(lexpr): def float_product(factors): """Build product of float factors. - Simplify ones and zeros and returning 1.0 if empty sequence. + Simplify ones and returning 1.0 if empty sequence. """ factors = [f for f in factors if not is_one_lexpr(f)] if len(factors) == 0: @@ -94,9 +94,6 @@ def float_product(factors): elif len(factors) == 1: return factors[0] else: - for f in factors: - if is_zero_lexpr(f): - return f return Product(factors) From 95daf8feac968cd46f240fd505d834978011bb27 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Wed, 5 Jun 2024 16:23:05 +0100 Subject: [PATCH 19/25] Type updates from Basix (#703) * Type fixes * Updates for Basix * Small revert * Small fix --- demo/ExpressionInterpolation.py | 2 +- ffcx/element_interface.py | 13 ++++++------- test/test_jit_expression.py | 7 ++----- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/demo/ExpressionInterpolation.py b/demo/ExpressionInterpolation.py index af5467342..d199738a5 100644 --- a/demo/ExpressionInterpolation.py +++ b/demo/ExpressionInterpolation.py @@ -49,7 +49,7 @@ powq = 3 * q**2 # Extract basix cell type -b_cell = basix.cell.string_to_type(cell) +b_cell = basix.CellType[cell] # Find quadrature points for quadrature element b_rule = basix.quadrature.string_to_type(q_rule) diff --git a/ffcx/element_interface.py b/ffcx/element_interface.py index f099523cb..4508a7727 100644 --- a/ffcx/element_interface.py +++ b/ffcx/element_interface.py @@ -9,6 +9,7 @@ import basix.ufl import numpy as np import numpy.typing as npt +from basix import CellType as _CellType def basix_index(indices: tuple[int]) -> int: @@ -18,12 +19,12 @@ def basix_index(indices: tuple[int]) -> int: def create_quadrature( cellname: str, degree: int, rule: str, elements: list[basix.ufl._ElementBase] -) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: +) -> tuple[npt.ArrayLike, npt.ArrayLike]: """Create a quadrature rule.""" if cellname == "vertex": return (np.ones((1, 0), dtype=np.float64), np.ones(1, dtype=np.float64)) else: - celltype = basix.cell.string_to_type(cellname) + celltype = _CellType[cellname] polyset_type = basix.PolysetType.standard for e in elements: polyset_type = basix.polyset_superset(celltype, polyset_type, e.polyset_type) @@ -34,17 +35,15 @@ def create_quadrature( def reference_cell_vertices(cellname: str) -> npt.NDArray[np.float64]: """Get the vertices of a reference cell.""" - return basix.geometry(basix.cell.string_to_type(cellname)) + return np.asarray(basix.geometry(_CellType[cellname])) def map_facet_points( points: npt.NDArray[np.float64], facet: int, cellname: str ) -> npt.NDArray[np.float64]: """Map points from a reference facet to a physical facet.""" - geom = basix.geometry(basix.cell.string_to_type(cellname)) - facet_vertices = [ - geom[i] for i in basix.topology(basix.cell.string_to_type(cellname))[-2][facet] - ] + geom = np.asarray(basix.geometry(_CellType[cellname])) + facet_vertices = [geom[i] for i in basix.topology(_CellType[cellname])[-2][facet]] return np.asarray( [ facet_vertices[0] diff --git a/test/test_jit_expression.py b/test/test_jit_expression.py index 20ccf4e37..9dd3724ef 100644 --- a/test/test_jit_expression.py +++ b/test/test_jit_expression.py @@ -148,7 +148,6 @@ def test_rank1(compile_args): ) u_correct = np.array([f[1], f[0]]) + gradf0 - assert np.allclose(u_ffcx, u_correct.T) @@ -166,7 +165,7 @@ def test_elimiate_zero_tables_tensor(compile_args): # Get vertices of cell # Coords storage XYZXYZXYZ basix_c_e = basix.create_element( - basix.ElementFamily.P, basix.cell.string_to_type(cell), 1, discontinuous=False + basix.ElementFamily.P, basix.CellType[cell], 1, discontinuous=False ) coords = basix_c_e.points @@ -174,9 +173,7 @@ def test_elimiate_zero_tables_tensor(compile_args): coeff_points = basix_c_e.points # Compile expression at interpolation points of second order Lagrange space - b_el = basix.create_element( - basix.ElementFamily.P, basix.cell.string_to_type(cell), 0, discontinuous=True - ) + b_el = basix.create_element(basix.ElementFamily.P, basix.CellType[cell], 0, discontinuous=True) points = b_el.points obj, module, code = ffcx.codegeneration.jit.compile_expressions( [(expr, points)], cffi_extra_compile_args=compile_args From 2a0f480d969cbbeeb9c2b99d0d20a1064a807beb Mon Sep 17 00:00:00 2001 From: Joe Dean Date: Thu, 6 Jun 2024 14:30:52 +0100 Subject: [PATCH 20/25] Add support for mixed-dimensional kernels (codimension 1) (#675) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Get working without perms * Tidy * Add TODOs * Add TODOs * Check if mixed dim * Permute tables * Ruff * Coeffs * Try with mt * Handle coeffs * Tidy * Ruff * Change to codim * Ruff * Add is_mixed_dim * Tidy * Don't permute facet element * Simplify * Tidy * Use domain * Add comment * Compute reference * Split data * Fix test * Docs * Docs * Remove TODO --------- Co-authored-by: Jørgen Schartum Dokken --- ffcx/ir/elementtables.py | 34 ++++++++++++-- ffcx/ir/integral.py | 11 ++++- test/test_jit_forms.py | 98 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 4 deletions(-) diff --git a/ffcx/ir/elementtables.py b/ffcx/ir/elementtables.py index b98d9629f..89443e4de 100644 --- a/ffcx/ir/elementtables.py +++ b/ffcx/ir/elementtables.py @@ -77,7 +77,15 @@ def clamp_table_small_numbers( def get_ffcx_table_values( - points, cell, integral_type, element, avg, entity_type, derivative_counts, flat_component + points, + cell, + integral_type, + element, + avg, + entity_type, + derivative_counts, + flat_component, + codim, ): """Extract values from FFCx element table. @@ -135,7 +143,12 @@ def get_ffcx_table_values( component_element, offset, stride = element.get_component_element(flat_component) for entity in range(num_entities): - entity_points = map_integral_points(points, integral_type, cell, entity) + if codim == 0: + entity_points = map_integral_points(points, integral_type, cell, entity) + elif codim == 1: + entity_points = points + else: + raise RuntimeError("Codimension > 1 isn't supported.") tbl = component_element.tabulate(deriv_order, entity_points) tbl = tbl[basix_index(derivative_counts)] component_tables.append(tbl) @@ -287,6 +300,7 @@ def build_optimized_tables( modified_terminals, existing_tables, use_sum_factorization, + is_mixed_dim, rtol=default_rtol, atol=default_atol, ): @@ -341,7 +355,16 @@ def build_optimized_tables( # the dofmap offset may differ due to restriction. tdim = cell.topological_dimension() - if integral_type == "interior_facet": + codim = tdim - element.cell.topological_dimension() + assert codim >= 0 + if codim > 1: + raise RuntimeError("Codimension > 1 isn't supported.") + + # Only permute quadrature rules for interior facets integrals and for + # the codim zero element in mixed-dimensional integrals. The latter is + # needed because a cell may see its sub-entities as being oriented + # differently to their global orientation + if integral_type == "interior_facet" or (is_mixed_dim and codim == 0): if tdim == 1: t = get_ffcx_table_values( quadrature_rule.points, @@ -352,6 +375,7 @@ def build_optimized_tables( entity_type, local_derivatives, flat_component, + codim, ) elif tdim == 2: new_table = [] @@ -366,6 +390,7 @@ def build_optimized_tables( entity_type, local_derivatives, flat_component, + codim, ) ) @@ -387,6 +412,7 @@ def build_optimized_tables( entity_type, local_derivatives, flat_component, + codim, ) ) t = new_table[0] @@ -407,6 +433,7 @@ def build_optimized_tables( entity_type, local_derivatives, flat_component, + codim, ) ) t = new_table[0] @@ -421,6 +448,7 @@ def build_optimized_tables( entity_type, local_derivatives, flat_component, + codim, ) # Clean up table tbl = clamp_table_small_numbers(t["array"], rtol=rtol, atol=atol) diff --git a/ffcx/ir/integral.py b/ffcx/ir/integral.py index 382ced092..ef42f003f 100644 --- a/ffcx/ir/integral.py +++ b/ffcx/ir/integral.py @@ -82,6 +82,12 @@ def compute_integral_ir(cell, integral_type, entity_type, integrands, argument_s if is_modified_terminal(v["expression"]) } + # Check if we have a mixed-dimensional integral + is_mixed_dim = False + for domain in integrand.ufl_domains(): + if domain.topological_dimension() != cell.topological_dimension(): + is_mixed_dim = True + mt_table_reference = build_optimized_tables( quadrature_rule, cell, @@ -90,6 +96,7 @@ def compute_integral_ir(cell, integral_type, entity_type, integrands, argument_s initial_terminals.values(), ir["unique_tables"], use_sum_factorization=p["sum_factorization"], + is_mixed_dim=is_mixed_dim, rtol=p["table_rtol"], atol=p["table_atol"], ) @@ -281,7 +288,9 @@ def compute_integral_ir(cell, integral_type, entity_type, integrands, argument_s } restrictions = [i.restriction for i in initial_terminals.values()] - ir["needs_facet_permutations"] = "+" in restrictions and "-" in restrictions + ir["needs_facet_permutations"] = ( + "+" in restrictions and "-" in restrictions + ) or is_mixed_dim return ir diff --git a/test/test_jit_forms.py b/test/test_jit_forms.py index dd0f45451..6f96e3c8a 100644 --- a/test/test_jit_forms.py +++ b/test/test_jit_forms.py @@ -1124,3 +1124,101 @@ def test_integral_grouping(compile_args): ] ) assert len(unique_integrals) == 2 + + +@pytest.mark.parametrize("dtype", ["float64"]) +@pytest.mark.parametrize("permutation", [[0], [1]]) +def test_mixed_dim_form(compile_args, dtype, permutation): + """Test that the local element tensor corresponding to a mixed-dimensional form is correct. + The form involves an integral over a facet of the cell. The trial function and a coefficient f + are of codim 0. The test function and a coefficient g are of codim 1. We compare against another + form where the test function and g are codim 0 but have the same trace on the facet. + """ + + def tabulate_tensor(ele_type, V_cell_type, W_cell_type, coeffs): + "Helper function to create a form and compute the local element tensor" + V_ele = basix.ufl.element(ele_type, V_cell_type, 2) + W_ele = basix.ufl.element(ele_type, W_cell_type, 1) + + gdim = 2 + V_domain = ufl.Mesh(basix.ufl.element("Lagrange", V_cell_type, 1, shape=(gdim,))) + W_domain = ufl.Mesh(basix.ufl.element("Lagrange", W_cell_type, 1, shape=(gdim,))) + + V = ufl.FunctionSpace(V_domain, V_ele) + W = ufl.FunctionSpace(W_domain, W_ele) + + u = ufl.TrialFunction(V) + q = ufl.TestFunction(W) + + f = ufl.Coefficient(V) + g = ufl.Coefficient(W) + + ds = ufl.Measure("ds", domain=V_domain) + + n = ufl.FacetNormal(V_domain) + forms = [ufl.inner(f * g * ufl.grad(u), n * q) * ds] + compiled_forms, module, code = ffcx.codegeneration.jit.compile_forms( + forms, options={"scalar_type": dtype}, cffi_extra_compile_args=compile_args + ) + form0 = compiled_forms[0] + default_integral = form0.form_integrals[0] + kernel = getattr(default_integral, f"tabulate_tensor_{dtype}") + + A = np.zeros((W_ele.dim, V_ele.dim), dtype=dtype) + w = np.array(coeffs, dtype=dtype) + c = np.array([], dtype=dtype) + facet = np.array([0], dtype=np.intc) + perm = np.array(permutation, dtype=np.uint8) + + xdtype = dtype_to_scalar_dtype(dtype) + coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=xdtype) + + c_type = dtype_to_c_type(dtype) + c_xtype = dtype_to_c_type(xdtype) + + ffi = module.ffi + kernel( + ffi.cast(f"{c_type} *", A.ctypes.data), + ffi.cast(f"{c_type} *", w.ctypes.data), + ffi.cast(f"{c_type} *", c.ctypes.data), + ffi.cast(f"{c_xtype} *", coords.ctypes.data), + ffi.cast("int *", facet.ctypes.data), + ffi.cast("uint8_t *", perm.ctypes.data), + ) + + return A + + # Define the element type + ele_type = "Lagrange" + # Define the cell type for each space + V_cell_type = "triangle" + Vbar_cell_type = "interval" + + # Coefficient data + # f is a quadratic on each edge that is 0 at the vertices and 1 at the midpoint + f_data = [0, 0, 0, 1, 1, 1] + # g is a linear function along the edge that is 0 at one vertex and 1 at the other + g_data = [0, 1] + # Collect coefficient data + coeffs = f_data + g_data + + # Tabulate the tensor for the mixed-dimensional form + A = tabulate_tensor(ele_type, V_cell_type, Vbar_cell_type, coeffs) + + # Compare to a reference result. Here, we compare against the same kernel but with + # the interval element replaced with a triangle. + # We create some data for g on the triangle whose trace coincides with g on the interval + g_data = [0, 0, 1] + coeffs_ref = f_data + g_data + A_ref = tabulate_tensor(ele_type, V_cell_type, V_cell_type, coeffs_ref) + # Remove the entries for the extra test DOF on the triangle element + A_ref = A_ref[1:][:] + + # If the permutation is 1, this means the triangle sees its edge as being flipped + # relative to the edge's global orientation. Thus the result is the same as swapping + # cols 1 and 2 and cols 4 and 5 of the reference result. + if permutation[0] == 1: + A_ref[:, [1, 2]] = A_ref[:, [2, 1]] + A_ref[:, [4, 5]] = A_ref[:, [5, 4]] + + assert np.allclose(A, A_ref) From 9d1c19bb640b0de4fee65df1b59b3f73107e803f Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Mon, 10 Jun 2024 15:51:06 +0200 Subject: [PATCH 21/25] Disable Windows test. (#704) --- .github/workflows/pythonapp.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 6fb7dcfb0..f1a2610d9 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -23,8 +23,6 @@ jobs: os: [ubuntu-latest] python-version: ['3.9', '3.10', '3.11', '3.12'] include: - - os: windows-latest - python-version: '3.11' - os: macos-latest python-version: '3.12' From 20f0dbdf1cb97a6a280849536d1b94b959f0e0ca Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Mon, 10 Jun 2024 21:21:17 +0100 Subject: [PATCH 22/25] Update for UFL deprecation (#705) --- ffcx/ir/integral.py | 2 +- ffcx/naming.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ffcx/ir/integral.py b/ffcx/ir/integral.py index ef42f003f..f399526c8 100644 --- a/ffcx/ir/integral.py +++ b/ffcx/ir/integral.py @@ -84,7 +84,7 @@ def compute_integral_ir(cell, integral_type, entity_type, integrands, argument_s # Check if we have a mixed-dimensional integral is_mixed_dim = False - for domain in integrand.ufl_domains(): + for domain in ufl.domain.extract_domains(integrand): if domain.topological_dimension() != cell.topological_dimension(): is_mixed_dim = True diff --git a/ffcx/naming.py b/ffcx/naming.py index 293d0d69c..3b76f1c13 100644 --- a/ffcx/naming.py +++ b/ffcx/naming.py @@ -47,13 +47,13 @@ def compute_signature( domains: list[ufl.Mesh] = [] for coeff in coeffs: - domains.append(*coeff.ufl_domains()) + domains.append(*ufl.domain.extract_domains(coeff)) for arg in args: - domains.append(*arg.ufl_function_space().ufl_domains()) + domains.append(*ufl.domain.extract_domains(arg)) for gc in ufl.algorithms.analysis.extract_type(expr, ufl.classes.GeometricQuantity): - domains.append(*gc.ufl_domains()) + domains.append(*ufl.domain.extract_domains(gc)) for const in consts: - domains.append(const.ufl_domain()) + domains.append(*ufl.domain.extract_domains(const)) domains = ufl.algorithms.analysis.unique_tuple(domains) rn.update(dict((d, i) for i, d in enumerate(domains))) From 3c97c346965813ed2ecb3a80c11ca860dcf5359e Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 10 Jul 2024 20:46:32 +0200 Subject: [PATCH 23/25] Export required C standard for UFCx header compile. (#708) --- cmake/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 332fa9d18..cc2fbef4c 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -9,6 +9,7 @@ file(SHA1 ${PROJECT_SOURCE_DIR}/../ffcx/codegeneration/ufcx.h UFCX_HASH) message("Test hash: ${UFCX_HASH}") add_library(${PROJECT_NAME} INTERFACE) +target_compile_features(${PROJECT_NAME} PUBLIC c_std_17) add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_include_directories(${PROJECT_NAME} INTERFACE $ From 8de75ef95f4d3446a2c0248f5d2b2eb2db815e6e Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 11 Jul 2024 16:34:00 +0200 Subject: [PATCH 24/25] Change to INTERFACE in C17 requirement for ufcx target (#710) --- cmake/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index cc2fbef4c..25bb27c2f 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -9,7 +9,7 @@ file(SHA1 ${PROJECT_SOURCE_DIR}/../ffcx/codegeneration/ufcx.h UFCX_HASH) message("Test hash: ${UFCX_HASH}") add_library(${PROJECT_NAME} INTERFACE) -target_compile_features(${PROJECT_NAME} PUBLIC c_std_17) +target_compile_features(${PROJECT_NAME} INTERFACE c_std_17) add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_include_directories(${PROJECT_NAME} INTERFACE $ From b0f97347d16c0fd54cc8511d814089b808754950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Mon, 7 Oct 2024 12:00:17 +0200 Subject: [PATCH 25/25] Remove quadrature permutation on element tables of codim-1 (#717) * Do not add quadrature permutations for codim 1 interior facet tables, as the input has a consistent orientation from DOLFINx * Ruff formatting * More ruff --- ffcx/ir/elementtables.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ffcx/ir/elementtables.py b/ffcx/ir/elementtables.py index 89443e4de..5be8ad2f8 100644 --- a/ffcx/ir/elementtables.py +++ b/ffcx/ir/elementtables.py @@ -365,7 +365,9 @@ def build_optimized_tables( # needed because a cell may see its sub-entities as being oriented # differently to their global orientation if integral_type == "interior_facet" or (is_mixed_dim and codim == 0): - if tdim == 1: + if tdim == 1 or codim == 1: + # Do not add permutations if codim-1 as facets have already gotten a global + # orientation in DOLFINx t = get_ffcx_table_values( quadrature_rule.points, cell,