[
  {
    "path": ".gitattributes",
    "content": "release.conf export-ignore\n.gitignore export-ignore\n.gitattributes export-ignore\n.mailmap export-ignore\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: FEniCS # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"pip\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/build-wheels.yml",
    "content": "name: Build wheels\n\n# By default this action does not push to test or production PyPI.  The wheels\n# are available as an artifact that can be downloaded and tested locally.\n\non:\n  workflow_dispatch:\n    inputs:\n      ffcx_ref:\n        description: \"FFCx git ref to checkout\"\n        default: \"main\"\n        type: string\n      test_pypi_publish:\n        description: \"Publish to Test PyPi\"\n        default: false\n        type: boolean\n      pypi_publish:\n        description: \"Publish to PyPi\"\n        default: false\n        type: boolean\n\n  workflow_call:\n    inputs:\n      ffcx_ref:\n        description: \"FFCx git ref to checkout\"\n        default: \"main\"\n        type: string\n      test_pypi_publish:\n        description: \"Publish to Test PyPi\"\n        default: false\n        type: boolean\n      pypi_publish:\n        description: \"Publish to PyPi\"\n        default: false\n        type: boolean\n\njobs:\n  build:\n    name: Build wheels and source distributions\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout FFCx\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ github.event.inputs.ffcx_ref }}\n      - name: Upgrade pip and setuptools\n        run: python -m pip install setuptools pip build --upgrade\n      - name: Build sdist and wheel\n        run: python -m build .\n      - uses: actions/upload-artifact@v7\n        with:\n          path: dist/* \n\n  upload_test_pypi:\n    name: Upload to test PyPI (optional)\n    if: ${{ github.event.inputs.test_pypi_publish == 'true' }}\n    needs: [build]\n    runs-on: ubuntu-latest\n    environment:\n      name: testpypi\n      url: https://test.pypi.org/p/fenics-ffcx\n    permissions:\n      id-token: write\n\n    steps:\n      - uses: actions/download-artifact@v8\n        with:\n          name: artifact\n          path: dist\n\n      - name: Push to test PyPI\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          repository-url: https://test.pypi.org/legacy/\n\n  upload_pypi:\n    name: Upload to PyPI (optional)\n    if: ${{ github.event.inputs.pypi_publish == 'true' }}\n    needs: [build]\n    runs-on: ubuntu-latest\n    environment:\n      name: pypi\n      url: https://pypi.org/p/fenics-ffcx\n    permissions:\n      id-token: write\n\n    steps:\n      - uses: actions/download-artifact@v8\n        with:\n          name: artifact\n          path: dist\n\n      - name: Push to PyPI\n        uses: pypa/gh-action-pypi-publish@release/v1\n"
  },
  {
    "path": ".github/workflows/dolfinx-tests.yml",
    "content": "# This workflow will install Basix, FFCx, DOLFINx and run the DOLFINx unit tests.\n\nname: DOLFINx integration\n\non:\n  pull_request:\n    branches: [main]\n  merge_group:\n    branches: [main]\n  workflow_dispatch:\n    inputs:\n      dolfinx_ref:\n        description: \"DOLFINx branch or tag\"\n        default: \"main\"\n        type: string\n      basix_ref:\n        description: \"Basix branch or tag\"\n        default: \"main\"\n        type: string\n      ufl_ref:\n        description: \"UFL branch or tag\"\n        default: \"main\"\n        type: string\n  # Weekly build on Mondays at 8 am\n  schedule:\n    - cron: \"0 8 * * 1\"\n\njobs:\n  build:\n    name: Run DOLFINx tests\n    runs-on: ubuntu-latest\n    container: ghcr.io/fenics/test-env:current-openmpi\n    env:\n      PETSC_ARCH: linux-gnu-complex64-32\n      OMPI_ALLOW_RUN_AS_ROOT: 1\n      OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1\n      PRTE_MCA_rmaps_default_mapping_policy: :oversubscribe   # Newer OpenMPI\n      OMPI_MCA_rmaps_base_oversubscribe: true                 # Older OpenMPI\n    steps:\n      - uses: actions/checkout@v6\n      - name: Install UFL and Basix (default branches/tags)\n        if: github.event_name != 'workflow_dispatch'\n        run: |\n          python3 -m pip install --break-system-packages git+https://github.com/FEniCS/ufl.git\n          python3 -m pip install --break-system-packages git+https://github.com/FEniCS/basix.git\n      - name: Install UFL and Basix (specified branches/tags)\n        if: github.event_name == 'workflow_dispatch'\n        run: |\n          python3 -m pip install --break-system-packages git+https://github.com/FEniCS/ufl.git@${{ github.event.inputs.ufl_ref }}\n          python3 -m pip install --break-system-packages git+https://github.com/FEniCS/basix.git@${{ github.event.inputs.basix_ref }}\n      - name: Install FFCx\n        run: |\n          pip3 install --break-system-packages .\n      - name: Get DOLFINx source (default branch/tag)\n        if: github.event_name != 'workflow_dispatch'\n        uses: actions/checkout@v6\n        with:\n          path: ./dolfinx\n          repository: FEniCS/dolfinx\n          ref: main\n      - name: Get DOLFINx source (specified branch/tag)\n        if: github.event_name == 'workflow_dispatch'\n        uses: actions/checkout@v6\n        with:\n          path: ./dolfinx\n          repository: FEniCS/dolfinx\n          ref: ${{ github.event.inputs.dolfinx_ref }}\n      - name: Install DOLFINx (C++)\n        run: |\n          cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -DBUILD_TESTING=true -B build -S dolfinx/cpp/\n          cmake --build build\n          cmake --install build\n      - name: Run DOLFINx C++ unit tests\n        working-directory: build\n        run: ctest -V --output-on-failure -R unittests\n      - name: Install DOLFINx (Python)\n        run: |\n          python3 -m pip -v install --break-system-packages nanobind scikit-build-core[pyproject]\n          python3 -m pip -v install --break-system-packages --check-build-dependencies --no-build-isolation dolfinx/python/\n      - name: Install Python demo/test dependencies\n        run: python3 -m pip install --break-system-packages matplotlib numba pyamg pytest pytest-xdist scipy pyvista networkx\n      - name: Run DOLFINx Python unit tests\n        run: python3 -m pytest -n auto dolfinx/python/test/unit\n      - name: Run DOLFINx Python demos\n        run: python3 -m pytest -n=2 -m serial dolfinx/python/demo/test.py\n"
  },
  {
    "path": ".github/workflows/pythonapp.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint\n# with a single version of Python For more information see:\n# https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: FFCx CI\n\non:\n  push:\n    branches:\n      - \"**\"\n    tags:\n      - \"v*\"\n  pull_request:\n    branches: [main]\n  merge_group:\n    branches: [main]\n  workflow_dispatch:\n\n  # Weekly build on Mondays at 8 am\n  schedule:\n    - cron: \"0 8 * * 1\"\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [\"windows-2022\", \"ubuntu-latest\", \"macos-latest\"]\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n      - name: Checkout FFCx\n        uses: actions/checkout@v6\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Export GitHub Actions cache environment variables (Windows)\n        if: runner.os == 'Windows'\n        uses: actions/github-script@v9\n        with:\n          script: |\n            core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');\n            core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');\n\n      - name: Set up CMake\n        if: runner.os == 'Windows'\n        uses: lukka/get-cmake@latest\n        with:\n          cmakeVersion: \"~3.30.0\"\n\n      - name: Install dependencies (non-Python, Linux)\n        if: runner.os == 'Linux'\n        run: |\n          sudo apt-get install -y graphviz libgraphviz-dev ninja-build pkg-config libblas-dev liblapack-dev\n      - name: Install dependencies (non-Python, macOS)\n        if: runner.os == 'macOS'\n        run: brew install ninja\n\n      - name: Install FEniCS dependencies (Python, Unix)\n        if: runner.os == 'Linux' || runner.os == 'macOS'\n        run: |\n          pip install git+https://github.com/FEniCS/ufl.git\n          pip install git+https://github.com/FEniCS/basix.git\n\n      - name: Install FEniCS dependencies (Python, Windows)\n        if: runner.os == 'Windows'\n        env:\n          VCPKG_BINARY_SOURCES: \"clear;x-gha,readwrite\"\n        run: |\n          pip install git+https://github.com/FEniCS/ufl.git\n          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\n\n      - name: Install FFCx (Linux, with optional dependencies)\n        if: runner.os == 'Linux'\n        run: pip install .[ci,optional]\n      - name: Install FFCx (macOS, Windows)\n        if: runner.os != 'Linux'\n        run: pip install .[ci]\n\n      - name: ruff checks\n        run: |\n          ruff check .\n          ruff format --check .\n\n      - name: Static check with mypy\n        run: mypy -p ffcx\n\n      - name: Run unit tests\n        run: >\n          python -m pytest test/\n          -n auto\n          -W error\n          --cov=ffcx/\n          --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml\n\n      - name: Upload to Coveralls\n        if: ${{ github.repository == 'FEniCS/ffcx' && github.head_ref == '' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' }}\n        env:\n          COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}\n        run: coveralls\n        continue-on-error: true\n\n      - name: Upload pytest results\n        uses: actions/upload-artifact@v7\n        with:\n          name: pytest-results-${{ matrix.os }}-${{ matrix.python-version }}\n          path: junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml\n        # Use always() to always run this step to publish test results\n        # when there are test failures\n        if: always()\n\n      - name: Setup cl.exe (Windows)\n        if: runner.os == 'Windows'\n        uses: ilammy/msvc-dev-cmd@v1\n\n      - name: Run FFCx demos\n        run: >\n          pytest demo/test_demos.py\n          -W error\n          # -n auto\n\n      - name: Build documentation\n        run: |\n          cd doc\n          make html\n      - name: Upload documentation artifact\n        uses: actions/upload-artifact@v7\n        with:\n          name: doc-${{ matrix.os }}-${{ matrix.python-version }}\n          path: doc/build/html/\n          retention-days: 2\n          if-no-files-found: error\n\n      - name: Checkout FEniCS/docs\n        if: ${{ github.repository == 'FEniCS/ffcx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.12 }}\n        uses: actions/checkout@v6\n        with:\n          repository: \"FEniCS/docs\"\n          path: \"docs\"\n          ssh-key: \"${{ secrets.SSH_GITHUB_DOCS_PRIVATE_KEY }}\"\n      - name: Set version name\n        if: ${{ github.repository == 'FEniCS/ffcx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.12 }}\n        run: |\n          echo \"VERSION_NAME=${GITHUB_REF#refs/*/}\" >> $GITHUB_ENV\n      - name: Copy documentation into repository\n        if: ${{ github.repository == 'FEniCS/ffcx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.12 }}\n        run: |\n          cd docs\n          git rm -r --ignore-unmatch ffcx/${{ env.VERSION_NAME }}\n          mkdir -p ffcx/${{ env.VERSION_NAME }}\n          cp -r ../doc/build/html/* ffcx/${{ env.VERSION_NAME }}\n      - name: Commit and push documentation to FEniCS/docs\n        if: ${{ github.repository == 'FEniCS/ffcx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.12 }}\n        run: |\n          cd docs\n          git config --global user.email \"fenics@github.com\"\n          git config --global user.name \"FEniCS GitHub Actions\"\n          git add --all\n          git commit --allow-empty -m \"Python FEniCS/ffcx@${{ github.sha }}\"\n          git push\n"
  },
  {
    "path": ".github/workflows/spack.yml",
    "content": "name: Spack install\n\non:\n  # Uncomment the below 'push' to trigger on push\n  # push:\n  #  branches:\n  #    - \"**\"\n  schedule:\n    # '*' is a special character in YAML, so string must be quoted\n    - cron: \"0 2 * * SUN\"\n  workflow_dispatch:\n    inputs:\n      spack_package_repo:\n        description: \"Spack package repository to test\"\n        default: \"spack/spack-packages\"\n        type: string\n      spack_package_ref:\n        description: \"Spack package repository branch/tag to test\"\n        default: \"develop\"\n        type: string\n      ffcx_version:\n        description: \"Spack build tag\"\n        default: \"main\"\n        type: string\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    container: ubuntu:latest\n    steps:\n      - name: Install Spack requirements\n        run: |\n          apt-get -y update\n          apt-get install -y bzip2 curl file git gzip make patch python3-minimal tar xz-utils\n          apt-get install -y g++ gfortran  # compilers\n\n      - name: Get Spack\n        uses: actions/checkout@v6\n        with:\n          path: ./spack\n          repository: spack/spack\n      - name: Get Spack packages\n        if: github.event_name == 'workflow_dispatch'\n        run: |\n          . ./spack/share/spack/setup-env.sh\n          spack repo update\n          spack repo add --name test_pkgs https://github.com/${{ github.event.inputs.spack_package_repo }}.git ~/test_pkgs\n          spack repo update --branch ${{ github.event.inputs.spack_package_ref }} test_pkgs\n          spack repo list\n          spack config get repos\n\n      - name: Install FFCx and run tests\n        if: github.event_name != 'workflow_dispatch'\n        run: |\n          . ./spack/share/spack/setup-env.sh\n          spack env create ffcx-main\n          spack env activate ffcx-main\n          spack add py-fenics-ffcx@main\n          spack install\n\n      - name: Install FFCx and run tests\n        if: github.event_name == 'workflow_dispatch'\n        run: |\n          . ./spack/share/spack/setup-env.sh\n          spack env create ffcx-testing\n          spack env activate ffcx-testing\n          spack add py-fenics-ffcx@${{ github.event.inputs.ffcx_version }}\n          spack install\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled source\n*.o\n*.Plo\n*.Po\n*.lo\n*.la\n*.a\n*.os\n*.pyc\n*.so\n*.pc\n*.pyc\n*.pyd\n*.def\n*.dll\n*.exe\n*.dylib\n\n# Files generated by setup\n/ffcx/git_commit_hash.py\n\n# CMake and Make files\nCMakeCache.txt\nCMakeFiles\ncmake_install.cmake\ncmake_uninstall.cmake\nMakefile\ninstall_manifest.txt\n/cmake/templates/UFCConfig.cmake\n/cmake/templates/UFCConfigVersion.cmake\n/cmake/templates/UseUFC.cmake\n\n# Temporaries\n*~\n\n# OS X files\n.DS_Store\n.DS_Store?\n\n# Local build files\n/build\n/bench/bench.log\n/doc/sphinx/build\n/doc/sphinx/source/api-doc\n\n# Visual Studio Code project config\n.vscode\n\n# c, h and so files due to running demos\ndemo/*.c\ndemo/*.h\ndemo/*.so\ntest/*.c\ntest/*.h\ntest/*.so\n\n# Tests\n**/.cache/\n__pycache__\ntest/compile-cache/\ntest/libffcx_*\ntest/*.pdf\n\n# setuptools temps\n*.egg-info/\n\n# Release\n/dist\n/release\n\n.*.swp\n"
  },
  {
    "path": "AUTHORS",
    "content": "Credits for FFC\n===============\n\nMain authors:\n\n    Anders Logg\n    email: logg@simula.no\n    www:   http://home.simula.no/~logg/\n\n    Kristian B. Ølgaard\n    email: k.b.oelgaard@gmail.com\n\n    Marie Rognes\n    email: meg@simula.no\n\nMain contributors:\n\n    Garth N. Wells\n    email: gnw20@cam.ac.uk\n    www:   http://www.eng.cam.ac.uk/~gnw20/\n\n    Martin Sandve Alnæs\n    email: martinal@simula.no\n\nContributors:\n\n    Jan Blechta\n    email: blechta@karlin.mff.cuni.cz\n\n    Peter Brune\n    email: brune@uchicago.edu\n\n    Joachim B Haga\n    email: jobh@broadpark.no\n\n    Johan Jansson\n    email: johanjan@math.chalmers.se\n    www:   http://www.math.chalmers.se/~johanjan/\n\n    Robert C. Kirby\n    email: kirby@cs.uchicago.edu\n    www:   http://people.cs.uchicago.edu/~kirby/\n\n    Matthew G. Knepley\n    email: knepley@mcs.anl.gov\n    www:   http://www-unix.mcs.anl.gov/~knepley/\n\n    Dag Lindbo\n    email: dag@f.kth.se\n    www:   http://www.f.kth.se/~dag/\n\n    Ola Skavhaug\n    email: skavhaug@simula.no\n    www:   http://home.simula.no/~skavhaug/\n\n    Andy R. Terrel\n    email: aterrel@uchicago.edu\n    www:   http://people.cs.uchicago.edu/~aterrel/\n\n    Ivan Yashchuk\n    email: ivan.yashchuk@aalto.fi\n\n    Matthew Scroggs\n    email: matthew.scroggs.14@ucl.ac.uk\n    www:   https://mscroggs.co.uk\n\nCredits for UFC\n===============\n\nUFC was merged into FFC 2014-02-18. Below is the list of credits for\nUFC at the time of the merge.\n\nMain authors:\n\n    Martin Sandve Alnaes   <martinal@simula.no>\n    Anders Logg            <logg@simula.no>\n    Kent-Andre Mardal      <kent-and@simula.no\n    Ola Skavhaug           <skavhaug@simula.no>\n    Hans Petter Langtangen <hpl@simula.no>\n\nMain contributors:\n\n    Asmund Odegard         <aasmund@simula.no>\n    Kristian Oelgaard      <k.b.oelgaard@tudelft.nl>\n    Johan Hake             <hake@simula.no>\n    Garth N. Wells         <gnw20@cam.ac.uk>\n    Marie E. Rognes        <meg@simula.no>\n    Johannes Ring          <johannr@simula.no>\n\n\nCredits for UFLACS\n==================\n\nUFLACS was merged into FFC 2016-02-16.\n\nAuthor:\n\n    Martin Sandve Alnæs    <martinal@simula.no>\n\nContributors:\n\n    Anders Logg            <logg@chalmers.se>\n    Garth N. Wells         <gnw20@cam.ac.uk>\n    Johannes Ring          <johannr@simula.no>\n    Matthias Liertzer      <matthias@liertzer.at>\n    Steffen Müthing        <steffen.muething@ipvs.uni-stuttgart.de>\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "Code of Conduct\n===============\n\nOur Pledge\n----------\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our\nproject and our community a harassment-free experience for everyone,\nregardless of age, body size, disability, ethnicity, sex\ncharacteristics, gender identity and expression, level of experience,\neducation, socio-economic status, nationality, personal appearance,\nrace, religion, or sexual identity and orientation.\n\nOur Standards\n-------------\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others’ private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\nOur Responsibilities\n--------------------\nProject maintainers are responsible for clarifying the standards of\nacceptable behavior and are expected to take appropriate and fair\ncorrective action in response to any instances of unacceptable\nbehavior.\n\nProject maintainers have the right and responsibility to remove, edit,\nor reject comments, commits, code, wiki edits, issues, and other\ncontributions that are not aligned to this Code of Conduct, or to ban\ntemporarily or permanently any contributor for other behaviors that\nthey deem inappropriate, threatening, offensive, or harmful.\n\nScope\n-----\nThis Code of Conduct applies both within project spaces and in public\nspaces when an individual is representing the project or its\ncommunity. Examples of representing a project or community include\nusing an official project e-mail address, posting via an official\nsocial media account, or acting as an appointed representative at an\nonline or offline event. Representation of a project may be further\ndefined and clarified by project maintainers.\n\nEnforcement\n-----------\nInstances of abusive, harassing, or otherwise unacceptable behavior\nmay be reported by contacting the project team at\nfenics-steering-council@googlegroups.com. Alternatively, you may\nreport individually to one of the members of the Steering\nCouncil. Complaints will be reviewed and investigated and will result\nin a response that is deemed necessary and appropriate to the\ncircumstances. The project team is obligated to maintain\nconfidentiality with regard to the reporter of an incident. Further\ndetails of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct\nin good faith may face temporary or permanent repercussions as\ndetermined by other members of the project’s leadership.\n\nIf you feel that your report has not been followed up satisfactorily,\nthen you may contact our parent organisation NumFOCUS at\ninfo@numfocus.org for further redress.\n\nAttribution\n-----------\nThis Code of Conduct is adapted from the Contributor Covenant, version\n1.4, available at\nhttps://www.contributor-covenant.org/version/1/4/code-of-conduct.html.\n\nAdaptations\n-----------\n\n* Allow reporting to individual Steering Council members\n* Added the option to contact NumFOCUS for further redress.\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq"
  },
  {
    "path": "COPYING",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "COPYING.LESSER",
    "content": "\t\t   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions. \n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version. \n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "ChangeLog.rst",
    "content": "Changelog\n=========\n\n0.6.0\n-----\nSee https://github.com/FEniCS/ffcx/compare/v0.5.0...v0.6.0\n\n0.5.0\n-----\nSee: https://github.com/FEniCS/ffcx/compare/v0.5.0...v0.4.0\n\n0.4.0\n-----\nSee: https://github.com/FEniCS/ffcx/compare/v0.4.0...v0.3.0\n\n0.3.0\n-----\nSee: https://github.com/FEniCS/ffcx/compare/v0.3.0...v0.2.0\n\n0.2.0\n-----\n\n- No changes\n\n0.1.0\n-----\nAlpha release of ffcx\n\n2018.2.0.dev0\n-------------\n\n- No changes\n\n2018.1.0.dev0 (no release)\n--------------------------\n\n- Forked FFCx\n\n2017.2.0 (2017-12-05)\n---------------------\n\n- Some fixes for ufc::eval for esoteric element combinations\n- Reimplement code generation for all ufc classes with new class\n  ufc::coordinate_mapping which can map between coordinates, compute\n  jacobians, etc. for a coordinate mapping parameterized by a specific\n  finite element.\n- New functions in ufc::finite_element:\n  - evaluate_reference_basis\n  - evaluate_reference_basis_derivatives\n  - transform_reference_basis_derivatives\n  - tabulate_reference_dof_coordinates\n- New functions in ufc::dofmap:\n  - num_global_support_dofs\n  - num_element_support_dofs\n- Improved docstrings for parts of ufc.h\n- FFC now accepts Q and DQ finite element families defined on quadrilaterals and hexahedrons\n- Some fixes for ufc_geometry.h for quadrilateral and hexahedron cells\n\n2017.1.0.post2 (2017-09-12)\n---------------------------\n\n- Change PyPI package name to fenics-ffc.\n\n2017.1.0 (2017-05-09)\n---------------------\n\n- Let ffc -O parameter take an optional integer level like -O2, -O0\n- Implement blockwise optimizations in uflacs code generation\n- Expose uflacs optimization parameters through parameter system\n\n2016.2.0 (2016-11-30)\n---------------------\n\n- Jit compiler now compiles elements separately from forms to avoid duplicate work\n- Add parameter max_signature_length to optionally shorten signatures in the jit cache\n- Move uflacs module into ffc.uflacs\n- Remove installation of pkg-config and CMake files (UFC path and\n  compiler flags are available from ffc module)\n- Add dependency on dijitso and remove dependency on instant\n- Add experimental Bitbucket pipelines\n- Tidy the repo after UFC and UFLACS merge, and general spring cleanup. This\n  includes removal of instructions how to merge two repos, commit hash\n  c8389032268041fe94682790cb773663bdf27286.\n\n2016.1.0 (2016-06-23)\n---------------------\n\n- Add function get_ufc_include to get path to ufc.h\n- Merge UFLACS into FFC\n- Generalize ufc interface to non-affine parameterized coordinates\n- Add ufc::coordinate_mapping class\n- Make ufc interface depend on C++11 features requiring gcc version >= 4.8\n- Add function ufc_signature() to the form compiler interface\n- Add function git_commit_hash()\n\n1.6.0 (2015-07-28)\n------------------\n\n- Rename and modify a number of UFC interface functions. See docstrings in ufc.h for details.\n- Bump required SWIG version to 3.0.3\n- Disable dual basis (tabulate_coordinates and evaluate_dofs) for enriched\n  elements until correct implementation is brought up\n\n1.5.0 (2015-01-12)\n------------------\n\n- Remove FErari support\n- Add support for new integral type custom_integral\n- Support for new form compiler backend \"uflacs\", downloaded separately\n\n1.4.0 (2014-06-02)\n------------------\n\n- Add support for integrals that know which coefficients they use\n- Many bug fixes for facet integrals over manifolds\n- Merge UFC into FFC; ChangeLog for UFC appended below\n- Various updates mirroring UFL changes\n- Experimental: New custom integral with user defined quadrature points\n\n1.3.0 (2014-01-07)\n------------------\n\n- Fix bug with runtime check of SWIG version\n- Move DOLFIN wrappers here from DOLFIN\n- Add support for new UFL operators cell_avg and facet_avg\n- Add new reference data handling system, now data is kept in an external repository\n- Fix bugs with ignoring quadrature rule arguments\n- Use cpp optimization by default in jit compiler\n\n1.2.0 (2013-03-24)\n------------------\n\n- New feature: Add basic support for point integrals on vertices\n- New feature: Add general support for m-dimensional cells in n-dimensional space (n >= m, n, m = 1, 2, 3)\n\n1.1.0 (2013-01-07)\n------------------\n\n- Fix bug for Conditionals related to DG constant Coefficients. Bug #1082048.\n- Fix bug for Conditionals, precedence rules for And and Or. Bug #1075149.\n- Changed data structure from list to deque when pop(0) operation is needed, speeding up split_expression operation considerable\n- Other minor fixes\n\n1.0.0 (2011-12-07)\n------------------\n\n- Issue warning when form integration requires more than 100 points\n\n1.0-rc1 (2011-11-28)\n--------------------\n\n- Fix bug with coordinates on facet integrals (intervals). Bug #888682.\n- Add support for FacetArea, new geometric quantity in UFL.\n- Fix bug in optimised quadrature code, AlgebraOperators demo. Bug #890859.\n- Fix bug with undeclared variables in optimised quadrature code. Bug #883202.\n\n1.0-beta2 (2011-10-11)\n----------------------\n\n- Added support for bessel functions, bessel_* (I,J,K,Y), in UFL.\n- Added support for error function, erf(), new math function in UFL.\n- Fix dof map 'need_entities' for Real spaces\n- Improve performance for basis function computation\n\n1.0-beta (2011-08-11)\n---------------------\n\n- Improve formatting of floats with up to one non-zero decimal place.\n- Fix bug involving zeros in products and sums. Bug #804160.\n- Fix bug for new conditions '&&', '||' and '!' in UFL. Bug #802560.\n- Fix bug involving VectorElement with dim=1. Bug #798578.\n- Fix bug with mixed element of symmetric tensor elements. Bug #745646.\n- Fix bug when using geometric coordinates with one quadrature point\n\n0.9.10 (2011-05-16)\n-------------------\n\n- Change license from GPL v3 or later to LGPL v3 or later\n- Add some schemes for low-order simplices\n- Request quadrature schemes by polynomial degree (not longer by number\n  of points in each direction)\n- Get quadrature schemes via ffc.quadrature_schemes\n- Improved lock handling in JIT compiler\n- Include common_cell in form signature\n- Add possibility to set swig binary and swig path\n\n0.9.9 (2011-02-23)\n------------------\n\n- Add support for generating error control forms with option -e\n- Updates for UFC 2.0\n- Set minimal degree to 1 in automatic degree selection for expressions\n- Add command-line option -f no_ferari\n- Add support for plotting of elements\n- Add utility function compute_tensor_representation\n\n0.9.4 (2010-09-01)\n------------------\n\n- Added memory cache in jit(), for preprocessed forms\n- Added support for Conditional and added demo/Conditional.ufl.\n- Added support for new geometric quantity Circumradius in UFL.\n- Added support for new geometric quantity CellVolume in UFL.\n\n0.9.3 (2010-07-01)\n------------------\n\n- Make global_dimension for Real return an int instead of double, bug # 592088\n- Add support for facet normal in 1D.\n- Expose -feliminate_zeros for quadrature optimisations to give user more\n  control\n- Remove return of form in compile_form\n- Remove object_names argument to compile_element\n- Rename ElementUnion -> EnrichedElement\n- Add support for tan() and inverse trigonometric functions\n- Added support for ElementUnion (i.e. span of combinations of elements)\n- Added support for Bubble elements\n- Added support for UFL.SpatialCoordinate.\n\n0.9.2 (2010-02-17)\n------------------\n\n- Bug fix in removal of unused variables in Piola-mapped terms for tensor\n  representation\n\n0.9.1 (2010-02-15)\n------------------\n\n- Add back support for FErari optimizations\n- Bug fixes in JIT compiler\n\n0.9.0 (2010-02-02)\n------------------\n\n- Updates for FIAT 0.9.0\n- Updates for UFC 1.4.0 (now supporting the full interface)\n- Automatic selection of representation\n- Change quadrature_order --> quadrature_degree\n- Split compile() --> compile_form(), compile_element()\n- Major cleanup and reorganization of code (flatter directories)\n- Updates for changes in UFL: Argument, Coefficient, FormData\n\n0.7.1\n-----\n\n- Handle setting quadrature degree when it is set to None in UFL form\n- Added demo: HyperElasticity.ufl\n\n0.7.0\n-----\n\n- Move contents of TODO to: https://blueprints.launchpad.net/ffc\n- Support for restriction of finite elements to only consider facet dofs\n- Use quadrature_order from metadata when integrating terms using tensor representation\n- Use loop to reset the entries of the local element tensor\n- Added new symbolic classes for quadrature optimisation (speed up compilation)\n- Added demos: Biharmonic.ufl, div(grad(v)) term;\n               ReactionDiffusion.ufl, tuple notation;\n               MetaData.ufl, how to attach metadata to the measure;\n               ElementRestriction.ufl, restriction of elements to facets\n- Tabulate the coordinates of the integration points in the tabulate_tensor() function\n- Change command line option '-f split_implementation' -> '-f split'\n- Renaming of files and restructuring of the compiler directory\n- Added option -q rule (--quadrature-rule rule) to specify which rule to use\n  for integration of a given integral. (Can also bet set through the metadata\n  through \"quadrature_rule\"). No rules have yet been implemented, so default\n  is the FIAT rule.\n- Remove support for old style .form files/format\n\n0.6.2 (2009-04-07)\n------------------\n\n- Experimental support for UFL, supporting both .form and .ufl\n- Moved configuration and construction of python extension module to ufc_module\n\n0.6.1 (2009-02-18)\n------------------\n\n- Initial work on UFL transition\n- Minor bug fixes\n- The version of ufc and swig is included in the form signature\n- Better system configuration for JIT compiled forms\n- The JIT compiled python extension module use shared_ptr for all classes\n\n0.6.0 (2009-01-05)\n------------------\n\n- Update DOLFIN output format (-l dolfin) for DOLFIN 0.9.0\n- Cross-platform fixes for test scripts\n- Minor bug fix for quadrature code generation (forms affected by this bug would not be able to compile\n- Fix bug with output of ``*.py``.\n- Permit dot product bewteen rectangular matrices (Frobenius norm)\n\n0.5.1 (2008-10-20)\n------------------\n\n- New operator skew()\n- Allow JIT compilation of elements and dof maps\n- Rewrite JIT compiler to rely on Instant for caching\n- Display flop count for evaluating the element tensor during compilation\n- Add arguments language and representation to options dictionary\n- Fix installation on Windows\n- Add option -f split_implementation for separate .h and .cpp files\n\n0.5.0 (2008-06-23)\n------------------\n\n- Remove default restriction +/- for Constant\n- Make JIT optimization (-O0 / -O2) optional\n- Add in-memory cache to speed up JIT compiler for repeated assembly\n- Allow subdomain integrals without needing full range of integrals\n- Allow simple subdomain integral specification dx(0), dx(1), ds(0) etc\n\n0.4.5 (2008-04-30)\n------------------\n\n- Optimizations in generated quadrature code\n- Change formatting of floats from %g to %e, fixes problem with too long integers\n- Bug fix for order of values in interpolate_vertex_values, now according to UFC\n- Speed up JIT compiler\n- Add index ranges to form printing\n- Throw runtime error in functions not generated\n- Update DOLFIN format for new location of include files\n\n0.4.4 (2008-02-18)\n------------------\n\n- RT, BDM, BDFM and Nedelec now working in 2D and 3D\n- New element type QuadratureElement\n- Add support for 1D elements\n- Add experimental support for new Darcy-Stokes element\n- Use FIAT transformed spaces instead of mapping in FFC\n- Updates for UFC 1.1\n- Implement caching of forms/modules in ~/.ffc/cache for JIT compiler\n- Add script ffc-clean\n- New operators lhs() and rhs()\n- Bug fixes in simplify\n- Bug fixes for Nedelec and BDFM\n- Fix bug in mult()\n- Fix bug with restrictions on exterior facet integrals\n- Fix bug in grad() for vectors\n- Add divergence operator for matrices\n\n0.4.3 (2007-10-23)\n------------------\n\n- Require FIAT to use UFC reference cells\n- Fix bug in form simplification\n- Rename abs --> modulus to avoid conflict with builtin abs\n- Fix bug in operators invert, abs, sqrt\n- Fix bug in integral tabulation\n- Add BDFM and Nedelec elements (nonworking)\n- Fix bug in JIT compiler\n\n0.4.2 (2007-08-31)\n------------------\n\n- Change license from GPL v2 to GPL v3 or later\n- Add JIT (just-in-time) compiler\n- Fix bug for constants on interior facets\n\n0.4.1 (2007-06-22)\n------------------\n\n- Fix bug in simplification of forms\n- Optimize removal of unused terms in code formattting\n\n0.4.0 (2007-06-20)\n------------------\n\n- Move to UFC interface for code generation\n- Major rewrite, restructure, cleanup\n- Add support for Brezzi-Douglas-Marini (BDM) elements\n- Add support for Raviart-Thomas (RT) elements\n- Add support for Discontinuous Galerkin (DG) methods\n- Operators jump() and avg()\n- Add quadrature compilation mode (experimental)\n- Simplification of forms\n- Operators sqrt(), abs() and inverse\n- Improved Python interface\n- Add flag -f precision=n\n- Generate code for basis functions and derivatives\n- Use Set from set module for Python2.3 compatibility\n\n0.3.5 (2006-12-01)\n------------------\n\n- Bug fixes\n- Move from Numeric to numpy\n\n0.3.4 (2006-10-27)\n------------------\n\n- Updates for new DOLFIN mesh library\n- Add support for evaluation of functionals\n- Add operator outer() for outer product of vector-valued functions\n- Enable optimization of linear forms (in addition to bilinear forms)\n- Remove DOLFIN SWIG format\n- Fix bug in ffc -v/--version (thanks to Ola Skavhaug)\n- Consolidate DOLFIN and DOLFIN SWIG formats (patch from Johan Jansson)\n- Fix bug in optimized compilation (-O) for some forms (\"too many values to unpack\")\n\n0.3.3 (2006-09-05)\n------------------\n\n- Fix bug in operator div()\n- Add operation count (number of multiplications) with -d0\n- Add hint for printing more informative error messages (flag -d1)\n- Modify implementation of vertexeval()\n- Add support for boundary integrals (Garth N. Wells)\n\n0.3.2 (2006-04-01)\n------------------\n\n- Add support for FErari optimizations, new flag -O\n\n0.3.1 (2006-03-28)\n------------------\n\n- Remove verbose output: silence means success\n- Generate empty boundary integral eval() to please Intel C++ compiler\n- New classes TestFunction and TrialFunction\n\n0.3.0 (2006-03-01)\n------------------\n\n- Work on manual, document command-line and user-interfaces\n- Name change: u --> U\n- Add compilation of elements without form\n- Add generation of FiniteElementSpec in DOLFIN formats\n- Fix bugs in raw and XML formats\n- Fix bug in LaTeX format\n- Fix path and predefine tokens to enable import in .form file\n- Report number of entries in reference tensor during compilation\n\n0.2.5 (2005-12-28)\n------------------\n\n- Add demo Stabilization.form\n- Further speedup computation of reference tensor (use ufunc Numeric.add)\n\n0.2.4 (2005-12-05)\n------------------\n\n- Report time taken to compute reference tensor\n- Restructure computation of reference tensor to use less memory.\n  As a side effect, the speed has also been improved.\n- Update for DOLFIN name change node --> vertex\n- Update finite element interface for DOLFIN\n- Check for FIAT bug in discontinuous vector Lagrange elements\n- Fix signatures for vector-valued elements\n\n0.2.3 (2005-11-28)\n------------------\n\n- New fast Numeric/BLAS based algorithm for computing reference tensor\n- Bug fix: reassign indices for complete subexpressions\n- Bug fix: operator Function * Integral\n- Check tensor notation for completeness\n- Bug fix: mixed elements with more than two function spaces\n- Don't declare unused coefficients (or gcc will complain)\n\n0.2.2 (2005-11-14)\n------------------\n\n- Add command-line argument -v / --version\n- Add new operator mean() for projection onto piecewise constants\n- Add support for projections\n- Bug fix for higher order mixed elements: declaration of edge/face_ordering\n- Generate code for sub elements of mixed elements\n- Add new test form: TensorWeighteLaplacian\n- Add new test form: EnergyNorm\n- Fix bugs in mult() and vec() (skavhaug)\n- Reset correct entries of G for interior in BLAS mode\n- Only assign to entries of G that meet nonzero entries of A in BLAS mode\n\n0.2.1 (2005-10-11)\n------------------\n\n- Only generate declarations that are needed according to format\n- Check for missing options and add missing default options\n- Simplify usage of FFC as Python module: from ffc import *\n- Fix bug in division with constants\n- Generate output for BLAS (with option -f blas)\n- Add new XML output format\n- Remove command-line option --license (collect in compiler options -f)\n- Modify demo Mass.form to use 3:rd order Lagrange on tets\n- Fix bug in dofmap() for equal order mixed elements\n- Add compiler option -d debuglevel\n- Fix Python Numeric bug: vdot --> dot\n\n0.2.0 (2005-09-23)\n------------------\n\n- Generate function vertexeval() for evaluation at vertices\n- Add support for arbitrary mixed elements\n- Add man page\n- Work on manual, chapters on form language, quickstart and installation\n- Handle exceptions gracefully in command-line interface\n- Use new template fenicsmanual.cls for manual\n- Add new operators grad, div, rot (curl), D, rank, trace, dot, cross\n- Factorize common reference tensors from terms with equal signatures\n- Collect small building blocks for form algebra in common module tokens.py\n\n0.1.9 (2005-07-05)\n------------------\n\n- Complete support for general order Lagrange elements on triangles and tetrahedra\n- Compute reordering of dofs on tets correctly\n- Update manual with ordering of dofs\n- Break compilation into two phases: build() and write()\n- Add new output format ASE (Matt Knepley)\n- Improve python interface to FFC\n- Remove excessive logging at compilation\n- Fix bug in raw output format\n\n0.1.8 (2005-05-17)\n------------------\n\n- Access data through map in DOLFIN format\n- Experimental support for computation of coordinate maps\n- Add first draft of manual\n- Experimental support for computation of dof maps\n- Allow specification of the number of components for vector Lagrange\n- Count the number of zeros dropped\n- Fix bug in handling command-line arguments\n- Use module sets instead of built-in set (fix for Python 2.3)\n- Handle constant indices correctly (bug reported by Garth N. Wells)\n\n0.1.7 (2005-05-02)\n------------------\n\n- Write version number to output\n- Add command-line option for choosing license\n- Display usage if no input is given\n- Bug fix for finding correct prefix of file name\n- Automatically choose name of output file (if not supplied)\n- Use FIAT tabulation mode for vector-valued elements (speedup a factor 5)\n- Use FIAT tabulation mode for scalar elements (speedup a factor 1000)\n- Fig bug in demo elasticity.form (change order of u and v)\n- Make references to constants const in DOLFIN format\n- Don't generate code for unused entries of geometry tensor\n- Update formats to write numeric constants with full precision\n\n0.1.6 (2005-03-17)\n------------------\n\n- Add support for mixing multiple different finite elements\n- Add support for division with constants\n- Fix index bug (reverse order of multi-indices)\n\n0.1.5 (2005-03-14)\n------------------\n\n- Automatically choose the correct quadrature rule for precomputation\n- Add test program for verification of FIAT quadrature rules\n- Fix bug for derivative of sum\n- Improve common interface for debugging: add indentation\n- Add support for constants\n- Fix bug for sums of more than one term (make copies of references in lists)\n- Add '_' in naming of geometry tensor (needed for large dimensions)\n- Add example elasticity.form\n- Cleanup build_indices()\n\n0.1.4-1 (2005-02-07)\n--------------------\n\n- Fix version number and remove build directory from tarball\n\n0.1.4 (2005-02-04)\n------------------\n\n- Fix bug for systems, seems to work now\n- Add common interface for debugging\n- Modify DOLFIN output to initialize functions\n- Create unique numbers for each function\n- Use namespaces for DOLFIN output instead of class names\n- Temporary implementation of dof mapping for vector-valued elements\n- Make DOLFIN output format put entries into PETSc block\n- Change name of coefficient data: c%d[%d] -> c[%d][%d]\n- Change ordering of basis functions (one component at a time)\n- Add example poissonsystem.form\n- Modifications for new version of FIAT (FIAT-L)\n  FIAT version 0.1 a factor 5 slower (no memoization)\n  FIAT version 0.1.1 a little faster, only a factor 2 slower\n- Add setup.py script\n\n0.1.3 (2004-12-06)\n------------------\n\n- Fix bug in DOLFIN format (missing value when zero)\n- Add output of reference tensor to LaTeX format\n- Make raw output format print data with full precision\n- Add component diagram\n- Change order of declaration of basis functions\n- Add new output format raw\n\n0.1.2 (2004-11-17)\n------------------\n\n- Add command-line interface ffc\n- Add support for functions (coefficients)\n- Add support for constants\n- Allow multiple forms (left- and right-hand side) in same file\n- Add test examples: poisson.form, mass.form, navierstokes.form\n- Wrap FIAT to create vector-valued finite element spaces\n- Check ranks of operands\n- Clean up algebra, add base class Element\n- Add some documentation (class diagram)\n- Add support for LaTeX output\n\n0.1.1-1 (2004-11-10)\n--------------------\n\n- Add missing file declaration.py\n\n0.1.1 (2004-11-10)\n------------------\n\n- Make output variable names configurable\n- Clean up DOLFIN code generation\n- Post-process form to create reference, geometry, and element tensors\n- Experimental support for general tensor-valued elements\n- Clean up and improve index reassignment\n- Use string formatting for generation of output\n- Change index ordering to access row-wise\n\n0.1.0 (2004-10-22)\n------------------\n\n- First iteration of the FEniCS Form Compiler\n- Change boost::shared_ptr --> std::shared_ptr\n\nChangeLog for UFC\n=================\n\nUFC was merged into FFC 2014-02-18. Below is the ChangeLog for\nUFC at the time of the merge. From this point onward, UFC version\nnumbering restarts at the same version number as FFC and the rest\nof FEniCS.\n\n2.3.0 (2014-01-07)\n------------------\n\n- Use std::vector<std::vector<std::size_t> > for topology data\n- Remove vertex coordinates from ufc::cell\n- Improve detection of compatible Python libraries\n- Add current swigversion to the JIT compiled extension module\n- Remove dofmap::max_local_dimension()\n- Remove cell argument from dofmap::local_dimension()\n\n2.2.0 (2013-03-24)\n------------------\n\n- Add new class ufc::point_integral\n- Use CMake to configure JIT compilation of forms\n- Generate UseUFC.cmake during configuration\n- Remove init_mesh(), init_cell(), init_mesh_finalize()\n- Remove ufc::mesh and add a vector of num_mesh_entities to global_dimension() and tabulate_dofs().\n\n2.1.0 (2013-01-07)\n------------------\n\n- Fix bug introduced by SWIG 2.0.5, which treated uint as Python long\n- Add optimization SWIG flags, fixing bug lp:987657\n\n2.0.5 (2011-12-07)\n------------------\n\n- Improve configuration of libboost-math\n\n2.0.4 (2011-11-28)\n------------------\n\n- Add boost_math_tr1 to library flags when JIT compiling an\n  extension module\n\n2.0.3 (2011-10-26)\n------------------\n\n- CMake config improvements\n\n2.0.2 (2011-08-11)\n------------------\n\n- Some tweaks of installation\n\n2.0.1 (2011-05-16)\n------------------\n\n- Make SWIG version >= 2.0 a requirement\n- Add possibility to set swig binary and swig path\n- Add missing const for map_{from,to}_reference_cell\n\n2.0.0 (2011-02-23)\n------------------\n\n- Add quadrature version of tabulate_tensor\n- Add finite_element::map_{from,to}_reference_cell\n- Add finite_element::{topological,geometric}_dimension\n- Add dofmap::topological_dimension\n- Rename num_foo_integrals --> num_foo_domains\n- Rename dof_map --> dofmap\n- Add finite_element::create\n- Add dofmap::create\n\n1.4.2 (2010-09-01)\n------------------\n\n- Move to CMake build system\n\n1.4.1 (2010-07-01)\n------------------\n\n- Make functions introduced in UFC 1.1 mandatory (now pure virtual)\n- Update templates to allow constructor arguments in form classes\n\n1.4.0 (2010-02-01)\n------------------\n\n- Changed behavior of create_foo_integral (returning 0 when integral is 0)\n- Bug fixes in installation\n\n1.2.0 (2009-09-23)\n------------------\n\n- Add new function ufc::dof_map::max_local_dimension()\n- Change ufc::dof_map::local_dimension() to ufc::dof_map::local_dimension(const ufc::cell c)\n\n1.1.2 (2009-04-07)\n------------------\n\n- Added configuration and building of python extension module to ufc_utils.build_ufc_module\n\n1.1.1 (2009-02-20)\n------------------\n\n- The extension module is now not built, if the conditions for shared_ptr are not met\n- Added SCons build system\n- The swig generated extension module will be compiled with shared_ptr support if boost is found on system and swig is of version 1.3.35 or higher\n- The swig generated extension module is named ufc.py and expose all ufc base classes to python\n- Added a swig generated extention module to ufc. UFC now depends on swig\n- Changed name of the python utility module from \"ufc\" to \"ufc_utils\"\n\n1.1.0 (2008-02-18)\n------------------\n\n- Add new function ufc::finite_element::evaluate_dofs\n- Add new function ufc::finite_element::evaluate_basis_all\n- Add new function ufc::finite_element::evaluate_basis_derivatives_all\n- Add new function ufc::dof_map::geometric_dimension\n- Add new function ufc::dof_map::num_entity_dofs\n- Add new function ufc::dof_map::tabulate_entity_dofs\n\n1.0.0 (2007-06-17)\n------------------\n\n- Release of UFC 1.0\n"
  },
  {
    "path": "LICENSE",
    "content": "The header file ufcx.h is released using the UNLICENSE. See UNLICENSE for the\nlicense text.\n\n------------------------------------------------------------------------------\n\nOther files, unless stated otherwise in their head, are licensed by GNU Lesser\nGeneral Public License, version 3, or later. See COPYING and COPYING.LESSER for\nthe license text.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include AUTHORS\ninclude COPYING\ninclude COPYING.LESSER\ninclude ChangeLog.rst\ninclude INSTALL\ninclude LICENSE\ninclude ffcx/codegeneration/ufcx.h\nrecursive-include cmake *\nrecursive-include demo *\nrecursive-include doc *\nrecursive-include ffcx *.in\nrecursive-include libs *\nrecursive-include test *\nglobal-exclude __pycache__ *.pyc\n"
  },
  {
    "path": "README.md",
    "content": "# FFCx: The FEniCSx Form Compiler\n\n[![FFCx CI](https://github.com/FEniCS/ffcx/actions/workflows/pythonapp.yml/badge.svg)](https://github.com/FEniCS/ffcx/actions/workflows/pythonapp.yml)\n[![Spack install](https://github.com/FEniCS/ffcx/actions/workflows/spack.yml/badge.svg)](https://github.com/FEniCS/ffcx/actions/workflows/spack.yml)\n[![Coverage Status](https://coveralls.io/repos/github/FEniCS/ffcx/badge.svg?branch=main)](https://coveralls.io/github/FEniCS/ffcx?branch=main)\n\nFFCx is a new version of the FEniCS Form Compiler. It is being actively\ndeveloped and is compatible with DOLFINx.\n\nFFCx is a compiler for finite element variational forms. From a\nhigh-level description of the form in the Unified Form Language (UFL),\nit generates efficient low-level C code that can be used to assemble the\ncorresponding discrete operator (tensor). In particular, a bilinear form\nmay be assembled into a matrix and a linear form may be assembled into a\nvector.  FFCx may be used either from the command line (by invoking the\n`ffcx` command) or as a Python module (`import ffcx`).\n\nFFCx is part of the FEniCS Project. For more information, visit\nhttps://www.fenicsproject.org\n\n\n## Installation\n\nTo install FFCx from PyPI:\n```\n$ pip install fenics-ffcx\n```\n\nTo install FFCx from the source directory:\n```\n$ pip install .\n```\n\n## Documentation\n\nDocumentation can be viewed at https://docs.fenicsproject.org/ffcx/main\n\n\n## Interface file installation only\n\nFFCx provides the `ufcx.h` interface header for finite element kernels,\nused by DOLFINx. `ufcx.h` is installed by FFCx within the Python site\npackages, but it is sometimes helpful to install only the header file.\nThis can be done using `cmake`:\n```\n$ cmake -B build-dir -S cmake/\n$ cmake --build build-dir\n$ cmake --install build-dir\n```\n\n## License\n\n  This program is free software: you can redistribute it and/or modify\n  it under the terms of the GNU Lesser General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  This program is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n  GNU Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public License\n  along with this program. If not, see <https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "UNLICENSE",
    "content": "The software in the file ufcx.h is free and unencumbered software released into\nthe public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\n\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <https://unlicense.org>\n"
  },
  {
    "path": "_clang-format",
    "content": "---\nLanguage:        Cpp\n# BasedOnStyle:  LLVM\nAccessModifierOffset: -2\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignEscapedNewlinesLeft: false\nAlignOperands:   true\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: All\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterDefinitionReturnType: None\nAlwaysBreakAfterReturnType: None\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: true\nBinPackArguments: true\nBinPackParameters: true\nBraceWrapping:\n  AfterClass:      false\n  AfterControlStatement: false\n  AfterEnum:       false\n  AfterFunction:   false\n  AfterNamespace:  false\n  AfterObjCDeclaration: false\n  AfterStruct:     false\n  AfterUnion:      false\n  BeforeCatch:     false\n  BeforeElse:      false\n  IndentBraces:    false\nBreakBeforeBinaryOperators: All\nBreakBeforeBraces: Allman\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializersBeforeComma: false\nBreakAfterJavaFieldAnnotations: false\nBreakStringLiterals: true\nColumnLimit:     80\nCommentPragmas:  '^ IWYU pragma:'\nConstructorInitializerAllOnOneLineOrOnePerLine: false\nConstructorInitializerIndentWidth: 4\nContinuationIndentWidth: 4\nCpp11BracedListStyle: true\nDerivePointerAlignment: false\nDisableFormat:   false\nExperimentalAutoDetectBinPacking: false\nForEachMacros:   [ foreach, Q_FOREACH, BOOST_FOREACH ]\nIncludeCategories:\n  - Regex:           '^\"(llvm|llvm-c|clang|clang-c)/'\n    Priority:        2\n  - Regex:           '^(<|\"(gtest|isl|json)/)'\n    Priority:        3\n  - Regex:           '.*'\n    Priority:        1\nIncludeIsMainRegex: '$'\nIndentCaseLabels: false\nIndentWidth:     2\nIndentWrappedFunctionNames: false\nJavaScriptQuotes: Leave\nJavaScriptWrapImports: true\nKeepEmptyLinesAtTheStartOfBlocks: true\nMacroBlockBegin: ''\nMacroBlockEnd:   ''\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: None\nObjCBlockIndentWidth: 2\nObjCSpaceAfterProperty: false\nObjCSpaceBeforeProtocolList: true\nPenaltyBreakBeforeFirstCallParameter: 19\nPenaltyBreakComment: 300\nPenaltyBreakFirstLessLess: 120\nPenaltyBreakString: 1000\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 60\nPointerAlignment: Left\nReflowComments:  true\nSortIncludes:    true\nSpaceAfterCStyleCast: false\nSpaceAfterTemplateKeyword: true\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeParens: ControlStatements\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles:  false\nSpacesInContainerLiterals: true\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard:        Cpp11\nTabWidth:        8\nUseTab:          Never\n...\n"
  },
  {
    "path": "cmake/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.19)\n\n# Development version: x.x.x.0\n# Release version: x.x.x\nproject(ufcx VERSION 0.11.0.0 DESCRIPTION \"UFCx interface header for finite element kernels\"\n  LANGUAGES C\n  HOMEPAGE_URL https://github.com/fenics/ffcx)\ninclude(GNUInstallDirs)\n\nfile(SHA1 ${PROJECT_SOURCE_DIR}/../ffcx/codegeneration/ufcx.h UFCX_HASH)\nmessage(\"Test hash: ${UFCX_HASH}\")\n\nadd_library(${PROJECT_NAME} INTERFACE)\ntarget_compile_features(${PROJECT_NAME} INTERFACE c_std_17)\nadd_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})\ntarget_include_directories(${PROJECT_NAME}\n  INTERFACE $<BUILD_INTERFACE:${${PROJECT_NAME}_SOURCE_DIR}/include>\n            $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)\n\n# Prepare and install CMake target/config files\ninstall(TARGETS ${PROJECT_NAME}\n        EXPORT ${PROJECT_NAME}_Targets\n        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}\n        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}\n        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})\ninclude(CMakePackageConfigHelpers)\nwrite_basic_package_version_file(\"${PROJECT_NAME}ConfigVersion.cmake\"\n                                 VERSION ${PROJECT_VERSION}\n                                 COMPATIBILITY AnyNewerVersion)\nconfigure_package_config_file(\"${PROJECT_NAME}Config.cmake.in\" \"${PROJECT_NAME}Config.cmake\"\n  INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)\ninstall(EXPORT ${PROJECT_NAME}_Targets FILE ${PROJECT_NAME}Targets.cmake\n        NAMESPACE ${PROJECT_NAME}::\n        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)\ninstall(FILES \"${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake\"\n              \"${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake\"\n        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)\n\n# Install header file\ninstall(FILES ${PROJECT_SOURCE_DIR}/../ffcx/codegeneration/ufcx.h TYPE INCLUDE)\n\n# Configure and install pkgconfig file\nconfigure_file(ufcx.pc.in ufcx.pc @ONLY)\ninstall(FILES ${PROJECT_BINARY_DIR}/ufcx.pc DESTINATION ${CMAKE_INSTALL_DATADIR}/pkgconfig)\n"
  },
  {
    "path": "cmake/ufcx.pc.in",
    "content": "prefix=\"@CMAKE_INSTALL_PREFIX@\"\nexec_prefix=\"${prefix}\"\nincludedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@\ndefinitions=@\n\nName: @PROJECT_NAME@\nDescription: @CMAKE_PROJECT_DESCRIPTION@\nURL: @CMAKE_PROJECT_HOMEPAGE_URL@\nVersion: @PROJECT_VERSION@\nCflags: -I\"${includedir}\"\nLibs:"
  },
  {
    "path": "cmake/ufcxConfig.cmake.in",
    "content": "@PACKAGE_INIT@\n\nset(UFCX_SIGNATURE @UFCX_HASH@)\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake\")\ncheck_required_components(\"@PROJECT_NAME@\")"
  },
  {
    "path": "demo/BiharmonicHHJ.py",
    "content": "# Copyright (C) 2016 Lizao Li\n\"\"\"Biharmonis HHJ demo.\n\nThe bilinear form a(u, v) and linear form L(v) for Biharmonic equation\nin Hellan-Herrmann-Johnson (HHJ) formulation.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    Coefficient,\n    FacetNormal,\n    FunctionSpace,\n    Mesh,\n    TestFunctions,\n    TrialFunctions,\n    dot,\n    dS,\n    ds,\n    dx,\n    grad,\n    inner,\n    jump,\n)\n\nHHJ = basix.ufl.element(\"HHJ\", \"triangle\", 2)\nP = basix.ufl.element(\"P\", \"triangle\", 3)\nmixed_element = basix.ufl.mixed_element([HHJ, P])\ndomain = Mesh(basix.ufl.element(\"P\", \"triangle\", 1, shape=(2,)))\nmixed_space = FunctionSpace(domain, mixed_element)\np_space = FunctionSpace(domain, P)\n\n(sigma, u) = TrialFunctions(mixed_space)\n(tau, v) = TestFunctions(mixed_space)\nf = Coefficient(p_space)\n\n\ndef b(sigma, v):\n    \"\"\"The form b.\"\"\"\n    n = FacetNormal(domain)\n    return (\n        inner(sigma, grad(grad(v))) * dx\n        - dot(dot(sigma(\"+\"), n(\"+\")), n(\"+\")) * jump(grad(v), n) * dS\n        - dot(dot(sigma, n), n) * dot(grad(v), n) * ds\n    )\n\n\na = inner(sigma, tau) * dx - b(tau, u) + b(sigma, v)\nL = f * v * dx\n"
  },
  {
    "path": "demo/BiharmonicRegge.py",
    "content": "# Copyright (C) 2016 Lizao Li\n\"\"\"Biharmonic Regge demo.\n\nThe bilinear form a(u, v) and linear form L(v) for Biharmonic equation in Regge formulation.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    Coefficient,\n    FacetNormal,\n    FunctionSpace,\n    Identity,\n    Mesh,\n    TestFunctions,\n    TrialFunctions,\n    dot,\n    dS,\n    ds,\n    dx,\n    grad,\n    inner,\n    jump,\n    tr,\n)\n\nREG = basix.ufl.element(\"Regge\", \"tetrahedron\", 1)\nP = basix.ufl.element(\"Lagrange\", \"tetrahedron\", 2)\nmixed_element = basix.ufl.mixed_element([REG, P])\ndomain = Mesh(basix.ufl.element(\"P\", \"tetrahedron\", 1, shape=(3,)))\nmixed_space = FunctionSpace(domain, mixed_element)\np_space = FunctionSpace(domain, P)\n\n(sigma, u) = TrialFunctions(mixed_space)\n(tau, v) = TestFunctions(mixed_space)\nf = Coefficient(p_space)\n\n\ndef S(mu):\n    \"\"\"The form S.\"\"\"\n    return mu - Identity(3) * tr(mu)\n\n\ndef b(mu, v):\n    \"\"\"The form b.\"\"\"\n    n = FacetNormal(domain)\n    return (\n        inner(S(mu), grad(grad(v))) * dx\n        - dot(dot(S(mu(\"+\")), n(\"+\")), n(\"+\")) * jump(grad(v), n) * dS\n        - dot(dot(S(mu), n), n) * dot(grad(v), n) * ds\n    )\n\n\na = inner(S(sigma), S(tau)) * dx - b(tau, u) + b(sigma, v)\nL = f * v * dx\n"
  },
  {
    "path": "demo/CellGeometry.py",
    "content": "# Copyright (C) 2013 Martin S. Alnaes\n\"\"\"Cell geometry demo.\n\nA functional M involving a bunch of cell geometry quantities.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    CellVolume,\n    Circumradius,\n    Coefficient,\n    FacetArea,\n    FacetNormal,\n    FunctionSpace,\n    Mesh,\n    SpatialCoordinate,\n    TrialFunction,\n    ds,\n    dx,\n)\nfrom ufl.geometry import FacetEdgeVectors\n\nV = basix.ufl.element(\"P\", \"tetrahedron\", 1)\ndomain = Mesh(basix.ufl.element(\"P\", \"tetrahedron\", 1, shape=(3,)))\nspace = FunctionSpace(domain, V)\nu = Coefficient(space)\n\n# TODO: Add all geometry for all cell types to this and other demo\n# files, need for regression test.\nx = SpatialCoordinate(domain)\nn = FacetNormal(domain)\nvol = CellVolume(domain)\nrad = Circumradius(domain)\narea = FacetArea(domain)\n\nM = u * (x[0] * vol * rad) * dx + u * (x[0] * vol * rad * area) * ds\n\n# Test some obscure functionality\nfev = FacetEdgeVectors(domain)\nv = TrialFunction(space)\nL = fev[0, 0] * v * ds\n"
  },
  {
    "path": "demo/ComplexPoisson.py",
    "content": "# Copyright (C) 2023 Chris Richardson\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Complex Poisson demo.\n\nThe bilinear form a(u, v) and linear form L(v) for\nPoisson's equation using bilinear elements on bilinear mesh geometry.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner\n\ncoords = basix.ufl.element(\"P\", \"triangle\", 2, shape=(2,))\nmesh = Mesh(coords)\ndx = dx(mesh)\n\nelement = basix.ufl.element(\"P\", mesh.ufl_cell().cellname, 2)\nspace = FunctionSpace(mesh, element)\n\nu = TrialFunction(space)\nv = TestFunction(space)\nf = Coefficient(space)\n\n# Test literal complex number in form\nk = 3.213 + 1.023j\n\na = k * inner(grad(u), grad(v)) * dx\nL = inner(k * f, v) * dx\n"
  },
  {
    "path": "demo/Components.py",
    "content": "# Copyright (C) 2011 Garth N. Wells\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Components demo.\n\nThis example demonstrates how to create vectors component-wise.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import Coefficient, FunctionSpace, Mesh, TestFunction, as_vector, dx, inner\n\nelement = basix.ufl.element(\"Lagrange\", \"tetrahedron\", 1, shape=(3,))\ndomain = Mesh(element)\nspace = FunctionSpace(domain, element)\n\nv = TestFunction(space)\nf = Coefficient(space)\n\n# Create vector\nv0 = as_vector([v[0], v[1], 0.0])\n\n# Use created vector in linear form\nL = inner(f, v0) * dx\n"
  },
  {
    "path": "demo/Conditional.py",
    "content": "# Copyright (C) 2010-2011 Kristian B. Oelgaard\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Conditional demo.\n\nIllustration on how to use Conditional to define a source term.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    And,\n    Constant,\n    FunctionSpace,\n    Mesh,\n    Not,\n    Or,\n    SpatialCoordinate,\n    TestFunction,\n    conditional,\n    dx,\n    ge,\n    gt,\n    inner,\n    le,\n    lt,\n)\n\nelement = basix.ufl.element(\"Lagrange\", \"triangle\", 2)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nv = TestFunction(space)\ng = Constant(domain)\n\nx = SpatialCoordinate(domain)\nc0 = conditional(le((x[0] - 0.33) ** 2 + (x[1] - 0.67) ** 2, 0.015), -1.0, 5.0)\nc = conditional(le((x[0] - 0.33) ** 2 + (x[1] - 0.67) ** 2, 0.025), c0, 0.0)\n\nt0 = And(ge(x[0], 0.55), le(x[0], 0.95))\nt1 = Or(lt(x[1], 0.05), gt(x[1], 0.45))\nt2 = And(t0, Not(t1))\nt = conditional(And(ge(x[1] - x[0] - 0.05 + 0.55, 0.0), t2), -1.0, 0.0)\n\nk = conditional(gt(1, 0), g, g + 1)\n\nf = c + t + k\n\nL = inner(f, v) * dx\n"
  },
  {
    "path": "demo/ExpressionInterpolation.py",
    "content": "# Copyright (C) 2022 Jørgen S. Dokken\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFC. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Expression interpolation demo.\n\nDefines an Expression which evaluates the several different functions at\na set of interpolation points.\n\"\"\"\n\nimport basix\nimport basix.ufl\nfrom ufl import Coefficient, FunctionSpace, Mesh, grad\n\n# Define mesh\ncell = \"triangle\"\nv_el = basix.ufl.element(\"Lagrange\", cell, 1, shape=(2,))\nmesh = Mesh(v_el)\n\n# Define mixed function space\nel = basix.ufl.element(\"P\", cell, 2)\nel_int = basix.ufl.element(\"Discontinuous Lagrange\", cell, 1, shape=(2,))\nme = basix.ufl.mixed_element([el, el_int])\nV = FunctionSpace(mesh, me)\nu = Coefficient(V)\n\n# Define expressions on each sub-space\ndu0 = grad(u[0])\ndu1 = grad(u[1])\n\n# Define an expression using quadrature elements\nq_rule = \"gauss_jacobi\"\nq_degree = 3\nq_el = basix.ufl.quadrature_element(cell, scheme=q_rule, degree=q_degree)\nQ = FunctionSpace(mesh, q_el)\nq = Coefficient(Q)\npowq = 3 * q**2\n\n# Extract basix cell type\nb_cell = basix.CellType[cell]\n\n# Find quadrature points for quadrature element\nb_rule = basix.quadrature.string_to_type(q_rule)\nquadrature_points, _ = basix.quadrature.make_quadrature(b_cell, q_degree, rule=b_rule)\n\n# Get interpolation points for output space\nfamily = basix.finite_element.string_to_family(\"Lagrange\", cell)\nb_element = basix.create_element(\n    family, b_cell, 4, basix.LagrangeVariant.gll_warped, discontinuous=True\n)\ninterpolation_points = b_element.points\n\n# Create expressions that can be used for interpolation\nexpressions = [(du0, interpolation_points), (du1, interpolation_points), (powq, quadrature_points)]\n"
  },
  {
    "path": "demo/FacetIntegrals.py",
    "content": "# Copyright (C) 2009-2010 Anders Logg\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Facet integrals demo.\n\nSimple example of a form defined over exterior and interior facets.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    FacetNormal,\n    FunctionSpace,\n    Mesh,\n    TestFunction,\n    TrialFunction,\n    avg,\n    dS,\n    ds,\n    grad,\n    inner,\n    jump,\n)\n\nelement = basix.ufl.element(\"Discontinuous Lagrange\", \"triangle\", 1)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nu = TrialFunction(space)\nv = TestFunction(space)\n\nn = FacetNormal(domain)\n\na = (\n    inner(u, v) * ds\n    + inner(u(\"+\"), v(\"-\")) * dS\n    + inner(jump(u, n), avg(grad(v))) * dS\n    + inner(avg(grad(u)), jump(v, n)) * dS\n)\n"
  },
  {
    "path": "demo/FacetRestrictionAD.py",
    "content": "# Copyright (C) 2010 Garth N. Wells\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Facet restriction demo.\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    Coefficient,\n    FunctionSpace,\n    Mesh,\n    TestFunction,\n    TrialFunction,\n    avg,\n    derivative,\n    dS,\n    dx,\n    grad,\n    inner,\n)\n\nelement = basix.ufl.element(\"Discontinuous Lagrange\", \"triangle\", 1)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nv = TestFunction(space)\nw = Coefficient(space)\nL = inner(grad(w), grad(v)) * dx - inner(avg(grad(w)), avg(grad(v))) * dS\n\nu = TrialFunction(space)\na = derivative(L, w, u)\n"
  },
  {
    "path": "demo/HyperElasticity.py",
    "content": "# Author: Martin Sandve Alnes\n# Date: 2008-12-22\n# Modified by Garth N. Wells, 2009\n\"\"\"Hyper-elasticity demo.\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    Coefficient,\n    Constant,\n    FacetNormal,\n    FunctionSpace,\n    Identity,\n    Mesh,\n    SpatialCoordinate,\n    TestFunction,\n    TrialFunction,\n    derivative,\n    det,\n    diff,\n    ds,\n    dx,\n    exp,\n    grad,\n    inner,\n    inv,\n    tetrahedron,\n    tr,\n    variable,\n)\n\n# Cell and its properties\ncell = tetrahedron\nd = 3\n\n# Elements\nu_element = basix.ufl.element(\"P\", cell.cellname, 2, shape=(3,))\np_element = basix.ufl.element(\"P\", cell.cellname, 1)\nA_element = basix.ufl.element(\"P\", cell.cellname, 1, shape=(3, 3))\n\n# Spaces\ndomain = Mesh(basix.ufl.element(\"Lagrange\", cell.cellname, 1, shape=(3,)))\nu_space = FunctionSpace(domain, u_element)\np_space = FunctionSpace(domain, p_element)\nA_space = FunctionSpace(domain, A_element)\n\n# Cell properties\nN = FacetNormal(domain)\nx = SpatialCoordinate(domain)\n\n# Test and trial functions\nv = TestFunction(u_space)\nw = TrialFunction(u_space)\n\n# Displacement at current and two previous timesteps\nu = Coefficient(u_space)\nup = Coefficient(u_space)\nupp = Coefficient(u_space)\n\n# Time parameters\ndt = Constant(domain)\n\n# Fiber field\nA = Coefficient(A_space)\n\n# External forces\nT = Coefficient(u_space)\np0 = Coefficient(p_space)\n\n# Material parameters FIXME\nrho = Constant(domain)\nK = Constant(domain)\nc00 = Constant(domain)\nc11 = Constant(domain)\nc22 = Constant(domain)\n\n# Deformation gradient\nIdent = Identity(d)\nF = Ident + grad(u)\nF = variable(F)\nFinv = inv(F)\nJ = det(F)\n\n# Left Cauchy-Green deformation tensor\nB = F * F.T\nI1_B = tr(B)\nI2_B = (I1_B**2 - tr(B * B)) / 2\nI3_B = J**2\n\n# Right Cauchy-Green deformation tensor\nC = F.T * F\nI1_C = tr(C)\nI2_C = (I1_C**2 - tr(C * C)) / 2\nI3_C = J**2\n\n# Green strain tensor\nE = (C - Ident) / 2\n\n# Mapping of strain in fiber directions\nEf = A * E * A.T\n\n# Strain energy function W(Q(Ef))\nQ = (\n    c00 * Ef[0, 0] ** 2 + c11 * Ef[1, 1] ** 2 + c22 * Ef[2, 2] ** 2\n)  # FIXME: insert some simple law here\nW = (K / 2) * (exp(Q) - 1)  # + p stuff\n\n# First Piola-Kirchoff stress tensor\nP = diff(W, F)\n\n# Acceleration term discretized with finite differences\nk = dt / rho\nacc = u - 2 * up + upp\n\n# Residual equation # FIXME: Can contain errors, not tested!\na_F = (\n    inner(acc, v) * dx\n    + k * inner(P, grad(v)) * dx\n    - k * inner(J * Finv * T, v) * ds(0)\n    - k * inner(J * Finv * p0 * N, v) * ds(1)\n)\n\n# Jacobi matrix of residual equation\na_J = derivative(a_F, u, w)\n\n# Export forms\nforms = [a_F, a_J]\n"
  },
  {
    "path": "demo/MassAction.py",
    "content": "# Copyright (C) 2023 Igor A. Baratta\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Mass action demo.\"\"\"\n\nimport basix.ufl\nimport ufl\n\nP = 3\ncell_type = basix.CellType.hexahedron\n# create element with tensor product order\nelement = basix.ufl.wrap_element(\n    basix.create_tp_element(basix.ElementFamily.P, cell_type, P, basix.LagrangeVariant.gll_warped)\n)\n\ncoords = basix.ufl.element(basix.ElementFamily.P, cell_type, 1, shape=(3,))\nmesh = ufl.Mesh(coords)\nV = ufl.FunctionSpace(mesh, element)\nx = ufl.SpatialCoordinate(mesh)\n\nv = ufl.TestFunction(V)\nu = ufl.TrialFunction(V)\na = ufl.inner(u, v) * ufl.dx\n\nw = ufl.Coefficient(V)\nL = ufl.action(a, w)\n"
  },
  {
    "path": "demo/MassDG0.py",
    "content": "# Copyright (C) 2021 Igor Baratta\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"DG mass matrix demo.\n\nThe bilinear form for a mass matrix.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, dx, inner\n\nelement = basix.ufl.element(\"DG\", \"tetrahedron\", 0)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"tetrahedron\", 1, shape=(3,)))\nspace = FunctionSpace(domain, element)\n\nv = TestFunction(space)\nu = TrialFunction(space)\n\na = inner(u, v) * dx\n"
  },
  {
    "path": "demo/MassHcurl_2D_1.py",
    "content": "# Copyright (C) 2004-2010 Anders Logg\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"H(curl) mass matrix demo.\"\"\"\n\nimport basix.ufl\nfrom ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, dx, inner\n\nelement = basix.ufl.element(\"N1curl\", \"triangle\", 1)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nv = TestFunction(space)\nu = TrialFunction(space)\n\na = inner(u, v) * dx\n"
  },
  {
    "path": "demo/MassHdiv_2D_1.py",
    "content": "# Copyright (C) 2010 Anders Logg\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"H(div) mass matrix demo.\"\"\"\n\nimport basix.ufl\nfrom ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, dx, inner\n\nelement = basix.ufl.element(\"BDM\", \"triangle\", 1)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nv = TestFunction(space)\nu = TrialFunction(space)\n\na = inner(u, v) * dx\n"
  },
  {
    "path": "demo/MathFunctions.py",
    "content": "# Copyright (C) 2010 Kristian B. Oelgaard\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Math function demo.\n\nTest all algebra operators on Coefficients.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    Coefficient,\n    FunctionSpace,\n    Mesh,\n    acos,\n    asin,\n    atan,\n    bessel_J,\n    bessel_Y,\n    cos,\n    dx,\n    erf,\n    exp,\n    ln,\n    sin,\n    sqrt,\n    tan,\n)\n\nelement = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nc0 = Coefficient(space)\nc1 = Coefficient(space)\n\ns0 = 3 * c0 - c1\np0 = c0 * c1\nf0 = c0 / c1\n\nintegrand = (\n    sqrt(c0)\n    + sqrt(s0)\n    + sqrt(p0)\n    + sqrt(f0)\n    + exp(c0)\n    + exp(s0)\n    + exp(p0)\n    + exp(f0)\n    + ln(c0)\n    + ln(s0)\n    + ln(p0)\n    + ln(f0)\n    + cos(c0)\n    + cos(s0)\n    + cos(p0)\n    + cos(f0)\n    + sin(c0)\n    + sin(s0)\n    + sin(p0)\n    + sin(f0)\n    + tan(c0)\n    + tan(s0)\n    + tan(p0)\n    + tan(f0)\n    + acos(c0)\n    + acos(s0)\n    + acos(p0)\n    + acos(f0)\n    + asin(c0)\n    + asin(s0)\n    + asin(p0)\n    + asin(f0)\n    + atan(c0)\n    + atan(s0)\n    + atan(p0)\n    + atan(f0)\n    + erf(c0)\n    + erf(s0)\n    + erf(p0)\n    + erf(f0)\n    + bessel_J(1, c0)\n    + bessel_J(1, s0)\n    + bessel_J(0, p0)\n    + bessel_J(0, f0)\n    + bessel_Y(1, c0)\n    + bessel_Y(1, s0)\n    + bessel_Y(0, p0)\n    + bessel_Y(0, f0)\n)\n\na = integrand * dx\n"
  },
  {
    "path": "demo/MetaData.py",
    "content": "# Copyright (C) 2009 Kristian B. Oelgaard\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Metadata demo.\n\nTest form for metadata.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    Coefficient,\n    Constant,\n    FunctionSpace,\n    Mesh,\n    TestFunction,\n    TrialFunction,\n    dx,\n    grad,\n    inner,\n)\n\nelement = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\nvector_element = basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,))\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\nvector_space = FunctionSpace(domain, vector_element)\n\nu = TrialFunction(space)\nv = TestFunction(space)\nc = Coefficient(vector_space)\nc2 = Constant(domain)\n\n# Terms on the same subdomain using different quadrature degree\na = (\n    inner(grad(u), grad(v)) * dx(0, degree=8)\n    + inner(c, c) * inner(grad(u), grad(v)) * dx(1, degree=4)\n    + inner(c, c) * inner(grad(u), grad(v)) * dx(1, degree=2)\n    + inner(grad(u), grad(v)) * dx(1, degree=-1)\n)\n\nL = inner(c2, v) * dx(0, metadata={\"precision\": 1})\n"
  },
  {
    "path": "demo/Mini.py",
    "content": "# Copyright (C) 2010 Marie E. Rognes\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Mini element demo.\n\nIllustration of vector sum of elements (EnrichedElement): The\nbilinear form a(u, v) for the Stokes equations using a mixed\nformulation involving the Mini element. The velocity element is\ncomposed of a P1 element augmented by the cubic bubble function.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dx, grad, inner\n\nP1 = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\nB = basix.ufl.element(\"Bubble\", \"triangle\", 3)\nV = basix.ufl.blocked_element(basix.ufl.enriched_element([P1, B]), shape=(2,))\nQ = basix.ufl.element(\"P\", \"triangle\", 1)\n\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nMini = FunctionSpace(domain, basix.ufl.mixed_element([V, Q]))\n\n(u, p) = TrialFunctions(Mini)\n(v, q) = TestFunctions(Mini)\n\na = (inner(grad(u), grad(v)) - inner(p, div(v)) + inner(div(u), q)) * dx\n"
  },
  {
    "path": "demo/MixedCoefficient.py",
    "content": "# Copyright (C) 2016 Miklós Homolya\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Mixed coefficient demo.\"\"\"\n\nimport basix.ufl\nfrom ufl import Coefficients, FunctionSpace, Mesh, dot, dS, dx\n\nDG = basix.ufl.element(\"DG\", \"triangle\", 0, shape=(2,))\nCG = basix.ufl.element(\"Lagrange\", \"triangle\", 2)\nRT = basix.ufl.element(\"RT\", \"triangle\", 3)\n\nelement = basix.ufl.mixed_element([DG, CG, RT])\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nf, g, h = Coefficients(space)\n\nforms = [dot(f(\"+\"), h(\"-\")) * dS + g * dx]\n"
  },
  {
    "path": "demo/MixedPoissonDual.py",
    "content": "# Copyright (C) 2014 Jan Blechta\n#\n# This file is part of FFCx.\n#\n# DOLFINx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# DOLFINx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with DOLFINx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Mixed Poisson dual demo.\n\nThe bilinear form a(u, v) and linear form L(v) for a two-field\n(mixed) formulation of Poisson's equation.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import Coefficient, FunctionSpace, Mesh, TestFunctions, TrialFunctions, ds, dx, grad, inner\n\nDRT = basix.ufl.element(\"Discontinuous RT\", \"triangle\", 2)\nP = basix.ufl.element(\"P\", \"triangle\", 3)\nW = basix.ufl.mixed_element([DRT, P])\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, W)\n\n(sigma, u) = TrialFunctions(space)\n(tau, v) = TestFunctions(space)\n\nP1 = basix.ufl.element(\"P\", \"triangle\", 1)\nspace = FunctionSpace(domain, P1)\nf = Coefficient(space)\ng = Coefficient(space)\n\na = (inner(sigma, tau) + inner(grad(u), tau) + inner(sigma, grad(v))) * dx\nL = -inner(f, v) * dx - inner(g, v) * ds\n"
  },
  {
    "path": "demo/Normals.py",
    "content": "# Copyright (C) 2009 Peter Brune\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Normals demo.\n\nThis example demonstrates how to use the facet normals\nMerely project the normal onto a vector section.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import FacetNormal, FunctionSpace, Mesh, TestFunction, TrialFunction, ds, inner, triangle\n\ncell = triangle\n\nelement = basix.ufl.element(\"Lagrange\", cell.cellname, 1, shape=(2,))\ndomain = Mesh(basix.ufl.element(\"Lagrange\", cell.cellname, 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nn = FacetNormal(domain)\n\nv = TrialFunction(space)\nu = TestFunction(space)\n\na = inner(v, u) * ds\nL = inner(n, u) * ds\n"
  },
  {
    "path": "demo/Poisson1D.py",
    "content": "# Copyright (C) 2004-2007 Anders Logg\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"1D Poisson demo.\n\nThe bilinear form a(u, v) and linear form L(v) for Poisson's equation.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner\n\nelement = basix.ufl.element(\"Lagrange\", \"interval\", 1)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"interval\", 1, shape=(1,)))\nspace = FunctionSpace(domain, element)\n\nu = TrialFunction(space)\nv = TestFunction(space)\nf = Coefficient(space)\n\na = inner(grad(u), grad(v)) * dx\nL = inner(f, v) * dx\n"
  },
  {
    "path": "demo/PoissonQuad.py",
    "content": "# Copyright (C) 2016 Jan Blechta\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Quadrilateral Poisson demo.\n\nThe bilinear form a(u, v) and linear form L(v) for\nPoisson's equation using bilinear elements on bilinear mesh geometry.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner\n\ncoords = basix.ufl.element(\"P\", \"triangle\", 2, shape=(2,))\nmesh = Mesh(coords)\ndx = dx(mesh)\n\nelement = basix.ufl.element(\"P\", mesh.ufl_cell().cellname, 2)\nspace = FunctionSpace(mesh, element)\n\nu = TrialFunction(space)\nv = TestFunction(space)\nf = Coefficient(space)\n\na = inner(grad(u), grad(v)) * dx\nL = inner(f, v) * dx\n"
  },
  {
    "path": "demo/ProjectionManifold.py",
    "content": "# Copyright (C) 2012 Marie E. Rognes and David Ham\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Projection manifold demo.\n\nThis demo illustrates use of finite element spaces defined over\nsimplicies embedded in higher dimensions.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dx, inner\n\n# Define element over this domain\nV = basix.ufl.element(\"RT\", \"triangle\", 1)\nQ = basix.ufl.element(\"DG\", \"triangle\", 0)\nelement = basix.ufl.mixed_element([V, Q])\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(3,)))\nspace = FunctionSpace(domain, element)\n\n(u, p) = TrialFunctions(space)\n(v, q) = TestFunctions(space)\n\na = (inner(u, v) + inner(div(u), q) + inner(p, div(v))) * dx\n"
  },
  {
    "path": "demo/ReactionDiffusion.py",
    "content": "# Copyright (C) 2009 Anders Logg\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Reaction-diffusion demo.\n\nThe bilinear form a(u, v) and linear form L(v) for a simple\nreaction-diffusion equation using simplified tuple notation.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner\n\nelement = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nu = TrialFunction(space)\nv = TestFunction(space)\nf = Coefficient(space)\n\na = (inner(grad(u), grad(v)) + inner(u, v)) * dx\nL = inner(f, v) * dx\n"
  },
  {
    "path": "demo/SpatialCoordinates.py",
    "content": "# Copyright (C) 2010 Kristian B. Oelgaard\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Spatial coordinates demo.\n\nThe bilinear form a(u, v) and linear form L(v) for Poisson's equation where\nspatial coordinates are used to define the source and boundary flux terms.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    FunctionSpace,\n    Mesh,\n    SpatialCoordinate,\n    TestFunction,\n    TrialFunction,\n    ds,\n    dx,\n    exp,\n    grad,\n    inner,\n    sin,\n)\n\nelement = basix.ufl.element(\"Lagrange\", \"triangle\", 2)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, element)\n\nu = TrialFunction(space)\nv = TestFunction(space)\n\nx = SpatialCoordinate(domain)\nd_x = x[0] - 0.5\nd_y = x[1] - 0.5\nf = 10.0 * exp(-(d_x * d_x + d_y * d_y) / 0.02)\ng = sin(5.0 * x[0])\n\na = inner(grad(u), grad(v)) * dx\nL = inner(f, v) * dx + inner(g, v) * ds\n"
  },
  {
    "path": "demo/StabilisedStokes.py",
    "content": "# Copyright (c) 2005-2007 Anders Logg\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Stabilised Stokes demo.\n\nThe bilinear form a(u, v) and Linear form L(v) for the Stokes\nequations using a mixed formulation (equal-order stabilized).\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    Coefficient,\n    FunctionSpace,\n    Mesh,\n    TestFunctions,\n    TrialFunctions,\n    div,\n    dot,\n    dx,\n    grad,\n    inner,\n)\n\nvector = basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,))\nscalar = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\nsystem = basix.ufl.mixed_element([vector, scalar])\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nsystem_space = FunctionSpace(domain, system)\nscalar_space = FunctionSpace(domain, scalar)\nvector_space = FunctionSpace(domain, vector)\n\n(u, p) = TrialFunctions(system_space)\n(v, q) = TestFunctions(system_space)\n\nf = Coefficient(vector_space)\nh = Coefficient(scalar_space)\n\nbeta = 0.2\ndelta = beta * h * h\n\na = (inner(grad(u), grad(v)) - div(v) * p + div(u) * q + delta * dot(grad(p), grad(q))) * dx\nL = dot(f, v + delta * grad(q)) * dx\n"
  },
  {
    "path": "demo/Symmetry.py",
    "content": "\"\"\"Symmetry demo.\"\"\"\n\nimport basix.ufl\nfrom ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner\n\nP1 = basix.ufl.element(\"P\", \"triangle\", 1, shape=(2, 2), symmetry=True)\ndomain = Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\nspace = FunctionSpace(domain, P1)\n\nu = TrialFunction(space)\nv = TestFunction(space)\n\na = inner(grad(u), grad(v)) * dx\n"
  },
  {
    "path": "demo/VectorConstant.py",
    "content": "# Copyright (C) 2016 Jan Blechta\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Vector constant demo.\n\nThe bilinear form a(u, v) and linear form L(v) for\nPoisson's equation using bilinear elements on bilinear mesh geometry.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    Coefficient,\n    Constant,\n    FunctionSpace,\n    Mesh,\n    TestFunction,\n    TrialFunction,\n    dx,\n    grad,\n    inner,\n)\n\ncoords = basix.ufl.element(\"P\", \"triangle\", 2, shape=(2,))\nmesh = Mesh(coords)\ndx = dx(mesh)\n\nelement = basix.ufl.element(\"P\", mesh.ufl_cell().cellname, 2)\nspace = FunctionSpace(mesh, element)\n\nu = TrialFunction(space)\nv = TestFunction(space)\nf = Coefficient(space)\n\nL = inner(f, v) * dx\n\nmu = Constant(mesh, shape=(3,))\ntheta = -(mu[1] - 2) / mu[0] - (2 * (2 * mu[0] - 2) * (mu[0] - 1)) / (mu[0] * (mu[1] - 2))\na = theta * inner(grad(u), grad(v)) * dx\n"
  },
  {
    "path": "demo/VectorPoisson.py",
    "content": "# Copyright (C) 2010 Anders Logg\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Vector Poisson demo.\n\nThe bilinear form a(u, v) and linear form L(v) for the vector-valued Poisson's equation.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner\n\nelement = basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,))\ndomain = Mesh(element)\nspace = FunctionSpace(domain, element)\n\nu = TrialFunction(space)\nv = TestFunction(space)\nf = Coefficient(space)\n\na = inner(grad(u), grad(v)) * dx\nL = inner(f, v) * dx\n"
  },
  {
    "path": "demo/test_demos.py",
    "content": "\"\"\"Test demos.\"\"\"\n\nimport os\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nimport pytest\n\ndemo_dir = Path(__file__).parent\n\nufl_files = [\n    f\n    for f in demo_dir.iterdir()\n    if f.suffix == \".py\" and not f.stem.endswith(\"_numba\") and f != Path(__file__)\n]\n\nskip_complex = [\"BiharmonicHHJ\", \"BiharmonicRegge\", \"StabilisedStokes\"]\n\n\ndef skip_unsupported(test):\n    \"\"\"Decorate test case to skip unsupported cases.\"\"\"\n\n    def check_skip(file, scalar_type):\n        \"\"\"Skip scalar_type file combinations not supported.\"\"\"\n        if \"complex\" in scalar_type and file.stem in skip_complex:\n            pytest.skip(reason=\"Not implemented for complex types\")\n        elif \"Complex\" in file.stem and scalar_type in [\"float64\", \"float32\"]:\n            pytest.skip(reason=\"Not implemented for real types\")\n\n        return test(file, scalar_type)\n\n    return check_skip\n\n\n@pytest.mark.parametrize(\"file\", ufl_files)\n@pytest.mark.parametrize(\"scalar_type\", [\"float64\", \"float32\", \"complex128\", \"complex64\"])\n@skip_unsupported\ndef test_C(file, scalar_type):\n    \"\"\"Test a demo.\"\"\"\n    if sys.platform.startswith(\"win32\") and \"complex\" in scalar_type:\n        # Skip complex demos on win32\n        pytest.skip(reason=\"_Complex not supported on Windows\")\n\n    subprocess.run([\"ffcx\", \"--scalar_type\", scalar_type, file], cwd=demo_dir, check=True)\n\n    if sys.platform.startswith(\"win32\"):\n        extra_flags = \"/std:c17\"\n        for compiler in [\"cl.exe\", \"clang-cl.exe\"]:\n            subprocess.run(\n                [\n                    compiler,\n                    \"/I\",\n                    f\"{demo_dir.parent / 'ffcx/codegeneration'}\",\n                    *extra_flags.split(\" \"),\n                    \"/c\",\n                    file.with_suffix(\".c\"),\n                ],\n                cwd=demo_dir,\n                check=True,\n            )\n    else:\n        cc = os.environ.get(\"CC\", \"cc\")\n        extra_flags = (\n            \"-std=c17 -Wunused-variable -Werror -fPIC -Wno-error=implicit-function-declaration\"\n        )\n        subprocess.run(\n            [\n                cc,\n                f\"-I{demo_dir.parent / 'ffcx/codegeneration'}\",\n                *extra_flags.split(\" \"),\n                \"-c\",\n                file.with_suffix(\".c\"),\n            ],\n            cwd=demo_dir,\n            check=True,\n        )\n\n\n@pytest.mark.parametrize(\"file\", ufl_files)\n@pytest.mark.parametrize(\"scalar_type\", [\"float64\", \"float32\", \"complex128\", \"complex64\"])\n@skip_unsupported\ndef test_numba(file, scalar_type):\n    \"\"\"Test numba generation.\"\"\"\n    opts = f\"--language numba --scalar_type {scalar_type}\"\n    subprocess.run([\"ffcx\", *opts.split(\" \"), file], cwd=demo_dir, check=True)\n    subprocess.run([\"python\", file], cwd=demo_dir, check=True)\n"
  },
  {
    "path": "doc/source/conf.py",
    "content": "\"\"\"Configuration file for the Sphinx documentation builder.\"\"\"\n# This file does only contain a selection of the most common options. For a\n# full list see the documentation:\n# http://www.sphinx-doc.org/en/stable/config\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\n# import os\n# import sys\n# sys.path.insert(0, os.path.abspath('.'))\nimport datetime\n\nimport ffcx\n\n# -- Project information -----------------------------------------------------\n\nproject = \"FEniCS Form Compiler X\"\nnow = datetime.datetime.now()\ndate = now.date()\ncopyright = f\"{date.year}, FEniCS Project\"\nauthor = \"FEniCS Project\"\n\n# The short X.Y version\nversion = ffcx.__version__\n# The full version, including alpha/beta/rc tags\nrelease = ffcx.__version__\n\n\n# -- General configuration ---------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.autosummary\",\n    \"sphinx.ext.doctest\",\n    \"sphinx.ext.todo\",\n    \"sphinx.ext.coverage\",\n    \"sphinx.ext.mathjax\",\n    \"sphinx.ext.napoleon\",\n    \"sphinx.ext.viewcode\",\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\n# source_suffix = ['.rst', '.md']\nsource_suffix = \".rst\"\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = None\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path .\nexclude_patterns = []\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\n# html_theme = 'alabaster'\nhtml_theme = \"sphinx_rtd_theme\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#\n# html_theme_options = {}\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\n\n# Custom sidebar templates, must be a dictionary that maps document names\n# to template names.\n#\n# The default sidebars (for documents that don't match any pattern) are\n# defined by theme itself.  Builtin themes are using these templates by\n# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',\n# 'searchbox.html']``.\n#\n# html_sidebars = {}\n\n\n# -- Options for HTMLHelp output ---------------------------------------------\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"FEniCSFormCompilerXdoc\"\n\n\n# -- Options for LaTeX output ------------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #\n    # 'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    #\n    # 'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    #\n    # 'preamble': '',\n    # Latex figure (float) alignment\n    #\n    # 'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (\n        master_doc,\n        \"FEniCSFormCompilerX.tex\",\n        \"FEniCS Form Compiler X Documentation\",\n        \"FEniCS Project\",\n        \"manual\",\n    ),\n]\n\n\n# -- Options for manual page output ------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (master_doc, \"fenicsformcompilerx\", \"FEniCS Form Compiler X Documentation\", [author], 1)\n]\n\n\n# -- Options for Texinfo output ----------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        master_doc,\n        \"FEniCSFormCompilerX\",\n        \"FEniCS Form Compiler X Documentation\",\n        author,\n        \"FEniCSFormCompilerX\",\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    ),\n]\n\n\n# -- Extension configuration -------------------------------------------------\n\n# -- Options for todo extension ----------------------------------------------\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = True\nautodoc_default_options = {\n    \"members\": True,\n    \"show-inheritance\": True,\n    \"imported-members\": True,\n    \"undoc-members\": True,\n}\nautosummary_generate = True\nautoclass_content = \"both\"\n\n\nautodoc_default_flags = [\"members\", \"show-inheritance\"]\nnapoleon_numpy_docstring = True\nnapoleon_google_docstring = True\n"
  },
  {
    "path": "doc/source/index.rst",
    "content": "FEniCS Form Compiler 'X' documentation\n======================================\n\nThe is an experimental version of the FEniCS Form Compiler.\nIt is developed at https://github.com/FEniCS/ffcx.\n\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Contents:\n\nAPI reference\n=============\n\n.. autosummary::\n   :toctree: _autogenerated\n\n   ffcx\n   ffcx.__main__\n   ffcx.analysis\n   ffcx.compiler\n   ffcx.element_interface\n   ffcx.formatting\n   ffcx.main\n   ffcx.naming\n   ffcx.codegeneration\n   ffcx.options\n   ffcx.ir.representation\n   ffcx.ir.representationutils\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "ffcx/__init__.py",
    "content": "# Copyright (C) 2009-2018 FEniCS Project\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"FEniCS Form Compiler (FFCx).\n\nFFCx compiles finite element variational forms into C code.\n\"\"\"\n\nimport importlib.metadata\nimport logging\n\n# Import default options\nfrom ffcx.options import get_options  # noqa: F401\n\n__version__ = importlib.metadata.version(\"fenics-ffcx\")\n\nlogger = logging.getLogger(\"ffcx\")\n"
  },
  {
    "path": "ffcx/__main__.py",
    "content": "#!/usr/bin/env python\n# Copyright (C) 2017-2017 Martin Sandve Alnæs\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Run ffcx on a UFL file.\"\"\"\n\nfrom ffcx.main import main\n\nif __name__ == \"__main__\":\n    import sys\n\n    sys.exit(main())\n"
  },
  {
    "path": "ffcx/analysis.py",
    "content": "# Copyright (C) 2007-2020 Anders Logg, Martin Alnaes, Kristian B. Oelgaard,\n#                         Michal Habera and others\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Compiler stage 1: Analysis.\n\nThis module implements the analysis/preprocessing of variational forms,\nincluding automatic selection of elements, degrees and form\nrepresentation type.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport typing\n\nif typing.TYPE_CHECKING:\n    from ufl.algorithms.formdata import FormData\n\nimport basix.ufl\nimport numpy as np\nimport numpy.typing as npt\nimport ufl.algorithms\n\nlogger = logging.getLogger(\"ffcx\")\n\n\nclass UFLData(typing.NamedTuple):\n    \"\"\"UFL data.\"\"\"\n\n    # Tuple of ufl form data\n    form_data: tuple[FormData, ...]\n    # List of unique elements\n    unique_elements: list[basix.ufl._ElementBase]\n    # Lookup table from each unique element to its index in `unique_elements`\n    element_numbers: dict[basix.ufl._ElementBase, int]\n    # List of unique coordinate elements\n    unique_coordinate_elements: list[basix.ufl._ElementBase]\n    # List of ufl Expressions as tuples (expression, points, original_expression)\n    expressions: list[tuple[ufl.core.expr.Expr, npt.NDArray[np.floating], ufl.core.expr.Expr]]\n\n\ndef analyze_ufl_objects(\n    ufl_objects: list[\n        ufl.form.Form\n        | basix.ufl._ElementBase\n        | ufl.Mesh\n        | tuple[ufl.core.expr.Expr, npt.NDArray[np.floating]]\n    ],\n    scalar_type: npt.DTypeLike,\n) -> UFLData:\n    \"\"\"Analyze ufl object(s).\n\n    Args:\n        ufl_objects: UFL objects\n        scalar_type: Scalar type that should be used for the analysis\n\n    Returns:\n        A data structure holding:\n            form_datas: Form_data objects\n            unique_elements: Unique elements across all forms and expressions\n            element_numbers: Mapping to unique numbers for all elements\n            unique_coordinate_elements: Unique coordinate elements across all forms and expressions\n            expressions: List of all expressions after post-processing, with its evaluation points\n                         and the original expression\n    \"\"\"\n    logger.info(79 * \"*\")\n    logger.info(\"Compiler stage 1: Analyzing UFL objects\")\n    logger.info(79 * \"*\")\n\n    elements: list[basix.ufl._ElementBase] = []\n    coordinate_elements: list[basix.ufl._ElementBase] = []\n\n    # Group objects by types\n    forms: list[ufl.form.Form] = []\n    expressions: list[tuple[ufl.core.expr.Expr, npt.NDArray[np.floating]]] = []\n    processed_expressions: list[\n        tuple[ufl.core.expr.Expr, npt.NDArray[np.floating], ufl.core.expr.Expr]\n    ] = []\n\n    for ufl_object in ufl_objects:\n        if isinstance(ufl_object, ufl.form.Form):\n            forms.append(ufl_object)\n        elif isinstance(ufl_object, ufl.AbstractFiniteElement):\n            elements.append(ufl_object)\n        elif isinstance(ufl_object, ufl.Mesh):\n            coordinate_elements.append(ufl_object.ufl_coordinate_element())\n        elif isinstance(ufl_object[0], ufl.core.expr.Expr):\n            original_expression = ufl_object[0]\n            points = np.asarray(ufl_object[1])\n            expressions.append((original_expression, points))\n        else:\n            raise TypeError(\"UFL objects not recognised.\")\n\n    form_data = tuple(_analyze_form(form, scalar_type) for form in forms)\n    for data in form_data:\n        elements += data.unique_sub_elements\n        coordinate_elements += data.coordinate_elements\n\n    for original_expression, points in expressions:\n        elements += ufl.algorithms.extract_elements(original_expression)\n        processed_expression = _analyze_expression(original_expression, scalar_type)\n        processed_expressions += [(processed_expression, points, original_expression)]\n\n    elements += ufl.algorithms.analysis.extract_sub_elements(elements)\n\n    # Sort elements so sub-elements come before mixed elements\n    unique_elements = ufl.algorithms.sort_elements(set(elements))\n    unique_coordinate_element_list = sorted(set(coordinate_elements), key=lambda x: repr(x))\n\n    for e in unique_elements:\n        assert isinstance(e, basix.ufl._ElementBase)\n\n    # Compute dict (map) from element to index\n    element_numbers = {element: i for i, element in enumerate(unique_elements)}\n\n    return UFLData(\n        form_data=form_data,\n        unique_elements=unique_elements,\n        element_numbers=element_numbers,\n        unique_coordinate_elements=unique_coordinate_element_list,\n        expressions=processed_expressions,\n    )\n\n\ndef _analyze_expression(\n    expression: ufl.core.expr.Expr, scalar_type: npt.DTypeLike\n) -> ufl.core.expr.Expr:\n    \"\"\"Analyzes and preprocesses expressions.\"\"\"\n    preserve_geometry_types = (ufl.classes.Jacobian,)\n    expression = ufl.algorithms.apply_algebra_lowering.apply_algebra_lowering(expression)\n    expression = ufl.algorithms.apply_derivatives.apply_derivatives(expression)\n    expression = ufl.algorithms.apply_function_pullbacks.apply_function_pullbacks(expression)\n    expression = ufl.algorithms.apply_geometry_lowering.apply_geometry_lowering(\n        expression, preserve_geometry_types\n    )\n    expression = ufl.algorithms.apply_derivatives.apply_derivatives(expression)\n    expression = ufl.algorithms.apply_geometry_lowering.apply_geometry_lowering(\n        expression, preserve_geometry_types\n    )\n    expression = ufl.algorithms.apply_derivatives.apply_derivatives(expression)\n\n    # Remove complex nodes if scalar type is real valued\n    if not np.issubdtype(scalar_type, np.complexfloating):\n        expression = ufl.algorithms.remove_complex_nodes.remove_complex_nodes(expression)\n\n    return expression\n\n\ndef _analyze_form(form: ufl.Form, scalar_type: npt.DTypeLike) -> FormData:\n    \"\"\"Analyzes UFL form and attaches metadata.\n\n    Args:\n        form: forms\n        scalar_type: Scalar type used for form. This is used to simplify\n            real valued forms.\n\n    Returns:\n        Form data computed by UFL with metadata attached\n\n    Note:\n        The main workload of this function is extraction of\n        unique/default metadata from options, integral metadata or\n        inherited from UFL (in case of quadrature degree).\n    \"\"\"\n    if form.empty():\n        raise RuntimeError(f\"Form ({form}) seems to be zero: cannot compile it.\")\n    if _has_custom_integrals(form):\n        raise RuntimeError(f\"Form ({form}) contains unsupported custom integrals.\")\n\n    # Check that coordinate element is based on basix.ufl._ElementBase\n    for _integral in form._integrals:\n        assert isinstance(_integral._ufl_domain._ufl_coordinate_element, basix.ufl._ElementBase)\n\n    # Check for complex mode\n    complex_mode = np.issubdtype(scalar_type, np.complexfloating)\n\n    # Compute form metadata\n    form_data: FormData = ufl.algorithms.compute_form_data(\n        form,\n        do_apply_function_pullbacks=True,\n        do_apply_integral_scaling=True,\n        do_apply_geometry_lowering=True,\n        preserve_geometry_types=(ufl.classes.Jacobian,),\n        do_apply_restrictions=True,\n        do_append_everywhere_integrals=False,  # do not add dx integrals to dx(i) in UFL\n        complex_mode=complex_mode,\n    )\n\n    # Determine unique quadrature degree and quadrature scheme\n    # per each integral data\n    for id, integral_data in enumerate(form_data.integral_data):\n        # Iterate through groups of integral data. There is one integral\n        # data for all integrals with same domain, itype, subdomain_id\n        # (but possibly different metadata).\n        #\n        # Quadrature degree and quadrature scheme must be the same for\n        # all integrals in this integral data group, i.e. must be the\n        # same for for the same (domain, itype, subdomain_id)\n\n        for i, integral in enumerate(integral_data.integrals):\n            metadata = integral.metadata()\n\n            # Vertex integrals do not support discontinuous integrands.\n            if integral.integral_type() == \"vertex\":\n                elements = ufl.algorithms.extract_elements(integral)\n                if any(e.discontinuous for e in elements):\n                    raise TypeError(\"Vertex integrals not supported for discontinuous elements.\")\n\n            # If form contains a quadrature element, use the custom\n            # quadrature scheme\n            custom_q = None\n            for e in ufl.algorithms.extract_elements(integral):\n                if e.has_custom_quadrature:\n                    if custom_q is None:\n                        custom_q = e.custom_quadrature()\n                    else:\n                        p, w = e.custom_quadrature()\n                        assert np.allclose(p, custom_q[0])\n                        assert np.allclose(w, custom_q[1])\n\n            if custom_q is None:\n                # Extract quadrature degree\n                qd = -1\n                if \"quadrature_degree\" in metadata.keys():\n                    qd = metadata[\"quadrature_degree\"]\n\n                # Sending in a negative quadrature degree means that we want to be\n                # able to customize it at a later stage.\n                if qd < 0:\n                    qd = int(np.max(integral.metadata()[\"estimated_polynomial_degree\"]))\n                # Extract quadrature rule\n                qr = integral.metadata().get(\"quadrature_rule\", \"default\")\n\n                logger.info(f\"Integral {i}, integral group {id}:\")\n                logger.info(f\"--- quadrature rule: {qr}\")\n                logger.info(f\"--- quadrature degree: {qd}\")\n\n                metadata.update({\"quadrature_degree\": qd, \"quadrature_rule\": qr})\n            else:\n                metadata.update(\n                    {\n                        \"quadrature_points\": custom_q[0],\n                        \"quadrature_weights\": custom_q[1],\n                        \"quadrature_rule\": \"custom\",\n                    }\n                )\n\n            integral_data.integrals[i] = integral.reconstruct(metadata=metadata)\n\n    return form_data\n\n\ndef _has_custom_integrals(\n    o: ufl.integral.Integral | ufl.classes.Form | list | tuple,\n) -> bool:\n    \"\"\"Check for custom integrals.\"\"\"\n    if isinstance(o, ufl.integral.Integral):\n        return o.integral_type() in ufl.custom_integral_types\n    elif isinstance(o, ufl.classes.Form):\n        return any(_has_custom_integrals(itg) for itg in o.integrals())\n    elif isinstance(o, list | tuple):\n        return any(_has_custom_integrals(itg) for itg in o)\n    else:\n        raise NotImplementedError\n"
  },
  {
    "path": "ffcx/codegeneration/C/__init__.py",
    "content": "\"\"\"Generation of C code.\"\"\"\n\nfrom typing import TYPE_CHECKING\n\nfrom ffcx.codegeneration import interface\nfrom ffcx.codegeneration.C import expression, file, form, integral\n\nfrom .formatter import Formatter\n\n__all__ = [\n    \"Formatter\",\n    \"expression\",\n    \"file\",\n    \"form\",\n    \"integral\",\n]\n\nif TYPE_CHECKING:\n    # ensure protocol compliance\n    import numpy as np\n\n    _formatter: interface.Formatter = Formatter(np.float64)\n    _expression: interface.expression_generator = expression.generator\n    _file: interface.file_generator = file.generator\n    _form: interface.form_generator = form.generator\n    _integral: interface.integral_generator = integral.generator\n"
  },
  {
    "path": "ffcx/codegeneration/C/expression.py",
    "content": "# Copyright (C) 2019 Michal Habera\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Generate UFCx code for an expression.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\n\nimport numpy as np\n\nfrom ffcx.codegeneration.backend import FFCXBackend\nfrom ffcx.codegeneration.C import expression_template\nfrom ffcx.codegeneration.C.formatter import Formatter\nfrom ffcx.codegeneration.common import template_keys\nfrom ffcx.codegeneration.expression_generator import ExpressionGenerator\nfrom ffcx.codegeneration.utils import dtype_to_c_type, dtype_to_scalar_dtype\nfrom ffcx.ir.representation import ExpressionIR\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef generator(ir: ExpressionIR, options):\n    \"\"\"Generate UFCx code for an expression.\"\"\"\n    logger.info(\"Generating code for expression:\")\n    assert len(ir.expression.integrand) == 1, \"Expressions only support single quadrature rule\"\n    points = next(iter(ir.expression.integrand))[1].points\n    logger.info(f\"--- points: {points}\")\n    factory_name = ir.expression.name\n    logger.info(f\"--- name: {factory_name}\")\n\n    # Format declaration\n    declaration = expression_template.declaration.format(\n        factory_name=factory_name, name_from_uflfile=ir.name_from_uflfile\n    )\n\n    backend = FFCXBackend(ir, options)\n    eg = ExpressionGenerator(ir, backend)\n\n    d: dict[str, str | int] = {}\n    d[\"name_from_uflfile\"] = ir.name_from_uflfile\n    d[\"factory_name\"] = factory_name\n    parts = eg.generate()\n\n    format = Formatter(options[\"scalar_type\"])\n    d[\"tabulate_expression\"] = format(parts)\n\n    if len(ir.original_coefficient_positions) > 0:\n        d[\"original_coefficient_positions\"] = f\"original_coefficient_positions_{factory_name}\"\n        values = \", \".join(str(i) for i in ir.original_coefficient_positions)\n        sizes = len(ir.original_coefficient_positions)\n        d[\"original_coefficient_positions_init\"] = (\n            f\"static int original_coefficient_positions_{factory_name}[{sizes}] = {{{values}}};\"\n        )\n    else:\n        d[\"original_coefficient_positions\"] = \"NULL\"\n        d[\"original_coefficient_positions_init\"] = \"\"\n\n    values = \", \".join(str(p) for p in points.flatten())\n    sizes = points.size\n    d[\"points_init\"] = f\"static double points_{factory_name}[{sizes}] = {{{values}}};\"\n    d[\"points\"] = f\"points_{factory_name}\"\n\n    if len(ir.expression.shape) > 0:\n        values = \", \".join(str(i) for i in ir.expression.shape)\n        sizes = len(ir.expression.shape)\n        d[\"value_shape_init\"] = f\"static int value_shape_{factory_name}[{sizes}] = {{{values}}};\"\n        d[\"value_shape\"] = f\"value_shape_{factory_name}\"\n    else:\n        d[\"value_shape_init\"] = \"\"\n        d[\"value_shape\"] = \"NULL\"\n    d[\"num_components\"] = len(ir.expression.shape)\n    d[\"num_coefficients\"] = len(ir.expression.coefficient_numbering)\n    d[\"num_constants\"] = len(ir.constant_names)\n    d[\"num_points\"] = points.shape[0]\n    d[\"entity_dimension\"] = points.shape[1]\n    d[\"scalar_type\"] = dtype_to_c_type(options[\"scalar_type\"])\n    d[\"geom_type\"] = dtype_to_c_type(dtype_to_scalar_dtype(options[\"scalar_type\"]))\n    d[\"np_scalar_type\"] = np.dtype(options[\"scalar_type\"]).name\n\n    d[\"rank\"] = len(ir.expression.tensor_shape)\n\n    if len(ir.coefficient_names) > 0:\n        values = \", \".join(f'\"{name}\"' for name in ir.coefficient_names)\n        sizes = len(ir.coefficient_names)\n        d[\"coefficient_names_init\"] = (\n            f\"static const char* coefficient_names_{factory_name}[{sizes}] = {{{values}}};\"\n        )\n        d[\"coefficient_names\"] = f\"coefficient_names_{factory_name}\"\n    else:\n        d[\"coefficient_names_init\"] = \"\"\n        d[\"coefficient_names\"] = \"NULL\"\n\n    if len(ir.constant_names) > 0:\n        values = \", \".join(f'\"{name}\"' for name in ir.constant_names)\n        sizes = len(ir.constant_names)\n        d[\"constant_names_init\"] = (\n            f\"static const char* constant_names_{factory_name}[{sizes}] = {{{values}}};\"\n        )\n        d[\"constant_names\"] = f\"constant_names_{factory_name}\"\n    else:\n        d[\"constant_names_init\"] = \"\"\n        d[\"constant_names\"] = \"NULL\"\n\n    d[\"coordinate_element_hash\"] = f\"UINT64_C({ir.expression.coordinate_element_hash})\"\n\n    # Format implementation code\n    assert set(d.keys()) == template_keys(expression_template.factory)\n    implementation = expression_template.factory.format_map(d)\n\n    return declaration, implementation\n"
  },
  {
    "path": "ffcx/codegeneration/C/expression_template.py",
    "content": "# Copyright (C) 2019 Michal Habera\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Code generation strings for an expression.\"\"\"\n\ndeclaration = \"\"\"\nextern ufcx_expression {factory_name};\n\n// Helper used to create expression using name which was given to the\n// expression in the UFL file.\n// This helper is called in user c++ code.\n//\nextern ufcx_expression* {name_from_uflfile};\n\"\"\"\n\nfactory = \"\"\"\n// Code for expression {factory_name}\n\nvoid tabulate_tensor_{factory_name}({scalar_type}* restrict A,\n                                    const {scalar_type}* restrict w,\n                                    const {scalar_type}* restrict c,\n                                    const {geom_type}* restrict coordinate_dofs,\n                                    const int* restrict entity_local_index,\n                                    const uint8_t* restrict quadrature_permutation,\n                                    void* custom_data)\n{{\n{tabulate_expression}\n}}\n\n{points_init}\n{value_shape_init}\n{original_coefficient_positions_init}\n{coefficient_names_init}\n{constant_names_init}\n\n\nufcx_expression {factory_name} =\n{{\n  .tabulate_tensor_{np_scalar_type} = tabulate_tensor_{factory_name},\n  .num_coefficients = {num_coefficients},\n  .num_constants = {num_constants},\n  .original_coefficient_positions = {original_coefficient_positions},\n  .coefficient_names = {coefficient_names},\n  .constant_names = {constant_names},\n  .num_points = {num_points},\n  .entity_dimension = {entity_dimension},\n  .points = {points},\n  .value_shape = {value_shape},\n  .num_components = {num_components},\n  .rank = {rank},\n  .coordinate_element_hash = {coordinate_element_hash},\n}};\n\n// Alias name\nufcx_expression* {name_from_uflfile} = &{factory_name};\n\n// End of code for expression {factory_name}\n\"\"\"\n"
  },
  {
    "path": "ffcx/codegeneration/C/file.py",
    "content": "# Copyright (C) 2009-2018 Anders Logg, Martin Sandve Alnæs and Garth N. Wells\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n#\n# Note: Most of the code in this file is a direct translation from the\n# old implementation in FFC\n\"\"\"Generate a file.\"\"\"\n\nimport logging\nimport pprint\nimport textwrap\n\nimport numpy as np\n\nfrom ffcx import __version__ as FFCX_VERSION\nfrom ffcx.codegeneration import __version__ as UFC_VERSION\nfrom ffcx.codegeneration.C import file_template\n\nsuffixes = (\".h\", \".c\")\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef generator(options):\n    \"\"\"Generate UFCx code for file output.\"\"\"\n    logger.info(\"Generating code for file\")\n\n    # Attributes\n    d = {\"ffcx_version\": FFCX_VERSION, \"ufcx_version\": UFC_VERSION}\n    d[\"options\"] = textwrap.indent(pprint.pformat(options), \"//  \")\n    extra_c_includes = []\n    if np.issubdtype(options[\"scalar_type\"], np.complexfloating):\n        extra_c_includes += [\"complex.h\"]\n    d[\"extra_c_includes\"] = \"\\n\".join(f\"#include <{header}>\" for header in extra_c_includes)\n\n    # Format declaration code\n    code_pre = (\n        file_template.declaration_pre.format_map(d),\n        file_template.implementation_pre.format_map(d),\n    )\n\n    # Format implementation code\n    code_post = (\n        file_template.declaration_post.format_map(d),\n        file_template.implementation_post.format_map(d),\n    )\n\n    return code_pre, code_post\n"
  },
  {
    "path": "ffcx/codegeneration/C/file_template.py",
    "content": "# Code generation format strings for UFCx (Unified Form-assembly Code)\n# This code is released into the public domain.\n#\n# The FEniCS Project (http://www.fenicsproject.org/) 2018.\n\"\"\"Code generation strings for a file.\"\"\"\n\nimport sys\n\ndeclaration_pre = \"\"\"\n// This code conforms with the UFCx specification version {ufcx_version}\n// and was automatically generated by FFCx version {ffcx_version}.\n//\n// This code was generated with the following options:\n//\n{options}\n\n#pragma once\n#include <ufcx.h>\n\n#ifdef __cplusplus\nextern \"C\" {{\n#endif\n\"\"\"\n\ndeclaration_post = \"\"\"\n#ifdef __cplusplus\n}}\n#endif\n\"\"\"\n\nimplementation_pre = \"\"\"\n// This code conforms with the UFCx specification version {ufcx_version}\n// and was automatically generated by FFCx version {ffcx_version}.\n//\n// This code was generated with the following options:\n//\n{options}\n\n#include <math.h>\n#include <stdalign.h>\n#include <stdlib.h>\n#include <string.h>\n#include <ufcx.h>\n{extra_c_includes}\n\n\"\"\"\n\n\nlibraries: list[str] = [] if sys.platform.startswith(\"win32\") else [\"m\"]\nimplementation_post = \"\"\n"
  },
  {
    "path": "ffcx/codegeneration/C/form.py",
    "content": "# Copyright (C) 2009-2017 Anders Logg and Martin Sandve Alnæs\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n#\n# Modified by Chris Richardson and Jørgen S. Dokken 2023\n#\n# Note: Most of the code in this file is a direct translation from the\n# old implementation in FFC\n\"\"\"Generate UFCx code for a form.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\n\nfrom ffcx.codegeneration.C import form_template\nfrom ffcx.codegeneration.common import integral_data, template_keys\nfrom ffcx.ir.representation import FormIR\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef generator(ir: FormIR, options):\n    \"\"\"Generate UFCx code for a form.\"\"\"\n    logger.info(\"Generating code for form:\")\n    logger.info(f\"--- rank: {ir.rank}\")\n    logger.info(f\"--- name: {ir.name}\")\n\n    d: dict[str, int | str] = {}\n    d[\"factory_name\"] = ir.name\n    d[\"name_from_uflfile\"] = ir.name_from_uflfile\n    d[\"signature\"] = f'\"{ir.signature}\"'\n    d[\"rank\"] = ir.rank\n    d[\"num_coefficients\"] = ir.num_coefficients\n\n    if len(ir.original_coefficient_positions) > 0:\n        values = \", \".join(str(i) for i in ir.original_coefficient_positions)\n        sizes = len(ir.original_coefficient_positions)\n\n        d[\"original_coefficient_position_init\"] = (\n            f\"int original_coefficient_position_{ir.name}[{sizes}] = {{{values}}};\"\n        )\n        d[\"original_coefficient_positions\"] = f\"original_coefficient_position_{ir.name}\"\n    else:\n        d[\"original_coefficient_position_init\"] = \"\"\n        d[\"original_coefficient_positions\"] = \"NULL\"\n\n    if len(ir.coefficient_names) > 0:\n        values = \", \".join(f'\"{name}\"' for name in ir.coefficient_names)\n        sizes = len(ir.coefficient_names)\n        d[\"coefficient_names_init\"] = (\n            f\"static const char* coefficient_names_{ir.name}[{sizes}] = {{{values}}};\"\n        )\n        d[\"coefficient_names\"] = f\"coefficient_names_{ir.name}\"\n    else:\n        d[\"coefficient_names_init\"] = \"\"\n        d[\"coefficient_names\"] = \"NULL\"\n\n    d[\"num_constants\"] = ir.num_constants\n    if ir.num_constants > 0:\n        d[\"constant_ranks_init\"] = (\n            f\"static const int constant_ranks_{ir.name}[{ir.num_constants}] = \"\n            f\"{{{str(ir.constant_ranks)[1:-1]}}};\"\n        )\n        d[\"constant_ranks\"] = f\"constant_ranks_{ir.name}\"\n\n        shapes = [\n            f\"static const int constant_shapes_{ir.name}_{i}[{len(shape)}] = \"\n            f\"{{{str(shape)[1:-1]}}};\"\n            for i, shape in enumerate(ir.constant_shapes)\n            if len(shape) > 0\n        ]\n        names = [f\"constant_shapes_{ir.name}_{i}\" for i in range(ir.num_constants)]\n        shapes1 = f\"static const int* constant_shapes_{ir.name}[{ir.num_constants}] = \" + \"{\"\n        for rank, name in zip(ir.constant_ranks, names):\n            if rank > 0:\n                shapes1 += f\"{name},\\n\"\n            else:\n                shapes1 += \"NULL,\\n\"\n        shapes1 += \"};\"\n        shapes.append(shapes1)\n\n        d[\"constant_shapes_init\"] = \"\\n\".join(shapes)\n        d[\"constant_shapes\"] = f\"constant_shapes_{ir.name}\"\n    else:\n        d[\"constant_ranks_init\"] = \"\"\n        d[\"constant_ranks\"] = \"NULL\"\n        d[\"constant_shapes_init\"] = \"\"\n        d[\"constant_shapes\"] = \"NULL\"\n\n    if len(ir.constant_names) > 0:\n        values = \", \".join(f'\"{name}\"' for name in ir.constant_names)\n        sizes = len(ir.constant_names)\n        d[\"constant_names_init\"] = (\n            f\"static const char* constant_names_{ir.name}[{sizes}] = {{{values}}};\"\n        )\n        d[\"constant_names\"] = f\"constant_names_{ir.name}\"\n    else:\n        d[\"constant_names_init\"] = \"\"\n        d[\"constant_names\"] = \"NULL\"\n\n    if len(ir.finite_element_hashes) > 0:\n        d[\"finite_element_hashes\"] = f\"finite_element_hashes_{ir.name}\"\n        values = \", \".join(\n            f\"UINT64_C({0 if el is None else el})\" for el in ir.finite_element_hashes\n        )\n        sizes = len(ir.finite_element_hashes)\n        d[\"finite_element_hashes_init\"] = (\n            f\"uint64_t finite_element_hashes_{ir.name}[{sizes}] = {{{values}}};\"\n        )\n    else:\n        d[\"finite_element_hashes\"] = \"NULL\"\n        d[\"finite_element_hashes_init\"] = \"\"\n\n    integrals = integral_data(ir)\n\n    if len(integrals.names) > 0:\n        sizes = sum(len(domains) for domains in integrals.domains)\n        values = \", \".join(\n            [\n                f\"&{name}_{domain.name}\"\n                for name, domains in zip(integrals.names, integrals.domains)\n                for domain in domains\n            ]\n        )\n        d[\"form_integrals_init\"] = (\n            f\"static ufcx_integral* form_integrals_{ir.name}[{sizes}] = {{{values}}};\"\n        )\n        d[\"form_integrals\"] = f\"form_integrals_{ir.name}\"\n        values = \", \".join(\n            f\"{i}\" for i, domains in zip(integrals.ids, integrals.domains) for _ in domains\n        )\n        d[\"form_integral_ids_init\"] = f\"int form_integral_ids_{ir.name}[{sizes}] = {{{values}}};\"\n        d[\"form_integral_ids\"] = f\"form_integral_ids_{ir.name}\"\n    else:\n        d[\"form_integrals_init\"] = \"\"\n        d[\"form_integrals\"] = \"NULL\"\n        d[\"form_integral_ids_init\"] = \"\"\n        d[\"form_integral_ids\"] = \"NULL\"\n\n    sizes = len(integrals.offsets)\n    values = \", \".join(str(i) for i in integrals.offsets)\n    d[\"form_integral_offsets_init\"] = (\n        f\"int form_integral_offsets_{ir.name}[{sizes}] = {{{values}}};\"\n    )\n\n    # Format implementation code\n    assert set(d.keys()) == template_keys(form_template.factory)\n    implementation = form_template.factory.format_map(d)\n\n    # Format declaration\n    declaration = form_template.declaration.format(\n        factory_name=d[\"factory_name\"], name_from_uflfile=d[\"name_from_uflfile\"]\n    )\n\n    return declaration, implementation\n"
  },
  {
    "path": "ffcx/codegeneration/C/form_template.py",
    "content": "# Code generation format strings for UFCx (Unified Form-assembly Code)\n# This code is released into the public domain.\n#\n# The FEniCS Project (http://www.fenicsproject.org/) 2020.\n\"\"\"Code generation strings for a form.\"\"\"\n\ndeclaration = \"\"\"\nextern ufcx_form {factory_name};\n\n// Helper used to create form using name which was given to the\n// form in the UFL file.\n// This helper is called in user c++ code.\n//\nextern ufcx_form* {name_from_uflfile};\n\n\"\"\"\n\nfactory = \"\"\"\n// Code for form {factory_name}\n\n{original_coefficient_position_init}\n{finite_element_hashes_init}\n{form_integral_offsets_init}\n{form_integrals_init}\n{form_integral_ids_init}\n\n{coefficient_names_init}\n{constant_names_init}\n{constant_ranks_init}\n{constant_shapes_init}\n\nufcx_form {factory_name} =\n{{\n\n  .signature = {signature},\n  .rank = {rank},\n\n  .num_coefficients = {num_coefficients},\n  .original_coefficient_positions = {original_coefficient_positions},\n  .coefficient_name_map = {coefficient_names},\n\n  .num_constants = {num_constants},\n  .constant_ranks = {constant_ranks},\n  .constant_shapes = {constant_shapes},\n  .constant_name_map = {constant_names},\n\n  .finite_element_hashes = {finite_element_hashes},\n\n  .form_integrals = {form_integrals},\n  .form_integral_ids = {form_integral_ids},\n  .form_integral_offsets = form_integral_offsets_{factory_name}\n}};\n\n// Alias name\nufcx_form* {name_from_uflfile} = &{factory_name};\n\n// End of code for form {factory_name}\n\"\"\"\n"
  },
  {
    "path": "ffcx/codegeneration/C/formatter.py",
    "content": "# Copyright (C) 2023-2025 Chris Richardson and Paul T. Kühner\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"C implementation.\"\"\"\n\nimport warnings\nfrom functools import singledispatchmethod\n\nimport numpy as np\nimport numpy.typing as npt\n\nimport ffcx.codegeneration.lnodes as L\nfrom ffcx.codegeneration.interface import Formatter as FormatterInterface\nfrom ffcx.codegeneration.utils import dtype_to_c_type, dtype_to_scalar_dtype\n\nmath_table = {\n    \"float64\": {\n        \"sqrt\": \"sqrt\",\n        \"abs\": \"fabs\",\n        \"cos\": \"cos\",\n        \"sin\": \"sin\",\n        \"tan\": \"tan\",\n        \"acos\": \"acos\",\n        \"asin\": \"asin\",\n        \"atan\": \"atan\",\n        \"cosh\": \"cosh\",\n        \"sinh\": \"sinh\",\n        \"tanh\": \"tanh\",\n        \"acosh\": \"acosh\",\n        \"asinh\": \"asinh\",\n        \"atanh\": \"atanh\",\n        \"power\": \"pow\",\n        \"exp\": \"exp\",\n        \"ln\": \"log\",\n        \"erf\": \"erf\",\n        \"atan_2\": \"atan2\",\n        \"min_value\": \"fmin\",\n        \"max_value\": \"fmax\",\n        \"bessel_y\": \"yn\",\n        \"bessel_j\": \"jn\",\n    },\n    \"float32\": {\n        \"sqrt\": \"sqrtf\",\n        \"abs\": \"fabsf\",\n        \"cos\": \"cosf\",\n        \"sin\": \"sinf\",\n        \"tan\": \"tanf\",\n        \"acos\": \"acosf\",\n        \"asin\": \"asinf\",\n        \"atan\": \"atanf\",\n        \"cosh\": \"coshf\",\n        \"sinh\": \"sinhf\",\n        \"tanh\": \"tanhf\",\n        \"acosh\": \"acoshf\",\n        \"asinh\": \"asinhf\",\n        \"atanh\": \"atanhf\",\n        \"power\": \"powf\",\n        \"exp\": \"expf\",\n        \"ln\": \"logf\",\n        \"erf\": \"erff\",\n        \"atan_2\": \"atan2f\",\n        \"min_value\": \"fminf\",\n        \"max_value\": \"fmaxf\",\n        \"bessel_y\": \"yn\",\n        \"bessel_j\": \"jn\",\n    },\n    \"longdouble\": {\n        \"sqrt\": \"sqrtl\",\n        \"abs\": \"fabsl\",\n        \"cos\": \"cosl\",\n        \"sin\": \"sinl\",\n        \"tan\": \"tanl\",\n        \"acos\": \"acosl\",\n        \"asin\": \"asinl\",\n        \"atan\": \"atanl\",\n        \"cosh\": \"coshl\",\n        \"sinh\": \"sinhl\",\n        \"tanh\": \"tanhl\",\n        \"acosh\": \"acoshl\",\n        \"asinh\": \"asinhl\",\n        \"atanh\": \"atanhl\",\n        \"power\": \"powl\",\n        \"exp\": \"expl\",\n        \"ln\": \"logl\",\n        \"erf\": \"erfl\",\n        \"atan_2\": \"atan2l\",\n        \"min_value\": \"fminl\",\n        \"max_value\": \"fmaxl\",\n    },\n    \"complex128\": {\n        \"sqrt\": \"csqrt\",\n        \"abs\": \"cabs\",\n        \"cos\": \"ccos\",\n        \"sin\": \"csin\",\n        \"tan\": \"ctan\",\n        \"acos\": \"cacos\",\n        \"asin\": \"casin\",\n        \"atan\": \"catan\",\n        \"cosh\": \"ccosh\",\n        \"sinh\": \"csinh\",\n        \"tanh\": \"ctanh\",\n        \"acosh\": \"cacosh\",\n        \"asinh\": \"casinh\",\n        \"atanh\": \"catanh\",\n        \"power\": \"cpow\",\n        \"exp\": \"cexp\",\n        \"ln\": \"clog\",\n        \"real\": \"creal\",\n        \"imag\": \"cimag\",\n        \"conj\": \"conj\",\n        \"max_value\": \"fmax\",\n        \"min_value\": \"fmin\",\n        \"bessel_y\": \"yn\",\n        \"bessel_j\": \"jn\",\n    },\n    \"complex64\": {\n        \"sqrt\": \"csqrtf\",\n        \"abs\": \"cabsf\",\n        \"cos\": \"ccosf\",\n        \"sin\": \"csinf\",\n        \"tan\": \"ctanf\",\n        \"acos\": \"cacosf\",\n        \"asin\": \"casinf\",\n        \"atan\": \"catanf\",\n        \"cosh\": \"ccoshf\",\n        \"sinh\": \"csinhf\",\n        \"tanh\": \"ctanhf\",\n        \"acosh\": \"cacoshf\",\n        \"asinh\": \"casinhf\",\n        \"atanh\": \"catanhf\",\n        \"power\": \"cpowf\",\n        \"exp\": \"cexpf\",\n        \"ln\": \"clogf\",\n        \"real\": \"crealf\",\n        \"imag\": \"cimagf\",\n        \"conj\": \"conjf\",\n        \"max_value\": \"fmaxf\",\n        \"min_value\": \"fminf\",\n        \"bessel_y\": \"yn\",\n        \"bessel_j\": \"jn\",\n    },\n}\n\n\nclass Formatter(FormatterInterface):\n    \"\"\"C formatter.\"\"\"\n\n    scalar_type: np.dtype\n    real_type: np.dtype\n\n    def __init__(self, dtype: npt.DTypeLike) -> None:\n        \"\"\"Initialise.\"\"\"\n        self.scalar_type = np.dtype(dtype)\n        self.real_type = dtype_to_scalar_dtype(dtype)\n\n    def _dtype_to_name(self, dtype) -> str:\n        \"\"\"Convert dtype to C name.\"\"\"\n        if dtype == L.DataType.SCALAR:\n            return dtype_to_c_type(self.scalar_type)\n        if dtype == L.DataType.REAL:\n            return dtype_to_c_type(self.real_type)\n        if dtype == L.DataType.INT:\n            return \"int\"\n        if dtype == L.DataType.BOOL:\n            return \"bool\"\n        raise ValueError(f\"Invalid dtype: {dtype}\")\n\n    def _format_number(self, x):\n        \"\"\"Format a number.\"\"\"\n        # Use 16sf for precision (good for float64 or less)\n        if isinstance(x, complex):\n            return f\"({x.real:.16}+I*{x.imag:.16})\"\n        elif isinstance(x, float):\n            return f\"{x:.16}\"\n        return str(x)\n\n    def _build_initializer_lists(self, values):\n        \"\"\"Build initializer lists.\"\"\"\n        arr = \"{\"\n        if len(values.shape) == 1:\n            arr += \", \".join(self._format_number(v) for v in values)\n        elif len(values.shape) > 1:\n            arr += \",\\n  \".join(self._build_initializer_lists(v) for v in values)\n        arr += \"}\"\n        return arr\n\n    @singledispatchmethod\n    def __call__(self, obj: L.LNode) -> str:\n        \"\"\"Format an L Node.\"\"\"\n        raise NotImplementedError(f\"Can not format object to type {type(obj)}\")\n\n    @__call__.register\n    def _(self, slist: L.StatementList) -> str:\n        \"\"\"Format a statement list.\"\"\"\n        return \"\".join(self(s) for s in slist.statements)\n\n    @__call__.register\n    def _(self, section: L.Section) -> str:\n        \"\"\"Format a section.\"\"\"\n        # add new line before section\n        comments = (\n            f\"// ------------------------ \\n\"\n            f\"// Section: {section.name}\\n\"\n            f\"// Inputs: {', '.join(w.name for w in section.input)}\\n\"\n            f\"// Outputs: {', '.join(w.name for w in section.output)}\\n\"\n        )\n        declarations = \"\".join(self(s) for s in section.declarations)\n\n        body = \"\"\n        if len(section.statements) > 0:\n            declarations += \"{\\n  \"\n            body = \"\".join(self(s) for s in section.statements)\n            body = body.replace(\"\\n\", \"\\n  \")\n            body = body[:-2] + \"}\\n\"\n\n        body += \"// ------------------------ \\n\"\n        return str(comments + declarations + body)\n\n    @__call__.register\n    def _(self, c: L.Comment) -> str:\n        \"\"\"Format a comment.\"\"\"\n        return f\"// {c.comment}\\n\"\n\n    @__call__.register\n    def _(self, arr: L.ArrayDecl) -> str:\n        \"\"\"Format an array declaration.\"\"\"\n        dtype = arr.symbol.dtype\n        typename = self._dtype_to_name(dtype)\n\n        symbol = self(arr.symbol)\n        dims = \"\".join([f\"[{i}]\" for i in arr.sizes])\n        if arr.values is None:\n            assert arr.const is False\n            return f\"{typename} {symbol}{dims};\\n\"\n\n        vals = self._build_initializer_lists(arr.values)\n        cstr = \"static const \" if arr.const else \"\"\n        return f\"{cstr}{typename} {symbol}{dims} = {vals};\\n\"\n\n    @__call__.register\n    def _(self, arr: L.ArrayAccess) -> str:\n        \"\"\"Format an array access.\"\"\"\n        name = self(arr.array)\n        indices = f\"[{']['.join(self(i) for i in arr.indices)}]\"\n        return f\"{name}{indices}\"\n\n    @__call__.register\n    def _(self, v: L.VariableDecl) -> str:\n        \"\"\"Format a variable declaration.\"\"\"\n        val = self(v.value)\n        symbol = self(v.symbol)\n        typename = self._dtype_to_name(v.symbol.dtype)\n        return f\"{typename} {symbol} = {val};\\n\"\n\n    @__call__.register\n    def _(self, oper: L.NaryOp) -> str:\n        \"\"\"Format an n-ary operation.\"\"\"\n        # Format children\n        args = [self(arg) for arg in oper.args]\n\n        # Apply parentheses\n        for i in range(len(args)):\n            if oper.args[i].precedence >= oper.precedence:\n                args[i] = \"(\" + args[i] + \")\"\n\n        # Return combined string\n        return f\" {oper.op} \".join(args)\n\n    @__call__.register\n    def _(self, oper: L.BinOp) -> str:\n        \"\"\"Format a binary operation.\"\"\"\n        # Format children\n        lhs = self(oper.lhs)\n        rhs = self(oper.rhs)\n\n        # Apply parentheses\n        if oper.lhs.precedence >= oper.precedence:\n            lhs = f\"({lhs})\"\n        if oper.rhs.precedence >= oper.precedence:\n            rhs = f\"({rhs})\"\n\n        # Return combined string\n        return f\"{lhs} {oper.op} {rhs}\"\n\n    @__call__.register(L.Neg)\n    @__call__.register(L.Not)\n    def _(self, oper: L.Neg | L.Not) -> str:\n        \"\"\"Format a unary operation.\"\"\"\n        arg = self(oper.arg)\n        if oper.arg.precedence >= oper.precedence:\n            return f\"{oper.op}({arg})\"\n        return f\"{oper.op}{arg}\"\n\n    @__call__.register\n    def _(self, val: L.LiteralFloat) -> str:\n        \"\"\"Format a literal float.\"\"\"\n        value = self._format_number(val.value)\n        return f\"{value}\"\n\n    @__call__.register\n    def _(self, val: L.LiteralInt) -> str:\n        \"\"\"Format a literal int.\"\"\"\n        return f\"{val.value}\"\n\n    @__call__.register\n    def _(self, r: L.ForRange) -> str:\n        \"\"\"Format a for loop over a range.\"\"\"\n        begin = self(r.begin)\n        end = self(r.end)\n        index = self(r.index)\n        output = f\"for (int {index} = {begin}; {index} < {end}; ++{index})\\n\"\n        output += \"{\\n\"\n        body = self(r.body)\n        for line in body.split(\"\\n\"):\n            if len(line) > 0:\n                output += f\"  {line}\\n\"\n        output += \"}\\n\"\n        return output\n\n    @__call__.register\n    def _(self, s: L.Statement) -> str:\n        \"\"\"Format a statement.\"\"\"\n        return self(s.expr)\n\n    @__call__.register(L.Assign)\n    @__call__.register(L.AssignAdd)\n    def _(self, expr: L.Assign | L.AssignAdd) -> str:\n        \"\"\"Format an assignment.\"\"\"\n        rhs = self(expr.rhs)\n        lhs = self(expr.lhs)\n        return f\"{lhs} {expr.op} {rhs};\\n\"\n\n    @__call__.register\n    def _(self, s: L.Conditional) -> str:\n        \"\"\"Format a conditional.\"\"\"\n        # Format children\n        c = self(s.condition)\n        t = self(s.true)\n        f = self(s.false)\n\n        # Apply parentheses\n        if s.condition.precedence >= s.precedence:\n            c = \"(\" + c + \")\"\n        if s.true.precedence >= s.precedence:\n            t = \"(\" + t + \")\"\n        if s.false.precedence >= s.precedence:\n            f = \"(\" + f + \")\"\n\n        # Return combined string\n        return c + \" ? \" + t + \" : \" + f\n\n    @__call__.register\n    def _(self, s: L.Symbol) -> str:\n        \"\"\"Format a symbol.\"\"\"\n        return f\"{s.name}\"\n\n    @__call__.register\n    def _(self, mi: L.MultiIndex) -> str:\n        \"\"\"Format a multi-index.\"\"\"\n        return self(mi.global_index)\n\n    @__call__.register\n    def _(self, c: L.MathFunction) -> str:\n        \"\"\"Format a mathematical function.\"\"\"\n        # Get a table of functions for this type, if available\n        arg_type = self.scalar_type\n        if hasattr(c.args[0], \"dtype\"):\n            if c.args[0].dtype == L.DataType.REAL:\n                arg_type = self.real_type\n        else:\n            warnings.warn(f\"Syntax item without dtype {c.args[0]}\")\n\n        dtype_math_table = math_table[arg_type.name]\n\n        # Get a function from the table, if available, else just use bare name\n        func = dtype_math_table.get(c.function, c.function)\n        args = \", \".join(self(arg) for arg in c.args)\n        return f\"{func}({args})\"\n"
  },
  {
    "path": "ffcx/codegeneration/C/integral.py",
    "content": "# Copyright (C) 2015-2021 Martin Sandve Alnæs, Michal Habera, Igor Baratta\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Generate UFCx code for an integral.\"\"\"\n\nimport logging\nimport sys\n\nimport basix\nimport numpy as np\nfrom numpy import typing as npt\n\nfrom ffcx.codegeneration.backend import FFCXBackend\nfrom ffcx.codegeneration.C import integral_template as ufcx_integrals\nfrom ffcx.codegeneration.C.formatter import Formatter\nfrom ffcx.codegeneration.integral_generator import IntegralGenerator\nfrom ffcx.codegeneration.utils import dtype_to_c_type, dtype_to_scalar_dtype\nfrom ffcx.ir.representation import IntegralIR\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef generator(\n    ir: IntegralIR, domain: basix.CellType, options: dict[str, int | float | npt.DTypeLike]\n) -> tuple[str, str]:\n    \"\"\"Generate C code for an integral.\n\n    Args:\n        ir: IR of the integral\n        domain: basix cell type\n        options: dict of kernel generation options\n\n    Returns:\n        Tuple of declaration (header) and implementation (source) strings.\n\n    \"\"\"\n    logger.info(\"Generating code for integral:\")\n    logger.info(f\"--- type: {ir.expression.integral_type}\")\n    logger.info(f\"--- name: {ir.expression.name}\")\n\n    factory_name = f\"{ir.expression.name}_{domain.name}\"\n\n    # Format declaration\n    declaration = ufcx_integrals.declaration.format(factory_name=factory_name)\n\n    # Create FFCx backend\n    backend = FFCXBackend(ir, options)\n\n    # Configure kernel generator\n    ig = IntegralGenerator(ir, backend)\n\n    # Generate code ast for the tabulate_tensor body\n    parts = ig.generate(domain)\n\n    # Format code as string\n    format = Formatter(options[\"scalar_type\"])  # type: ignore\n    body = format(parts)\n\n    # Generate generic FFCx code snippets and add specific parts\n    code = {}\n\n    if len(ir.enabled_coefficients) > 0:\n        values = \", \".join(\"1\" if i else \"0\" for i in ir.enabled_coefficients)\n        sizes = len(ir.enabled_coefficients)\n        code[\"enabled_coefficients_init\"] = (\n            f\"bool enabled_coefficients_{ir.expression.name}_{domain.name}[{sizes}] = {{{values}}};\"\n        )\n        code[\"enabled_coefficients\"] = f\"enabled_coefficients_{ir.expression.name}_{domain.name}\"\n    else:\n        code[\"enabled_coefficients_init\"] = \"\"\n        code[\"enabled_coefficients\"] = \"NULL\"\n\n    code[\"tabulate_tensor\"] = body\n\n    code[\"tabulate_tensor_float32\"] = \".tabulate_tensor_float32 = NULL,\"\n    code[\"tabulate_tensor_float64\"] = \".tabulate_tensor_float64 = NULL,\"\n    if sys.platform.startswith(\"win32\"):\n        code[\"tabulate_tensor_complex64\"] = \"\"\n        code[\"tabulate_tensor_complex128\"] = \"\"\n    else:\n        code[\"tabulate_tensor_complex64\"] = \".tabulate_tensor_complex64 = NULL,\"\n        code[\"tabulate_tensor_complex128\"] = \".tabulate_tensor_complex128 = NULL,\"\n    np_scalar_type = np.dtype(options[\"scalar_type\"]).name  # type: ignore\n    code[f\"tabulate_tensor_{np_scalar_type}\"] = (\n        f\".tabulate_tensor_{np_scalar_type} = tabulate_tensor_{factory_name},\"\n    )\n\n    assert ir.expression.coordinate_element_hash is not None\n    implementation = ufcx_integrals.factory.format(\n        factory_name=factory_name,\n        enabled_coefficients=code[\"enabled_coefficients\"],\n        enabled_coefficients_init=code[\"enabled_coefficients_init\"],\n        tabulate_tensor=code[\"tabulate_tensor\"],\n        needs_facet_permutations=\"true\" if ir.expression.needs_facet_permutations else \"false\",\n        scalar_type=dtype_to_c_type(options[\"scalar_type\"]),  # type: ignore\n        geom_type=dtype_to_c_type(dtype_to_scalar_dtype(options[\"scalar_type\"])),  # type: ignore\n        coordinate_element_hash=f\"UINT64_C({ir.expression.coordinate_element_hash})\",\n        tabulate_tensor_float32=code[\"tabulate_tensor_float32\"],\n        tabulate_tensor_float64=code[\"tabulate_tensor_float64\"],\n        tabulate_tensor_complex64=code[\"tabulate_tensor_complex64\"],\n        tabulate_tensor_complex128=code[\"tabulate_tensor_complex128\"],\n        domain=int(domain),\n    )\n\n    # TODO: Check that no keys are redundant or have been missed (ref. numba/integrals.py)\n    # assert set(d.keys()) == template_keys(ufcx_integrals.factory)\n\n    return declaration, implementation\n"
  },
  {
    "path": "ffcx/codegeneration/C/integral_template.py",
    "content": "# Code generation format strings for UFCx (Unified Form-assembly Code)\n# This code is released into the public domain.\n#\n# The FEniCS Project (http://www.fenicsproject.org/) 2018\n\"\"\"Code generation strings for an integral.\"\"\"\n\ndeclaration = \"\"\"\nextern ufcx_integral {factory_name};\n\"\"\"\n\nfactory = \"\"\"\n// Code for integral {factory_name}\n\nvoid tabulate_tensor_{factory_name}({scalar_type}* restrict A,\n                                    const {scalar_type}* restrict w,\n                                    const {scalar_type}* restrict c,\n                                    const {geom_type}* restrict coordinate_dofs,\n                                    const int* restrict entity_local_index,\n                                    const uint8_t* restrict quadrature_permutation,\n                                    void* custom_data)\n{{\n{tabulate_tensor}\n}}\n\n{enabled_coefficients_init}\n\nufcx_integral {factory_name} =\n{{\n  .enabled_coefficients = {enabled_coefficients},\n  {tabulate_tensor_float32}\n  {tabulate_tensor_float64}\n  {tabulate_tensor_complex64}\n  {tabulate_tensor_complex128}\n  .needs_facet_permutations = {needs_facet_permutations},\n  .coordinate_element_hash = {coordinate_element_hash},\n  .domain = {domain},\n}};\n\n// End of code for integral {factory_name}\n\"\"\"\n"
  },
  {
    "path": "ffcx/codegeneration/__init__.py",
    "content": "\"\"\"FFCx code generation.\"\"\"\n\nimport hashlib\nimport os\n\n# Version of FFCx header files\n__author__ = \"FEniCS Project\"\n__license__ = \"This code is released into the public domain\"\n__version__ = \"2018.2.0.dev0\"\n\n# Get abspath on import, it can in some cases be a relative path w.r.t.\n# curdir on startup\n_include_path = os.path.dirname(os.path.abspath(__file__))\n\n\ndef get_include_path():\n    \"\"\"Return location of UFCx header files.\"\"\"\n    return _include_path\n\n\ndef _compute_signature():\n    \"\"\"Compute signature of UFCx header files.\"\"\"\n    h = hashlib.sha1()\n    with open(os.path.join(get_include_path(), \"ufcx.h\")) as f:\n        h.update(f.read().encode(\"utf-8\"))\n    return h.hexdigest()\n\n\n_signature = _compute_signature()\n\n\ndef get_signature():\n    \"\"\"Return SHA-1 hash of the contents of ufcx.h.\n\n    In this implementation, the value is computed on import.\n    \"\"\"\n    return _signature\n"
  },
  {
    "path": "ffcx/codegeneration/access.py",
    "content": "# Copyright (C) 2011-2017 Martin Sandve Alnæs\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"FFCx/UFCx specific variable access.\"\"\"\n\nimport logging\nimport warnings\n\nimport basix.ufl\nimport ufl\n\nimport ffcx.codegeneration.lnodes as L\nfrom ffcx.definitions import entity_types\nfrom ffcx.ir.analysis.modified_terminals import ModifiedTerminal\nfrom ffcx.ir.elementtables import UniqueTableReferenceT\nfrom ffcx.ir.representationutils import QuadratureRule\n\nlogger = logging.getLogger(\"ffcx\")\n\n\nclass FFCXBackendAccess:\n    \"\"\"FFCx specific formatter class.\"\"\"\n\n    entity_type: entity_types\n\n    def __init__(self, entity_type: entity_types, integral_type: str, symbols, options):\n        \"\"\"Initialise.\"\"\"\n        # Store ir and options\n        self.entity_type = entity_type\n        self.integral_type = integral_type\n        self.symbols = symbols\n        self.options = options\n\n        # Lookup table for handler to call when the \"get\" method (below)\n        # is called, depending on the first argument type.\n        self.call_lookup = {\n            ufl.coefficient.Coefficient: self.coefficient,\n            ufl.constant.Constant: self.constant,\n            ufl.geometry.Jacobian: self.jacobian,\n            ufl.geometry.CellCoordinate: self.cell_coordinate,\n            ufl.geometry.FacetCoordinate: self.facet_coordinate,\n            ufl.geometry.CellVertices: self.cell_vertices,\n            ufl.geometry.FacetEdgeVectors: self.facet_edge_vectors,\n            ufl.geometry.CellEdgeVectors: self.cell_edge_vectors,\n            ufl.geometry.CellFacetJacobian: self.cell_facet_jacobian,\n            ufl.geometry.CellRidgeJacobian: self.cell_ridge_jacobian,\n            ufl.geometry.ReferenceCellVolume: self.reference_cell_volume,\n            ufl.geometry.ReferenceFacetVolume: self.reference_facet_volume,\n            ufl.geometry.ReferenceCellEdgeVectors: self.reference_cell_edge_vectors,\n            ufl.geometry.ReferenceFacetEdgeVectors: self.reference_facet_edge_vectors,\n            ufl.geometry.ReferenceNormal: self.reference_normal,\n            ufl.geometry.CellOrientation: self._pass,\n            ufl.geometry.FacetOrientation: self.facet_orientation,\n            ufl.geometry.SpatialCoordinate: self.spatial_coordinate,\n        }\n\n    def get(\n        self,\n        mt: ModifiedTerminal,\n        tabledata: UniqueTableReferenceT,\n        quadrature_rule: QuadratureRule,\n    ):\n        \"\"\"Format a terminal.\"\"\"\n        e = mt.terminal\n        # Call appropriate handler, depending on the type of e\n        handler = self.call_lookup.get(type(e), False)  # type: ignore\n\n        if not handler:\n            # Look for parent class types instead\n            for k in self.call_lookup.keys():\n                if isinstance(e, k):\n                    handler = self.call_lookup[k]\n                    break\n        if handler:\n            return handler(mt, tabledata, quadrature_rule)\n        else:\n            raise RuntimeError(f\"Not handled: {type(e)}\")\n\n    def coefficient(\n        self,\n        mt: ModifiedTerminal,\n        tabledata: UniqueTableReferenceT,\n        quadrature_rule: QuadratureRule,\n    ):\n        \"\"\"Access a coefficient.\"\"\"\n        ttype = tabledata.ttype\n        assert ttype != \"zeros\"\n\n        num_dofs = tabledata.values.shape[3]\n        begin = tabledata.offset\n        assert begin is not None\n        assert tabledata.block_size is not None\n        end = begin + tabledata.block_size * (num_dofs - 1) + 1\n\n        if ttype == \"ones\" and (end - begin) == 1:\n            # f = 1.0 * f_{begin}, just return direct reference to dof\n            # array at dof begin (if mt is restricted, begin contains\n            # cell offset)\n            return self.symbols.coefficient_dof_access(mt.terminal, begin)\n        else:\n            # Return symbol, see definitions for computation\n            return self.symbols.coefficient_value(mt)\n\n    def constant(\n        self,\n        mt: ModifiedTerminal,\n        tabledata: UniqueTableReferenceT | None,\n        quadrature_rule: QuadratureRule | None,\n    ):\n        \"\"\"Access a constant.\"\"\"\n        # Access to a constant is handled trivially, directly through\n        # constants symbol\n        return self.symbols.constant_index_access(mt.terminal, mt.flat_component)\n\n    def spatial_coordinate(\n        self, mt: ModifiedTerminal, tabledata: UniqueTableReferenceT, num_points: QuadratureRule\n    ):\n        \"\"\"Access a spatial coordinate.\"\"\"\n        if mt.global_derivatives:\n            raise RuntimeError(\"Not expecting global derivatives of SpatialCoordinate.\")\n        if mt.averaged is not None:\n            raise RuntimeError(\"Not expecting average of SpatialCoordinates.\")\n\n        if self.integral_type in ufl.custom_integral_types:\n            if mt.local_derivatives:\n                raise RuntimeError(\"FIXME: Jacobian in custom integrals is not implemented.\")\n\n            # Access predefined quadrature points table\n            x = self.symbols.custom_points_table\n            iq = self.symbols.quadrature_loop_index\n            (gdim,) = mt.terminal.ufl_shape\n            if gdim == 1:\n                index = iq\n            else:\n                index = iq * gdim + mt.flat_component\n            return x[index]\n        elif self.integral_type == \"expression\":\n            # Physical coordinates are computed by code generated in\n            # definitions\n            return self.symbols.x_component(mt)\n        else:\n            # Physical coordinates are computed by code generated in\n            # definitions\n            return self.symbols.x_component(mt)\n\n    def cell_coordinate(self, mt, tabledata, num_points):\n        \"\"\"Access a cell coordinate.\"\"\"\n        if mt.global_derivatives:\n            raise RuntimeError(\"Not expecting derivatives of CellCoordinate.\")\n        if mt.local_derivatives:\n            raise RuntimeError(\"Not expecting derivatives of CellCoordinate.\")\n        if mt.averaged is not None:\n            raise RuntimeError(\"Not expecting average of CellCoordinate.\")\n\n        if self.integral_type == \"cell\" and not mt.restriction:\n            # Access predefined quadrature points table\n            X = self.symbols.points_table(num_points)\n            (tdim,) = mt.terminal.ufl_shape\n            iq = self.symbols.quadrature_loop_index()\n            if num_points == 1:\n                index = mt.flat_component\n            elif tdim == 1:\n                index = iq\n            else:\n                index = iq * tdim + mt.flat_component\n            return X[index]\n        else:\n            # X should be computed from x or Xf symbolically instead of\n            # getting here\n            raise RuntimeError(\"Expecting reference cell coordinate to be symbolically rewritten.\")\n\n    def facet_coordinate(self, mt, tabledata, num_points):\n        \"\"\"Access a facet coordinate.\"\"\"\n        if mt.global_derivatives:\n            raise RuntimeError(\"Not expecting derivatives of FacetCoordinate.\")\n        if mt.local_derivatives:\n            raise RuntimeError(\"Not expecting derivatives of FacetCoordinate.\")\n        if mt.averaged is not None:\n            raise RuntimeError(\"Not expecting average of FacetCoordinate.\")\n        if mt.restriction:\n            raise RuntimeError(\"Not expecting restriction of FacetCoordinate.\")\n\n        if self.integral_type in (\"interior_facet\", \"exterior_facet\"):\n            (tdim,) = mt.terminal.ufl_shape\n            if tdim == 0:\n                raise RuntimeError(\"Vertices have no facet coordinates.\")\n            elif tdim == 1:\n                warnings.warn(\n                    \"Vertex coordinate is always 0, should get rid of this in UFL \"\n                    \"geometry lowering.\"\n                )\n                return L.LiteralFloat(0.0)\n            Xf = self.points_table(num_points)\n            iq = self.symbols.quadrature_loop_index()\n            assert 0 <= mt.flat_component < (tdim - 1)\n            if num_points == 1:\n                index = mt.flat_component\n            elif tdim == 2:\n                index = iq\n            else:\n                index = iq * (tdim - 1) + mt.flat_component\n            return Xf[index]\n        else:\n            # Xf should be computed from X or x symbolically instead of\n            # getting here\n            raise RuntimeError(\"Expecting reference facet coordinate to be symbolically rewritten.\")\n\n    def jacobian(self, mt, tabledata, num_points):\n        \"\"\"Access a jacobian.\"\"\"\n        if mt.averaged is not None:\n            raise RuntimeError(\"Not expecting average of Jacobian.\")\n        return self.symbols.J_component(mt)\n\n    def reference_cell_volume(self, mt, tabledata, access):\n        \"\"\"Access a reference cell volume.\"\"\"\n        cellname = ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n        if cellname in (\"interval\", \"triangle\", \"tetrahedron\", \"quadrilateral\", \"hexahedron\"):\n            return L.Symbol(f\"{cellname}_reference_cell_volume\", dtype=L.DataType.REAL)\n        else:\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n    def reference_facet_volume(self, mt, tabledata, access):\n        \"\"\"Access a reference facet volume.\"\"\"\n        cellname = ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n        if cellname in (\"interval\", \"triangle\", \"tetrahedron\", \"quadrilateral\", \"hexahedron\"):\n            return L.Symbol(f\"{cellname}_reference_facet_volume\", dtype=L.DataType.REAL)\n        else:\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n    def reference_normal(self, mt, tabledata, access):\n        \"\"\"Access a reference normal.\"\"\"\n        cellname = ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n        if cellname in (\"interval\", \"triangle\", \"tetrahedron\", \"quadrilateral\", \"hexahedron\"):\n            table = L.Symbol(f\"{cellname}_reference_normals\", dtype=L.DataType.REAL)\n            facet = self.symbols.entity(\"facet\", mt.restriction)\n            return table[facet][mt.component[0]]\n        else:\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n    def cell_facet_jacobian(self, mt, tabledata, num_points):\n        \"\"\"Access a cell facet jacobian.\"\"\"\n        cellname = ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n        if cellname in (\n            \"triangle\",\n            \"tetrahedron\",\n            \"quadrilateral\",\n            \"hexahedron\",\n            \"prism\",\n            \"pyramid\",\n        ):\n            table = L.Symbol(f\"{cellname}_cell_facet_jacobian\", dtype=L.DataType.REAL)\n            facet = self.symbols.entity(\"facet\", mt.restriction)\n            return table[facet][mt.component[0]][mt.component[1]]\n        elif cellname == \"interval\":\n            raise RuntimeError(\"The reference facet jacobian doesn't make sense for interval cell.\")\n        else:\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n    def cell_ridge_jacobian(self, mt, tabledata, num_points):\n        \"\"\"Access a cell ridge jacobian.\"\"\"\n        cellname = ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n        if cellname in (\"tetrahedron\", \"prism\", \"hexahedron\"):\n            table = L.Symbol(f\"{cellname}_cell_ridge_jacobian\", dtype=L.DataType.REAL)\n            ridge = self.symbols.entity(\"ridge\", mt.restriction)\n            return table[ridge][mt.component[0]][mt.component[1]]\n        elif cellname in [\"triangle\", \"quadrilateral\"]:\n            raise RuntimeError(\"The ridge jacobian doesn't make sense for 2D cells.\")\n        else:\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n    def reference_cell_edge_vectors(self, mt, tabledata, num_points):\n        \"\"\"Access a reference cell edge vector.\"\"\"\n        cellname = ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n        if cellname in (\"triangle\", \"tetrahedron\", \"quadrilateral\", \"hexahedron\"):\n            table = L.Symbol(f\"{cellname}_reference_cell_edge_vectors\", dtype=L.DataType.REAL)\n            return table[mt.component[0]][mt.component[1]]\n        elif cellname == \"interval\":\n            raise RuntimeError(\n                \"The reference cell edge vectors doesn't make sense for interval cell.\"\n            )\n        else:\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n    def reference_facet_edge_vectors(self, mt, tabledata, num_points):\n        \"\"\"Access a reference facet edge vector.\"\"\"\n        cellname = ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n        if cellname in (\"tetrahedron\", \"hexahedron\"):\n            table = L.Symbol(f\"{cellname}_reference_facet_edge_vectors\", dtype=L.DataType.REAL)\n            return table[mt.component[0]][mt.component[1]]\n        elif cellname in (\"interval\", \"triangle\", \"quadrilateral\"):\n            raise RuntimeError(\n                \"The reference cell facet edge vectors doesn't make sense for interval \"\n                \"or triangle cell.\"\n            )\n        else:\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n    def facet_orientation(self, mt, tabledata, num_points):\n        \"\"\"Access a facet orientation.\"\"\"\n        cellname = ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n        if cellname not in (\"interval\", \"triangle\", \"tetrahedron\"):\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n        table = L.Symbol(f\"{cellname}_facet_orientation\", dtype=L.DataType.INT)\n        facet = self.symbols.entity(\"facet\", mt.restriction)\n        return table[facet]\n\n    def cell_vertices(self, mt, tabledata, num_points):\n        \"\"\"Access a cell vertex.\"\"\"\n        # Get properties of domain\n        domain = ufl.domain.extract_unique_domain(mt.terminal)\n        gdim = domain.geometric_dimension\n        coordinate_element = domain.ufl_coordinate_element()\n\n        # Get dimension and dofmap of scalar element\n        assert isinstance(coordinate_element, basix.ufl._BlockedElement)\n        assert coordinate_element.reference_value_shape == (gdim,)\n        (ufl_scalar_element,) = set(coordinate_element.sub_elements)\n        scalar_element = ufl_scalar_element\n        assert scalar_element.reference_value_size == 1 and scalar_element.block_size == 1\n\n        vertex_scalar_dofs = scalar_element.entity_dofs[0]\n        num_scalar_dofs = scalar_element.dim\n\n        # Get dof and component\n        (dof,) = vertex_scalar_dofs[mt.component[0]]\n        component = mt.component[1]\n\n        expr = self.symbols.domain_dof_access(dof, component, gdim, num_scalar_dofs, mt.restriction)\n        return expr\n\n    def cell_edge_vectors(self, mt, tabledata, num_points):\n        \"\"\"Access a cell edge vector.\"\"\"\n        # Get properties of domain\n        domain = ufl.domain.extract_unique_domain(mt.terminal)\n        cellname = domain.ufl_cell().cellname\n        gdim = domain.geometric_dimension\n        coordinate_element = domain.ufl_coordinate_element()\n\n        if cellname in (\"triangle\", \"tetrahedron\", \"quadrilateral\", \"hexahedron\"):\n            pass\n        elif cellname == \"interval\":\n            raise RuntimeError(\n                \"The physical cell edge vectors doesn't make sense for interval cell.\"\n            )\n        else:\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n        # Get dimension and dofmap of scalar element\n        assert isinstance(coordinate_element, basix.ufl._BlockedElement)\n        assert coordinate_element.reference_value_shape == (gdim,)\n        (ufl_scalar_element,) = set(coordinate_element.sub_elements)\n        scalar_element = ufl_scalar_element\n        assert scalar_element.reference_value_size == 1 and scalar_element.block_size == 1\n\n        vertex_scalar_dofs = scalar_element.entity_dofs[0]\n        num_scalar_dofs = scalar_element.dim\n\n        # Get edge vertices\n        edge = mt.component[0]\n        vertex0, vertex1 = scalar_element.reference_topology[1][edge]\n\n        # Get dofs and component\n        (dof0,) = vertex_scalar_dofs[vertex0]\n        (dof1,) = vertex_scalar_dofs[vertex1]\n        component = mt.component[1]\n\n        return self.symbols.domain_dof_access(\n            dof0, component, gdim, num_scalar_dofs, mt.restriction\n        ) - self.symbols.domain_dof_access(dof1, component, gdim, num_scalar_dofs, mt.restriction)\n\n    def facet_edge_vectors(self, mt, tabledata, num_points):\n        \"\"\"Access a facet edge vector.\"\"\"\n        # Get properties of domain\n        domain = ufl.domain.extract_unique_domain(mt.terminal)\n        cellname = domain.ufl_cell().cellname\n        gdim = domain.geometric_dimension\n        coordinate_element = domain.ufl_coordinate_element()\n\n        if cellname in (\"tetrahedron\", \"hexahedron\"):\n            pass\n        elif cellname in (\"interval\", \"triangle\", \"quadrilateral\"):\n            raise RuntimeError(\n                f\"The physical facet edge vectors doesn't make sense for {cellname} cell.\"\n            )\n        else:\n            raise RuntimeError(f\"Unhandled cell types {cellname}.\")\n\n        # Get dimension and dofmap of scalar element\n        assert isinstance(coordinate_element, basix.ufl._BlockedElement)\n        assert coordinate_element.reference_value_shape == (gdim,)\n        (ufl_scalar_element,) = set(coordinate_element.sub_elements)\n        scalar_element = ufl_scalar_element\n        assert scalar_element.reference_value_size == 1 and scalar_element.block_size == 1\n\n        scalar_element = ufl_scalar_element\n        num_scalar_dofs = scalar_element.dim\n\n        # Get edge vertices\n        facet = self.symbols.entity(\"facet\", mt.restriction)\n        facet_edge = mt.component[0]\n        facet_edge_vertices = L.Symbol(f\"{cellname}_facet_edge_vertices\", dtype=L.DataType.INT)\n        vertex0 = facet_edge_vertices[facet][facet_edge][0]\n        vertex1 = facet_edge_vertices[facet][facet_edge][1]\n\n        # Get dofs and component\n        component = mt.component[1]\n        assert coordinate_element.embedded_superdegree == 1, \"Assuming degree 1 element\"\n        dof0 = vertex0\n        dof1 = vertex1\n        expr = self.symbols.domain_dof_access(\n            dof0, component, gdim, num_scalar_dofs, mt.restriction\n        ) - self.symbols.domain_dof_access(dof1, component, gdim, num_scalar_dofs, mt.restriction)\n\n        return expr\n\n    def _pass(self, *args, **kwargs):\n        \"\"\"Return one.\"\"\"\n        return 1\n\n    def table_access(\n        self,\n        tabledata: UniqueTableReferenceT,\n        entity_type: entity_types,\n        restriction: str,\n        quadrature_index: L.MultiIndex,\n        dof_index: L.MultiIndex,\n    ):\n        \"\"\"Access element table for given entity, quadrature point, and dof index.\n\n        Args:\n            tabledata: Table data object\n            entity_type: Entity type\n            restriction: Restriction (\"+\", \"-\")\n            quadrature_index: Quadrature index\n            dof_index: Dof index\n        \"\"\"\n        entity = self.symbols.entity(entity_type, restriction)\n        iq_global_index = quadrature_index.global_index\n        ic_global_index = dof_index.global_index\n        qp = 0  # quadrature permutation\n\n        symbols = []\n        if tabledata.is_uniform:\n            entity = L.LiteralInt(0)\n\n        if tabledata.is_piecewise:\n            iq_global_index = L.LiteralInt(0)\n\n        # FIXME: Hopefully tabledata is not permuted when applying sum\n        # factorization\n        if tabledata.is_permuted:\n            qp = self.symbols.quadrature_permutation[0]\n            if restriction == \"-\":\n                qp = self.symbols.quadrature_permutation[1]\n\n        if dof_index.dim == 1 and quadrature_index.dim == 1:\n            symbols += [L.Symbol(tabledata.name, dtype=L.DataType.REAL)]\n            return self.symbols.element_tables[tabledata.name][qp][entity][iq_global_index][\n                ic_global_index\n            ], symbols\n        else:\n            FE = []\n            assert tabledata.tensor_factors is not None\n            for i in range(dof_index.dim):\n                factor = tabledata.tensor_factors[i]\n                iq_i = quadrature_index.local_index(i)\n                ic_i = dof_index.local_index(i)\n                table = self.symbols.element_tables[factor.name][qp][entity][iq_i][ic_i]\n                symbols += [L.Symbol(factor.name, dtype=L.DataType.REAL)]\n                FE.append(table)\n            return L.Product(FE), symbols\n"
  },
  {
    "path": "ffcx/codegeneration/backend.py",
    "content": "# Copyright (C) 2011-2017 Martin Sandve Alnæs\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Collection of FFCx specific pieces for the code generation phase.\"\"\"\n\nfrom __future__ import annotations\n\nfrom ffcx.codegeneration.access import FFCXBackendAccess\nfrom ffcx.codegeneration.definitions import FFCXBackendDefinitions\nfrom ffcx.codegeneration.symbols import FFCXBackendSymbols\nfrom ffcx.ir.representation import ExpressionIR, IntegralIR\n\n\nclass FFCXBackend:\n    \"\"\"Class collecting all aspects of the FFCx backend.\"\"\"\n\n    def __init__(self, ir: IntegralIR | ExpressionIR, options):\n        \"\"\"Initialise.\"\"\"\n        coefficient_numbering = ir.expression.coefficient_numbering\n        coefficient_offsets = ir.expression.coefficient_offsets\n\n        original_constant_offsets = ir.expression.original_constant_offsets\n\n        self.symbols = FFCXBackendSymbols(\n            coefficient_numbering, coefficient_offsets, original_constant_offsets\n        )\n        self.access = FFCXBackendAccess(\n            ir.expression.entity_type, ir.expression.integral_type, self.symbols, options\n        )\n        self.definitions = FFCXBackendDefinitions(\n            ir.expression.entity_type, ir.expression.integral_type, self.access, options\n        )\n"
  },
  {
    "path": "ffcx/codegeneration/codegeneration.py",
    "content": "# Copyright (C) 2009-2017 Anders Logg, Martin Sandve Alnæs, Marie E. Rognes,\n# Kristian B. Oelgaard, and others\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Compiler stage 4: Code generation.\n\nThis module implements the generation of C code for the body of each\nUFCx function from an intermediate representation (IR).\n\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport typing\nfrom importlib import import_module\n\nimport numpy.typing as npt\n\nfrom ffcx.ir.representation import DataIR\nfrom ffcx.options import get_language\n\nlogger = logging.getLogger(\"ffcx\")\n\n\nclass CodeBlocks(typing.NamedTuple):\n    \"\"\"Storage of code blocks of the form (declaration, implementation).\n\n    Blocks for integrals, forms and expressions, and start and end of\n    file output.\n    \"\"\"\n\n    file_pre: list[tuple[str, str]]\n    integrals: list[tuple[str, str]]\n    forms: list[tuple[str, str]]\n    expressions: list[tuple[str, str]]\n    file_post: list[tuple[str, str]]\n\n\ndef generate_code(\n    ir: DataIR, options: dict[str, int | float | npt.DTypeLike]\n) -> tuple[CodeBlocks, tuple[str, ...]]:\n    \"\"\"Generate code blocks from intermediate representation.\"\"\"\n    logger.info(79 * \"*\")\n    logger.info(\"Compiler stage 3: Generating code\")\n    logger.info(79 * \"*\")\n\n    mod = import_module(get_language(options))\n\n    integral_generator = mod.integral.generator\n    form_generator = mod.form.generator\n    expression_generator = mod.expression.generator\n    file_generator = mod.file.generator\n\n    code_integrals = [\n        integral_generator(integral_ir, domain, options)\n        for integral_ir in ir.integrals\n        for domain in set(i[0] for i in integral_ir.expression.integrand.keys())\n    ]\n    code_forms = [form_generator(form_ir, options) for form_ir in ir.forms]\n    code_expressions = [\n        expression_generator(expression_ir, options) for expression_ir in ir.expressions\n    ]\n    code_file_pre, code_file_post = file_generator(options)\n    return CodeBlocks(\n        file_pre=[code_file_pre],\n        integrals=code_integrals,\n        forms=code_forms,\n        expressions=code_expressions,\n        file_post=[code_file_post],\n    ), mod.file.suffixes\n"
  },
  {
    "path": "ffcx/codegeneration/common.py",
    "content": "# Copyright (C) 2025 Paul T. Kühner\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\n\"\"\"Common code for backend implemenations.\"\"\"\n\nimport string\nfrom functools import singledispatch\nfrom typing import NamedTuple\n\nimport basix\nimport numpy as np\n\nfrom ffcx.ir.representation import ExpressionIR, FormIR, IntegralIR\n\n\ndef template_keys(template: str) -> set[str]:\n    \"\"\"Set of expected data keys of a template.\"\"\"\n    return set(fname for _, fname, _, _ in string.Formatter().parse(template) if fname)\n\n\nclass IntegralData(NamedTuple):\n    \"\"\"Sorted integral data.\"\"\"\n\n    names: list[str]\n    ids: list[int]\n    offsets: list[int]\n    domains: list[list[basix.CellType]]\n\n\ndef integral_data(ir: FormIR) -> IntegralData:\n    \"\"\"Extracts sorted integral data from form.\"\"\"\n    names, ids, domains = [], [], []\n    offsets = [0]\n    # Note: the order of this list is defined by the enum ufcx_integral_type in ufcx.h\n    for itg_type in (\"cell\", \"exterior_facet\", \"interior_facet\", \"vertex\", \"ridge\"):\n        _ids = ir.subdomain_ids[itg_type]\n        id_sort = np.argsort(_ids)\n\n        ids += [_ids[i] for i in id_sort]\n        names += [ir.integral_names[itg_type][i] for i in id_sort]\n        domains += [ir.integral_domains[itg_type][i] for i in id_sort]\n\n        offsets.append(offsets[-1] + sum(len(d) for d in domains[offsets[-1] :]))\n\n    return IntegralData(names, ids, offsets, domains)\n\n\nclass KernelTensorSizes(NamedTuple):\n    \"\"\"Size information of kernel input data.\"\"\"\n\n    A: int\n    w: int\n    c: int\n    coords: int\n    local_index: int\n    permutation: int\n\n\n@singledispatch\ndef tensor_sizes(ir: IntegralIR | ExpressionIR) -> KernelTensorSizes:\n    \"\"\"Compute tensor sizes given IR type.\"\"\"\n    raise NotImplementedError\n\n\n@tensor_sizes.register\ndef _(ir: IntegralIR) -> KernelTensorSizes:\n    \"\"\"Compute tensor sizes of integral IR input data.\"\"\"\n    A = np.prod(ir.expression.tensor_shape, dtype=int)\n    w = sum(coeff.ufl_element().dim for coeff in ir.expression.coefficient_offsets.keys())\n    c = sum(\n        np.prod(constant.ufl_shape, dtype=int)\n        for constant in ir.expression.original_constant_offsets.keys()\n    )\n    coords = ir.expression.number_coordinate_dofs * 3\n    local_index = 2  # TODO: this is just an upper bound, harmful?\n    permutation = 2 if ir.expression.needs_facet_permutations else 0\n\n    return KernelTensorSizes(A, w, c, coords, local_index, permutation)\n\n\n@tensor_sizes.register\ndef _(ir: ExpressionIR) -> KernelTensorSizes:\n    \"\"\"Compute tensor sizes of expression IR input data.\"\"\"\n    num_points = next(iter(ir.expression.integrand))[1].points.shape[0]\n    num_components = np.prod(ir.expression.shape, dtype=np.int32)\n    num_argument_dofs = np.prod(ir.expression.tensor_shape, dtype=np.int32)\n    A = num_points * num_components * num_argument_dofs  # ref. ufcx.h\n    w = sum(coeff.ufl_element().dim for coeff in ir.expression.coefficient_offsets.keys())\n    c = sum(\n        np.prod(constant.ufl_shape, dtype=int)\n        for constant in ir.expression.original_constant_offsets.keys()\n    )\n    coords = ir.expression.number_coordinate_dofs * 3\n    local_index = 2  # TODO: this is just an upper bound, harmful?\n    permutation = 2 if ir.expression.needs_facet_permutations else 0\n\n    return KernelTensorSizes(A, w, c, coords, local_index, permutation)\n"
  },
  {
    "path": "ffcx/codegeneration/definitions.py",
    "content": "# Copyright (C) 2011-2023 Martin Sandve Alnæs, Igor A. Baratta\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"FFCx/UFCx specific variable definitions.\"\"\"\n\nimport logging\n\nimport ufl\n\nimport ffcx.codegeneration.lnodes as L\nfrom ffcx.definitions import entity_types\nfrom ffcx.ir.analysis.modified_terminals import ModifiedTerminal\nfrom ffcx.ir.elementtables import UniqueTableReferenceT\nfrom ffcx.ir.representationutils import QuadratureRule\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef create_quadrature_index(quadrature_rule, quadrature_index_symbol):\n    \"\"\"Create a multi index for the quadrature loop.\"\"\"\n    ranges = [0]\n    name = quadrature_index_symbol.name\n    indices = [L.Symbol(name, dtype=L.DataType.INT)]\n    if quadrature_rule:\n        ranges = [quadrature_rule.weights.size]\n        if quadrature_rule.has_tensor_factors:\n            dim = len(quadrature_rule.tensor_factors)\n            ranges = [factor[1].size for factor in quadrature_rule.tensor_factors]\n            indices = [L.Symbol(name + f\"{i}\", dtype=L.DataType.INT) for i in range(dim)]\n\n    return L.MultiIndex(indices, ranges)\n\n\ndef create_dof_index(tabledata, dof_index_symbol):\n    \"\"\"Create a multi index for the coefficient dofs.\"\"\"\n    name = dof_index_symbol.name\n    if tabledata.has_tensor_factorisation:\n        dim = len(tabledata.tensor_factors)\n        ranges = [factor.values.shape[-1] for factor in tabledata.tensor_factors]\n        indices = [L.Symbol(f\"{name}{i}\", dtype=L.DataType.INT) for i in range(dim)]\n    else:\n        ranges = [tabledata.values.shape[-1]]\n        indices = [L.Symbol(name, dtype=L.DataType.INT)]\n\n    return L.MultiIndex(indices, ranges)\n\n\nclass FFCXBackendDefinitions:\n    \"\"\"FFCx specific code definitions.\"\"\"\n\n    entity_type: entity_types\n\n    def __init__(self, entity_type: entity_types, integral_type: str, access, options):\n        \"\"\"Initialise.\"\"\"\n        # Store ir and options\n        self.integral_type = integral_type\n        self.entity_type = entity_type\n        self.access = access\n        self.options = options\n\n        # called, depending on the first argument type.\n        self.handler_lookup = {\n            ufl.coefficient.Coefficient: self.coefficient,\n            ufl.geometry.Jacobian: self._define_coordinate_dofs_lincomb,\n            ufl.geometry.SpatialCoordinate: self.spatial_coordinate,\n            ufl.constant.Constant: self.pass_through,\n            ufl.geometry.CellVertices: self.pass_through,\n            ufl.geometry.FacetEdgeVectors: self.pass_through,\n            ufl.geometry.CellEdgeVectors: self.pass_through,\n            ufl.geometry.CellFacetJacobian: self.pass_through,\n            ufl.geometry.CellRidgeJacobian: self.pass_through,\n            ufl.geometry.ReferenceCellVolume: self.pass_through,\n            ufl.geometry.ReferenceFacetVolume: self.pass_through,\n            ufl.geometry.ReferenceCellEdgeVectors: self.pass_through,\n            ufl.geometry.ReferenceFacetEdgeVectors: self.pass_through,\n            ufl.geometry.ReferenceNormal: self.pass_through,\n            ufl.geometry.CellOrientation: self.pass_through,\n            ufl.geometry.FacetOrientation: self.pass_through,\n        }\n\n    @property\n    def symbols(self):\n        \"\"\"Return formatter.\"\"\"\n        return self.access.symbols\n\n    def get(\n        self,\n        mt: ModifiedTerminal,\n        tabledata: UniqueTableReferenceT,\n        quadrature_rule: QuadratureRule,\n        access: L.Symbol,\n    ) -> L.Section | list:\n        \"\"\"Return definition code for a terminal.\"\"\"\n        # Call appropriate handler, depending on the type of terminal\n        terminal = mt.terminal\n        ttype = type(terminal)\n\n        # Look for parent class of ttype or direct handler\n        while ttype not in self.handler_lookup and ttype.__bases__:\n            ttype = ttype.__bases__[0]\n\n        # Get the handler from the lookup, or None if not found\n        handler = self.handler_lookup.get(ttype)  # type: ignore\n\n        if handler is None:\n            raise NotImplementedError(f\"No handler for terminal type: {ttype}\")\n\n        # Call the handler\n        return handler(mt, tabledata, quadrature_rule, access)  # type: ignore\n\n    def coefficient(\n        self,\n        mt: ModifiedTerminal,\n        tabledata: UniqueTableReferenceT,\n        quadrature_rule: QuadratureRule,\n        access: L.Symbol,\n    ) -> L.Section | list:\n        \"\"\"Return definition code for coefficients.\"\"\"\n        # For applying tensor product to coefficients, we need to know\n        # if the coefficient has a tensor factorisation and if the\n        # quadrature rule has a tensor factorisation. If both are true,\n        # we can apply the tensor product to the coefficient.\n\n        iq_symbol = self.symbols.quadrature_loop_index\n        ic_symbol = self.symbols.coefficient_dof_sum_index\n\n        iq = create_quadrature_index(quadrature_rule, iq_symbol)\n        ic = create_dof_index(tabledata, ic_symbol)\n\n        # Get properties of tables\n        ttype = tabledata.ttype\n        num_dofs = tabledata.values.shape[3]\n        bs = tabledata.block_size\n        begin = tabledata.offset\n        assert bs is not None\n        assert begin is not None\n        end = begin + bs * (num_dofs - 1) + 1\n\n        if ttype == \"zeros\":\n            logger.debug(\"Not expecting zero coefficients to get this far.\")\n            return []\n\n        # For a constant coefficient we reference the dofs directly, so\n        # no definition needed\n        if ttype == \"ones\" and end - begin == 1:\n            return []\n\n        assert begin < end\n\n        # Get access to element table\n        FE, tables = self.access.table_access(tabledata, self.entity_type, mt.restriction, iq, ic)\n        dof_access: L.ArrayAccess = self.symbols.coefficient_dof_access(\n            mt.terminal, (ic.global_index) * bs + begin\n        )\n\n        declaration: list[L.Declaration] = [L.VariableDecl(access, 0.0)]\n        body = [L.AssignAdd(access, dof_access * FE)]\n        code = [L.create_nested_for_loops([ic], body)]\n\n        name = type(mt.terminal).__name__\n        input = [dof_access.array, *tables]\n        output = [access]\n        annotations = [L.Annotation.fuse]\n\n        # assert input and output are Symbol objects\n        assert all(isinstance(i, L.Symbol) for i in input)\n        assert all(isinstance(o, L.Symbol) for o in output)\n\n        return L.Section(name, code, declaration, input, output, annotations)\n\n    def _define_coordinate_dofs_lincomb(\n        self,\n        mt: ModifiedTerminal,\n        tabledata: UniqueTableReferenceT,\n        quadrature_rule: QuadratureRule,\n        access: L.Symbol,\n    ) -> L.Section | list:\n        \"\"\"Define x or J as a linear combination of coordinate dofs with given table data.\"\"\"\n        # Get properties of domain\n        domain = ufl.domain.extract_unique_domain(mt.terminal)\n        assert isinstance(domain, ufl.Mesh)\n        coordinate_element = domain.ufl_coordinate_element()\n        num_scalar_dofs = coordinate_element._sub_element.dim\n\n        num_dofs = tabledata.values.shape[3]\n        begin = tabledata.offset\n\n        assert num_scalar_dofs == num_dofs\n\n        # Find table name\n        ttype = tabledata.ttype\n\n        assert ttype != \"zeros\"\n        assert ttype != \"ones\"\n\n        # Get access to element table\n        ic_symbol = self.symbols.coefficient_dof_sum_index\n        iq_symbol = self.symbols.quadrature_loop_index\n        ic = create_dof_index(tabledata, ic_symbol)\n        iq = create_quadrature_index(quadrature_rule, iq_symbol)\n        FE, tables = self.access.table_access(tabledata, self.entity_type, mt.restriction, iq, ic)\n\n        dof_access = L.Symbol(\"coordinate_dofs\", dtype=L.DataType.REAL)\n\n        # coordinate dofs is always 3d\n        dim = 3\n        offset = 0\n        if mt.restriction == \"-\":\n            offset = num_scalar_dofs * dim\n\n        code = []\n        declaration = [L.VariableDecl(access, 0.0)]\n        body = [L.AssignAdd(access, dof_access[ic.global_index * dim + begin + offset] * FE)]\n        code = [L.create_nested_for_loops([ic], body)]\n\n        name = type(mt.terminal).__name__\n        output = [access]\n        input = [dof_access, *tables]\n        annotations = [L.Annotation.fuse]\n\n        # assert input and output are Symbol objects\n        assert all(isinstance(i, L.Symbol) for i in input)\n        assert all(isinstance(o, L.Symbol) for o in output)\n\n        return L.Section(name, code, declaration, input, output, annotations)\n\n    def spatial_coordinate(\n        self,\n        mt: ModifiedTerminal,\n        tabledata: UniqueTableReferenceT,\n        quadrature_rule: QuadratureRule,\n        access: L.Symbol,\n    ) -> L.Section | list:\n        \"\"\"Return definition code for the physical spatial coordinates.\n\n        If physical coordinates are given:\n          No definition needed.\n\n        If reference coordinates are given:\n          x = sum_k xdof_k xphi_k(X)\n\n        If reference facet coordinates are given:\n          x = sum_k xdof_k xphi_k(Xf)\n        \"\"\"\n        if self.integral_type in ufl.custom_integral_types:\n            # FIXME: Jacobian may need adjustment for custom_integral_types\n            if mt.local_derivatives:\n                logger.exception(\"FIXME: Jacobian in custom integrals is not implemented.\")\n            return []\n        else:\n            return self._define_coordinate_dofs_lincomb(mt, tabledata, quadrature_rule, access)\n\n    def jacobian(\n        self,\n        mt: ModifiedTerminal,\n        tabledata: UniqueTableReferenceT,\n        quadrature_rule: QuadratureRule,\n        access: L.Symbol,\n    ) -> L.Section | list:\n        \"\"\"Return definition code for the Jacobian of x(X).\"\"\"\n        return self._define_coordinate_dofs_lincomb(mt, tabledata, quadrature_rule, access)\n\n    def pass_through(\n        self,\n        mt: ModifiedTerminal,\n        tabledata: UniqueTableReferenceT,\n        quadrature_rule: QuadratureRule,\n        access: L.Symbol,\n    ) -> L.Section | list:\n        \"\"\"Return definition code for pass through terminals.\"\"\"\n        return []\n"
  },
  {
    "path": "ffcx/codegeneration/expression_generator.py",
    "content": "# Copyright (C) 2019 Michal Habera\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Expression generator.\"\"\"\n\nimport collections\nimport logging\nfrom itertools import pairwise, product\nfrom typing import Any\n\nimport ufl\n\nimport ffcx.codegeneration.lnodes as L\nfrom ffcx.codegeneration import geometry\nfrom ffcx.codegeneration.backend import FFCXBackend\nfrom ffcx.codegeneration.lnodes import LNode\nfrom ffcx.ir.representation import ExpressionIR\n\nlogger = logging.getLogger(\"ffcx\")\n\n\nclass ExpressionGenerator:\n    \"\"\"Expression generator.\"\"\"\n\n    def __init__(self, ir: ExpressionIR, backend: FFCXBackend):\n        \"\"\"Initialise.\"\"\"\n        if len(list(ir.expression.integrand.keys())) != 1:\n            raise RuntimeError(\"Only one set of points allowed for expression evaluation\")\n\n        self.ir = ir\n        self.backend = backend\n        self.scope: dict[Any, LNode] = {}\n        self._ufl_names: set[Any] = set()\n        self.symbol_counters: collections.defaultdict[Any, int] = collections.defaultdict(int)\n        self.shared_symbols: dict[Any, Any] = {}\n        self.quadrature_rule = next(iter(self.ir.expression.integrand.keys()))\n\n    def generate(self):\n        \"\"\"Generate.\"\"\"\n        parts = []\n        parts += self.generate_element_tables()\n\n        # Generate the tables of geometry data that are needed\n        parts += self.generate_geometry_tables()\n        parts += self.generate_piecewise_partition()\n\n        all_preparts = []\n        all_quadparts = []\n\n        preparts, quadparts = self.generate_quadrature_loop()\n        all_preparts += preparts\n        all_quadparts += quadparts\n\n        # Collect parts before, during, and after quadrature loops\n        parts += all_preparts\n        parts += all_quadparts\n\n        return L.StatementList(parts)\n\n    def generate_geometry_tables(self):\n        \"\"\"Generate static tables of geometry data.\"\"\"\n        ufl_geometry = {\n            ufl.geometry.FacetEdgeVectors: \"facet_edge_vectors\",\n            ufl.geometry.CellFacetJacobian: \"cell_facet_jacobian\",\n            ufl.geometry.ReferenceCellVolume: \"reference_cell_volume\",\n            ufl.geometry.ReferenceFacetVolume: \"reference_facet_volume\",\n            ufl.geometry.ReferenceCellEdgeVectors: \"reference_cell_edge_vectors\",\n            ufl.geometry.ReferenceFacetEdgeVectors: \"reference_facet_edge_vectors\",\n            ufl.geometry.ReferenceNormal: \"reference_normals\",\n        }\n\n        cells: dict[Any, set[Any]] = {t: set() for t in ufl_geometry.keys()}  # type: ignore\n        for integrand in self.ir.expression.integrand.values():\n            for attr in integrand[\"factorization\"].nodes.values():\n                mt = attr.get(\"mt\")\n                if mt is not None:\n                    t = type(mt.terminal)\n                    if self.ir.expression.entity_type == \"cell\" and issubclass(\n                        t, ufl.geometry.GeometricFacetQuantity\n                    ):\n                        raise RuntimeError(f\"Expressions for cells do not support {t}.\")\n                    if t in ufl_geometry:\n                        cells[t].add(\n                            ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n                        )\n\n        parts = []\n        for i, cell_list in cells.items():\n            for c in cell_list:\n                parts.append(geometry.write_table(ufl_geometry[i], c))\n\n        return parts\n\n    def generate_element_tables(self):\n        \"\"\"Generate tables of FE basis evaluated at specified points.\"\"\"\n        parts = []\n\n        tables = self.ir.expression.unique_tables[self.quadrature_rule[0]]\n        table_names = sorted(tables)\n\n        for name in table_names:\n            table = tables[name]\n            symbol = L.Symbol(name, dtype=L.DataType.REAL)\n            self.backend.symbols.element_tables[name] = symbol\n            decl = L.ArrayDecl(symbol, sizes=table.shape, values=table, const=True)\n            parts += [decl]\n\n        # Add leading comment if there are any tables\n        parts = L.commented_code_list(\n            parts,\n            [\n                \"Precomputed values of basis functions\",\n                \"FE* dimensions: [entities][points][dofs]\",\n            ],\n        )\n        return parts\n\n    def generate_quadrature_loop(self):\n        \"\"\"Generate quadrature loop for this quadrature rule.\n\n        In the context of expressions quadrature loop is not accumulated.\n        \"\"\"\n        # Generate varying partition\n        body = self.generate_varying_partition()\n        body = L.commented_code_list(\n            body, f\"Points loop body setup quadrature loop {self.quadrature_rule[1].id()}\"\n        )\n\n        # Generate dofblock parts, some of this\n        # will be placed before or after quadloop\n        preparts, quadparts = self.generate_dofblock_partition()\n        body += quadparts\n\n        # Wrap body in loop or scope\n        if not body:\n            # Could happen for integral with everything zero and optimized away\n            quadparts = []\n        else:\n            iq = self.backend.symbols.quadrature_loop_index\n            num_points = self.quadrature_rule[1].points.shape[0]\n            quadparts = [L.ForRange(iq, 0, num_points, body=body)]\n        return preparts, quadparts\n\n    def generate_varying_partition(self):\n        \"\"\"Generate factors of blocks which are not cellwise constant.\"\"\"\n        # Get annotated graph of factorisation\n        F = self.ir.expression.integrand[self.quadrature_rule][\"factorization\"]\n\n        arraysymbol = L.Symbol(f\"sv_{self.quadrature_rule[1].id()}\", dtype=L.DataType.SCALAR)\n        parts = self.generate_partition(arraysymbol, F, \"varying\")\n        parts = L.commented_code_list(\n            parts,\n            f\"Unstructured varying computations for quadrature rule {self.quadrature_rule[1].id()}\",\n        )\n        return parts\n\n    def generate_piecewise_partition(self):\n        \"\"\"Generate factors of blocks which are constant.\n\n        I.e. do not depend on quadrature points).\n        \"\"\"\n        # Get annotated graph of factorisation\n        F = self.ir.expression.integrand[self.quadrature_rule][\"factorization\"]\n\n        arraysymbol = L.Symbol(\"sp\", dtype=L.DataType.SCALAR)\n        parts = self.generate_partition(arraysymbol, F, \"piecewise\")\n        parts = L.commented_code_list(parts, \"Unstructured piecewise computations\")\n        return parts\n\n    def generate_dofblock_partition(self):\n        \"\"\"Generate assignments of blocks multiplied with their factors into final tensor A.\"\"\"\n        block_contributions = self.ir.expression.integrand[self.quadrature_rule][\n            \"block_contributions\"\n        ]\n\n        preparts = []\n        quadparts = []\n\n        blocks = [\n            (blockmap, blockdata)\n            for blockmap, contributions in sorted(block_contributions.items())\n            for blockdata in contributions\n        ]\n\n        for blockmap, blockdata in blocks:\n            # Define code for block depending on mode\n            block_preparts, block_quadparts = self.generate_block_parts(blockmap, blockdata)\n\n            # Add definitions\n            preparts.extend(block_preparts)\n\n            # Add computations\n            quadparts.extend(block_quadparts)\n\n        return preparts, quadparts\n\n    def generate_block_parts(self, blockmap, blockdata):\n        \"\"\"Generate and return code parts for a given block.\"\"\"\n        # The parts to return\n        preparts = []\n        quadparts = []\n\n        block_rank = len(blockmap)\n        blockdims = tuple(len(dofmap) for dofmap in blockmap)\n\n        ttypes = blockdata.ttypes\n        if \"zeros\" in ttypes:\n            raise RuntimeError(\"Not expecting zero arguments to be left in dofblock generation.\")\n\n        arg_indices = tuple(self.backend.symbols.argument_loop_index(i) for i in range(block_rank))\n\n        F = self.ir.expression.integrand[self.quadrature_rule][\"factorization\"]\n\n        assert not blockdata.transposed, \"Not handled yet\"\n        components = ufl.product(self.ir.expression.shape)\n\n        num_points = self.quadrature_rule[1].points.shape[0]\n        A_shape = [num_points, components] + self.ir.expression.tensor_shape\n        A = self.backend.symbols.element_tensor\n        iq = self.backend.symbols.quadrature_loop_index\n\n        # Check if DOFs in dofrange are equally spaced.\n        expand_loop = False\n        for bm in blockmap:\n            for a, b in pairwise(bm):\n                if b - a != bm[1] - bm[0]:\n                    expand_loop = True\n                    break\n            else:\n                continue\n            break\n\n        if expand_loop:\n            # If DOFs in dofrange are not equally spaced, then expand out the for loop\n            for A_indices, B_indices in zip(\n                product(*blockmap), product(*[range(len(b)) for b in blockmap])\n            ):\n                B_indices = tuple([iq] + list(B_indices))\n                A_indices = tuple([iq] + A_indices)\n                for fi_ci in blockdata.factor_indices_comp_indices:\n                    f = self.get_var(F.nodes[fi_ci[0]][\"expression\"])\n                    arg_factors = self.get_arg_factors(blockdata, block_rank, B_indices)\n                    Brhs = L.float_product([f] + arg_factors)\n                    multi_index = L.MultiIndex([A_indices[0], fi_ci[1]] + A_indices[1:], A_shape)\n                    quadparts.append(L.AssignAdd(A[multi_index], Brhs))\n        else:\n            # Prepend dimensions of dofmap block with free index\n            # for quadrature points and expression components\n            B_indices = tuple([iq] + list(arg_indices))\n\n            # Fetch code to access modified arguments\n            # An access to FE table data\n            arg_factors = self.get_arg_factors(blockdata, block_rank, B_indices)\n\n            # TODO: handle non-contiguous dof ranges\n\n            A_indices = []\n            for bm, index in zip(blockmap, arg_indices):\n                # TODO: switch order here? (optionally)\n                offset = bm[0]\n                if len(bm) == 1:\n                    A_indices.append(index + offset)\n                else:\n                    block_size = bm[1] - bm[0]\n                    A_indices.append(block_size * index + offset)\n            A_indices = tuple([iq] + A_indices)\n\n            # Multiply collected factors\n            # For each component of the factor expression\n            # add result inside quadloop\n            body = []\n\n            for fi_ci in blockdata.factor_indices_comp_indices:\n                f = self.get_var(F.nodes[fi_ci[0]][\"expression\"])\n                Brhs = L.float_product([f] + arg_factors)\n                indices = [A_indices[0], fi_ci[1]] + list(A_indices[1:])\n                multi_index = L.MultiIndex(indices, A_shape)\n                body.append(L.AssignAdd(A[multi_index], Brhs))\n\n            for i in reversed(range(block_rank)):\n                body = L.ForRange(B_indices[i + 1], 0, blockdims[i], body=body)\n            quadparts += [body]\n\n        return preparts, quadparts\n\n    def get_arg_factors(self, blockdata, block_rank, indices):\n        \"\"\"Get argument factors (i.e. blocks).\n\n        Args:\n            blockdata: block data\n            block_rank: block rank\n            indices: Indices used to index element tables\n        \"\"\"\n        arg_factors = []\n        for i in range(block_rank):\n            mad = blockdata.ma_data[i]\n            td = mad.tabledata\n            mt = self.ir.expression.integrand[self.quadrature_rule][\"modified_arguments\"][\n                mad.ma_index\n            ]\n\n            table = self.backend.symbols.element_table(\n                td, self.ir.expression.entity_type, mt.restriction\n            )\n\n            assert td.ttype != \"zeros\"\n\n            if td.ttype == \"ones\":\n                arg_factor = L.LiteralFloat(1.0)\n            else:\n                arg_factor = table[indices[i + 1]]\n            arg_factors.append(arg_factor)\n        return arg_factors\n\n    def new_temp_symbol(self, basename):\n        \"\"\"Create a new code symbol named basename + running counter.\"\"\"\n        name = f\"{basename}{self.symbol_counters[basename]:d}\"\n        self.symbol_counters[basename] += 1\n        return L.Symbol(name, dtype=L.DataType.SCALAR)\n\n    def get_var(self, v):\n        \"\"\"Get a variable.\"\"\"\n        if v._ufl_is_literal_:\n            return L.ufl_to_lnodes(v)\n        f = self.scope.get(v)\n        return f\n\n    def generate_partition(self, symbol, F, mode):\n        \"\"\"Generate computations of factors of blocks.\"\"\"\n        definitions = []\n        intermediates = []\n\n        for _, attr in F.nodes.items():\n            if attr[\"status\"] != mode:\n                continue\n            v = attr[\"expression\"]\n            mt = attr.get(\"mt\")\n\n            if v._ufl_is_literal_:\n                vaccess = L.ufl_to_lnodes(v)\n            elif mt is not None:\n                # All finite element based terminals have table data, as well\n                # as some, but not all, of the symbolic geometric terminals\n                tabledata = attr.get(\"tr\")\n\n                # Backend specific modified terminal translation\n                vaccess = self.backend.access.get(mt, tabledata, 0)\n                vdef = self.backend.definitions.get(mt, tabledata, 0, vaccess)\n\n                if vdef:\n                    assert isinstance(vdef, L.Section)\n                    vdef = vdef.declarations + vdef.statements\n\n                # Store definitions of terminals in list\n                assert isinstance(vdef, list)\n                definitions.extend(vdef)\n            else:\n                # Get previously visited operands\n                vops = [self.get_var(op) for op in v.ufl_operands]\n\n                # Mapping UFL operator to target language\n                self._ufl_names.add(v._ufl_handler_name_)\n                vexpr = L.ufl_to_lnodes(v, *vops)\n\n                is_cond = isinstance(v, ufl.classes.Condition)\n                is_real = isinstance(v, (ufl.classes.Real, ufl.classes.Imag))\n                if is_cond:\n                    dtype = L.DataType.BOOL\n                elif is_real:\n                    dtype = L.DataType.REAL\n                else:\n                    dtype = L.DataType.SCALAR\n\n                j = len(intermediates)\n                vaccess = L.Symbol(f\"{symbol.name}_{j}\", dtype=dtype)\n                intermediates.append(L.VariableDecl(vaccess, vexpr))\n\n            # Store access node for future reference\n            self.scope[v] = vaccess\n\n        # Join terminal computation, array of intermediate expressions,\n        # and intermediate computations\n        parts = []\n\n        parts += definitions\n        parts += intermediates\n\n        return parts\n"
  },
  {
    "path": "ffcx/codegeneration/geometry.py",
    "content": "# Copyright (C) 2021 Matthew Scroggs\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Geometry.\"\"\"\n\nimport basix\nimport numpy as np\n\nimport ffcx.codegeneration.lnodes as L\n\n\ndef write_table(tablename, cellname):\n    \"\"\"Write a table.\"\"\"\n    if tablename == \"facet_edge_vertices\":\n        return facet_edge_vertices(tablename, cellname)\n    if tablename == \"cell_facet_jacobian\":\n        return cell_facet_jacobian(tablename, cellname)\n    if tablename == \"cell_ridge_jacobian\":\n        return cell_ridge_jacobian(tablename, cellname)\n    if tablename == \"reference_cell_volume\":\n        return reference_cell_volume(tablename, cellname)\n    if tablename == \"reference_facet_volume\":\n        return reference_facet_volume(tablename, cellname)\n    if tablename == \"reference_cell_edge_vectors\":\n        return reference_cell_edge_vectors(tablename, cellname)\n    if tablename == \"reference_facet_edge_vectors\":\n        return reference_facet_edge_vectors(tablename, cellname)\n    if tablename == \"reference_normals\":\n        return reference_normals(tablename, cellname)\n    if tablename == \"facet_orientation\":\n        return facet_orientation(tablename, cellname)\n    raise ValueError(f\"Unknown geometry table name: {tablename}\")\n\n\ndef facet_edge_vertices(tablename, cellname):\n    \"\"\"Write facet edge vertices.\"\"\"\n    celltype = getattr(basix.CellType, cellname)\n    topology = basix.topology(celltype)\n    triangle_edges = basix.topology(basix.CellType.triangle)[1]\n    quadrilateral_edges = basix.topology(basix.CellType.quadrilateral)[1]\n\n    if len(topology) != 4:\n        raise ValueError(\"Can only get facet edges for 3D cells.\")\n\n    edge_vertices = []\n    for facet in topology[-2]:\n        if len(facet) == 3:\n            edge_vertices += [[[facet[i] for i in edge] for edge in triangle_edges]]\n        elif len(facet) == 4:\n            edge_vertices += [[[facet[i] for i in edge] for edge in quadrilateral_edges]]\n        else:\n            raise ValueError(\"Only triangular and quadrilateral faces supported.\")\n\n    out = np.array(edge_vertices, dtype=int)\n    symbol = L.Symbol(f\"{cellname}_{tablename}\", dtype=L.DataType.INT)\n    return L.ArrayDecl(symbol, values=out, const=True)\n\n\ndef cell_facet_jacobian(tablename, cellname):\n    \"\"\"Write a reference facet jacobian.\"\"\"\n    celltype = getattr(basix.CellType, cellname)\n    out = basix.cell.facet_jacobians(celltype)\n    symbol = L.Symbol(f\"{cellname}_{tablename}\", dtype=L.DataType.REAL)\n    return L.ArrayDecl(symbol, values=out, const=True)\n\n\ndef cell_ridge_jacobian(tablename, cellname):\n    \"\"\"Write a reference ridge jacobian.\"\"\"\n    celltype = getattr(basix.CellType, cellname)\n    out = basix.cell.edge_jacobians(celltype)\n    symbol = L.Symbol(f\"{cellname}_{tablename}\", dtype=L.DataType.REAL)\n    return L.ArrayDecl(symbol, values=out, const=True)\n\n\ndef reference_cell_volume(tablename, cellname):\n    \"\"\"Write a reference cell volume.\"\"\"\n    celltype = getattr(basix.CellType, cellname)\n    out = basix.cell.volume(celltype)\n    symbol = L.Symbol(f\"{cellname}_{tablename}\", dtype=L.DataType.REAL)\n    return L.VariableDecl(symbol, out)\n\n\ndef reference_facet_volume(tablename, cellname):\n    \"\"\"Write a reference facet volume.\"\"\"\n    celltype = getattr(basix.CellType, cellname)\n    volumes = basix.cell.facet_reference_volumes(celltype)\n    for i in volumes[1:]:\n        if not np.isclose(i, volumes[0]):\n            raise ValueError(\"Reference facet volume not supported for this cell type.\")\n    symbol = L.Symbol(f\"{cellname}_{tablename}\", dtype=L.DataType.REAL)\n    return L.VariableDecl(symbol, volumes[0])\n\n\ndef reference_cell_edge_vectors(tablename, cellname):\n    \"\"\"Write reference edge vectors.\"\"\"\n    celltype = getattr(basix.CellType, cellname)\n    topology = basix.topology(celltype)\n    geometry = basix.geometry(celltype)\n    edge_vectors = [geometry[j] - geometry[i] for i, j in topology[1]]\n    out = np.array(edge_vectors)\n    symbol = L.Symbol(f\"{cellname}_{tablename}\", dtype=L.DataType.REAL)\n    return L.ArrayDecl(symbol, values=out, const=True)\n\n\ndef reference_facet_edge_vectors(tablename, cellname):\n    \"\"\"Write facet reference edge vectors.\"\"\"\n    celltype = getattr(basix.CellType, cellname)\n    topology = basix.topology(celltype)\n    geometry = basix.geometry(celltype)\n    triangle_edges = basix.topology(basix.CellType.triangle)[1]\n    quadrilateral_edges = basix.topology(basix.CellType.quadrilateral)[1]\n\n    if len(topology) != 4:\n        raise ValueError(\"Can only get facet edges for 3D cells.\")\n\n    edge_vectors = []\n    for facet in topology[-2]:\n        if len(facet) == 3:\n            edge_vectors += [geometry[facet[j]] - geometry[facet[i]] for i, j in triangle_edges]\n        elif len(facet) == 4:\n            edge_vectors += [\n                geometry[facet[j]] - geometry[facet[i]] for i, j in quadrilateral_edges\n            ]\n        else:\n            raise ValueError(\"Only triangular and quadrilateral faces supported.\")\n\n    out = np.array(edge_vectors)\n    symbol = L.Symbol(f\"{cellname}_{tablename}\", dtype=L.DataType.REAL)\n    return L.ArrayDecl(symbol, values=out, const=True)\n\n\ndef reference_normals(tablename, cellname):\n    \"\"\"Write reference facet normals.\"\"\"\n    celltype = getattr(basix.CellType, cellname)\n    out = basix.cell.facet_outward_normals(celltype)\n    symbol = L.Symbol(f\"{cellname}_{tablename}\", dtype=L.DataType.REAL)\n    return L.ArrayDecl(symbol, values=out, const=True)\n\n\ndef facet_orientation(tablename, cellname):\n    \"\"\"Write facet orientations.\"\"\"\n    celltype = getattr(basix.CellType, cellname)\n    out = basix.cell.facet_orientations(celltype)\n    symbol = L.Symbol(f\"{cellname}_{tablename}\", dtype=L.DataType.REAL)\n    return L.ArrayDecl(symbol, values=np.asarray(out), const=True)\n"
  },
  {
    "path": "ffcx/codegeneration/integral_generator.py",
    "content": "# Copyright (C) 2015-2024 Martin Sandve Alnæs, Michal Habera, Igor Baratta, Chris Richardson\n#\n# Modified by Jørgen S. Dokken, 2024, 2026\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Integral generator.\"\"\"\n\nimport collections\nimport logging\nfrom numbers import Integral\nfrom typing import Any\n\nimport basix\nimport ufl\n\nimport ffcx.codegeneration.lnodes as L\nfrom ffcx.codegeneration import geometry\nfrom ffcx.codegeneration.definitions import create_dof_index, create_quadrature_index\nfrom ffcx.codegeneration.optimizer import optimize\nfrom ffcx.ir.elementtables import piecewise_ttypes\nfrom ffcx.ir.integral import BlockDataT, CommonExpressionIR, TensorPart\nfrom ffcx.ir.representation import IntegralIR\nfrom ffcx.ir.representationutils import QuadratureRule\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef extract_dtype(v, vops: list[Any]):\n    \"\"\"Extract dtype from ufl expression v and its operands.\"\"\"\n    dtypes = []\n    for op in vops:\n        if hasattr(op, \"dtype\"):\n            dtypes.append(op.dtype)\n        elif hasattr(op, \"symbol\"):\n            dtypes.append(op.symbol.dtype)\n        elif isinstance(op, Integral):\n            dtypes.append(L.DataType.INT)\n        else:\n            raise RuntimeError(f\"Not expecting this type of operand {type(op)}\")\n    is_cond = isinstance(v, ufl.classes.Condition)\n    if is_cond:\n        return L.DataType.BOOL\n    is_real = isinstance(v, (ufl.classes.Real, ufl.classes.Imag))\n    if is_real:\n        return L.DataType.REAL\n    return L.merge_dtypes(dtypes)\n\n\nclass IntegralGenerator:\n    \"\"\"Integral generator.\"\"\"\n\n    def __init__(self, ir: IntegralIR, backend):\n        \"\"\"Initialise.\"\"\"\n        # Store ir\n        self.ir = ir\n\n        # Backend specific plugin with attributes\n        # - symbols: for translating ufl operators to target language\n        # - definitions: for defining backend specific variables\n        # - access: for accessing backend specific variables\n        self.backend = backend\n\n        # Set of operator names code has been generated for, used in the\n        # end for selecting necessary includes\n        self._ufl_names: set[str] = set()\n\n        # Initialize lookup tables for variable scopes\n        self.init_scopes()\n\n        # Cache\n        self.temp_symbols: dict[Any, L.Symbol] = {}\n\n        # Set of counters used for assigning names to intermediate\n        # variables\n        self.symbol_counters: dict[str, int] = collections.defaultdict(int)\n\n    def init_scopes(self):\n        \"\"\"Initialize variable scope dicts.\"\"\"\n        # Reset variables, separate sets for each quadrature rule\n        self.scopes = {\n            quadrature_rule: {} for quadrature_rule in self.ir.expression.integrand.keys()\n        }\n        self.scopes[(None, None)] = {}\n\n    def set_var(self, quadrature_rule, domain, v, vaccess):\n        \"\"\"Set a new variable in variable scope dicts.\n\n        Scope is determined by quadrature_rule which identifies the\n        quadrature loop scope or None if outside quadrature loops.\n\n        Args:\n            quadrature_rule: Quadrature rule\n            domain: The domain of the integral\n            v: the ufl expression\n            vaccess: the LNodes expression to access the value in the code\n        \"\"\"\n        self.scopes[(domain, quadrature_rule)][v] = vaccess\n\n    def get_var(self, quadrature_rule, domain, v):\n        \"\"\"Lookup ufl expression v in variable scope dicts.\n\n        Scope is determined by quadrature rule which identifies the\n        quadrature loop scope or None if outside quadrature loops.\n\n        If v is not found in quadrature loop scope, the piecewise\n        scope (None) is checked.\n\n        Returns the LNodes expression to access the value in the code.\n        \"\"\"\n        if v._ufl_is_literal_:\n            return L.ufl_to_lnodes(v)\n\n        # quadrature loop scope\n        f = self.scopes[(domain, quadrature_rule)].get(v)\n\n        # piecewise scope\n        if f is None:\n            f = self.scopes[(None, None)].get(v)\n        return f\n\n    def new_temp_symbol(self, basename):\n        \"\"\"Create a new code symbol named basename + running counter.\"\"\"\n        name = f\"{basename}{self.symbol_counters[basename]:d}\"\n        self.symbol_counters[basename] += 1\n        return L.Symbol(name, dtype=L.DataType.SCALAR)\n\n    def get_temp_symbol(self, tempname, key):\n        \"\"\"Get a temporary symbol.\"\"\"\n        key = (tempname,) + key\n        s = self.temp_symbols.get(key)\n        defined = s is not None\n        if not defined:\n            s = self.new_temp_symbol(tempname)\n            self.temp_symbols[key] = s\n        return s, defined\n\n    def generate(self, domain: basix.CellType):\n        \"\"\"Generate entire tabulate_tensor body.\n\n        Assumes that the code returned from here will be wrapped in a\n        context that matches a suitable version of the UFC\n        tabulate_tensor signatures.\n        \"\"\"\n        # Assert that scopes are empty: expecting this to be called only\n        # once\n        assert not any(d for d in self.scopes.values())\n\n        parts = []\n\n        # Generate the tables of quadrature points and weights\n        parts += self.generate_quadrature_tables(domain, self.ir.expression)\n\n        # Generate the tables of basis function values and\n        # pre-integrated blocks\n        parts += self.generate_element_tables(domain)\n\n        # Generate the tables of geometry data that are needed\n        parts += self.generate_geometry_tables()\n\n        # Loop generation code will produce parts to go before\n        # quadloops, to define the quadloops, and to go after the\n        # quadloops\n        all_preparts = []\n        all_quadparts = []\n\n        # Pre-definitions are collected across all quadrature loops to\n        # improve re-use and avoid name clashes\n        for cell, rule in self.ir.expression.integrand.keys():\n            if domain == cell:\n                # Generate code to compute piecewise constant scalar factors\n                all_preparts += self.generate_piecewise_partition(rule, cell)\n\n                # Generate code to integrate reusable blocks of final\n                # element tensor\n                all_quadparts += self.generate_quadrature_loop(rule, cell)\n\n        # Collect parts before, during, and after quadrature loops\n        parts += all_preparts\n        parts += all_quadparts\n\n        return L.StatementList(parts)\n\n    def generate_quadrature_tables(self, domain: basix.CellType, expression: CommonExpressionIR):\n        \"\"\"Generate static tables of quadrature points and weights.\"\"\"\n        parts: list[L.LNode] = []\n        # No quadrature tables for custom (given argument)\n        skip = ufl.custom_integral_types\n        if expression.integral_type in skip:\n            return parts\n\n        # Loop over quadrature rules\n        for (cell, quadrature_rule), _ in expression.integrand.items():\n            if domain == cell:\n                # Generate quadrature weights array\n                wsym = self.backend.symbols.weights_table(quadrature_rule)\n                parts += [L.ArrayDecl(wsym, values=quadrature_rule.weights, const=True)]\n\n        # Add leading comment if there are any tables\n        parts = L.commented_code_list(parts, \"Quadrature rules\")\n        return parts\n\n    def generate_geometry_tables(self):\n        \"\"\"Generate static tables of geometry data.\"\"\"\n        ufl_geometry = {\n            ufl.geometry.FacetEdgeVectors: \"facet_edge_vertices\",\n            ufl.geometry.CellFacetJacobian: \"cell_facet_jacobian\",\n            ufl.geometry.CellRidgeJacobian: \"cell_ridge_jacobian\",\n            ufl.geometry.ReferenceCellVolume: \"reference_cell_volume\",\n            ufl.geometry.ReferenceFacetVolume: \"reference_facet_volume\",\n            ufl.geometry.ReferenceCellEdgeVectors: \"reference_cell_edge_vectors\",\n            ufl.geometry.ReferenceFacetEdgeVectors: \"reference_facet_edge_vectors\",\n            ufl.geometry.ReferenceNormal: \"reference_normals\",\n            ufl.geometry.FacetOrientation: \"facet_orientation\",\n        }\n        cells: dict[Any, set[Any]] = {t: set() for t in ufl_geometry.keys()}  # type: ignore\n\n        for integrand in self.ir.expression.integrand.values():\n            for attr in integrand[\"factorization\"].nodes.values():\n                mt = attr.get(\"mt\")\n                if mt is not None:\n                    t = type(mt.terminal)\n                    if t in ufl_geometry:\n                        cells[t].add(\n                            ufl.domain.extract_unique_domain(mt.terminal).ufl_cell().cellname\n                        )\n\n        parts = []\n        for i, cell_list in cells.items():\n            for c in cell_list:\n                parts.append(geometry.write_table(ufl_geometry[i], c))\n\n        return parts\n\n    def generate_element_tables(self, domain: basix.CellType):\n        \"\"\"Generate static tables.\n\n        With precomputed element basis function values in quadrature points.\n        \"\"\"\n        parts = []\n        tables = self.ir.expression.unique_tables[domain]\n        table_types = self.ir.expression.unique_table_types[domain]\n        if self.ir.expression.integral_type in ufl.custom_integral_types:\n            # Define only piecewise tables\n            table_names = [name for name in sorted(tables) if table_types[name] in piecewise_ttypes]\n        else:\n            # Define all tables\n            table_names = sorted(tables)\n\n        for name in table_names:\n            table = tables[name]\n            parts += self.declare_table(name, table)\n\n        # Add leading comment if there are any tables\n        parts = L.commented_code_list(\n            parts,\n            [\n                \"Precomputed values of basis functions and precomputations\",\n                \"FE* dimensions: [permutation][entities][points][dofs]\",\n            ],\n        )\n        return parts\n\n    def declare_table(self, name, table):\n        \"\"\"Declare a table.\n\n        If the dof dimensions of the table have dof rotations, apply\n        these rotations.\n\n        \"\"\"\n        table_symbol = L.Symbol(name, dtype=L.DataType.REAL)\n        self.backend.symbols.element_tables[name] = table_symbol\n        return [L.ArrayDecl(table_symbol, values=table, const=True)]\n\n    def generate_quadrature_loop(self, quadrature_rule: QuadratureRule, domain: basix.CellType):\n        \"\"\"Generate quadrature loop with for this quadrature_rule.\"\"\"\n        # Generate varying partition\n        definitions, intermediates_0 = self.generate_varying_partition(quadrature_rule, domain)\n\n        # Generate dofblock parts, some of this will be placed before or after quadloop\n        tensor_comp, intermediates_fw = self.generate_dofblock_partition(quadrature_rule, domain)\n        assert all([isinstance(tc, L.Section) for tc in tensor_comp])\n\n        # Check if we only have Section objects\n        inputs = []\n        for definition in definitions:\n            assert isinstance(definition, L.Section)\n            inputs += definition.output\n\n        # Create intermediates section\n        output = []\n        declarations = []\n        for fw in intermediates_fw:\n            assert isinstance(fw, L.VariableDecl)\n            output += [fw.symbol]\n            declarations += [L.VariableDecl(fw.symbol, 0)]\n            intermediates_0 += [L.Assign(fw.symbol, fw.value)]\n        intermediates = [L.Section(\"Intermediates\", intermediates_0, declarations, inputs, output)]\n\n        iq_symbol = self.backend.symbols.quadrature_loop_index\n        iq = create_quadrature_index(quadrature_rule, iq_symbol)\n\n        code = definitions + intermediates + tensor_comp\n        code = optimize(code, quadrature_rule)\n\n        return [L.create_nested_for_loops([iq], code)]\n\n    def generate_piecewise_partition(self, quadrature_rule, domain: basix.CellType):\n        \"\"\"Generate a piecewise partition.\"\"\"\n        # Get annotated graph of factorisation\n        F = self.ir.expression.integrand[(domain, quadrature_rule)][\"factorization\"]\n        arraysymbol = L.Symbol(f\"sp_{quadrature_rule.id()}\", dtype=L.DataType.SCALAR)\n        return self.generate_partition(arraysymbol, F, \"piecewise\", None, None)\n\n    def generate_varying_partition(self, quadrature_rule, domain: basix.CellType):\n        \"\"\"Generate a varying partition.\"\"\"\n        # Get annotated graph of factorisation\n        F = self.ir.expression.integrand[(domain, quadrature_rule)][\"factorization\"]\n        arraysymbol = L.Symbol(f\"sv_{quadrature_rule.id()}\", dtype=L.DataType.SCALAR)\n        return self.generate_partition(arraysymbol, F, \"varying\", quadrature_rule, domain)\n\n    def generate_partition(self, symbol, F, mode, quadrature_rule, domain):\n        \"\"\"Generate a partition.\"\"\"\n        definitions = []\n        intermediates = []\n\n        for i, attr in F.nodes.items():\n            if attr[\"status\"] != mode:\n                continue\n            v = attr[\"expression\"]\n\n            # Generate code only if the expression is not already in cache\n            if not self.get_var(quadrature_rule, domain, v):\n                if v._ufl_is_literal_:\n                    vaccess = L.ufl_to_lnodes(v)\n                elif mt := attr.get(\"mt\"):\n                    tabledata = attr.get(\"tr\")\n\n                    # Backend specific modified terminal translation\n                    vaccess = self.backend.access.get(mt, tabledata, quadrature_rule)\n                    vdef = self.backend.definitions.get(mt, tabledata, quadrature_rule, vaccess)\n\n                    if vdef:\n                        assert isinstance(vdef, L.Section)\n                    # Only add if definition is unique.\n                    # This can happen when using sub-meshes\n                    if vdef not in definitions:\n                        definitions += [vdef]\n                else:\n                    # Get previously visited operands\n                    vops = [self.get_var(quadrature_rule, domain, op) for op in v.ufl_operands]\n                    dtype = extract_dtype(v, vops)\n\n                    # Mapping UFL operator to target language\n                    self._ufl_names.add(v._ufl_handler_name_)\n                    vexpr = L.ufl_to_lnodes(v, *vops)\n\n                    j = len(intermediates)\n                    vaccess = L.Symbol(f\"{symbol.name}_{j}\", dtype=dtype)\n                    intermediates.append(L.VariableDecl(vaccess, vexpr))\n\n                # Store access node for future reference\n                self.set_var(quadrature_rule, domain, v, vaccess)\n\n        # Optimize definitions\n        definitions = optimize(definitions, quadrature_rule)\n        return definitions, intermediates\n\n    def generate_dofblock_partition(\n        self,\n        quadrature_rule: QuadratureRule,\n        domain: basix.CellType,\n    ):\n        \"\"\"Generate a dofblock partition.\"\"\"\n        block_contributions = self.ir.expression.integrand[(domain, quadrature_rule)][\n            \"block_contributions\"\n        ]\n        quadparts = []\n        blocks = [\n            (blockmap, blockdata)\n            for blockmap, contributions in sorted(block_contributions.items())\n            for blockdata in contributions\n        ]\n\n        block_groups = collections.defaultdict(list)\n\n        # Group loops by blockmap, in Vector elements each component has\n        # a different blockmap\n        for blockmap, blockdata in blocks:\n            scalar_blockmap = []\n            assert len(blockdata.ma_data) == len(blockmap)\n            for i, b in enumerate(blockmap):\n                bs = blockdata.ma_data[i].tabledata.block_size\n                offset = blockdata.ma_data[i].tabledata.offset\n                b = tuple([(idx - offset) // bs for idx in b])\n                scalar_blockmap.append(b)\n            block_groups[tuple(scalar_blockmap)].append(blockdata)\n\n        intermediates = []\n        for blockmap in block_groups:\n            block_quadparts, intermediate = self.generate_block_parts(\n                quadrature_rule,\n                domain,\n                blockmap,\n                block_groups[blockmap],\n            )\n            intermediates += intermediate\n\n            # Add computations\n            quadparts.extend(block_quadparts)\n\n        return quadparts, intermediates\n\n    def get_arg_factors(self, blockdata, block_rank, quadrature_rule, domain, iq, indices):\n        \"\"\"Get arg factors.\"\"\"\n        arg_factors = []\n        tables = []\n        for i in range(block_rank):\n            mad = blockdata.ma_data[i]\n            td = mad.tabledata\n            scope = self.ir.expression.integrand[(domain, quadrature_rule)][\"modified_arguments\"]\n            mt = scope[mad.ma_index]\n            arg_tables = []\n\n            # Translate modified terminal to code\n            # TODO: Move element table access out of backend?\n            #       Not using self.backend.access.argument() here\n            #       now because it assumes too much about indices.\n\n            assert td.ttype != \"zeros\"\n\n            if td.ttype == \"ones\":\n                arg_factor = 1\n            else:\n                # Assuming B sparsity follows element table sparsity\n                arg_factor, arg_tables = self.backend.access.table_access(\n                    td, self.ir.expression.entity_type, mt.restriction, iq, indices[i]\n                )\n\n            tables += arg_tables\n            arg_factors.append(arg_factor)\n\n        return arg_factors, tables\n\n    def generate_block_parts(\n        self,\n        quadrature_rule: QuadratureRule,\n        domain: basix.CellType,\n        blockmap: tuple,\n        blocklist: list[BlockDataT],\n    ):\n        \"\"\"Generate and return code parts for a given block.\n\n        Returns parts occurring before, inside, and after the quadrature\n        loop identified by the quadrature rule.\n\n        Should be called with quadrature_rule=None for\n        quadloop-independent blocks.\n        \"\"\"\n        # The parts to return\n        quadparts: list[L.LNode] = []\n        intermediates: list[L.LNode] = []\n        tables = []\n        vars = []\n\n        # RHS expressions grouped by LHS \"dofmap\"\n        rhs_expressions = collections.defaultdict(list)\n\n        block_rank = len(blockmap)\n        iq_symbol = self.backend.symbols.quadrature_loop_index\n        iq = create_quadrature_index(quadrature_rule, iq_symbol)\n\n        A_shape = self.ir.expression.tensor_shape\n\n        for blockdata in blocklist:\n            B_indices = []\n            for i in range(block_rank):\n                table_ref = blockdata.ma_data[i].tabledata\n                symbol = self.backend.symbols.argument_loop_index(i)\n                index = create_dof_index(table_ref, symbol)\n                B_indices.append(index)\n\n            if self.ir.part == TensorPart.diagonal and block_rank == 2:\n                assert len(A_shape) == 1\n                B_indices = [B_indices[0], B_indices[0]]\n\n            ttypes = blockdata.ttypes\n            if \"zeros\" in ttypes:\n                raise RuntimeError(\n                    \"Not expecting zero arguments to be left in dofblock generation.\"\n                )\n\n            if len(blockdata.factor_indices_comp_indices) > 1:\n                raise RuntimeError(\"Code generation for non-scalar integrals unsupported\")\n\n            # We have scalar integrand here, take just the factor index\n            factor_index = blockdata.factor_indices_comp_indices[0][0]\n\n            # Get factor expression\n            F = self.ir.expression.integrand[(domain, quadrature_rule)][\"factorization\"]\n\n            v = F.nodes[factor_index][\"expression\"]\n            f = self.get_var(quadrature_rule, domain, v)\n\n            # Quadrature weight was removed in representation, add it back now\n            if self.ir.expression.integral_type in ufl.custom_integral_types:\n                weights = self.backend.symbols.custom_weights_table\n                weight = weights[iq.global_index]\n            else:\n                weights = self.backend.symbols.weights_table(quadrature_rule)\n                weight = weights[iq.global_index]\n\n            # Define fw = f * weight\n            fw_rhs = L.float_product([f, weight])\n            if not isinstance(fw_rhs, L.Product):\n                fw = fw_rhs\n            else:\n                # Define and cache scalar temp variable\n                key = (quadrature_rule, factor_index, blockdata.all_factors_piecewise)\n                fw, defined = self.get_temp_symbol(\"fw\", key)\n                if not defined:\n                    input = [f, weight]\n                    # filter only L.Symbol in input\n                    input = [i for i in input if isinstance(i, L.Symbol)]\n                    output = [fw]\n\n                    # assert input and output are Symbol objects\n                    assert all(isinstance(i, L.Symbol) for i in input)\n                    assert all(isinstance(o, L.Symbol) for o in output)\n\n                    intermediates += [L.VariableDecl(fw, fw_rhs)]\n\n            var = fw if isinstance(fw, L.Symbol) else fw.array\n            vars += [var]\n            assert not blockdata.transposed, \"Not handled yet\"\n\n            # Fetch code to access modified arguments\n            arg_factors, table = self.get_arg_factors(\n                blockdata, block_rank, quadrature_rule, domain, iq, B_indices\n            )\n            tables += table\n            # Define B_rhs = fw * arg_factors\n            insert_rank = block_rank\n            if self.ir.part == TensorPart.diagonal:\n                insert_rank = 1\n                B_indices = [B_indices[0]]\n            B_rhs = L.float_product([fw] + arg_factors)\n\n            A_indices = []\n            for i in range(insert_rank):\n                index = B_indices[i]\n                tabledata = blockdata.ma_data[i].tabledata\n                offset = tabledata.offset\n                if len(blockmap[i]) == 1:\n                    A_indices.append(index.global_index + offset)\n                else:\n                    block_size = blockdata.ma_data[i].tabledata.block_size\n                    A_indices.append(block_size * index.global_index + offset)\n            rhs_expressions[tuple(A_indices)].append(B_rhs)\n\n        # List of statements to keep in the inner loop\n        keep = collections.defaultdict(list)\n\n        for indices in rhs_expressions:\n            keep[indices] = rhs_expressions[indices]\n\n        body: list[L.LNode] = []\n\n        A = self.backend.symbols.element_tensor\n        for indices in keep:\n            multi_index = L.MultiIndex(list(indices), A_shape)\n            for expression in keep[indices]:\n                body.append(L.AssignAdd(A[multi_index], expression))\n\n        # reverse B_indices\n        B_indices = B_indices[::-1]\n        body = [L.create_nested_for_loops(B_indices, body)]\n        input = [*vars, *tables]\n        output = [A]\n\n        # Make sure we don't have repeated symbols in input\n        input = list(set(input))\n\n        # assert input and output are Symbol objects\n        assert all(isinstance(i, L.Symbol) for i in input)\n        assert all(isinstance(o, L.Symbol) for o in output)\n\n        annotations = []\n        if len(B_indices) > 1:\n            annotations.append(L.Annotation.licm)\n\n        quadparts += [L.Section(\"Tensor Computation\", body, [], input, output, annotations)]\n\n        return quadparts, intermediates\n"
  },
  {
    "path": "ffcx/codegeneration/interface.py",
    "content": "# Copyright (C) 2025 Paul T. Kühner\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\n\"\"\"Backend interface declarations.\n\nEvery language backend needs to implement/overload this functionality.\n\"\"\"\n\nfrom collections.abc import Callable\nfrom typing import Protocol\n\nimport basix\nfrom numpy import typing as npt\n\nimport ffcx.codegeneration.lnodes as L\nfrom ffcx.ir.representation import ExpressionIR, FormIR, IntegralIR\n\n\nclass Formatter(Protocol):\n    \"\"\"Formatter interface.\"\"\"\n\n    def __init__(self, dtype: npt.DTypeLike) -> None:\n        \"\"\"Create.\"\"\"\n        ...\n\n    def __call__(self, obj: L.LNode) -> str:\n        \"\"\"Convert L-Node(s) to string representation.\"\"\"\n        ...\n\n\n\"\"\"File to source string.\n\nNote:\n    Needs to be callable as file.generator.\n\"\"\"\nfile_generator = Callable[[dict[str, int | float | npt.DTypeLike]], tuple[tuple[str], ...]]\n\n\"\"\"Form to source string.\n\nNote:\n    Needs to be callable as form.generator.\n\"\"\"\nform_generator = Callable[[FormIR, dict[str, int | float | npt.DTypeLike]], tuple[str]]\n\n\"\"\"Integral to source string.\n\nNote:\n    Needs to be callable as integral.generator.\n\"\"\"\nintegral_generator = Callable[\n    [IntegralIR, basix.CellType, dict[str, int | float | npt.DTypeLike]], tuple[str, ...]\n]\n\n\n\"\"\"Expression to source string.\n\nNote:\n    Needs to be callable as expression.generator.\n\"\"\"\nexpression_generator = Callable[\n    [ExpressionIR, dict[str, int | float | npt.DTypeLike]], tuple[str, ...]\n]\n"
  },
  {
    "path": "ffcx/codegeneration/jit.py",
    "content": "# Copyright (C) 2004-2019 Garth N. Wells\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Just-in-time compilation.\"\"\"\n\nfrom __future__ import annotations\n\nimport importlib\nimport io\nimport logging\nimport os\nimport re\nimport sys\nimport sysconfig\nimport tempfile\nimport time\nfrom contextlib import redirect_stdout\nfrom pathlib import Path\n\nimport cffi\nimport numpy as np\nimport numpy.typing as npt\nimport ufl\n\nimport ffcx\nimport ffcx.naming\nfrom ffcx.codegeneration.C.file_template import libraries as _libraries\n\nlogger = logging.getLogger(\"ffcx\")\nroot_logger = logging.getLogger()\n\n# Get declarations directly from ufcx.h\nfile_dir = os.path.dirname(os.path.abspath(__file__))\nwith open(file_dir + \"/ufcx.h\") as f:\n    ufcx_h = \"\".join(f.readlines())\n\n# Emulate C preprocessor on __STDC_NO_COMPLEX__\nif sys.platform.startswith(\"win32\"):\n    # Remove macro statements and content\n    ufcx_h = re.sub(\n        r\"\\#ifndef __STDC_NO_COMPLEX__.*?\\#endif // __STDC_NO_COMPLEX__\",\n        \"\",\n        ufcx_h,\n        flags=re.DOTALL,\n    )\nelse:\n    # Remove only macros keeping content\n    ufcx_h = ufcx_h.replace(\"#ifndef __STDC_NO_COMPLEX__\", \"\")\n    ufcx_h = ufcx_h.replace(\"#endif // __STDC_NO_COMPLEX__\", \"\")\n\nheader = ufcx_h.split(\"<HEADER_DECL>\")[1].split(\"</HEADER_DECL>\")[0].strip(\" /\\n\")\nheader = header.replace(\"{\", \"{{\").replace(\"}\", \"}}\")\nUFC_HEADER_DECL = header + \"\\n\"\n\nUFC_FORM_DECL = \"\\n\".join(re.findall(\"typedef struct ufcx_form.*?ufcx_form;\", ufcx_h, re.DOTALL))\n\nUFC_INTEGRAL_DECL = \"\\n\".join(\n    re.findall(r\"typedef void ?\\(ufcx_tabulate_tensor_float32\\).*?\\);\", ufcx_h, re.DOTALL)\n)\nUFC_INTEGRAL_DECL += \"\\n\".join(\n    re.findall(r\"typedef void ?\\(ufcx_tabulate_tensor_float64\\).*?\\);\", ufcx_h, re.DOTALL)\n)\nUFC_INTEGRAL_DECL += \"\\n\".join(\n    re.findall(r\"typedef void ?\\(ufcx_tabulate_tensor_complex64\\).*?\\);\", ufcx_h, re.DOTALL)\n)\nUFC_INTEGRAL_DECL += \"\\n\".join(\n    re.findall(r\"typedef void ?\\(ufcx_tabulate_tensor_complex128\\).*?\\);\", ufcx_h, re.DOTALL)\n)\n\nUFC_INTEGRAL_DECL += \"\\n\".join(\n    re.findall(\"typedef struct ufcx_integral.*?ufcx_integral;\", ufcx_h, re.DOTALL)\n)\n\nUFC_EXPRESSION_DECL = \"\\n\".join(\n    re.findall(\"typedef struct ufcx_expression.*?ufcx_expression;\", ufcx_h, re.DOTALL)\n)\n\n\ndef _compute_option_signature(options):\n    \"\"\"Return options signature (some options should not affect signature).\"\"\"\n    return str(sorted(options.items()))\n\n\ndef get_cached_module(module_name, object_names, cache_dir, timeout):\n    \"\"\"Look for an existing C file and wait for compilation, or if it does not exist, create it.\"\"\"\n    cache_dir = Path(cache_dir)\n    c_filename = cache_dir.joinpath(module_name).with_suffix(\".c\")\n    ready_name = c_filename.with_suffix(\".c.cached\")\n\n    # Ensure cache dir exists\n    cache_dir.mkdir(exist_ok=True, parents=True)\n\n    try:\n        # Create C file with exclusive access\n        with open(c_filename, \"x\"):\n            pass\n        return None, None\n    except FileExistsError:\n        logger.info(\"Cached C file already exists: \" + str(c_filename))\n        finder = importlib.machinery.FileFinder(\n            str(cache_dir),\n            (importlib.machinery.ExtensionFileLoader, importlib.machinery.EXTENSION_SUFFIXES),\n        )\n        finder.invalidate_caches()\n\n        # Now, wait for ready\n        for i in range(timeout):\n            if os.path.exists(ready_name):\n                spec = finder.find_spec(module_name)\n                if spec is None:\n                    raise ModuleNotFoundError(\"Unable to find JIT module.\")\n                compiled_module = importlib.util.module_from_spec(spec)\n                spec.loader.exec_module(compiled_module)\n\n                compiled_objects = [getattr(compiled_module.lib, name) for name in object_names]\n                return compiled_objects, compiled_module\n\n            logger.info(f\"Waiting for {ready_name} to appear.\")\n            time.sleep(1)\n        raise TimeoutError(\n            \"JIT compilation timed out, probably due to a failed previous compile. \"\n            f\"Try cleaning cache (e.g. remove {c_filename}) or increase timeout option.\"\n        )\n\n\ndef _compilation_signature(cffi_extra_compile_args, cffi_debug):\n    \"\"\"Compute the compilation-inputs part of the signature.\n\n    Used to avoid cache conflicts across Python versions, architectures, installs.\n\n    - SOABI includes platform, Python version, debug flags\n    - CFLAGS includes prefixes, arch targets\n    \"\"\"\n    if sys.platform.startswith(\"win32\"):\n        # NOTE: SOABI not defined on win32, EXT_SUFFIX contains e.g. '.cp312-win_amd64.pyd'\n        return (\n            str(cffi_extra_compile_args)\n            + str(cffi_debug)\n            + str(sysconfig.get_config_var(\"EXT_SUFFIX\"))\n        )\n    else:\n        return (\n            str(cffi_extra_compile_args)\n            + str(cffi_debug)\n            + str(sysconfig.get_config_var(\"CFLAGS\"))\n            + str(sysconfig.get_config_var(\"SOABI\"))\n        )\n\n\ndef compile_forms(\n    forms: list[ufl.Form],\n    options: dict = {},\n    cache_dir: Path | None = None,\n    timeout: int = 10,\n    cffi_extra_compile_args: list[str] = [],\n    cffi_verbose: bool = False,\n    cffi_debug: bool = False,\n    cffi_libraries: list[str] = [],\n    visualise: bool = False,\n):\n    \"\"\"Compile a list of UFL forms into UFCx Python objects.\n\n    Args:\n        forms: List of ufl.form to compile.\n        options: Options\n        cache_dir: Cache directory\n        timeout: Timeout\n        cffi_extra_compile_args: Extra compilation args for CFFI\n        cffi_verbose: Use verbose compile\n        cffi_debug: Use compiler debug mode\n        cffi_libraries: libraries to use with compiler\n        visualise: Toggle visualisation\n    \"\"\"\n    p = ffcx.options.get_options(options)\n\n    # If requested, replace bi-linear forms by their diagonal part\n    if p[\"part\"] == \"diagonal\":\n        for i, form in enumerate(forms):\n            arguments = form.arguments()\n            numbers = tuple(sorted(set(a.number() for a in arguments)))\n            arity = len(numbers)\n            if arity == 2:\n                blocked_form = ufl.extract_blocks(form, replace_argument=False)\n                if isinstance(blocked_form, ufl.form.Form):\n                    # If there are no sub-elements, continue\n                    continue\n                diagonal_form = ufl.ZeroBaseForm(())\n                for j in range(len(blocked_form)):\n                    if blocked_form[j][j] is not None:\n                        diagonal_form += blocked_form[j][j]\n                if diagonal_form == 0:\n                    raise RuntimeError(\"Diagonal form seems to be zero.\")\n                forms[i] = diagonal_form  # type: ignore\n\n    # Get a signature for these forms\n    module_name = \"libffcx_forms_\" + ffcx.naming.compute_signature(\n        forms,\n        _compute_option_signature(p) + _compilation_signature(cffi_extra_compile_args, cffi_debug),\n    )\n\n    form_names = [ffcx.naming.form_name(form, i, module_name) for i, form in enumerate(forms)]\n\n    if cache_dir is not None:\n        cache_dir = Path(cache_dir)\n        obj, mod = get_cached_module(module_name, form_names, cache_dir, timeout)\n        if obj is not None:\n            return obj, mod, (None, None)\n    else:\n        cache_dir = Path(tempfile.mkdtemp())\n\n    try:\n        decl = (\n            UFC_HEADER_DECL.format(np.dtype(p[\"scalar_type\"]).name)  # type: ignore\n            + UFC_INTEGRAL_DECL\n            + UFC_FORM_DECL\n        )\n\n        form_template = \"extern ufcx_form {name};\\n\"\n        for name in form_names:\n            decl += form_template.format(name=name)\n\n        impl = _compile_objects(\n            decl,\n            forms,\n            form_names,\n            module_name,\n            p,\n            cache_dir,\n            cffi_extra_compile_args,\n            cffi_verbose,\n            cffi_debug,\n            cffi_libraries,\n            visualise=visualise,\n        )\n    except Exception as e:\n        try:\n            # remove c file so that it will not timeout next time\n            c_filename = cache_dir.joinpath(module_name + \".c\")\n            os.replace(c_filename, c_filename.with_suffix(\".c.failed\"))\n        except Exception:\n            pass\n        raise e\n\n    obj, module = _load_objects(cache_dir, module_name, form_names)\n    return obj, module, (decl, impl)\n\n\ndef compile_expressions(\n    expressions: list[tuple[ufl.Expr, npt.NDArray[np.floating]]],  # type: ignore\n    options: dict = {},\n    cache_dir: Path | None = None,\n    timeout: int = 10,\n    cffi_extra_compile_args: list[str] = [],\n    cffi_verbose: bool = False,\n    cffi_debug: bool = False,\n    cffi_libraries: list[str] = [],\n    visualise: bool = False,\n):\n    \"\"\"Compile a list of UFL expressions into UFCx Python objects.\n\n    Args:\n        expressions: List of (UFL expression, evaluation points).\n        options: Options\n        cache_dir: Cache directory\n        timeout: Timeout\n        cffi_extra_compile_args: Extra compilation args for CFFI\n        cffi_verbose: Use verbose compile\n        cffi_debug: Use compiler debug mode\n        cffi_libraries: libraries to use with compiler\n        visualise: Toggle visualisation\n    \"\"\"\n    p = ffcx.options.get_options(options)\n\n    module_name = \"libffcx_expressions_\" + ffcx.naming.compute_signature(\n        expressions,\n        _compute_option_signature(p) + _compilation_signature(cffi_extra_compile_args, cffi_debug),\n    )\n    expr_names = [\n        ffcx.naming.expression_name(expression, module_name) for expression in expressions\n    ]\n\n    if cache_dir is not None:\n        cache_dir = Path(cache_dir)\n        obj, mod = get_cached_module(module_name, expr_names, cache_dir, timeout)\n        if obj is not None:\n            return obj, mod, (None, None)\n    else:\n        cache_dir = Path(tempfile.mkdtemp())\n\n    try:\n        decl = (\n            UFC_HEADER_DECL.format(np.dtype(p[\"scalar_type\"]).name)  # type: ignore\n            + UFC_INTEGRAL_DECL\n            + UFC_FORM_DECL\n            + UFC_EXPRESSION_DECL\n        )\n\n        expression_template = \"extern ufcx_expression {name};\\n\"\n        for name in expr_names:\n            decl += expression_template.format(name=name)\n\n        impl = _compile_objects(\n            decl,\n            expressions,\n            expr_names,\n            module_name,\n            p,\n            cache_dir,\n            cffi_extra_compile_args,\n            cffi_verbose,\n            cffi_debug,\n            cffi_libraries,\n            visualise=visualise,\n        )\n    except Exception as e:\n        try:\n            # remove c file so that it will not timeout next time\n            c_filename = cache_dir.joinpath(module_name + \".c\")\n            os.replace(c_filename, c_filename.with_suffix(\".c.failed\"))\n        except Exception:\n            pass\n        raise e\n\n    obj, module = _load_objects(cache_dir, module_name, expr_names)\n    return obj, module, (decl, impl)\n\n\ndef _compile_objects(\n    decl,\n    ufl_objects,\n    object_names,\n    module_name,\n    options,\n    cache_dir,\n    cffi_extra_compile_args,\n    cffi_verbose,\n    cffi_debug,\n    cffi_libraries,\n    visualise: bool = False,\n):\n    import ffcx.compiler\n\n    libraries = _libraries + cffi_libraries if cffi_libraries is not None else _libraries\n\n    # JIT uses module_name as prefix, which is needed to make names of all struct/function\n    # unique across modules\n    code, _ = ffcx.compiler.compile_ufl_objects(\n        ufl_objects, namespace=module_name, options=options, visualise=visualise\n    )\n    code_body = code[1]\n\n    # Raise error immediately prior to compilation if no support for C99\n    # _Complex. Doing this here allows FFCx to be used for complex codegen on\n    # Windows.\n    if sys.platform.startswith(\"win32\"):\n        if np.issubdtype(options[\"scalar_type\"], np.complexfloating):\n            raise NotImplementedError(\"win32 platform does not support C99 _Complex numbers\")\n        elif isinstance(options[\"scalar_type\"], str) and \"complex\" in options[\"scalar_type\"]:\n            raise NotImplementedError(\"win32 platform does not support C99 _Complex numbers\")\n\n    # Compile in C17 mode\n    if sys.platform.startswith(\"win32\"):\n        cffi_base_compile_args = [\"-std:c17\"]\n    else:\n        cffi_base_compile_args = [\"-std=c17\"]\n\n    cffi_final_compile_args = cffi_base_compile_args + cffi_extra_compile_args\n\n    ffibuilder = cffi.FFI()\n\n    ffibuilder.set_source(\n        module_name,\n        code_body,\n        include_dirs=[ffcx.codegeneration.get_include_path()],\n        extra_compile_args=cffi_final_compile_args,\n        libraries=libraries,\n    )\n\n    ffibuilder.cdef(decl)\n\n    c_filename = cache_dir.joinpath(module_name + \".c\")\n    ready_name = c_filename.with_suffix(\".c.cached\")\n\n    # Compile (ensuring that compile dir exists)\n    cache_dir.mkdir(exist_ok=True, parents=True)\n\n    logger.info(79 * \"#\")\n    logger.info(\"Calling JIT C compiler\")\n    logger.info(79 * \"#\")\n\n    t0 = time.time()\n    f = io.StringIO()\n    # Temporarily set root logger handlers to string buffer only\n    # since CFFI logs into root logger\n    old_handlers = root_logger.handlers.copy()\n    root_logger.handlers = [logging.StreamHandler(f)]\n    with redirect_stdout(f):\n        ffibuilder.compile(tmpdir=cache_dir, verbose=True, debug=cffi_debug)\n    s = f.getvalue()\n    if cffi_verbose:\n        print(s)\n\n    logger.info(f\"JIT C compiler finished in {time.time() - t0:.4f}\")\n\n    # Create a \"status ready\" file. If this fails, it is an error,\n    # because it should not exist yet.\n    # Copy the stdout verbose output of the build into the ready file\n    fd = open(ready_name, \"x\")\n    fd.write(s)\n    fd.close()\n\n    # Copy back the original handlers (in case someone is logging into\n    # root logger and has custom handlers)\n    root_logger.handlers = old_handlers\n\n    return code_body\n\n\ndef _load_objects(cache_dir, module_name, object_names):\n    # Create module finder that searches the compile path\n    finder = importlib.machinery.FileFinder(\n        str(cache_dir),\n        (importlib.machinery.ExtensionFileLoader, importlib.machinery.EXTENSION_SUFFIXES),\n    )\n\n    # Find module. Clear search cache to be sure dynamically created\n    # (new) modules are found\n    finder.invalidate_caches()\n    spec = finder.find_spec(module_name)\n    if spec is None:\n        raise ModuleNotFoundError(\"Unable to find JIT module.\")\n\n    # Load module\n    compiled_module = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(compiled_module)\n\n    compiled_objects = []\n    for name in object_names:\n        obj = getattr(compiled_module.lib, name)\n        compiled_objects.append(obj)\n\n    return compiled_objects, compiled_module\n"
  },
  {
    "path": "ffcx/codegeneration/lnodes.py",
    "content": "# Copyright (C) 2013-2023 Martin Sandve Alnæs, Chris Richardson\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"LNodes.\n\nLNodes is intended as a minimal generic language description.\nFormatting is done later, depending on the target language.\n\nSupported:\n Floating point (and complex) and integer variables and multidimensional arrays\n Range loops\n Simple arithmetic, +-*/\n Math operations\n Logic conditions\n Comments\nNot supported:\n Pointers\n Function Calls\n Flow control (if, switch, while)\n Booleans\n Strings\n\"\"\"\n\nimport numbers\nfrom collections.abc import Sequence\nfrom enum import Enum\n\nimport numpy as np\nimport ufl\n\n\nclass PRECEDENCE:\n    \"\"\"An enum-like class for operator precedence levels.\"\"\"\n\n    HIGHEST = 0\n    LITERAL = 0\n    SYMBOL = 0\n    SUBSCRIPT = 2\n\n    NOT = 3\n    NEG = 3\n\n    MUL = 4\n    DIV = 4\n\n    ADD = 5\n    SUB = 5\n\n    LT = 7\n    LE = 7\n    GT = 7\n    GE = 7\n    EQ = 8\n    NE = 8\n    AND = 11\n    OR = 12\n    CONDITIONAL = 13\n    ASSIGN = 13\n    LOWEST = 15\n\n\ndef is_zero_lexpr(lexpr):\n    \"\"\"Check if an expression is zero.\"\"\"\n    return (isinstance(lexpr, LiteralFloat) and lexpr.value == 0.0) or (\n        isinstance(lexpr, LiteralInt) and lexpr.value == 0\n    )\n\n\ndef is_one_lexpr(lexpr):\n    \"\"\"Check if an expression is one.\"\"\"\n    return (isinstance(lexpr, LiteralFloat) and lexpr.value == 1.0) or (\n        isinstance(lexpr, LiteralInt) and lexpr.value == 1\n    )\n\n\ndef is_negative_one_lexpr(lexpr):\n    \"\"\"Check if an expression is negative one.\"\"\"\n    return (isinstance(lexpr, LiteralFloat) and lexpr.value == -1.0) or (\n        isinstance(lexpr, LiteralInt) and lexpr.value == -1\n    )\n\n\ndef float_product(factors):\n    \"\"\"Build product of float factors.\n\n    Simplify ones and returning 1.0 if empty sequence.\n    \"\"\"\n    factors = [f for f in factors if not is_one_lexpr(f)]\n    if len(factors) == 0:\n        return LiteralFloat(1.0)\n    elif len(factors) == 1:\n        return factors[0]\n    else:\n        return Product(factors)\n\n\nclass DataType(Enum):\n    \"\"\"Representation of data types for variables in LNodes.\n\n    These can be REAL (same type as geometry),\n    SCALAR (same type as tensor), or INT (for entity indices etc.)\n    \"\"\"\n\n    REAL = 0\n    SCALAR = 1\n    INT = 2\n    BOOL = 3\n    NONE = 4\n\n\ndef merge_dtypes(dtypes: list[DataType]):\n    \"\"\"Promote dtype to SCALAR or REAL if either argument matches.\"\"\"\n    if DataType.NONE in dtypes:\n        raise ValueError(f\"Invalid DataType in LNodes {dtypes}\")\n    if DataType.SCALAR in dtypes:\n        return DataType.SCALAR\n    elif DataType.REAL in dtypes:\n        return DataType.REAL\n    elif DataType.INT in dtypes:\n        return DataType.INT\n    elif DataType.BOOL in dtypes:\n        return DataType.BOOL\n    else:\n        raise ValueError(f\"Can't get dtype for operation with {dtypes}\")\n\n\nclass LNode:\n    \"\"\"Base class for all AST nodes.\"\"\"\n\n    def __eq__(self, other):\n        \"\"\"Check for equality.\"\"\"\n        return NotImplemented\n\n    def __ne__(self, other):\n        \"\"\"Check for inequality.\"\"\"\n        return NotImplemented\n\n\nclass LExpr(LNode):\n    \"\"\"Base class for all expressions.\n\n    All subtypes should define a 'precedence' class attribute.\n    \"\"\"\n\n    dtype = DataType.NONE\n\n    def __getitem__(self, indices):\n        \"\"\"Get an item.\"\"\"\n        return ArrayAccess(self, indices)\n\n    def __neg__(self):\n        \"\"\"Negate.\"\"\"\n        if isinstance(self, LiteralFloat):\n            return LiteralFloat(-self.value)\n        if isinstance(self, LiteralInt):\n            return LiteralInt(-self.value)\n        return Neg(self)\n\n    def __add__(self, other):\n        \"\"\"Add.\"\"\"\n        other = as_lexpr(other)\n        if is_zero_lexpr(self):\n            return other\n        if is_zero_lexpr(other):\n            return self\n        if isinstance(other, Neg):\n            return Sub(self, other.arg)\n        return Add(self, other)\n\n    def __radd__(self, other):\n        \"\"\"Add.\"\"\"\n        other = as_lexpr(other)\n        if is_zero_lexpr(self):\n            return other\n        if is_zero_lexpr(other):\n            return self\n        if isinstance(self, Neg):\n            return Sub(other, self.arg)\n        return Add(other, self)\n\n    def __sub__(self, other):\n        \"\"\"Subtract.\"\"\"\n        other = as_lexpr(other)\n        if is_zero_lexpr(self):\n            return -other\n        if is_zero_lexpr(other):\n            return self\n        if isinstance(other, Neg):\n            return Add(self, other.arg)\n        if isinstance(self, LiteralInt) and isinstance(other, LiteralInt):\n            return LiteralInt(self.value - other.value)\n        return Sub(self, other)\n\n    def __rsub__(self, other):\n        \"\"\"Subtract.\"\"\"\n        other = as_lexpr(other)\n        if is_zero_lexpr(self):\n            return other\n        if is_zero_lexpr(other):\n            return -self\n        if isinstance(self, Neg):\n            return Add(other, self.arg)\n        return Sub(other, self)\n\n    def __mul__(self, other):\n        \"\"\"Multiply.\"\"\"\n        other = as_lexpr(other)\n        if is_zero_lexpr(self):\n            return self\n        if is_zero_lexpr(other):\n            return other\n        if is_one_lexpr(self):\n            return other\n        if is_one_lexpr(other):\n            return self\n        if is_negative_one_lexpr(other):\n            return Neg(self)\n        if is_negative_one_lexpr(self):\n            return Neg(other)\n        if isinstance(self, LiteralInt) and isinstance(other, LiteralInt):\n            return LiteralInt(self.value * other.value)\n        return Mul(self, other)\n\n    def __rmul__(self, other):\n        \"\"\"Multiply.\"\"\"\n        other = as_lexpr(other)\n        if is_zero_lexpr(self):\n            return self\n        if is_zero_lexpr(other):\n            return other\n        if is_one_lexpr(self):\n            return other\n        if is_one_lexpr(other):\n            return self\n        if is_negative_one_lexpr(other):\n            return Neg(self)\n        if is_negative_one_lexpr(self):\n            return Neg(other)\n        return Mul(other, self)\n\n    def __div__(self, other):\n        \"\"\"Divide.\"\"\"\n        other = as_lexpr(other)\n        if is_zero_lexpr(other):\n            raise ValueError(\"Division by zero!\")\n        if is_zero_lexpr(self):\n            return self\n        return Div(self, other)\n\n    def __rdiv__(self, other):\n        \"\"\"Divide.\"\"\"\n        other = as_lexpr(other)\n        if is_zero_lexpr(self):\n            raise ValueError(\"Division by zero!\")\n        if is_zero_lexpr(other):\n            return other\n        return Div(other, self)\n\n    # TODO: Error check types?\n    __truediv__ = __div__\n    __rtruediv__ = __rdiv__\n    __floordiv__ = __div__\n    __rfloordiv__ = __rdiv__\n\n\nclass LExprOperator(LExpr):\n    \"\"\"Base class for all expression operators.\"\"\"\n\n    precedence: int\n\n    sideeffect = False\n\n\nclass LExprTerminal(LExpr):\n    \"\"\"Base class for all  expression terminals.\"\"\"\n\n    precedence: int\n\n    sideeffect = False\n\n\nclass LiteralFloat(LExprTerminal):\n    \"\"\"A floating point literal value.\"\"\"\n\n    precedence: int\n\n    precedence = PRECEDENCE.LITERAL\n\n    def __init__(self, value):\n        \"\"\"Initialise.\"\"\"\n        assert isinstance(value, float | complex)\n        self.value = value\n        if isinstance(value, complex):\n            self.dtype = DataType.SCALAR\n        else:\n            self.dtype = DataType.REAL\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, LiteralFloat) and self.value == other.value\n\n    def __float__(self):\n        \"\"\"Convert to float.\"\"\"\n        return float(self.value)\n\n    def __repr__(self):\n        \"\"\"Representation.\"\"\"\n        return str(self.value)\n\n\nclass LiteralInt(LExprTerminal):\n    \"\"\"An integer literal value.\"\"\"\n\n    precedence = PRECEDENCE.LITERAL\n\n    def __init__(self, value):\n        \"\"\"Initialise.\"\"\"\n        assert isinstance(value, int | np.number)\n        self.value = value\n        self.dtype = DataType.INT\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, LiteralInt) and self.value == other.value\n\n    def __hash__(self):\n        \"\"\"Hash.\"\"\"\n        return hash(self.value)\n\n    def __repr__(self):\n        \"\"\"Representation.\"\"\"\n        return str(self.value)\n\n\nclass Symbol(LExprTerminal):\n    \"\"\"A named symbol.\"\"\"\n\n    precedence = PRECEDENCE.SYMBOL\n\n    def __init__(self, name: str, dtype):\n        \"\"\"Initialise.\"\"\"\n        assert isinstance(name, str)\n        assert name.replace(\"_\", \"\").isalnum()\n        self.name = name\n        self.dtype = dtype\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, Symbol) and self.name == other.name\n\n    def __hash__(self):\n        \"\"\"Hash.\"\"\"\n        return hash(self.name)\n\n    def __repr__(self):\n        \"\"\"Representation.\"\"\"\n        return self.name\n\n\nclass MultiIndex(LExpr):\n    \"\"\"A multi-index for accessing tensors flattened in memory.\"\"\"\n\n    precedence = PRECEDENCE.SYMBOL\n\n    def __init__(self, symbols: list, sizes: list):\n        \"\"\"Initialise.\"\"\"\n        self.dtype = DataType.INT\n        self.sizes = sizes\n        self.symbols = [as_lexpr(sym) for sym in symbols]\n        for sym in self.symbols:\n            assert sym.dtype == DataType.INT\n\n        dim = len(sizes)\n        if dim == 0:\n            self.global_index: LExpr = LiteralInt(0)\n        else:\n            stride = [np.prod(sizes[i:]) for i in range(dim)] + [LiteralInt(1)]\n            self.global_index = Sum(n * sym for n, sym in zip(stride[1:], symbols))\n\n    @property\n    def dim(self):\n        \"\"\"Dimension of the multi-index.\"\"\"\n        return len(self.sizes)\n\n    def size(self):\n        \"\"\"Size of the multi-index.\"\"\"\n        return np.prod(self.sizes)\n\n    def local_index(self, idx):\n        \"\"\"Get the local index.\"\"\"\n        assert idx < len(self.symbols)\n        return self.symbols[idx]\n\n    def intersection(self, other):\n        \"\"\"Get the intersection.\"\"\"\n        symbols = []\n        sizes = []\n        for sym, size in zip(self.symbols, self.sizes):\n            if sym in other.symbols:\n                i = other.symbols.index(sym)\n                assert other.sizes[i] == size\n                symbols.append(sym)\n                sizes.append(size)\n        return MultiIndex(symbols, sizes)\n\n    def union(self, other):\n        \"\"\"Get the union.\n\n        Note:\n            Result may depend on order a.union(b) != b.union(a)\n        \"\"\"\n        symbols = self.symbols.copy()\n        sizes = self.sizes.copy()\n        for sym, size in zip(other.symbols, other.sizes):\n            if sym in symbols:\n                i = symbols.index(sym)\n                assert sizes[i] == size\n            else:\n                symbols.append(sym)\n                sizes.append(size)\n        return MultiIndex(symbols, sizes)\n\n    def difference(self, other):\n        \"\"\"Get the difference.\"\"\"\n        symbols = []\n        sizes = []\n        for idx, size in zip(self.symbols, self.sizes):\n            if idx not in other.symbols:\n                symbols.append(idx)\n                sizes.append(size)\n        return MultiIndex(symbols, sizes)\n\n    def __hash__(self):\n        \"\"\"Hash.\"\"\"\n        return hash(self.global_index.__repr__)\n\n\nclass PrefixUnaryOp(LExprOperator):\n    \"\"\"Base class for unary operators.\"\"\"\n\n    def __init__(self, arg):\n        \"\"\"Initialise.\"\"\"\n        self.arg = as_lexpr(arg)\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, type(self)) and self.arg == other.arg\n\n\nclass BinOp(LExprOperator):\n    \"\"\"A binary operator.\"\"\"\n\n    op: str\n\n    def __init__(self, lhs, rhs):\n        \"\"\"Initialise.\"\"\"\n        self.lhs = as_lexpr(lhs)\n        self.rhs = as_lexpr(rhs)\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, type(self)) and self.lhs == other.lhs and self.rhs == other.rhs\n\n    def __hash__(self):\n        \"\"\"Hash.\"\"\"\n        return hash(self.lhs) + hash(self.rhs)\n\n    def __repr__(self):\n        \"\"\"Representation.\"\"\"\n        return f\"({self.lhs} {self.op} {self.rhs})\"\n\n\nclass ArithmeticBinOp(BinOp):\n    \"\"\"An artithmetic binary operator.\"\"\"\n\n    def __init__(self, lhs, rhs):\n        \"\"\"Initialise.\"\"\"\n        self.lhs = as_lexpr(lhs)\n        self.rhs = as_lexpr(rhs)\n        self.dtype = merge_dtypes([self.lhs.dtype, self.rhs.dtype])\n\n\nclass NaryOp(LExprOperator):\n    \"\"\"Base class for special n-ary operators.\"\"\"\n\n    op = \"\"\n\n    def __init__(self, args):\n        \"\"\"Initialise.\"\"\"\n        self.args = [as_lexpr(arg) for arg in args]\n        self.dtype = self.args[0].dtype\n        for arg in self.args:\n            self.dtype = merge_dtypes([self.dtype, arg.dtype])\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return (\n            isinstance(other, type(self))\n            and len(self.args) == len(other.args)\n            and all(a == b for a, b in zip(self.args, other.args))\n        )\n\n    def __repr__(self) -> str:\n        \"\"\"Representation.\"\"\"\n        return f\"{self.op} \".join(f\"{i} \" for i in self.args)\n\n    def __hash__(self):\n        \"\"\"Hash.\"\"\"\n        return hash(tuple(self.args))\n\n\nclass Neg(PrefixUnaryOp):\n    \"\"\"Negation operator.\"\"\"\n\n    precedence = PRECEDENCE.NEG\n    op = \"-\"\n\n    def __init__(self, arg):\n        \"\"\"Initialise.\"\"\"\n        self.arg = as_lexpr(arg)\n        self.dtype = self.arg.dtype\n\n\nclass Not(PrefixUnaryOp):\n    \"\"\"Not operator.\"\"\"\n\n    precedence = PRECEDENCE.NOT\n    op = \"!\"\n\n\nclass Add(ArithmeticBinOp):\n    \"\"\"Add operator.\"\"\"\n\n    precedence = PRECEDENCE.ADD\n    op = \"+\"\n\n\nclass Sub(ArithmeticBinOp):\n    \"\"\"Subtract operator.\"\"\"\n\n    precedence = PRECEDENCE.SUB\n    op = \"-\"\n\n\nclass Mul(ArithmeticBinOp):\n    \"\"\"Multiply operator.\"\"\"\n\n    precedence = PRECEDENCE.MUL\n    op = \"*\"\n\n\nclass Div(ArithmeticBinOp):\n    \"\"\"Division operator.\"\"\"\n\n    precedence = PRECEDENCE.DIV\n    op = \"/\"\n\n\nclass EQ(BinOp):\n    \"\"\"Equality operator.\"\"\"\n\n    precedence = PRECEDENCE.EQ\n    op = \"==\"\n\n\nclass NE(BinOp):\n    \"\"\"Inequality operator.\"\"\"\n\n    precedence = PRECEDENCE.NE\n    op = \"!=\"\n\n\nclass LT(BinOp):\n    \"\"\"Less than operator.\"\"\"\n\n    precedence = PRECEDENCE.LT\n    op = \"<\"\n\n\nclass GT(BinOp):\n    \"\"\"Greater than operator.\"\"\"\n\n    precedence = PRECEDENCE.GT\n    op = \">\"\n\n\nclass LE(BinOp):\n    \"\"\"Less than or equal to operator.\"\"\"\n\n    precedence = PRECEDENCE.LE\n    op = \"<=\"\n\n\nclass GE(BinOp):\n    \"\"\"Greater than or equal to operator.\"\"\"\n\n    precedence = PRECEDENCE.GE\n    op = \">=\"\n\n\nclass And(BinOp):\n    \"\"\"And operator.\"\"\"\n\n    precedence = PRECEDENCE.AND\n    op = \"&&\"\n\n\nclass Or(BinOp):\n    \"\"\"Or operator.\"\"\"\n\n    precedence = PRECEDENCE.OR\n    op = \"||\"\n\n\nclass Sum(NaryOp):\n    \"\"\"Sum of any number of operands.\"\"\"\n\n    precedence = PRECEDENCE.ADD\n    op = \"+\"\n\n\nclass Product(NaryOp):\n    \"\"\"Product of any number of operands.\"\"\"\n\n    precedence = PRECEDENCE.MUL\n    op = \"*\"\n\n\nclass MathFunction(LExprOperator):\n    \"\"\"A Math Function, with any arguments.\"\"\"\n\n    precedence = PRECEDENCE.HIGHEST\n\n    def __init__(self, func, args):\n        \"\"\"Initialise.\"\"\"\n        self.function = func\n        self.args = [as_lexpr(arg) for arg in args]\n        self.dtype = self.args[0].dtype\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return (\n            isinstance(other, type(self))\n            and self.function == other.function\n            and len(self.args) == len(other.args)\n            and all(a == b for a, b in zip(self.args, other.args))\n        )\n\n\nclass AssignOp(BinOp):\n    \"\"\"Base class for assignment operators.\"\"\"\n\n    precedence = PRECEDENCE.ASSIGN\n    sideeffect = True\n\n    def __init__(self, lhs, rhs):\n        \"\"\"Initialise.\"\"\"\n        assert isinstance(lhs, LNode)\n        BinOp.__init__(self, lhs, rhs)\n\n\nclass Assign(AssignOp):\n    \"\"\"Assign operator.\"\"\"\n\n    op = \"=\"\n\n\nclass AssignAdd(AssignOp):\n    \"\"\"Assign add operator.\"\"\"\n\n    op = \"+=\"\n\n\nclass AssignSub(AssignOp):\n    \"\"\"Assign subtract operator.\"\"\"\n\n    op = \"-=\"\n\n\nclass AssignMul(AssignOp):\n    \"\"\"Assign multiply operator.\"\"\"\n\n    op = \"*=\"\n\n\nclass AssignDiv(AssignOp):\n    \"\"\"Assign division operator.\"\"\"\n\n    op = \"/=\"\n\n\nclass ArrayAccess(LExprOperator):\n    \"\"\"Array access.\"\"\"\n\n    precedence = PRECEDENCE.SUBSCRIPT\n\n    def __init__(self, array, indices):\n        \"\"\"Initialise.\"\"\"\n        # Typecheck array argument\n        if isinstance(array, Symbol):\n            self.array = array\n            self.dtype = array.dtype\n        elif isinstance(array, ArrayDecl):\n            self.array = array.symbol\n            self.dtype = array.symbol.dtype\n        else:\n            raise ValueError(f\"Unexpected array type {type(array).__name__}\")\n\n        # Allow expressions or literals as indices\n        if not isinstance(indices, list | tuple):\n            indices = (indices,)\n        self.indices = tuple(as_lexpr(i) for i in indices)\n\n        # Early error checking for negative array dimensions\n        if any(isinstance(i, int) and i < 0 for i in self.indices):\n            raise ValueError(\"Index value < 0.\")\n\n        # Additional dimension checks possible if we get an ArrayDecl instead of just a name\n        if isinstance(array, ArrayDecl):\n            if len(self.indices) != len(array.sizes):\n                raise ValueError(\"Invalid number of indices.\")\n            ints = (int, LiteralInt)\n            if any(\n                (isinstance(i, ints) and isinstance(d, ints) and int(i) >= int(d))\n                for i, d in zip(self.indices, array.sizes)\n            ):\n                raise ValueError(\"Index value >= array dimension.\")\n\n    def __getitem__(self, indices):\n        \"\"\"Handle nested expr[i][j].\"\"\"\n        if isinstance(indices, list):\n            indices = tuple(indices)\n        elif not isinstance(indices, tuple):\n            indices = (indices,)\n        return ArrayAccess(self.array, self.indices + indices)\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return (\n            isinstance(other, type(self))\n            and self.array == other.array\n            and self.indices == other.indices\n        )\n\n    def __hash__(self):\n        \"\"\"Hash.\"\"\"\n        return hash(self.array)\n\n    def __repr__(self):\n        \"\"\"Representation.\"\"\"\n        return str(self.array) + \"[\" + \", \".join(str(i) for i in self.indices) + \"]\"\n\n\nclass Conditional(LExprOperator):\n    \"\"\"Conditional.\"\"\"\n\n    precedence = PRECEDENCE.CONDITIONAL\n\n    def __init__(self, condition, true, false):\n        \"\"\"Initialise.\"\"\"\n        self.condition = as_lexpr(condition)\n        self.true = as_lexpr(true)\n        self.false = as_lexpr(false)\n        self.dtype = merge_dtypes([self.true.dtype, self.false.dtype])\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return (\n            isinstance(other, type(self))\n            and self.condition == other.condition\n            and self.true == other.true\n            and self.false == other.false\n        )\n\n\ndef as_lexpr(node):\n    \"\"\"Typechecks and wraps an object as a valid LExpr.\n\n    Accepts LExpr nodes, treats int and float as literals.\n\n    \"\"\"\n    if isinstance(node, LExpr):\n        return node\n    elif isinstance(node, numbers.Integral):\n        return LiteralInt(node)\n    elif isinstance(node, numbers.Real):\n        return LiteralFloat(node)\n    else:\n        raise RuntimeError(f\"Unexpected LExpr type {type(node)}:\\n{node}\")\n\n\nclass Statement(LNode):\n    \"\"\"Make an expression into a statement.\"\"\"\n\n    def __init__(self, expr):\n        \"\"\"Initialise.\"\"\"\n        self.expr = as_lexpr(expr)\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, type(self)) and self.expr == other.expr\n\n    def __hash__(self) -> int:\n        \"\"\"Hash.\"\"\"\n        return hash(self.expr)\n\n\ndef as_statement(node):\n    \"\"\"Perform type checking on node and wrap in a suitable statement type if necessary.\"\"\"\n    if isinstance(node, StatementList) and len(node.statements) == 1:\n        # Cleans up the expression tree a bit\n        return node.statements[0]\n    elif isinstance(node, Statement):\n        # No-op\n        return node\n    elif isinstance(node, LExprOperator):\n        if node.sideeffect:\n            # Special case for using assignment expressions as statements\n            return Statement(node)\n        else:\n            raise RuntimeError(\n                f\"Trying to create a statement of lexprOperator type {type(node)}:\\n{node}\"\n            )\n\n    elif isinstance(node, list):\n        # Convenience case for list of statements\n        if len(node) == 1:\n            # Cleans up the expression tree a bit\n            return as_statement(node[0])\n        else:\n            return StatementList(node)\n    elif isinstance(node, Section):\n        return node\n    else:\n        raise RuntimeError(f\"Unexpected Statement type {type(node)}:\\n{node}\")\n\n\nclass Annotation(Enum):\n    \"\"\"Annotation.\"\"\"\n\n    fuse = 1  # fuse loops in section\n    unroll = 2  # unroll loop in section\n    licm = 3  # loop invariant code motion\n    factorize = 4  # apply sum factorization\n\n\nclass Declaration(Statement):\n    \"\"\"Base class for all declarations.\"\"\"\n\n    def __init__(self, symbol):\n        \"\"\"Initialise.\"\"\"\n        self.symbol = symbol\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, type(self)) and self.symbol == other.symbol\n\n\ndef is_declaration(node) -> bool:\n    \"\"\"Check if a node is a declaration.\"\"\"\n    return isinstance(node, VariableDecl) or isinstance(node, ArrayDecl)\n\n\nclass Section(LNode):\n    \"\"\"A section of code with a name and a list of statements.\"\"\"\n\n    def __init__(\n        self,\n        name: str,\n        statements: list[LNode],\n        declarations: Sequence[Declaration],\n        input: list[Symbol] | None = None,\n        output: list[Symbol] | None = None,\n        annotations: list[Annotation] | None = None,\n    ):\n        \"\"\"Initialise.\"\"\"\n        self.name = name\n        self.statements = [as_statement(st) for st in statements]\n        self.annotations = annotations or []\n        self.input = input or []\n        self.declarations = declarations or []\n        self.output = output or []\n\n        for decl in self.declarations:\n            assert is_declaration(decl)\n            if decl.symbol not in self.output:\n                self.output.append(decl.symbol)\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        attributes = (\"name\", \"input\", \"output\", \"annotations\", \"statements\")\n        return isinstance(other, type(self)) and all(\n            getattr(self, name) == getattr(other, name) for name in attributes\n        )\n\n\nclass StatementList(LNode):\n    \"\"\"A simple sequence of statements.\"\"\"\n\n    def __init__(self, statements):\n        \"\"\"Initialise.\"\"\"\n        self.statements = [as_statement(st) for st in statements]\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, type(self)) and self.statements == other.statements\n\n    def __hash__(self) -> int:\n        \"\"\"Hash.\"\"\"\n        return hash(tuple(self.statements))\n\n    def __repr__(self):\n        \"\"\"Representation.\"\"\"\n        return f\"StatementList({self.statements})\"\n\n\nclass Comment(Statement):\n    \"\"\"Line comment(s) used for annotating the generated code with human readable remarks.\"\"\"\n\n    def __init__(self, comment: str):\n        \"\"\"Initialise.\"\"\"\n        assert isinstance(comment, str)\n        self.comment = comment\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, type(self)) and self.comment == other.comment\n\n\ndef commented_code_list(code, comments):\n    \"\"\"Add comment to code list if the list is not empty.\"\"\"\n    if isinstance(code, LNode):\n        code = [code]\n    assert isinstance(code, list)\n    if code:\n        if not isinstance(comments, list | tuple):\n            comments = [comments]\n        comments = [Comment(c) for c in comments]\n        code = comments + code\n    return code\n\n\n# Type and variable declarations\n\n\nclass VariableDecl(Declaration):\n    \"\"\"Declare a variable, optionally define initial value.\"\"\"\n\n    def __init__(self, symbol, value=None):\n        \"\"\"Initialise.\"\"\"\n        assert isinstance(symbol, Symbol)\n        assert symbol.dtype is not None\n        self.symbol = symbol\n\n        if value is not None:\n            value = as_lexpr(value)\n        self.value = value\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return (\n            isinstance(other, type(self))\n            and self.typename == other.typename\n            and self.symbol == other.symbol\n            and self.value == other.value\n        )\n\n\nclass ArrayDecl(Declaration):\n    \"\"\"A declaration or definition of an array.\n\n    Note that just setting values=0 is sufficient to initialize the\n    entire array to zero.\n\n    Otherwise use nested lists of lists to represent multidimensional\n    array values to initialize to.\n\n    \"\"\"\n\n    def __init__(self, symbol, sizes=None, values=None, const=False):\n        \"\"\"Initialise.\"\"\"\n        assert isinstance(symbol, Symbol)\n        self.symbol = symbol\n        assert symbol.dtype\n\n        if sizes is None:\n            assert values is not None\n            sizes = values.shape\n        if isinstance(sizes, int):\n            sizes = (sizes,)\n        self.sizes = tuple(sizes)\n\n        if values is None:\n            assert sizes is not None\n\n        # NB! No type checking, assuming nested lists of literal values. Not applying as_lexpr.\n        if isinstance(values, list | tuple):\n            self.values = np.asarray(values)\n        else:\n            self.values = values\n\n        self.const = const\n        self.dtype = symbol.dtype\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        attributes = (\"dtype\", \"symbol\", \"sizes\", \"values\")\n        return isinstance(other, type(self)) and all(\n            getattr(self, name) == getattr(self, name) for name in attributes\n        )\n\n    def __hash__(self) -> int:\n        \"\"\"Hash.\"\"\"\n        return hash(self.symbol)\n\n\ndef is_simple_inner_loop(code):\n    \"\"\"Check if code is a simple inner loop.\"\"\"\n    if isinstance(code, ForRange) and is_simple_inner_loop(code.body):\n        return True\n    if isinstance(code, Statement) and isinstance(code.expr, AssignOp):\n        return True\n    return False\n\n\ndef depth(code) -> int:\n    \"\"\"Get depth of code.\"\"\"\n    if isinstance(code, ForRange):\n        return 1 + depth(code.body)\n    if isinstance(code, StatementList):\n        return max([depth(c) for c in code.statements])\n    return 0\n\n\nclass ForRange(Statement):\n    \"\"\"Slightly higher-level for loop assuming incrementing an index over a range.\"\"\"\n\n    def __init__(self, index, begin, end, body):\n        \"\"\"Initialise.\"\"\"\n        assert isinstance(index, Symbol) or isinstance(index, MultiIndex)\n        self.index = index\n        self.begin = as_lexpr(begin)\n        self.end = as_lexpr(end)\n        assert isinstance(body, list)\n        self.body = StatementList(body)\n\n    def as_tuple(self):\n        \"\"\"Convert to a tuple.\"\"\"\n        return (self.index, self.begin, self.end, self.body)\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        attributes = (\"index\", \"begin\", \"end\", \"body\")\n        return isinstance(other, type(self)) and all(\n            getattr(self, name) == getattr(self, name) for name in attributes\n        )\n\n    def __hash__(self) -> int:\n        \"\"\"Hash.\"\"\"\n        return hash(self.as_tuple())\n\n\ndef _math_function(op, *args):\n    \"\"\"Get a math function.\"\"\"\n    name = op._ufl_handler_name_\n    dtype = args[0].dtype\n    if name in (\"conj\", \"real\") and dtype == DataType.REAL:\n        assert len(args) == 1\n        return args[0]\n    if name == \"imag\" and dtype == DataType.REAL:\n        assert len(args) == 1\n        return LiteralFloat(0.0)\n    return MathFunction(name, args)\n\n\n# Lookup table for handler to call when the ufl_to_lnodes method (below) is\n# called, depending on the first argument type.\n_ufl_call_lookup = {\n    ufl.constantvalue.IntValue: lambda x: LiteralInt(int(x)),\n    ufl.constantvalue.FloatValue: lambda x: LiteralFloat(float(x)),\n    ufl.constantvalue.ComplexValue: lambda x: LiteralFloat(x.value()),\n    ufl.constantvalue.Zero: lambda x: LiteralFloat(0.0),\n    ufl.algebra.Product: lambda x, a, b: a * b,\n    ufl.algebra.Sum: lambda x, a, b: a + b,\n    ufl.algebra.Division: lambda x, a, b: a / b,\n    ufl.algebra.Abs: _math_function,\n    ufl.algebra.Power: _math_function,\n    ufl.algebra.Real: _math_function,\n    ufl.algebra.Imag: _math_function,\n    ufl.algebra.Conj: _math_function,\n    ufl.classes.GT: lambda x, a, b: GT(a, b),\n    ufl.classes.GE: lambda x, a, b: GE(a, b),\n    ufl.classes.EQ: lambda x, a, b: EQ(a, b),\n    ufl.classes.NE: lambda x, a, b: NE(a, b),\n    ufl.classes.LT: lambda x, a, b: LT(a, b),\n    ufl.classes.LE: lambda x, a, b: LE(a, b),\n    ufl.classes.AndCondition: lambda x, a, b: And(a, b),\n    ufl.classes.OrCondition: lambda x, a, b: Or(a, b),\n    ufl.classes.NotCondition: lambda x, a: Not(a),\n    ufl.classes.Conditional: lambda x, c, t, f: Conditional(c, t, f),\n    ufl.classes.MinValue: _math_function,\n    ufl.classes.MaxValue: _math_function,\n    ufl.mathfunctions.Sqrt: _math_function,\n    ufl.mathfunctions.Ln: _math_function,\n    ufl.mathfunctions.Exp: _math_function,\n    ufl.mathfunctions.Cos: _math_function,\n    ufl.mathfunctions.Sin: _math_function,\n    ufl.mathfunctions.Tan: _math_function,\n    ufl.mathfunctions.Cosh: _math_function,\n    ufl.mathfunctions.Sinh: _math_function,\n    ufl.mathfunctions.Tanh: _math_function,\n    ufl.mathfunctions.Acos: _math_function,\n    ufl.mathfunctions.Asin: _math_function,\n    ufl.mathfunctions.Atan: _math_function,\n    ufl.mathfunctions.Erf: _math_function,\n    ufl.mathfunctions.Atan2: _math_function,\n    ufl.mathfunctions.MathFunction: _math_function,\n    ufl.mathfunctions.BesselJ: _math_function,\n    ufl.mathfunctions.BesselY: _math_function,\n}\n\n\ndef ufl_to_lnodes(operator, *args):\n    \"\"\"Call appropriate handler, depending on the type of operator.\"\"\"\n    optype = type(operator)\n    if optype in _ufl_call_lookup:\n        return _ufl_call_lookup[optype](operator, *args)\n    else:\n        raise RuntimeError(f\"Missing lookup for expr type {optype}.\")\n\n\ndef create_nested_for_loops(indices: list[MultiIndex], body):\n    \"\"\"Create nested for loops over list of indices.\n\n    The depth of the nested for loops is equal to the sub-indices for all\n    MultiIndex combined.\n    \"\"\"\n    ranges = [r for idx in indices for r in idx.sizes]\n    indices = [idx.local_index(i) for idx in indices for i in range(len(idx.sizes))]\n    depth = len(ranges)\n    for i in reversed(range(depth)):\n        body = ForRange(indices[i], 0, ranges[i], body=[body])\n    return body\n"
  },
  {
    "path": "ffcx/codegeneration/numba/__init__.py",
    "content": "\"\"\"Generation of numba code.\"\"\"\n\nfrom typing import TYPE_CHECKING\n\nfrom ffcx.codegeneration import interface\nfrom ffcx.codegeneration.numba import expression, file, form, integral\n\nfrom .formatter import Formatter\n\n__all__ = [\n    \"Formatter\",\n    \"expression\",\n    \"file\",\n    \"form\",\n    \"integral\",\n]\n\nif TYPE_CHECKING:\n    # ensure protocol compliance\n    import numpy as np\n\n    _formatter: interface.Formatter = Formatter(np.float64)\n    _expression: interface.expression_generator = expression.generator\n    _file: interface.file_generator = file.generator\n    _form: interface.form_generator = form.generator\n    _integral: interface.integral_generator = integral.generator\n"
  },
  {
    "path": "ffcx/codegeneration/numba/expression.py",
    "content": "# Copyright (C) 2019-2025 Michal Habera, Chris Richardson and Paul T. Kühner\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Generate UFCx code for an expression.\"\"\"\n\nimport logging\n\nimport numpy as np\nimport numpy.typing as npt\n\nfrom ffcx.codegeneration.backend import FFCXBackend\nfrom ffcx.codegeneration.common import template_keys, tensor_sizes\nfrom ffcx.codegeneration.expression_generator import ExpressionGenerator\nfrom ffcx.codegeneration.numba import expression_template\nfrom ffcx.codegeneration.numba.formatter import Formatter\nfrom ffcx.ir.representation import ExpressionIR\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef generator(ir: ExpressionIR, options: dict[str, int | float | npt.DTypeLike]) -> tuple[str]:\n    \"\"\"Generate UFCx code for an expression.\"\"\"\n    logger.info(\"Generating code for expression:\")\n    assert len(ir.expression.integrand) == 1, \"Expressions only support single quadrature rule\"\n    points = next(iter(ir.expression.integrand))[1].points\n    logger.info(f\"--- points: {points}\")\n    factory_name = ir.expression.name\n    logger.info(f\"--- name: {factory_name}\")\n\n    backend = FFCXBackend(ir, options)\n    eg = ExpressionGenerator(ir, backend)\n\n    d: dict[str, str | int] = {}\n    d[\"name_from_uflfile\"] = ir.name_from_uflfile\n    d[\"factory_name\"] = factory_name\n    parts = eg.generate()\n\n    # tabulate_expression\n    sizes = tensor_sizes(ir)\n    header = f\"\"\"\n    A = numba.carray(_A, ({sizes.A}))\n    w = numba.carray(_w, ({sizes.w}))\n    c = numba.carray(_c, ({sizes.c}))\n    coordinate_dofs = numba.carray(_coordinate_dofs, ({sizes.coords}))\n    entity_local_index = numba.carray(_entity_local_index, ({sizes.local_index}))\n    quadrature_permutation = numba.carray(_quadrature_permutation, ({sizes.permutation}))\n    \"\"\"\n    format = Formatter(options[\"scalar_type\"])  # type: ignore\n    body = format(parts)\n    body = \"\\n\".join([\"    \" + line for line in body.split(\"\\n\")])\n\n    d[\"tabulate_expression\"] = header + body\n\n    # TODO: original_coefficient_positions_init\n    originals = \", \".join(str(i) for i in ir.original_coefficient_positions)\n    d[\"original_coefficient_positions\"] = f\"[{originals}]\"\n\n    # TODO: points_init\n    d[\"points\"] = f\"[{', '.join(str(p) for p in points.flatten())}]\"\n\n    # TODO: value_shape_init\n    shape = \", \".join(str(i) for i in ir.expression.shape)\n    d[\"value_shape\"] = f\"[{shape}]\"\n    d[\"num_components\"] = int(np.prod(ir.expression.shape, dtype=np.int32))\n    d[\"num_coefficients\"] = len(ir.expression.coefficient_numbering)\n    d[\"num_constants\"] = len(ir.constant_names)\n    d[\"num_points\"] = points.shape[0]\n    d[\"entity_dimension\"] = points.shape[1]\n\n    d[\"rank\"] = len(ir.expression.tensor_shape)\n\n    # TODO: coefficient_names_init\n    names = \", \".join(f'\"{name}\"' for name in ir.coefficient_names)\n    d[\"coefficient_names\"] = f\"[{names}]\"\n\n    # TODO: constant_names_init\n    names = \", \".join(f'\"{name}\"' for name in ir.constant_names)\n    d[\"constant_names\"] = f\"[{names}]\"\n\n    d[\"coordinate_element_hash\"] = ir.expression.coordinate_element_hash\n\n    assert set(d.keys()) == template_keys(expression_template.factory)\n    return (expression_template.factory.format_map(d),)\n"
  },
  {
    "path": "ffcx/codegeneration/numba/expression_template.py",
    "content": "# Copyright (C) 2019-2025 Michal Habera, Chris Richardson and Paul T.Kühner\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Template for expression output.\"\"\"\n\nfactory = \"\"\"\n# Code for expression {factory_name}\n\ndef tabulate_tensor_{factory_name}(_A, _w, _c, _coordinate_dofs,\n                                   _entity_local_index,\n                                   _quadrature_permutation, custom_data):\n{tabulate_expression}\n\n\n\nclass {factory_name}:\n    tabulate_tensor = tabulate_tensor_{factory_name}\n    num_coefficients = {num_coefficients}\n    num_constants = {num_constants}\n    original_coefficient_positions = {original_coefficient_positions}\n    coefficient_names = {coefficient_names}\n    constant_names = {constant_names}\n    num_points = {num_points}\n    entity_dimension = {entity_dimension}\n    points = {points}\n    value_shape = {value_shape}\n    num_components = {num_components}\n    rank = {rank}\n    coordinate_element_hash = {coordinate_element_hash}\n\n# Alias name\n{name_from_uflfile} = {factory_name}\n\n# End of code for expression {factory_name}\n\"\"\"\n"
  },
  {
    "path": "ffcx/codegeneration/numba/file.py",
    "content": "# Copyright (C) 2009-2025 Anders Logg, Martin Sandve Alnæs, Garth N. Wells, Chris Richardson and\n# Paul T. Kühner\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n#\n# Note: Most of the code in this file is a direct translation from the\n# old implementation in FFC\n\"\"\"Generate file output for numba.\"\"\"\n\nimport logging\nimport pprint\nimport textwrap\n\nfrom numpy import typing as npt\n\nfrom ffcx import __version__ as FFCX_VERSION\nfrom ffcx.codegeneration import __version__ as UFC_VERSION\nfrom ffcx.codegeneration.common import template_keys\nfrom ffcx.codegeneration.numba import file_template\n\nsuffixes = (\"_numba.py\",)\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef generator(\n    options: dict[str, int | float | npt.DTypeLike],\n) -> tuple[tuple[str], tuple[str]]:\n    \"\"\"Generate UFCx code for file output.\n\n    Args:\n        options: Dict of options specified the kenerl generation, these will be documented in the\n        generated file.\n\n    Returns: tuple of file start- and end sections, each for declaration and implementation.\n\n    \"\"\"\n    logger.info(\"Generating code for file\")\n\n    # Attributes\n    d = {\"ffcx_version\": FFCX_VERSION, \"ufcx_version\": UFC_VERSION}\n    d[\"options\"] = textwrap.indent(pprint.pformat(options), \"#  \")\n\n    assert set(d.keys()) == template_keys(file_template.factory)\n    return (file_template.factory.format_map(d),), (\"\",)\n"
  },
  {
    "path": "ffcx/codegeneration/numba/file_template.py",
    "content": "# Copyright (C) 2025 Chris Richardson and Paul T. Kühner\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Template for file output.\"\"\"\n\nfactory = \"\"\"\n# This code conforms with the UFCx specification version {ufcx_version}\n# and was automatically generated by FFCx version {ffcx_version}.\n#\n# WARNING: The numba backend is not production ready. Sub-optimal performance compared to the\n#          C-backend is to be expected.\n#\n# This code was generated with the following options:\n#\n{options}\n\nimport numba\nimport numpy as np\nimport math\n\n# ufcx enums\ninterval = 10\ntriangle = 20\nquadrilateral = 30\ntetrahedron = 40\nhexahedron = 50\nvertex = 60\nprism = 70\npyramid = 80\n\ncell = 0\nexterior_facet = 1\ninterior_facet = 2\n\nufcx_basix_element = 0\nufcx_mixed_element = 1\nufcx_quadrature_element = 2\nufcx_basix_custom_element = 3\n\n\"\"\"\n"
  },
  {
    "path": "ffcx/codegeneration/numba/form.py",
    "content": "# Copyright (C) 2009-2025 Anders Logg, Martin Sandve Alnæs, Chris Richardson and Paul T. Kühner\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\n# Note: Most of the code in this file is a direct translation from the\n# old implementation in FFC\n\"\"\"Template for form output.\"\"\"\n\nimport logging\n\nfrom numpy import typing as npt\n\nfrom ffcx.codegeneration.common import integral_data, template_keys\nfrom ffcx.codegeneration.numba import form_template\nfrom ffcx.ir.representation import FormIR\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef generator(ir: FormIR, options: dict[str, int | float | npt.DTypeLike]) -> tuple[str]:\n    \"\"\"Generate UFCx code for a form.\"\"\"\n    logger.info(\"Generating code for form:\")\n    logger.info(f\"--- rank: {ir.rank}\")\n    logger.info(f\"--- name: {ir.name}\")\n\n    d: dict[str, int | str] = {}\n    d[\"factory_name\"] = ir.name\n    d[\"name_from_uflfile\"] = ir.name_from_uflfile\n    d[\"signature\"] = f'\"{ir.signature}\"'\n    d[\"rank\"] = ir.rank\n    d[\"num_coefficients\"] = ir.num_coefficients\n\n    if len(ir.original_coefficient_positions) > 0:\n        values = \", \".join(str(i) for i in ir.original_coefficient_positions)\n\n        d[\"original_coefficient_position_init\"] = (\n            f\"original_coefficient_position_{ir.name} = [{values}]\"\n        )\n        d[\"original_coefficient_positions\"] = f\"original_coefficient_position_{ir.name}\"\n    else:\n        d[\"original_coefficient_position_init\"] = \"\"\n        d[\"original_coefficient_positions\"] = \"None\"\n\n    if len(ir.coefficient_names) > 0:\n        values = \", \".join(f'\"{name}\"' for name in ir.coefficient_names)\n        d[\"coefficient_names_init\"] = f\"coefficient_names_{ir.name} = [{values}]\"\n        d[\"coefficient_names\"] = f\"coefficient_names_{ir.name}\"\n    else:\n        d[\"coefficient_names_init\"] = \"\"\n        d[\"coefficient_names\"] = \"None\"\n\n    d[\"num_constants\"] = ir.num_constants\n    if ir.num_constants > 0:\n        d[\"constant_ranks_init\"] = f\"constant_ranks_{ir.name} = [{str(ir.constant_ranks)[1:-1]}]\"\n        d[\"constant_ranks\"] = f\"constant_ranks_{ir.name}\"\n\n        shapes = [\n            f\"constant_shapes_{ir.name}_{i} = [{str(shape)[1:-1]}]\"\n            for i, shape in enumerate(ir.constant_shapes)\n            if len(shape) > 0\n        ]\n        names = [f\"constant_shapes_{ir.name}_{i}\" for i in range(ir.num_constants)]\n        shapes1 = f\"constant_shapes_{ir.name} = [\"\n        for rank, name in zip(ir.constant_ranks, names):\n            if rank > 0:\n                shapes1 += f\"{name},\\n\"\n            else:\n                shapes1 += \"None,\\n\"\n        shapes1 += \"]\"\n        shapes.append(shapes1)\n\n        d[\"constant_shapes_init\"] = \"\\n\".join(shapes)\n        d[\"constant_shapes\"] = f\"constant_shapes_{ir.name}\"\n    else:\n        d[\"constant_ranks_init\"] = \"\"\n        d[\"constant_ranks\"] = \"None\"\n        d[\"constant_shapes_init\"] = \"\"\n        d[\"constant_shapes\"] = \"None\"\n\n    if len(ir.constant_names) > 0:\n        values = \", \".join(f'\"{name}\"' for name in ir.constant_names)\n        d[\"constant_names_init\"] = f\"constant_names_{ir.name} = [{values}]\"\n        d[\"constant_names\"] = f\"constant_names_{ir.name}\"\n    else:\n        d[\"constant_names_init\"] = \"\"\n        d[\"constant_names\"] = \"None\"\n\n    if len(ir.finite_element_hashes) > 0:\n        d[\"finite_element_hashes\"] = f\"finite_element_hashes_{ir.name}\"\n        values = \", \".join(f\"{0 if el is None else el}\" for el in ir.finite_element_hashes)\n        d[\"finite_element_hashes_init\"] = f\"finite_element_hashes_{ir.name} = [{values}]\"\n    else:\n        d[\"finite_element_hashes\"] = \"None\"\n        d[\"finite_element_hashes_init\"] = \"\"\n\n    integrals = integral_data(ir)\n\n    if len(integrals.names) > 0:\n        values = \", \".join(\n            [\n                f\"{name}_{domain.name}\"\n                for name, domains in zip(integrals.names, integrals.domains)\n                for domain in domains\n            ]\n        )\n        d[\"form_integrals_init\"] = f\"form_integrals_{ir.name} = [{values}]\"\n        d[\"form_integrals\"] = f\"form_integrals_{ir.name}\"\n        values = \", \".join(\n            f\"{i}\" for i, domains in zip(integrals.ids, integrals.domains) for _ in domains\n        )\n        d[\"form_integral_ids_init\"] = f\"form_integral_ids_{ir.name} = [{values}]\"\n        d[\"form_integral_ids\"] = f\"form_integral_ids_{ir.name}\"\n    else:\n        d[\"form_integrals_init\"] = \"\"\n        d[\"form_integrals\"] = \"None\"\n        d[\"form_integral_ids_init\"] = \"\"\n        d[\"form_integral_ids\"] = \"None\"\n\n    values = \", \".join(str(i) for i in integrals.offsets)\n    d[\"form_integral_offsets_init\"] = f\"form_integral_offsets_{ir.name} = [{values}]\"\n\n    # Format implementation code\n    assert set(d.keys()) == template_keys(form_template.factory)\n    return (form_template.factory.format_map(d),)\n"
  },
  {
    "path": "ffcx/codegeneration/numba/form_template.py",
    "content": "# Copyright (C) 2025 Chris Richardson\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Template for file output.\"\"\"\n\nfactory = \"\"\"\n# Code for form {factory_name}\n\n{original_coefficient_position_init}\n{finite_element_hashes_init}\n{form_integral_offsets_init}\n{form_integrals_init}\n{form_integral_ids_init}\n\n{coefficient_names_init}\n{constant_names_init}\n{constant_ranks_init}\n{constant_shapes_init}\n\nclass {factory_name}(object):\n\n  signature = {signature}\n  rank = {rank}\n\n  num_coefficients = {num_coefficients}\n  original_coefficient_positions = {original_coefficient_positions}\n  coefficient_name_map = {coefficient_names}\n\n  num_constants = {num_constants}\n  constant_ranks = {constant_ranks}\n  constant_shapes = {constant_shapes}\n  constant_name_map = {constant_names}\n\n  finite_element_hashes = {finite_element_hashes}\n\n  form_integrals = {form_integrals}\n  form_integral_ids = {form_integral_ids}\n  form_integral_offsets = form_integral_offsets_{factory_name}\n\n\n# Alias name\n{name_from_uflfile} = {factory_name}\n\n# End of code for form {factory_name}\n\"\"\"\n"
  },
  {
    "path": "ffcx/codegeneration/numba/formatter.py",
    "content": "# Copyright (C) 2025 Chris Richardson and Paul T. Kühner\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Numba implementation for output.\"\"\"\n\nfrom functools import singledispatchmethod\n\nimport numpy as np\nfrom numpy import typing as npt\n\nimport ffcx.codegeneration.lnodes as L\nfrom ffcx.codegeneration.interface import Formatter as FormatterInterface\nfrom ffcx.codegeneration.utils import dtype_to_scalar_dtype\n\n\ndef build_initializer_lists(values: npt.NDArray) -> str:\n    \"\"\"Build list of values.\"\"\"\n    arr = \"[\"\n    if len(values.shape) == 1:\n        return \"[\" + \", \".join(str(v) for v in values) + \"]\"\n    elif len(values.shape) > 1:\n        arr += \",\\n\".join(build_initializer_lists(v) for v in values)\n    arr += \"]\"\n    return arr\n\n\nclass Formatter(FormatterInterface):\n    \"\"\"Implementation for numba output backend.\"\"\"\n\n    scalar_type: np.dtype\n    real_type: np.dtype\n\n    def __init__(self, dtype: npt.DTypeLike) -> None:\n        \"\"\"Initialise.\"\"\"\n        self.scalar_type = np.dtype(dtype)\n        self.real_type = dtype_to_scalar_dtype(dtype)\n\n    def _dtype_to_name(self, dtype: L.DataType) -> str:\n        \"\"\"Convert dtype to Python name.\"\"\"\n        if dtype == L.DataType.SCALAR:\n            return f\"np.{self.scalar_type}\"\n        if dtype == L.DataType.REAL:\n            return f\"np.{self.real_type}\"\n        if dtype == L.DataType.INT:\n            return f\"np.{np.int32}\"\n        if dtype == L.DataType.BOOL:\n            return f\"np.{np.bool}\"\n        raise ValueError(f\"Invalid dtype: {dtype}\")\n\n    @singledispatchmethod\n    def __call__(self, obj: L.LNode) -> str:\n        \"\"\"Format an L Node.\"\"\"\n        raise NotImplementedError(f\"Can not format object to type {type(obj)}\")\n\n    @__call__.register\n    def _(self, section: L.Section) -> str:\n        \"\"\"Format a section.\"\"\"\n        # add new line before section\n        comments = self._format_comment_str(\"------------------------\")\n        comments += self._format_comment_str(f\"Section: {section.name}\")\n        comments += self._format_comment_str(f\"Inputs: {', '.join(w.name for w in section.input)}\")\n        comments += self._format_comment_str(\n            f\"Outputs: {', '.join(w.name for w in section.output)}\"\n        )\n        declarations = \"\".join(self(s) for s in section.declarations)\n\n        body = \"\"\n        if len(section.statements) > 0:\n            body = \"\".join(self(s) for s in section.statements)\n\n        body += self._format_comment_str(\"------------------------\")\n        return comments + declarations + body\n\n    @__call__.register\n    def _(self, slist: L.StatementList) -> str:\n        \"\"\"Format a list of statements.\"\"\"\n        return \"\".join(self(s) for s in slist.statements)\n\n    def _format_comment_str(self, comment: str) -> str:\n        \"\"\"Format str to comment string.\"\"\"\n        return f\"# {comment} \\n\"\n\n    @__call__.register\n    def _(self, c: L.Comment) -> str:\n        \"\"\"Format a comment.\"\"\"\n        return self._format_comment_str(c.comment)\n\n    @__call__.register\n    def _(self, arr: L.ArrayDecl) -> str:\n        \"\"\"Format an array declaration.\"\"\"\n        dtype = arr.symbol.dtype\n        typename = self._dtype_to_name(dtype)\n\n        symbol = self(arr.symbol)\n        if arr.values is None:\n            return f\"{symbol} = np.empty({arr.sizes}, dtype={typename})\\n\"\n        elif arr.values.size == 1:\n            return f\"{symbol} = np.full({arr.sizes}, {arr.values[0]}, dtype={typename})\\n\"\n        av = build_initializer_lists(arr.values)\n        av = f\"np.array({av}, dtype={typename})\"\n        return f\"{symbol} = {av}\\n\"\n\n    @__call__.register\n    def _(self, arr: L.ArrayAccess) -> str:\n        \"\"\"Format array access.\"\"\"\n        array = self(arr.array)\n        idx = \", \".join(self(ix) for ix in arr.indices)\n        return f\"{array}[{idx}]\"\n\n    @__call__.register\n    def _(self, index: L.MultiIndex) -> str:\n        \"\"\"Format a multi-index.\"\"\"\n        return self(index.global_index)\n\n    @__call__.register\n    def _(self, v: L.VariableDecl) -> str:\n        \"\"\"Format a variable declaration.\"\"\"\n        sym = self(v.symbol)\n        val = self(v.value)\n        return f\"{sym} = {val}\\n\"\n\n    @__call__.register\n    def _(self, oper: L.NaryOp) -> str:\n        \"\"\"Format a n argument operation.\"\"\"\n        # Format children\n        args = [self(arg) for arg in oper.args]\n\n        # Apply parentheses\n        for i in range(len(args)):\n            if oper.args[i].precedence >= oper.precedence:\n                args[i] = f\"({args[i]})\"\n\n        # Return combined string\n        return f\" {oper.op} \".join(args)\n\n    @__call__.register\n    def _(self, oper: L.BinOp) -> str:\n        \"\"\"Format a binary operation.\"\"\"\n        # Format children\n        lhs = self(oper.lhs)\n        rhs = self(oper.rhs)\n\n        # Apply parentheses\n        if oper.lhs.precedence >= oper.precedence:\n            lhs = f\"({lhs})\"\n        if oper.rhs.precedence >= oper.precedence:\n            rhs = f\"({rhs})\"\n\n        # Return combined string\n        return f\"{lhs} {oper.op} {rhs}\"\n\n    @__call__.register(L.Neg)\n    @__call__.register(L.Not)\n    def _(self, oper: L.Not | L.Neg) -> str:\n        \"\"\"Format a unary operation.\"\"\"\n        arg = self(oper.arg)\n        if oper.arg.precedence >= oper.precedence:\n            return f\"{oper.op}({arg})\"\n        return f\"{oper.op}{arg}\"\n\n    @__call__.register(L.And)\n    @__call__.register(L.Or)\n    def _(self, oper: L.And | L.Or) -> str:\n        \"\"\"Format and or or operation.\"\"\"\n        # Format children\n        lhs = self(oper.lhs)\n        rhs = self(oper.rhs)\n\n        # Apply parentheses\n        if oper.lhs.precedence >= oper.precedence:\n            lhs = f\"({lhs})\"\n        if oper.rhs.precedence >= oper.precedence:\n            rhs = f\"({rhs})\"\n\n        opstr = {\"||\": \"or\", \"&&\": \"and\"}[oper.op]\n\n        # Return combined string\n        return f\"{lhs} {opstr} {rhs}\"\n\n    @__call__.register\n    def _(self, val: L.LiteralFloat) -> str:\n        \"\"\"Format a literal float.\"\"\"\n        return f\"{val.value}\"\n\n    @__call__.register\n    def _(self, val: L.LiteralInt) -> str:\n        \"\"\"Format a literal int.\"\"\"\n        return f\"{val.value}\"\n\n    @__call__.register\n    def _(self, r: L.ForRange) -> str:\n        \"\"\"Format a loop over a range.\"\"\"\n        begin = self(r.begin)\n        end = self(r.end)\n        index = self(r.index)\n        output = f\"for {index} in range({begin}, {end}):\\n\"\n        b = self(r.body).split(\"\\n\")\n        for line in b:\n            output += f\"    {line}\\n\"\n        return output\n\n    @__call__.register\n    def _(self, s: L.Statement) -> str:\n        \"\"\"Format a statement.\"\"\"\n        return self(s.expr)\n\n    @__call__.register(L.Assign)\n    @__call__.register(L.AssignAdd)\n    def _(self, expr: L.Assign | L.AssignAdd) -> str:\n        \"\"\"Format an assignment.\"\"\"\n        rhs = self(expr.rhs)\n        lhs = self(expr.lhs)\n        return f\"{lhs} {expr.op} {rhs}\\n\"\n\n    @__call__.register\n    def _(self, s: L.Conditional) -> str:\n        \"\"\"Format a conditional.\"\"\"\n        # Format children\n        c = self(s.condition)\n        t = self(s.true)\n        f = self(s.false)\n\n        # Apply parentheses\n        if s.condition.precedence >= s.precedence:\n            c = f\"({c})\"\n        if s.true.precedence >= s.precedence:\n            t = f\"({t})\"\n        if s.false.precedence >= s.precedence:\n            f = f\"({f})\"\n\n        # Return combined string\n        return f\"({t} if {c} else {f})\"\n\n    @__call__.register\n    def _(self, s: L.Symbol) -> str:\n        \"\"\"Format a symbol.\"\"\"\n        return f\"{s.name}\"\n\n    @__call__.register\n    def _(self, f: L.MathFunction) -> str:\n        \"\"\"Format a math function.\"\"\"\n        function_map = {\n            \"ln\": \"log\",\n            \"acos\": \"arccos\",\n            \"asin\": \"arcsin\",\n            \"atan\": \"arctan\",\n            \"atan2\": \"arctan2\",\n            \"acosh\": \"arccosh\",\n            \"asinh\": \"arcsinh\",\n            \"atanh\": \"arctanh\",\n        }\n        function = function_map.get(f.function, f.function)\n        args = [self(arg) for arg in f.args]\n        if \"bessel_y\" in function:\n            return \"scipy.special.yn\"\n        if \"bessel_j\" in function:\n            return \"scipy.special.jn\"\n        if function == \"erf\":\n            return f\"math.erf({args[0]})\"\n        argstr = \", \".join(args)\n        return f\"np.{function}({argstr})\"\n"
  },
  {
    "path": "ffcx/codegeneration/numba/integral.py",
    "content": "# Copyright (C) 2015-2021 Martin Sandve Alnæs, Michal Habera, Igor Baratta, Chris Richardson and\n# Paul T. Kühner\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Generate UFCx code for an integral.\"\"\"\n\nimport logging\n\nimport basix\nfrom numpy import typing as npt\n\nfrom ffcx.codegeneration.backend import FFCXBackend\nfrom ffcx.codegeneration.common import template_keys, tensor_sizes\nfrom ffcx.codegeneration.integral_generator import IntegralGenerator\nfrom ffcx.codegeneration.numba import integral_template as ufcx_integrals\nfrom ffcx.codegeneration.numba.formatter import Formatter\nfrom ffcx.ir.representation import IntegralIR\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef generator(\n    ir: IntegralIR, domain: basix.CellType, options: dict[str, int | float | npt.DTypeLike]\n) -> tuple[str]:\n    \"\"\"Generate numba code for an integral.\n\n    Args:\n        ir: IR of the integral\n        domain: basix cell type\n        options: dict of kernel generation options\n\n    Returns:\n        Tuple of declaration (header) and implementation (source) strings.\n\n    Note:\n        numba backend only provides a declaration. Implementation string will always be empty.\n\n    \"\"\"\n    logger.info(\"Generating code for integral:\")\n    logger.info(f\"--- type: {ir.expression.integral_type}\")\n    logger.info(f\"--- name: {ir.expression.name}\")\n\n    factory_name = f\"{ir.expression.name}_{domain.name}\"\n\n    # Create FFCx backend\n    backend = FFCXBackend(ir, options)\n\n    # Configure kernel generator\n    ig = IntegralGenerator(ir, backend)\n\n    # Generate code AST for the tabulate_tensor body\n    parts = ig.generate(domain)\n\n    # Format code as string\n    format = Formatter(options[\"scalar_type\"])  # type: ignore\n    body = format(parts)\n    body = \"\\n\".join([\"    \" + line for line in body.split(\"\\n\")])\n\n    # Generate generic FFCx code snippets and add specific parts\n    d: dict[str, str] = {}\n\n    d[\"factory_name\"] = factory_name\n\n    # TODO: enabled_coefficients_init - required?\n    vals = \", \".join(\"1\" if i else \"0\" for i in ir.enabled_coefficients)\n    d[\"enabled_coefficients\"] = f\"[{vals}]\"\n\n    # tabulate_tensor\n    # Note: In contrast to the C implementation we actually need to provide/compute the sizes of the\n    #       array.\n    sizes = tensor_sizes(ir)\n    header = f\"\"\"\n    A = numba.carray(_A, ({sizes.A}))\n    w = numba.carray(_w, ({sizes.w}))\n    c = numba.carray(_c, ({sizes.c}))\n    coordinate_dofs = numba.carray(_coordinate_dofs, ({sizes.coords}))\n    entity_local_index = numba.carray(_entity_local_index, ({sizes.local_index}))\n    quadrature_permutation = numba.carray(_quadrature_permutation, ({sizes.permutation}))\n    \"\"\"\n    d[\"tabulate_tensor\"] = header + body\n    d[\"needs_facet_permutations\"] = \"True\" if ir.expression.needs_facet_permutations else \"False\"\n    d[\"coordinate_element_hash\"] = ir.expression.coordinate_element_hash\n    d[\"domain\"] = str(int(domain))\n\n    assert ir.expression.coordinate_element_hash is not None\n\n    assert set(d.keys()) == template_keys(ufcx_integrals.factory)\n    return (ufcx_integrals.factory.format_map(d),)\n"
  },
  {
    "path": "ffcx/codegeneration/numba/integral_template.py",
    "content": "# Copyright (C) 2025 Chris Richardson and Paul T. Kühner\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Template for integral output.\"\"\"\n\nfactory = \"\"\"\n# Code for integral {factory_name}\n\ndef tabulate_tensor_{factory_name}(_A, _w, _c, _coordinate_dofs,\n                                   _entity_local_index, _quadrature_permutation, custom_data):\n{tabulate_tensor}\n\nclass {factory_name}(object):\n    enabled_coefficients = {enabled_coefficients}\n    tabulate_tensor = tabulate_tensor_{factory_name}\n    needs_facet_permutations = {needs_facet_permutations}\n    coordinate_element_hash = {coordinate_element_hash}\n    domain = {domain}\n\n# End of code for integral {factory_name}\n\"\"\"\n"
  },
  {
    "path": "ffcx/codegeneration/optimizer.py",
    "content": "\"\"\"Optimizer.\"\"\"\n\nfrom collections import defaultdict\n\nimport ffcx.codegeneration.lnodes as L\nfrom ffcx.ir.representationutils import QuadratureRule\n\n\ndef optimize(code: list[L.LNode], quadrature_rule: QuadratureRule) -> list[L.LNode]:\n    \"\"\"Optimize code.\n\n    Args:\n        code: List of LNodes to optimize.\n        quadrature_rule: TODO.\n\n    Returns:\n        Optimized list of LNodes.\n    \"\"\"\n    # Fuse sections with the same name and same annotations\n    code = fuse_sections(code, \"Coefficient\")\n    code = fuse_sections(code, \"Jacobian\")\n    for i, section in enumerate(code):\n        if isinstance(section, L.Section):\n            if L.Annotation.fuse in section.annotations:\n                section = fuse_loops(section)\n            if L.Annotation.licm in section.annotations:\n                section = licm(section, quadrature_rule)\n            code[i] = section\n\n    return code\n\n\ndef fuse_sections(code: list[L.LNode], name: str) -> list[L.LNode]:\n    \"\"\"Fuse sections with the same name.\n\n    Args:\n        code: List of LNodes to fuse.\n        name: Common name used by the sections that should be fused\n\n    Returns:\n        Fused list of LNodes.\n    \"\"\"\n    statements: list[L.LNode] = []\n    indices: list[int] = []\n    input: list[L.Symbol] = []\n    output: list[L.Symbol] = []\n    declarations: list[L.Declaration] = []\n    annotations: list[L.Annotation] = []\n\n    for i, section in enumerate(code):\n        if isinstance(section, L.Section):\n            if section.name == name:\n                declarations.extend(section.declarations)\n                statements.extend(section.statements)\n                indices.append(i)\n                input.extend(section.input)\n                output.extend(section.output)\n                annotations = section.annotations\n\n    # Remove duplicated inputs\n    input = list(set(input))\n    # Remove duplicated outputs\n    output = list(set(output))\n\n    section = L.Section(name, statements, declarations, input, output, annotations)\n\n    # Replace the first section with the fused section\n    code = code.copy()\n    if indices:\n        code[indices[0]] = section\n        # Remove the other sections\n        code = [c for i, c in enumerate(code) if i not in indices[1:]]\n\n    return code\n\n\ndef fuse_loops(code: L.Section) -> L.Section:\n    \"\"\"Fuse loops with the same range and same annotations.\n\n    Args:\n        code: List of LNodes to fuse.\n\n    Returns:\n        Fused list of LNodes.\n    \"\"\"\n    loops = defaultdict(list)\n    output_code = []\n    for statement in code.statements:\n        if isinstance(statement, L.ForRange):\n            id = (statement.index, statement.begin, statement.end)\n            loops[id].append(statement.body)\n        else:\n            output_code.append(statement)\n\n    for range, body in loops.items():\n        output_code.append(L.ForRange(*range, body))\n\n    return L.Section(code.name, output_code, code.declarations, code.input, code.output)\n\n\ndef get_statements(statement: L.Statement | L.StatementList) -> list[L.LNode]:\n    \"\"\"Get statements from a statement list.\n\n    Args:\n        statement: Statement list.\n\n    Returns:\n        List of statements.\n    \"\"\"\n    if isinstance(statement, L.StatementList):\n        return [statement.expr for statement in statement.statements]\n    else:\n        return [statement.expr]\n\n\ndef check_dependency(statement: L.Statement, index: L.Symbol) -> bool:\n    \"\"\"Check if a statement depends on a given index.\n\n    Args:\n        statement: Statement to check.\n        index: Index to check.\n\n    Returns:\n        True if statement depends on index, False otherwise.\n    \"\"\"\n    if isinstance(statement, L.ArrayAccess):\n        if index in statement.indices:\n            return True\n        else:\n            for i in statement.indices:\n                if isinstance(i, L.Sum) or isinstance(i, L.Product):\n                    if index in i.args:\n                        return True\n    elif isinstance(statement, L.Symbol):\n        return False\n    elif isinstance(statement, L.LiteralFloat) or isinstance(statement, L.LiteralInt):\n        return False\n    else:\n        raise NotImplementedError(f\"Statement {statement} not supported.\")\n\n    return False\n\n\ndef licm(section: L.Section, quadrature_rule: QuadratureRule) -> L.Section:\n    \"\"\"Perform loop invariant code motion.\n\n    Args:\n        section: List of LNodes to optimize.\n        quadrature_rule: TODO.\n\n    Returns:\n        Optimized list of LNodes.\n    \"\"\"\n    assert L.Annotation.licm in section.annotations\n\n    counter = 0\n\n    # Check depth of loops\n    depth = L.depth(section.statements[0])\n    if depth != 2:\n        return section\n\n    # Get statements in the inner loop\n    outer_loop = section.statements[0]\n    inner_loop = outer_loop.body.statements[0]\n\n    # Collect all expressions in the inner loop by corresponding RHS\n    expressions = defaultdict(list)\n    for body in inner_loop.body.statements:\n        statements = get_statements(body)\n        assert isinstance(statements, list)\n        for statement in statements:\n            assert isinstance(statement, L.AssignAdd)  # Expecting AssignAdd\n            rhs = statement.rhs\n            assert isinstance(rhs, L.Product)  # Expecting Sum\n            lhs = statement.lhs\n            assert isinstance(lhs, L.ArrayAccess)  # Expecting ArrayAccess\n            expressions[lhs].append(rhs)\n\n    pre_loop: list[L.LNode] = []\n    for lhs, rhs in expressions.items():\n        for r in rhs:\n            hoist_candidates = []\n            for arg in r.args:\n                dependency = check_dependency(arg, inner_loop.index)\n                if not dependency:\n                    hoist_candidates.append(arg)\n            if len(hoist_candidates) > 1:\n                # create new temp\n                name = f\"temp_{counter}\"\n                counter += 1\n                temp = L.Symbol(name, L.DataType.SCALAR)\n                for h in hoist_candidates:\n                    r.args.remove(h)\n                # update expression with new temp\n                r.args.append(L.ArrayAccess(temp, [outer_loop.index]))\n                # create code for hoisted term\n                size = outer_loop.end.value - outer_loop.begin.value\n                pre_loop.append(L.ArrayDecl(temp, size, [0]))\n                body = L.Assign(\n                    L.ArrayAccess(temp, [outer_loop.index]), L.Product(hoist_candidates)\n                )\n                pre_loop.append(\n                    L.ForRange(outer_loop.index, outer_loop.begin, outer_loop.end, [body])\n                )\n\n    section.statements = pre_loop + section.statements\n\n    return section\n"
  },
  {
    "path": "ffcx/codegeneration/symbols.py",
    "content": "# Copyright (C) 2011-2023 Martin Sandve Alnæs, Igor A. Baratta\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"FFCx/UFCx specific symbol naming.\"\"\"\n\nimport logging\n\nimport ufl\n\nimport ffcx.codegeneration.lnodes as L\nfrom ffcx.definitions import entity_types\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef ufcx_restriction_postfix(restriction):\n    \"\"\"Get restriction postfix.\"\"\"\n    # TODO: Get restriction postfix from somewhere central\n    if restriction == \"+\":\n        res = \"_0\"\n    elif restriction == \"-\":\n        res = \"_1\"\n    else:\n        res = \"\"\n    return res\n\n\ndef format_mt_name(basename, mt):\n    \"\"\"Format variable name for modified terminal.\"\"\"\n    access = str(basename)\n\n    # Add averaged state to name\n    if mt.averaged is not None:\n        avg = f\"_a{mt.averaged}\"\n        access += avg\n\n    # Format restriction\n    res = ufcx_restriction_postfix(mt.restriction).replace(\"_\", \"_r\")\n    access += res\n\n    # Format global derivatives\n    if mt.global_derivatives:\n        assert basename == \"J\"\n        der = f\"_deriv_{''.join(map(str, mt.global_derivatives))}\"\n        access += der\n\n    # Format local derivatives\n    if mt.local_derivatives:\n        # Convert \"listing\" derivative multindex into \"counting\" representation\n        gdim = ufl.domain.extract_unique_domain(mt.terminal).geometric_dimension\n        ld_counting = tuple(mt.local_derivatives.count(i) for i in range(gdim))\n        der = f\"_d{''.join(map(str, ld_counting))}\"\n        access += der\n\n    # Add flattened component to name\n    if mt.component:\n        comp = f\"_c{mt.flat_component}\"\n        access += comp\n\n    return access\n\n\nclass FFCXBackendSymbols:\n    \"\"\"FFCx specific symbol definitions. Provides non-ufl symbols.\"\"\"\n\n    def __init__(self, coefficient_numbering, coefficient_offsets, original_constant_offsets):\n        \"\"\"Initialise.\"\"\"\n        self.coefficient_numbering = coefficient_numbering\n        self.coefficient_offsets = coefficient_offsets\n\n        self.original_constant_offsets = original_constant_offsets\n\n        # Keep tabs on tables, so the symbols can be reused\n        self.quadrature_weight_tables = {}\n        self.element_tables = {}\n\n        # Reusing a single symbol for all quadrature loops, assumed not to be nested.\n        self.quadrature_loop_index = L.Symbol(\"iq\", dtype=L.DataType.INT)\n\n        # Symbols for the tabulate_tensor function arguments\n        self.element_tensor = L.Symbol(\"A\", dtype=L.DataType.SCALAR)\n        self.coefficients = L.Symbol(\"w\", dtype=L.DataType.SCALAR)\n        self.constants = L.Symbol(\"c\", dtype=L.DataType.SCALAR)\n        self.coordinate_dofs = L.Symbol(\"coordinate_dofs\", dtype=L.DataType.REAL)\n        self.entity_local_index = L.Symbol(\"entity_local_index\", dtype=L.DataType.INT)\n        self.quadrature_permutation = L.Symbol(\"quadrature_permutation\", dtype=L.DataType.INT)\n\n        # Index for loops over coefficient dofs, assumed to never be used in two nested loops.\n        self.coefficient_dof_sum_index = L.Symbol(\"ic\", dtype=L.DataType.INT)\n\n        # Table for chunk of custom quadrature weights (including cell measure scaling).\n        self.custom_weights_table = L.Symbol(\"weights_chunk\", dtype=L.DataType.REAL)\n\n        # Table for chunk of custom quadrature points (physical coordinates).\n        self.custom_points_table = L.Symbol(\"points_chunk\", dtype=L.DataType.REAL)\n\n    def entity(self, entity_type: entity_types, restriction):\n        \"\"\"Entity index for lookup in element tables.\"\"\"\n        if entity_type == \"cell\":\n            # Always 0 for cells (even with restriction)\n            return L.LiteralInt(0)\n\n        if entity_type == \"facet\":\n            if restriction == \"-\":\n                return self.entity_local_index[1]\n            else:\n                return self.entity_local_index[0]\n        elif entity_type == \"vertex\":\n            return self.entity_local_index[0]\n        elif entity_type == \"ridge\":\n            return self.entity_local_index[0]\n        else:\n            logger.exception(f\"Unknown entity_type {entity_type}\")\n\n    def argument_loop_index(self, iarg):\n        \"\"\"Loop index for argument iarg.\"\"\"\n        indices = [\"i\", \"j\", \"k\", \"l\"]\n        return L.Symbol(indices[iarg], dtype=L.DataType.INT)\n\n    def weights_table(self, quadrature_rule):\n        \"\"\"Table of quadrature weights.\"\"\"\n        key = f\"weights_{quadrature_rule.id()}\"\n        if key not in self.quadrature_weight_tables:\n            self.quadrature_weight_tables[key] = L.Symbol(\n                f\"weights_{quadrature_rule.id()}\", dtype=L.DataType.REAL\n            )\n        return self.quadrature_weight_tables[key]\n\n    def points_table(self, quadrature_rule):\n        \"\"\"Table of quadrature points (points on the reference integration entity).\"\"\"\n        return L.Symbol(f\"points_{quadrature_rule.id()}\", dtype=L.DataType.REAL)\n\n    def x_component(self, mt):\n        \"\"\"Physical coordinate component.\"\"\"\n        return L.Symbol(format_mt_name(\"x\", mt), dtype=L.DataType.REAL)\n\n    def J_component(self, mt):\n        \"\"\"Jacobian component.\"\"\"\n        return L.Symbol(\n            format_mt_name(f\"J{ufl.domain.extract_unique_domain(mt.expr).ufl_id()}\", mt),\n            dtype=L.DataType.REAL,\n        )\n\n    def domain_dof_access(self, dof, component, gdim, num_scalar_dofs, restriction):\n        \"\"\"Domain DOF access.\"\"\"\n        # FIXME: Add domain number or offset!\n        offset = 0\n        if restriction == \"-\":\n            offset = num_scalar_dofs * 3\n        return self.coordinate_dofs[3 * dof + component + offset]\n\n    def coefficient_dof_access(self, coefficient, dof_index):\n        \"\"\"Coefficient DOF access.\"\"\"\n        offset = self.coefficient_offsets[coefficient]\n        w = self.coefficients\n        return w[offset + dof_index]\n\n    def coefficient_dof_access_blocked(\n        self, coefficient: ufl.Coefficient, index, block_size, dof_offset\n    ):\n        \"\"\"Blocked coefficient DOF access.\"\"\"\n        coeff_offset = self.coefficient_offsets[coefficient]\n        w = self.coefficients\n        _w = L.Symbol(f\"_w_{coeff_offset}_{dof_offset}\", dtype=L.DataType.SCALAR)\n        unit_stride_access = _w[index]\n        original_access = w[coeff_offset + index * block_size + dof_offset]\n        return unit_stride_access, original_access\n\n    def coefficient_value(self, mt):\n        \"\"\"Symbol for variable holding value or derivative component of coefficient.\"\"\"\n        c = self.coefficient_numbering[mt.terminal]\n        return L.Symbol(format_mt_name(f\"w{c:d}\", mt), dtype=L.DataType.SCALAR)\n\n    def constant_index_access(self, constant, index):\n        \"\"\"Constant index access.\"\"\"\n        offset = self.original_constant_offsets[constant]\n        c = self.constants\n        return c[offset + index]\n\n    # TODO: Remove this, use table_access instead\n    def element_table(self, tabledata, entity_type: entity_types, restriction):\n        \"\"\"Get an element table.\"\"\"\n        entity = self.entity(entity_type, restriction)\n\n        if tabledata.is_uniform:\n            entity = 0\n        else:\n            entity = self.entity(entity_type, restriction)\n\n        if tabledata.is_piecewise:\n            iq = 0\n        else:\n            iq = self.quadrature_loop_index\n\n        if tabledata.is_permuted:\n            qp = self.quadrature_permutation[0]\n            if restriction == \"-\":\n                qp = self.quadrature_permutation[1]\n        else:\n            qp = 0\n\n        # Return direct access to element table, reusing symbol if possible\n        if tabledata.name not in self.element_tables:\n            self.element_tables[tabledata.name] = L.Symbol(tabledata.name, dtype=L.DataType.REAL)\n        return self.element_tables[tabledata.name][qp][entity][iq]\n"
  },
  {
    "path": "ffcx/codegeneration/ufcx.h",
    "content": "/// This is UFCx\n/// This software is released under the terms of the unlicense (see the file\n/// UNLICENSE).\n///\n/// The FEniCS Project (http://www.fenicsproject.org/) 2006-2021.\n///\n/// UFCx defines the interface between code generated by FFCx and the\n/// DOLFINx C++ library. Changes here must be reflected both in the FFCx\n/// code generation and in the DOLFINx library calls.\n\n#pragma once\n\n#include <stdbool.h>\n\n#define UFCX_VERSION_MAJOR 0\n#define UFCX_VERSION_MINOR 11\n#define UFCX_VERSION_MAINTENANCE 0\n#define UFCX_VERSION_IS_RELEASE false\n\n#if UFCX_VERSION_IS_RELEASE\n#define UFCX_VERSION                                                           \\\n  UFCX_VERSION_MAJOR \".\" UFCX_VERSION_MINOR \".\" UFCX_VERSION_MAINTENANCE\n#else\n#define UFCX_VERSION                                                           \\\n  UFCX_VERSION_MAJOR \".\" UFCX_VERSION_MINOR \".\" UFCX_VERSION_MAINTENANCE \".0\"\n#endif\n\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\"\n{\n\n#if defined(__clang__)\n#define restrict __restrict\n#elif defined(__GNUG__)\n#define restrict __restrict__\n#elif defined(_MSC_VER)\n#define restrict __restrict\n#define __STDC_NO_COMPLEX__\n#else\n#define restrict\n#endif // restrict\n#endif // __cplusplus\n\n  // <HEADER_DECL>\n\n  typedef enum\n  {\n    cell = 0,\n    exterior_facet = 1,\n    interior_facet = 2,\n    vertex = 3,\n    ridge = 4,\n  } ufcx_integral_type;\n\n  // </HEADER_DECL>\n\n  /// Tabulate integral into tensor A with compiled quadrature rule\n  ///\n  /// @param[out] A\n  /// @param[in] w Coefficients attached to the form to which the\n  /// tabulated integral belongs.\n  ///\n  /// Dimensions: w[coefficient][restriction][dof].\n  ///\n  /// Restriction dimension\n  /// applies to interior facet integrals, where coefficients restricted\n  /// to both cells sharing the facet must be provided.\n  /// @param[in] c Constants attached to the form to which the tabulated\n  /// integral belongs. Dimensions: c[constant][dim].\n  /// @param[in] coordinate_dofs Values of degrees of freedom of\n  /// coordinate element. Defines the geometry of the cell. Dimensions:\n  /// coordinate_dofs[restriction][num_dofs][3]. Restriction\n  /// dimension applies to interior facet integrals, where cell\n  /// geometries for both cells sharing the facet must be provided.\n  /// @param[in] entity_local_index Local index of mesh entity on which\n  /// to tabulate. This applies to facet integrals.\n  /// @param[in] quadrature_permutation For facet integrals, numbers to\n  /// indicate the permutation to be applied to each side of the facet\n  /// to make the orientations of the faces matched up should be passed\n  /// in. If an integer of value N is passed in, then:\n  ///\n  ///  - floor(N / 2) gives the number of rotations to apply to the\n  ///  facet\n  ///  - N % 2 gives the number of reflections to apply to the facet\n  ///\n  /// For integrals not on interior facets, this argument has no effect and a\n  /// null pointer can be passed. For interior facets the array will have size 2\n  /// (one permutation for each cell adjacent to the facet).\n  /// @param[in] custom_data Custom user data passed to the tabulate function.\n  /// For example, a struct with additional data needed for the tabulate\n  /// function. See the implementation of runtime integrals for further details.\n  typedef void(ufcx_tabulate_tensor_float32)(\n      float* restrict A, const float* restrict w, const float* restrict c,\n      const float* restrict coordinate_dofs,\n      const int* restrict entity_local_index,\n      const uint8_t* restrict quadrature_permutation, void* custom_data);\n\n  /// Tabulate integral into tensor A with compiled\n  /// quadrature rule and double precision\n  ///\n  /// @see ufcx_tabulate_tensor_single\n  typedef void(ufcx_tabulate_tensor_float64)(\n      double* restrict A, const double* restrict w, const double* restrict c,\n      const double* restrict coordinate_dofs,\n      const int* restrict entity_local_index,\n      const uint8_t* restrict quadrature_permutation, void* custom_data);\n\n#ifndef __STDC_NO_COMPLEX__\n  /// Tabulate integral into tensor A with compiled\n  /// quadrature rule and complex single precision\n  ///\n  /// @see ufcx_tabulate_tensor_single\n  typedef void(ufcx_tabulate_tensor_complex64)(\n      float _Complex* restrict A, const float _Complex* restrict w,\n      const float _Complex* restrict c, const float* restrict coordinate_dofs,\n      const int* restrict entity_local_index,\n      const uint8_t* restrict quadrature_permutation, void* custom_data);\n#endif // __STDC_NO_COMPLEX__\n\n#ifndef __STDC_NO_COMPLEX__\n  /// Tabulate integral into tensor A with compiled\n  /// quadrature rule and complex double precision\n  ///\n  /// @see ufcx_tabulate_tensor_single\n  typedef void(ufcx_tabulate_tensor_complex128)(\n      double _Complex* restrict A, const double _Complex* restrict w,\n      const double _Complex* restrict c, const double* restrict coordinate_dofs,\n      const int* restrict entity_local_index,\n      const uint8_t* restrict quadrature_permutation, void* custom_data);\n#endif // __STDC_NO_COMPLEX__\n\n  typedef struct ufcx_integral\n  {\n    const bool* enabled_coefficients;\n    ufcx_tabulate_tensor_float32* tabulate_tensor_float32;\n    ufcx_tabulate_tensor_float64* tabulate_tensor_float64;\n#ifndef __STDC_NO_COMPLEX__\n    ufcx_tabulate_tensor_complex64* tabulate_tensor_complex64;\n    ufcx_tabulate_tensor_complex128* tabulate_tensor_complex128;\n#endif // __STDC_NO_COMPLEX__\n    bool needs_facet_permutations;\n\n    /// Hash of the coordinate element associated with the geometry of the mesh.\n    uint64_t coordinate_element_hash;\n\n    uint8_t domain;\n  } ufcx_integral;\n\n  typedef struct ufcx_expression\n  {\n    /// Evaluate expression into tensor A with compiled evaluation\n    /// points.\n    ///\n    /// @param[out] A Dimensions:\n    /// `A[num_points][num_components][num_argument_dofs]`\n    ///\n    /// @see ufcx_tabulate_tensor\n    ufcx_tabulate_tensor_float32* tabulate_tensor_float32;\n    ufcx_tabulate_tensor_float64* tabulate_tensor_float64;\n#ifndef __STDC_NO_COMPLEX__\n    ufcx_tabulate_tensor_complex64* tabulate_tensor_complex64;\n    ufcx_tabulate_tensor_complex128* tabulate_tensor_complex128;\n#endif // __STDC_NO_COMPLEX__\n\n    /// Number of coefficients\n    int num_coefficients;\n\n    /// Number of constants\n    int num_constants;\n\n    /// Original coefficient position for each coefficient\n    const int* original_coefficient_positions;\n\n    /// List of names of coefficients\n    const char** coefficient_names;\n\n    /// List of names of constants\n    const char** constant_names;\n\n    /// Number of evaluation points\n    int num_points;\n\n    /// Dimension of evaluation point\n    int entity_dimension;\n\n    /// Coordinates of evaluations points. Dimensions:\n    /// `points[num_points][entity_dimension]`\n    const double* points;\n\n    /// Shape of expression. Dimension: value_shape[num_components]\n    const int* value_shape;\n\n    /// Number of components of return_shape\n    int num_components;\n\n    /// Rank, i.e. number of arguments\n    int rank;\n\n    /// Hash of the coordinate element associated with the geometry of\n    /// the mesh.\n    uint64_t coordinate_element_hash;\n  } ufcx_expression;\n\n  /// This class defines the interface for the assembly of the global\n  /// tensor corresponding to a form with r + n arguments, that is, a\n  /// mapping\n  ///\n  ///     a : V1 x V2 x ... Vr x W1 x W2 x ... x Wn -> R\n  ///\n  /// with arguments v1, v2, ..., vr, w1, w2, ..., wn. The rank r\n  /// global tensor A is defined by\n  ///\n  ///     A = a(V1, V2, ..., Vr, w1, w2, ..., wn),\n  ///\n  /// where each argument Vj represents the application to the sequence\n  /// of basis functions of Vj and w1, w2, ..., wn are given fixed\n  /// functions (coefficients).\n  typedef struct ufcx_form\n  {\n    /// String identifying the form\n    const char* signature;\n\n    /// Rank of the global tensor (r)\n    int rank;\n\n    /// Number of coefficients (n)\n    int num_coefficients;\n\n    /// Original coefficient position for each coefficient\n    int* original_coefficient_positions;\n\n    /// List of names of coefficients\n    const char** coefficient_name_map;\n\n    /// Number of constants\n    int num_constants;\n\n    /// Ranks of constants\n    const int* constant_ranks;\n\n    /// Shapes of constants\n    const int** constant_shapes;\n\n    /// List of names of constants\n    const char** constant_name_map;\n\n    /// Get the hash of the finite element for the i-th argument\n    /// function, where 0 <= i < r + n.\n    ///\n    /// @param i Argument number if 0 <= i < r Coefficient number j = i\n    /// - r if r + j <= i < r + n\n    uint64_t* finite_element_hashes;\n\n    /// List of cell, interior facet and exterior facet integrals\n    ufcx_integral** form_integrals;\n\n    /// IDs for each integral in form_integrals list\n    int* form_integral_ids;\n\n    /// Offsets for cell, interior facet and exterior facet integrals in\n    /// form_integrals list\n    int* form_integral_offsets;\n\n  } ufcx_form;\n\n#ifdef __cplusplus\n#undef restrict\n#undef __STDC_NO_COMPLEX__\n}\n#endif\n"
  },
  {
    "path": "ffcx/codegeneration/utils.py",
    "content": "# Copyright (C) 2020-2024 Michal Habera, Chris Richardson and Garth N. Wells\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Utilities.\"\"\"\n\nimport numpy as np\nimport numpy.typing as npt\n\ntry:\n    import numba\nexcept ImportError:\n    numba = None\n\n\ndef dtype_to_c_type(dtype: npt.DTypeLike | str) -> str:\n    \"\"\"For a NumPy dtype, return the corresponding C type.\n\n    Args:\n        dtype: Numpy data type,\n\n    Returns:\n        Corresponding C type.\n    \"\"\"\n    # Note: Possible aliases, e.g. numpy.longdouble, should test against char ID\n    if np.dtype(dtype).char == \"g\":\n        return \"long double\"\n    if np.dtype(dtype) == np.intc:\n        return \"int\"\n    elif np.dtype(dtype).char == \"f\":\n        return \"float\"\n    elif np.dtype(dtype).char == \"d\":\n        return \"double\"\n    elif np.dtype(dtype) == np.complex64:\n        return \"float _Complex\"\n    elif np.dtype(dtype) == np.complex128:\n        return \"double _Complex\"\n    else:\n        raise RuntimeError(f\"Unknown NumPy type for: {dtype}\")\n\n\ndef dtype_to_scalar_dtype(dtype: npt.DTypeLike | str) -> np.dtype:\n    \"\"\"For a NumPy dtype, return the corresponding real dtype.\n\n    Args:\n        dtype: Numpy data type\n\n    Returns:\n        ``numpy.dtype`` for the real component of ``dtype``.\n    \"\"\"\n    if np.issubdtype(dtype, np.floating):\n        return np.dtype(dtype)\n    elif np.issubdtype(dtype, np.complexfloating):\n        return np.dtype(dtype).type(0).real.dtype\n    elif np.issubdtype(dtype, np.integer):\n        return np.dtype(dtype)\n    else:\n        raise RuntimeError(f\"Cannot get value dtype for '{dtype}'. \")\n\n\ndef numba_ufcx_kernel_signature(dtype: npt.DTypeLike, xdtype: npt.DTypeLike):\n    \"\"\"Return a Numba C signature for the UFCx ``tabulate_tensor`` interface.\n\n    Args:\n        dtype: The scalar type for the finite element data.\n        xdtype: The geometry float type.\n\n    Returns:\n        A Numba signature (``numba.core.typing.templates.Signature``).\n\n    Raises:\n        ImportError: If ``numba`` cannot be imported.\n    \"\"\"\n    try:\n        import numba.types as types\n        from numba import from_dtype\n\n        return types.void(\n            types.CPointer(from_dtype(dtype)),\n            types.CPointer(from_dtype(dtype)),\n            types.CPointer(from_dtype(dtype)),\n            types.CPointer(from_dtype(xdtype)),\n            types.CPointer(types.intc),\n            types.CPointer(types.uint8),\n            types.CPointer(types.void),\n        )\n    except ImportError as e:\n        raise e\n\n\nif numba is not None:\n\n    @numba.extending.intrinsic\n    def empty_void_pointer(typingctx):\n        \"\"\"Custom intrinsic to return an empty void* pointer.\n\n        This function creates a void pointer initialized to null (0).\n        This is used to pass a nullptr to the UFCx tabulate_tensor interface.\n\n        Args:\n            typingctx: The typing context.\n\n        Returns:\n            A Numba signature and a code generation function that returns a void pointer.\n        \"\"\"\n\n        def codegen(context, builder, signature, args):\n            null_ptr = context.get_constant(numba.types.voidptr, 0)\n            return null_ptr\n\n        sig = numba.types.voidptr()\n        return sig, codegen\n\n    @numba.extending.intrinsic\n    def get_void_pointer(typingctx, arr):\n        \"\"\"Custom intrinsic to get a void* pointer from a NumPy array.\n\n        This function takes a NumPy array and returns a void pointer to the array's data.\n        This is used to pass custom data organised in a NumPy array\n        to the UFCx tabulate_tensor interface.\n\n        Args:\n            typingctx: The typing context.\n            arr: The NumPy array to get the void pointer to the first element from.\n            In a multi-dimensional NumPy array, the memory is laid out in a contiguous\n            block of memory, see\n            https://numpy.org/doc/stable/reference/arrays.ndarray.html#internal-memory-layout-of-an-ndarray\n\n        Returns:\n            sig: A Numba signature, which specifies the numba type (here voidptr),\n            codegen: A code generation function, which returns the LLVM IR to cast\n            the raw data pointer to the first element of the of the contiguous block of memory\n            of the NumPy array to void*.\n        \"\"\"\n        if not isinstance(arr, numba.types.Array):\n            raise TypeError(\"Expected a NumPy array\")\n\n        def codegen(context, builder, signature, args):\n            \"\"\"Generate LLVM IR code to convert a NumPy array to a void* pointer.\n\n            This function generates the necessary LLVM IR instructions to:\n            1. Allocate memory for the array on the stack.\n            2. Cast the allocated memory to a void* pointer.\n\n            Args:\n                context: The LLVM context.\n                builder: The LLVM IR builder.\n                signature: The function signature.\n                args: The input arguments (NumPy array).\n\n            Returns:\n                A void* pointer to the array's data.\n            \"\"\"\n            [arr] = args\n            raw_ptr = numba.core.cgutils.alloca_once_value(builder, arr)\n            void_ptr = builder.bitcast(raw_ptr, context.get_value_type(numba.types.voidptr))\n            return void_ptr\n\n        sig = numba.types.voidptr(arr)\n        return sig, codegen\n"
  },
  {
    "path": "ffcx/compiler.py",
    "content": "# Copyright (C) 2007-2020 Anders Logg and Michal Habera\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Main interface for compilation of forms.\n\nBreaks the compilation into several sequential stages.\nThe output of each stage is the input of the next stage.\n\nCompiler stages\n---------------\n\n0. Language, parsing\n\n   - Input:  Python code or .ufl file\n   - Output: UFL form\n\n   This stage consists of parsing and expressing a form in the UFL form\n   language. This stage is handled by UFL.\n\n1. Analysis\n\n   - Input:  UFL form\n   - Output: Preprocessed UFL form and FormData (metadata)\n\n   This stage preprocesses the UFL form and extracts form metadata. It\n   may also perform simplifications on the form.\n\n2. Code representation\n\n   - Input:  Preprocessed UFL form and FormData (metadata)\n   - Output: Intermediate Representation (IR)\n\n   This stage examines the input and generates all data needed for code\n   generation. This includes generation of finite element basis\n   functions, extraction of data for mapping of degrees of freedom and\n   possible precomputation of integrals. Most of the complexity of\n   compilation is handled in this stage.\n\n   The IR is stored as a dictionary, mapping names of UFCx functions to\n   data needed for generation of the corresponding code.\n\n3. Code generation\n\n   - Input:  Intermediate Representation (IR)\n   - Output: C code\n\n   This stage examines the IR and generates the actual C code for the\n   body of each UFCx function.\n\n   The code is stored as a dictionary, mapping names of UFCx functions to\n   strings containing the C code of the body of each function.\n\n4. Code formatting\n\n   - Input:  C code\n   - Output: C code files\n\n   This stage examines the generated C++ code and formats it according\n   to the UFCx format, generating as output one or more .h/.c files\n   conforming to the UFCx format.\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport typing\nfrom time import time\n\nimport numpy.typing as npt\n\nfrom ffcx.analysis import analyze_ufl_objects\nfrom ffcx.codegeneration.codegeneration import generate_code\nfrom ffcx.formatting import format_code\nfrom ffcx.ir.representation import compute_ir\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef _print_timing(stage: int, timing: float) -> None:\n    logger.info(f\"Compiler stage {stage} finished in {timing:.4f} seconds.\")\n\n\ndef compile_ufl_objects(\n    ufl_objects: list[typing.Any],\n    options: dict[str, int | float | npt.DTypeLike],\n    object_names: dict[int, str] | None = None,\n    namespace: str | None = None,\n    visualise: bool = False,\n) -> tuple[list[str], tuple[str, ...]]:\n    \"\"\"Generate UFCx code for a given UFL objects.\n\n    Args:\n          ufl_objects: Objects to be compiled. Accepts elements, forms,\n            integrals or coordinate mappings.\n          object_names: Map from object Python id to object name\n          namespace: Convenience namespace/prefix for generated code.\n          options: Options\n          visualise: Toggle visualisation\n\n     Returns: tuple containing list of code file strings and tuple of associated file suffixes.\n\n    \"\"\"\n    _object_names = object_names if object_names is not None else {}\n    _namespace = namespace if namespace is not None else \"\"\n\n    # Stage 1: analysis\n    cpu_time = time()\n    analysis = analyze_ufl_objects(ufl_objects, options[\"scalar_type\"])  # type: ignore\n    _print_timing(1, time() - cpu_time)\n\n    # Stage 2: intermediate representation\n    cpu_time = time()\n    ir = compute_ir(analysis, _object_names, _namespace, options, visualise)\n    _print_timing(2, time() - cpu_time)\n\n    # Stage 3: code generation\n    cpu_time = time()\n    code, suffixes = generate_code(ir, options)\n    _print_timing(3, time() - cpu_time)\n\n    # Stage 4: format code\n    cpu_time = time()\n    source = format_code(code)\n    _print_timing(4, time() - cpu_time)\n\n    return source, suffixes\n"
  },
  {
    "path": "ffcx/definitions.py",
    "content": "\"\"\"Module for storing type definitions used in the FFCx code base.\"\"\"\n\nfrom typing import Literal\n\nentity_types = Literal[\"cell\", \"facet\", \"vertex\", \"ridge\"]\n\nsupported_integral_types = Literal[\"cell\", \"interior_facet\", \"exterior_facet\", \"vertex\", \"ridge\"]\n"
  },
  {
    "path": "ffcx/element_interface.py",
    "content": "# Copyright (C) 2021 Matthew W. Scroggs and Chris Richardson\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Finite element interface.\"\"\"\n\nimport basix\nimport basix.ufl\nimport numpy as np\nimport numpy.typing as npt\nfrom basix import CellType as _CellType\n\n\ndef basix_index(indices: tuple[int]) -> int:\n    \"\"\"Get the Basix index of a derivative.\"\"\"\n    return basix.index(*indices)\n\n\ndef create_quadrature(\n    cellname: str, degree: int, rule: str, elements: list[basix.ufl._ElementBase]\n) -> tuple[npt.ArrayLike, npt.ArrayLike]:\n    \"\"\"Create a quadrature rule.\"\"\"\n    if cellname == \"vertex\":\n        return (np.ones((1, 0), dtype=np.float64), np.ones(1, dtype=np.float64))\n    else:\n        celltype = _CellType[cellname]\n        polyset_type = basix.PolysetType.standard\n        for e in elements:\n            polyset_type = basix.polyset_superset(celltype, polyset_type, e.polyset_type)\n        return basix.make_quadrature(\n            celltype, degree, rule=basix.quadrature.string_to_type(rule), polyset_type=polyset_type\n        )\n\n\ndef reference_cell_vertices(cellname: str) -> npt.NDArray[np.float64]:\n    \"\"\"Get the vertices of a reference cell.\"\"\"\n    return np.asarray(basix.geometry(_CellType[cellname]))\n\n\ndef map_facet_points(\n    points: npt.NDArray[np.float64], facet: int, cellname: str\n) -> npt.NDArray[np.float64]:\n    \"\"\"Map points from a reference facet to a physical facet.\"\"\"\n    geom = np.asarray(basix.geometry(_CellType[cellname]))\n    facet_vertices = [geom[i] for i in basix.topology(_CellType[cellname])[-2][facet]]\n    return np.asarray(\n        [\n            facet_vertices[0]\n            + sum((i - facet_vertices[0]) * j for i, j in zip(facet_vertices[1:], p))\n            for p in points\n        ],\n        dtype=np.float64,\n    )\n\n\ndef map_edge_points(\n    points: npt.NDArray[np.float64], edge: int, cellname: str\n) -> npt.NDArray[np.float64]:\n    \"\"\"Map points from a reference edge to a physical edge.\"\"\"\n    geom = np.asarray(basix.geometry(_CellType[cellname]))\n    edge_vertices = [geom[i] for i in basix.topology(_CellType[cellname])[-3][edge]]\n    return np.asarray(\n        [\n            edge_vertices[0] + sum((i - edge_vertices[0]) * j for i, j in zip(edge_vertices[1:], p))\n            for p in points\n        ],\n        dtype=np.float64,\n    )\n"
  },
  {
    "path": "ffcx/formatting.py",
    "content": "# Copyright (C) 2009-2018 Anders Logg and Garth N. Wells\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Compiler stage 5: Code formatting.\n\nThis module implements the formatting of UFCx code from a given\ndictionary of generated C++ code for the body of each UFCx function.\n\nIt relies on templates for UFCx code available as part of the module\nufcx_utils.\n\"\"\"\n\nimport logging\nfrom pathlib import Path\n\nfrom ffcx.codegeneration.codegeneration import CodeBlocks\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef format_code(code_blocks: CodeBlocks) -> list[str]:\n    \"\"\"Format given code in UFCx format.\n\n    Returns two strings with header and source file contents.\n    \"\"\"\n    logger.info(79 * \"*\")\n    logger.info(\"Compiler stage 5: Formatting code\")\n    logger.info(79 * \"*\")\n\n    code = [\"\"] * len(code_blocks[0][0])\n\n    for block in code_blocks:\n        for i in range(len(code)):\n            code[i] += \"\".join([c[i] for c in block])\n\n    return code\n\n\ndef write_code(code: list[str], prefix: str, suffixes: tuple[str, ...], output_dir: str) -> None:\n    \"\"\"Write code to files.\"\"\"\n    for source, suffix in zip(code, suffixes, strict=True):\n        with open(Path(output_dir) / (prefix + suffix), \"w\") as file:\n            file.write(source)\n"
  },
  {
    "path": "ffcx/ir/__init__.py",
    "content": "\"\"\"Intermediate representation.\"\"\"\n"
  },
  {
    "path": "ffcx/ir/analysis/__init__.py",
    "content": "# Copyright (C) 2011-2017 Martin Sandve Alnæs\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\n\"\"\"Algorithms for the analysis phase of the form compilation.\"\"\"\n"
  },
  {
    "path": "ffcx/ir/analysis/factorization.py",
    "content": "# Copyright (C) 2011-2017 Martin Sandve Alnæs\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Algorithms for factorizing argument dependent monomials.\"\"\"\n\nimport logging\nfrom functools import singledispatch\n\nfrom ufl import as_ufl, conditional\nfrom ufl.classes import Argument, Conditional, Conj, Division, Product, Sum, Zero\n\nfrom ffcx.ir.analysis.graph import ExpressionGraph\nfrom ffcx.ir.analysis.modified_terminals import analyse_modified_terminal, strip_modified_terminal\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef build_argument_indices(S):\n    \"\"\"Build ordered list of indices to modified arguments.\"\"\"\n    arg_indices = []\n    for i, v in S.nodes.items():\n        arg = strip_modified_terminal(v[\"expression\"])\n        if isinstance(arg, Argument):\n            arg_indices.append(i)\n\n    # Make a canonical ordering of vertex indices for modified arguments\n    def arg_ordering_key(i):\n        \"\"\"Return a key for sorting argument vertex indices.\n\n        Key is based on the properties of the modified terminal.\n        \"\"\"\n        mt = analyse_modified_terminal(S.nodes[i][\"expression\"])\n        return mt.argument_ordering_key()\n\n    ordered_arg_indices = sorted(arg_indices, key=arg_ordering_key)\n    return ordered_arg_indices\n\n\ndef graph_insert(F, expr):\n    \"\"\"Add new expression expr to factorisation graph or return existing index.\"\"\"\n    fi = F.e2i.get(expr)\n    if fi is None:\n        fi = F.number_of_nodes()\n        F.add_node(fi, expression=expr)\n        F.e2i[expr] = fi\n    return fi\n\n\n# Reuse these empty objects where appropriate to save memory\nnoargs = {}  # type: ignore\n\n\n@singledispatch\ndef handler(v, fac, sf, F):\n    \"\"\"Handler.\"\"\"\n    # Error checking\n    if any(fac):\n        raise RuntimeError(\n            f\"Assuming that a {type(v)} cannot be applied to arguments. \"\n            \"If this is wrong please report a bug.\"\n        )\n    # Record non-argument subexpression\n    raise RuntimeError(\"No arguments\")\n\n\n@handler.register(Sum)\ndef handle_sum(v, fac, sf, F):\n    \"\"\"Handle a sum.\"\"\"\n    if len(fac) != 2:\n        raise RuntimeError(\"Assuming binary sum here. This can be fixed if needed.\")\n\n    fac0 = fac[0]\n    fac1 = fac[1]\n    argkeys = set(fac0) | set(fac1)\n\n    if argkeys:  # f*arg + g*arg = (f+g)*arg\n        argkeys = sorted(argkeys)\n        keylen = len(argkeys[0])\n        factors = {}\n        for argkey in argkeys:\n            if len(argkey) != keylen:\n                raise RuntimeError(\"Expecting equal argument rank terms among summands.\")\n\n            fi0 = fac0.get(argkey)\n            fi1 = fac1.get(argkey)\n            if fi0 is None:\n                fisum = fi1\n            elif fi1 is None:\n                fisum = fi0\n            else:\n                f0 = F.nodes[fi0][\"expression\"]\n                f1 = F.nodes[fi1][\"expression\"]\n                fisum = graph_insert(F, f0 + f1)\n            factors[argkey] = fisum\n\n    else:  # non-arg + non-arg\n        raise RuntimeError(\"No arguments\")\n\n    return factors\n\n\n@handler.register(Product)\ndef handle_product(v, fac, sf, F):\n    \"\"\"Handle a product.\"\"\"\n    if len(fac) != 2:\n        raise RuntimeError(\"Assuming binary product here. This can be fixed if needed.\")\n    fac0 = fac[0]\n    fac1 = fac[1]\n\n    if not fac0 and not fac1:  # non-arg * non-arg\n        raise RuntimeError(\"No arguments\")\n\n    elif not fac0:  # non-arg * arg\n        # Record products of non-arg operand with each factor of arg-dependent operand\n        f0 = sf[0]\n        factors = {}\n        for k1 in sorted(fac1):\n            f1 = F.nodes[fac1[k1]][\"expression\"]\n            factors[k1] = graph_insert(F, f0 * f1)\n\n    elif not fac1:  # arg * non-arg\n        # Record products of non-arg operand with each factor of arg-dependent operand\n        f1 = sf[1]\n        factors = {}\n        for k0 in sorted(fac0):\n            f0 = F.nodes[fac0[k0]][\"expression\"]\n            factors[k0] = graph_insert(F, f1 * f0)\n\n    else:  # arg * arg\n        # Record products of each factor of arg-dependent operand\n        factors = {}\n        for k0 in sorted(fac0):\n            f0 = F.nodes[fac0[k0]][\"expression\"]\n            for k1 in sorted(fac1):\n                f1 = F.nodes[fac1[k1]][\"expression\"]\n                argkey = tuple(sorted(k0 + k1))  # sort key for canonical representation\n                factors[argkey] = graph_insert(F, f0 * f1)\n\n    return factors\n\n\n@handler.register(Conj)\ndef handle_conj(v, fac, sf, F):\n    \"\"\"Handle a conjugation.\"\"\"\n    fac = fac[0]\n    if fac:\n        factors = {}\n        for k in fac:\n            f0 = F.nodes[fac[k]][\"expression\"]\n            factors[k] = graph_insert(F, Conj(f0))\n    else:\n        raise RuntimeError(\"No arguments\")\n\n    return factors\n\n\n@handler.register(Division)\ndef handle_division(v, fac, sf, F):\n    \"\"\"Handle a division.\"\"\"\n    fac0 = fac[0]\n    fac1 = fac[1]\n    assert not fac1, \"Cannot divide by arguments.\"\n\n    if fac0:  # arg / non-arg\n        # Record products of non-arg operand with each factor of arg-dependent operand\n        f1 = sf[1]\n        factors = {}\n        for k0 in sorted(fac0):\n            f0 = F.nodes[fac0[k0]][\"expression\"]\n            factors[k0] = graph_insert(F, f0 / f1)\n\n    else:  # non-arg / non-arg\n        raise RuntimeError(\"No arguments\")\n\n    return factors\n\n\n@handler.register(Conditional)\ndef handle_conditional(v, fac, sf, F):\n    \"\"\"Handle a conditional.\"\"\"\n    fac0 = fac[0]\n    fac1 = fac[1]\n    fac2 = fac[2]\n    assert not fac0, \"Cannot have argument in condition.\"\n\n    if not (fac1 or fac2):  # non-arg ? non-arg : non-arg\n        raise RuntimeError(\"No arguments\")\n    else:\n        f0 = sf[0]\n        f1 = sf[1]\n        f2 = sf[2]\n\n        # Term conditional(c, argument, non-argument) is not legal unless non-argument is 0.0\n        assert fac1 or isinstance(f1, Zero)\n        assert fac2 or isinstance(f2, Zero)\n        assert () not in fac1\n        assert () not in fac2\n\n        z = as_ufl(0.0)\n\n        # In general, can decompose like this:\n        #    conditional(c, sum_i fi*ui, sum_j fj*uj) -> sum_i conditional(c, fi, 0)*ui\n        #    + sum_j conditional(c, 0, fj)*uj\n        mas = sorted(set(fac1.keys()) | set(fac2.keys()))\n        factors = {}\n        for k in mas:\n            fi1 = fac1.get(k)\n            fi2 = fac2.get(k)\n            f1 = z if fi1 is None else F.nodes[fi1][\"expression\"]\n            f2 = z if fi2 is None else F.nodes[fi2][\"expression\"]\n            factors[k] = graph_insert(F, conditional(f0, f1, f2))\n\n    return factors\n\n\ndef compute_argument_factorization(S, rank):\n    \"\"\"Factorize a scalar expression graph w.r.t. scalar Argument components.\n\n    Returns:\n        a triplet (AV, FV, IM), where:\n        - The scalar argument component subgraph:\n            AV[ai] = v\n          with the property\n            SV[arg_indices] == AV[:]\n\n        - An expression graph vertex list with all non-argument factors:\n            FV[fi] = f\n          with the property that none of the expressions depend on Arguments.\n        - A dict representation of the final integrand of rank r:\n            IM = { (ai1_1, ..., ai1_r): fi1, (ai2_1, ..., ai2_r): fi2, }\n          This mapping represents the factorization of SV[-1] w.r.t. Arguments s.t.:\n            SV[-1] := sum(FV[fik] * product(AV[ai] for ai in aik) for aik, fik in IM.items())\n          where := means equivalence in the mathematical sense,\n          of course in a different technical representation.\n    \"\"\"\n    # Extract argument component subgraph\n    arg_indices = build_argument_indices(S)\n    AV = [S.nodes[i][\"expression\"] for i in arg_indices]\n\n    # Data structure for building non-argument factors\n    F = ExpressionGraph()\n    # Attach a quick lookup dict for expression to index\n    F.e2i = {}\n\n    # Insert arguments as first entries in factorisation graph\n    # They will not be connected to other nodes, but will be available\n    # and referred to by the factorisation indices of the 'target' nodes.\n    for v in AV:\n        graph_insert(F, v)\n\n    # Adding 1.0 as an expression allows avoiding special representation\n    # of arguments when first visited by representing \"v\" as \"1*v\"\n    one_index = graph_insert(F, as_ufl(1.0))\n\n    # Intermediate factorization for each vertex in SV on the format\n    # SV_factors[si] = None # if SV[si] does not depend on arguments\n    # SV_factors[si] = { argkey: fi } # if SV[si] does depend on arguments, where:\n    #   FV[fi] is the expression SV[si] with arguments factored out\n    #   argkey is a tuple with indices into SV for each of the argument components SV[si] depends on\n    # SV_factors[si] = { argkey1: fi1, argkey2: fi2, ... } # if SV[si]\n    # is a linear combination of multiple argkey configurations\n\n    # Factorize each subexpression in order:\n    for si, attr in S.nodes.items():\n        deps = S.out_edges[si]\n        v = attr[\"expression\"]\n\n        if si in arg_indices:\n            assert len(deps) == 0\n            # v is a modified Argument\n            factors = {(si,): one_index}\n        else:\n            fac = [S.nodes[d][\"factors\"] for d in deps]\n            if not any(fac):\n                # Entirely scalar (i.e. no arg factors)\n                # Just add unchanged to F\n                graph_insert(F, v)\n                factors = noargs\n            else:\n                # Get scalar factors for dependencies\n                # which do not have arg factors\n                sf = []\n                for i, d in enumerate(deps):\n                    if fac[i]:\n                        sf.append(None)\n                    else:\n                        sf.append(S.nodes[d][\"expression\"])\n                # Use appropriate handler to deal with Sum, Product, etc.\n                factors = handler(v, fac, sf, F)\n\n        attr[\"factors\"] = factors\n\n    assert len(F.nodes) == len(F.e2i)\n\n    # Prepare a mapping from component of expression to factors\n    factors = {}\n    S_targets = [i for i, v in S.nodes.items() if v.get(\"target\", False)]\n\n    for S_target in S_targets:\n        # Get the factorizations of the target values\n        if S.nodes[S_target][\"factors\"] == {}:\n            if rank == 0:\n                # Functionals and expressions: store as no args * factor\n                for comp in S.nodes[S_target][\"component\"]:\n                    factors[comp] = {(): F.e2i[S.nodes[S_target][\"expression\"]]}\n            else:\n                # Zero form of arity 1 or higher: make factors empty\n                pass\n        else:\n            # Forms of arity 1 or higher:\n            # Map argkeys from indices into SV to indices into AV,\n            # and resort keys for canonical representation\n            for argkey, fi in S.nodes[S_target][\"factors\"].items():\n                ai_fi = {tuple(sorted(arg_indices.index(si) for si in argkey)): fi}\n                for comp in S.nodes[S_target][\"component\"]:\n                    if factors.get(comp):\n                        factors[comp].update(ai_fi)\n                    else:\n                        factors[comp] = ai_fi\n\n    # Indices into F that are needed for final result\n    for comp, target in factors.items():\n        for argkey, fi in target.items():\n            F.nodes[fi][\"target\"] = F.nodes[fi].get(\"target\", [])\n            F.nodes[fi][\"target\"].append(argkey)\n\n            F.nodes[fi][\"component\"] = F.nodes[fi].get(\"component\", [])\n            F.nodes[fi][\"component\"].append(comp)\n\n    # Compute dependencies in FV\n    for i, v in F.nodes.items():\n        expr = v[\"expression\"]\n        if not expr._ufl_is_terminal_ and not expr._ufl_is_terminal_modifier_:\n            for o in expr.ufl_operands:\n                F.add_edge(i, F.e2i[o])\n\n    return F\n"
  },
  {
    "path": "ffcx/ir/analysis/graph.py",
    "content": "# Copyright (C) 2011-2017 Martin Sandve Alnæs\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Linearized data structure for the computational graph.\"\"\"\n\nimport logging\n\nimport numpy as np\nimport ufl\n\nfrom ffcx.ir.analysis.modified_terminals import is_modified_terminal\nfrom ffcx.ir.analysis.reconstruct import reconstruct\nfrom ffcx.ir.analysis.valuenumbering import ValueNumberer\n\nlogger = logging.getLogger(\"ffcx\")\n\n\nclass ExpressionGraph:\n    \"\"\"A directed multi-edge graph.\n\n    ExpressionGraph allows multiple edges between the same nodes,\n    and respects the insertion order of nodes and edges.\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"Initialise.\"\"\"\n        # Data structures for directed multi-edge graph\n        self.nodes = {}\n        self.out_edges = {}\n        self.in_edges = {}\n        self.e2i = {}\n\n    def number_of_nodes(self):\n        \"\"\"Get number of nodes.\"\"\"\n        return len(self.nodes)\n\n    def add_node(self, key, **kwargs):\n        \"\"\"Add a node with optional properties.\"\"\"\n        self.nodes[key] = kwargs\n        self.out_edges[key] = []\n        self.in_edges[key] = []\n\n    def add_edge(self, node1, node2):\n        \"\"\"Add a directed edge from node1 to node2.\"\"\"\n        if node1 not in self.nodes or node2 not in self.nodes:\n            raise KeyError(\"Adding edge to unknown node\")\n\n        self.out_edges[node1] += [node2]\n        self.in_edges[node2] += [node1]\n\n\ndef build_graph_vertices(expressions, skip_terminal_modifiers=False) -> ExpressionGraph:\n    \"\"\"Build graph vertices.\"\"\"\n    # Count unique expression nodes\n    G = ExpressionGraph()\n    G.e2i = _count_nodes_with_unique_post_traversal(expressions, skip_terminal_modifiers)\n\n    # Invert the map to get index->expression\n    GV = sorted(G.e2i, key=G.e2i.get)\n\n    # Add nodes to 'new' graph structure\n    for i, v in enumerate(GV):\n        G.add_node(i, expression=v)\n\n    for comp, expr in enumerate(expressions):\n        # Get vertex index representing input expression root\n        V_target = G.e2i[expr]\n        G.nodes[V_target][\"target\"] = True\n        G.nodes[V_target][\"component\"] = G.nodes[V_target].get(\"component\", [])\n        G.nodes[V_target][\"component\"].append(comp)\n\n    return G\n\n\ndef build_scalar_graph(expression) -> ExpressionGraph:\n    \"\"\"Build list representation of expression graph covering the given expressions.\"\"\"\n    # Populate with vertices\n    G = build_graph_vertices([expression], skip_terminal_modifiers=False)\n\n    # Build more fine grained computational graph of scalar subexpressions\n    scalar_expressions = rebuild_with_scalar_subexpressions(G)\n\n    # Build new list representation of graph where all\n    # vertices of V represent single scalar operations\n    G = build_graph_vertices(scalar_expressions, skip_terminal_modifiers=True)\n\n    # Compute graph edges\n    V_deps: list[tuple[()] | list[int]] = []\n    for i, v in G.nodes.items():\n        expr = v[\"expression\"]\n        if expr._ufl_is_terminal_ or expr._ufl_is_terminal_modifier_:\n            V_deps.append(())\n        else:\n            V_deps.append([G.e2i[o] for o in expr.ufl_operands])\n\n    for i, edges in enumerate(V_deps):\n        for j in edges:\n            if i == j:\n                continue\n            G.add_edge(i, j)\n\n    return G\n\n\ndef rebuild_with_scalar_subexpressions(G):\n    \"\"\"Build a new expression2index mapping where each subexpression is scalar valued.\n\n    Input:\n    - G.e2i\n    - G.V\n    - G.V_symbols\n    - G.total_unique_symbols\n\n    Output:\n    - NV   - Array with reverse mapping from index to expression\n    - nvs  - Tuple of ne2i indices corresponding to the last vertex of G.V\n    \"\"\"\n    # Compute symbols over graph and rebuild scalar expression\n    #\n    # New expression which represents usually an algebraic operation\n    # generates a new symbol\n    value_numberer = ValueNumberer(G)\n\n    # V_symbols maps an index of a node to a list of\n    # symbols which are present in that node\n    V_symbols = value_numberer.compute_symbols()\n    total_unique_symbols = value_numberer.symbol_count\n\n    # Array to store the scalar subexpression in for each symbol\n    W = np.empty(total_unique_symbols, dtype=object)\n\n    # Iterate over each graph node in order\n    for i, v in G.nodes.items():\n        expr = v[\"expression\"]\n        # Find symbols of v components\n        vs = V_symbols[i]\n\n        # Skip if there's nothing new here (should be the case for indexing types)\n        # New symbols are not given to indexing types, so W[symbol] already equals\n        # an expression, since it was assigned to the symbol in a previous loop\n        # cycle\n        if all(W[s] is not None for s in vs):\n            continue\n\n        if is_modified_terminal(expr):\n            sh = expr.ufl_shape\n            if sh:\n                # Store each terminal expression component. We may not\n                # actually need all of these later, but that will be\n                # optimized away.\n                # Note: symmetries will be dealt with in the value numbering.\n                ws = [expr[c] for c in ufl.permutation.compute_indices(sh)]\n            else:\n                # Store single modified terminal expression component\n                if len(vs) != 1:\n                    raise RuntimeError(\n                        \"Expecting single symbol for scalar valued modified terminal.\"\n                    )\n                ws = [expr]\n            # FIXME: Replace ws[:] with 0's if its table is empty\n            # Possible redesign: loop over modified terminals only first,\n            # then build tables for them, set W[s] = 0.0 for modified terminals with zero table,\n            # then loop over non-(modified terminal)s to reconstruct expression.\n        else:\n            # Find symbols of operands\n            sops = []\n            for j, vop in enumerate(expr.ufl_operands):\n                if isinstance(vop, ufl.classes.MultiIndex):\n                    # TODO: Store MultiIndex in G.V and allocate a symbol to it for this to work\n                    if not isinstance(expr, ufl.classes.IndexSum):\n                        raise RuntimeError(f\"Not expecting a {type(expr)}.\")\n                    sops.append(())\n                else:\n                    # TODO: Build edge datastructure and use instead?\n                    # k = G.E[i][j]\n                    k = G.e2i[vop]\n                    sops.append(V_symbols[k])\n\n            # Fetch reconstructed operand expressions\n            wops = [tuple(W[k] for k in so) for so in sops]\n\n            # Reconstruct scalar subexpressions of v\n            ws = reconstruct(expr, wops)\n\n            # Store all scalar subexpressions for v symbols\n            if len(vs) != len(ws):\n                raise RuntimeError(\"Expecting one symbol for each expression.\")\n\n        # Store each new scalar subexpression in W at the index of its symbol\n        handled = set()\n        for s, w in zip(vs, ws):\n            if W[s] is None:\n                W[s] = w\n                handled.add(s)\n            else:\n                assert (\n                    s in handled\n                )  # Result of symmetry! - but I think this never gets reached anyway (CNR)\n\n    # Find symbols of final v from input graph\n    vs = V_symbols[-1]\n    scalar_expressions = W[vs]\n    return scalar_expressions\n\n\ndef _count_nodes_with_unique_post_traversal(expressions, skip_terminal_modifiers=False):\n    \"\"\"Yield o for each node o in expr, child before parent.\n\n    Never visits a node twice.\n    \"\"\"\n\n    def getops(e):\n        \"\"\"Get a modifiable list of operands of e.\n\n        Optionally treating modified terminals as a unit.\n        \"\"\"\n        # TODO: Maybe use e._ufl_is_terminal_modifier_\n        if e._ufl_is_terminal_ or (skip_terminal_modifiers and is_modified_terminal(e)):\n            return []\n        else:\n            return list(e.ufl_operands)\n\n    e2i = {}\n    stack = [(expr, getops(expr)) for expr in reversed(expressions)]\n    while stack:\n        expr, ops = stack[-1]\n\n        if expr in e2i:\n            stack.pop()\n            continue\n\n        for i, o in enumerate(ops):\n            if o is not None and o not in e2i:\n                stack.append((o, getops(o)))\n                ops[i] = None\n                break\n        else:\n            if not isinstance(expr, ufl.classes.MultiIndex | ufl.classes.Label):\n                count = len(e2i)\n                e2i[expr] = count\n            stack.pop()\n    return e2i\n"
  },
  {
    "path": "ffcx/ir/analysis/indexing.py",
    "content": "# Copyright (C) 2011-2017 Martin Sandve Alnæs\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Algorithms for working with multiindices.\"\"\"\n\nimport ufl\nfrom ufl.classes import ComponentTensor, FixedIndex, Index, Indexed\nfrom ufl.permutation import compute_indices\nfrom ufl.utils.indexflattening import flatten_multiindex, shape_to_strides\n\n\ndef map_indexed_arg_components(indexed):\n    \"\"\"Build a map from flattened components to subexpression.\n\n    Builds integer list mapping between flattened components\n    of indexed expression and its underlying tensor-valued subexpression.\n    \"\"\"\n    assert isinstance(indexed, Indexed)\n\n    # AKA indexed = tensor[multiindex]\n    tensor, multiindex = indexed.ufl_operands\n\n    # AKA e1 = e2[multiindex]\n    # (this renaming is historical, but kept for consistency with all the variables *1,*2 below)\n    e2 = tensor\n    e1 = indexed\n\n    # Get tensor and index shape\n    sh1 = e1.ufl_shape\n    sh2 = e2.ufl_shape\n    fi1 = e1.ufl_free_indices\n    fi2 = e2.ufl_free_indices\n    fid1 = e1.ufl_index_dimensions\n    fid2 = e2.ufl_index_dimensions\n\n    # Compute regular and total shape\n    tsh1 = sh1 + fid1\n    tsh2 = sh2 + fid2\n    # r1 = len(tsh1)\n    r2 = len(tsh2)\n    # str1 = shape_to_strides(tsh1)\n    str2 = shape_to_strides(tsh2)\n    assert not sh1\n    assert sh2  # Must have shape to be indexed in the first place\n    assert ufl.product(tsh1) <= ufl.product(tsh2)\n\n    # Build map from fi2/fid2 position (-offset nmui) to fi1/fid1 position\n    ind2_to_ind1_map = [None] * len(fi2)\n    for k, i in enumerate(fi2):\n        ind2_to_ind1_map[k] = fi1.index(i)\n\n    # Build map from fi1/fid1 position to mi position\n    nmui = len(multiindex)\n    multiindex_to_ind1_map = [None] * nmui\n    for k, i in enumerate(multiindex):\n        if isinstance(i, Index):\n            multiindex_to_ind1_map[k] = fi1.index(i.count())\n\n    # Build map from flattened e1 component to flattened e2 component\n    perm1 = compute_indices(tsh1)\n    ni1 = ufl.product(tsh1)\n\n    # Situation: e1 = e2[mi]\n    d1 = [None] * ni1\n    p2 = [None] * r2\n    assert len(sh2) == nmui\n    for k, i in enumerate(multiindex):\n        if isinstance(i, FixedIndex):\n            p2[k] = int(i)\n    for c1, p1 in enumerate(perm1):\n        for k, i in enumerate(multiindex):\n            if isinstance(i, Index):\n                p2[k] = p1[multiindex_to_ind1_map[k]]\n        for k, i in enumerate(ind2_to_ind1_map):\n            p2[nmui + k] = p1[i]\n        c2 = flatten_multiindex(p2, str2)\n        d1[c1] = c2\n\n    # Consistency checks\n    assert all(isinstance(x, int) for x in d1)\n    assert len(set(d1)) == len(d1)\n    return d1\n\n\ndef map_component_tensor_arg_components(tensor):\n    \"\"\"Build a map from flattened components to subexpression.\n\n    Builds integer list mapping between flattended components\n    of tensor and its underlying indexed subexpression.\n    \"\"\"\n    assert isinstance(tensor, ComponentTensor)\n\n    # AKA tensor = as_tensor(indexed, multiindex)\n    indexed, multiindex = tensor.ufl_operands\n\n    e1 = indexed\n    e2 = tensor  # e2 = as_tensor(e1, multiindex)\n    mi = [i for i in multiindex if isinstance(i, Index)]\n\n    # Get tensor and index shapes\n    sh1 = e1.ufl_shape  # (sh)ape of e1\n    sh2 = e2.ufl_shape  # (sh)ape of e2\n    fi1 = e1.ufl_free_indices  # (f)ree (i)ndices of e1\n    fi2 = e2.ufl_free_indices  # ...\n    fid1 = e1.ufl_index_dimensions  # (f)ree (i)ndex (d)imensions of e1\n    fid2 = e2.ufl_index_dimensions  # ...\n\n    # Compute total shape (tsh) of e1 and e2\n    tsh1 = sh1 + fid1\n    tsh2 = sh2 + fid2\n    r1 = len(tsh1)  # 'total rank' or e1\n    r2 = len(tsh2)  # ...\n    str1 = shape_to_strides(tsh1)\n    assert not sh1\n    assert sh2\n    assert len(mi) == len(multiindex)\n    assert ufl.product(tsh1) == ufl.product(tsh2)\n    assert fi1\n\n    assert all(i in fi1 for i in fi2)\n\n    nmui = len(multiindex)\n    assert nmui == len(sh2)\n\n    # Build map from fi2/fid2 position (-offset nmui) to fi1/fid1 position\n    p2_to_p1_map = [None] * r2\n    for k, i in enumerate(fi2):\n        p2_to_p1_map[k + nmui] = fi1.index(i)\n\n    # Build map from fi1/fid1 position to mi position\n    for k, i in enumerate(mi):\n        p2_to_p1_map[k] = fi1.index(mi[k].count())\n\n    # Build map from flattened e1 component to flattened e2 component\n    perm2 = compute_indices(tsh2)\n    ni2 = ufl.product(tsh2)\n\n    # Situation: e2 = as_tensor(e1, mi)\n    d2 = [None] * ni2\n    p1 = [None] * r1\n    for c2, p2 in enumerate(perm2):\n        for k2, k1 in enumerate(p2_to_p1_map):\n            p1[k1] = p2[k2]\n        c1 = flatten_multiindex(p1, str1)\n        d2[c2] = c1\n\n    # Consistency checks\n    assert all(isinstance(x, int) for x in d2)\n    assert len(set(d2)) == len(d2)\n    return d2\n"
  },
  {
    "path": "ffcx/ir/analysis/modified_terminals.py",
    "content": "# Copyright (C) 2011-2026 Martin Sandve Alnæs, Jørgen S. Dokken\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Modified terminals.\"\"\"\n\nimport logging\n\nfrom ufl.classes import (\n    Argument,\n    CellAvg,\n    FacetAvg,\n    FixedIndex,\n    FormArgument,\n    Grad,\n    Indexed,\n    Jacobian,\n    ReferenceGrad,\n    ReferenceValue,\n    Restricted,\n    SpatialCoordinate,\n)\nfrom ufl.permutation import build_component_numbering\n\nlogger = logging.getLogger(\"ffcx\")\n\n\nclass ModifiedTerminal:\n    \"\"\"A modified terminal.\"\"\"\n\n    def __init__(\n        self,\n        expr,\n        terminal,\n        reference_value: bool,\n        base_shape,\n        base_symmetry,\n        component: tuple[int, ...],\n        flat_component: int,\n        global_derivatives: tuple[int, ...],\n        local_derivatives: tuple[int, ...],\n        averaged: None | str,\n        restriction: None | str,\n    ):\n        \"\"\"Initialise.\n\n        Args:\n            expr: The original UFL expression\n            terminal: the underlying Terminal object\n            reference_value: whether this is represented in reference frame\n            base_shape: base shape\n            base_symmetry: base symmetry\n            component: the global component of the Terminal\n            flat_component: flattened local component of the Terminal, considering symmetry\n            global_derivatives: each entry is a derivative in that global direction\n            local_derivatives: each entry is a derivative in that local direction\n            averaged: Entity to average over (None, 'facet' or 'cell')\n            restriction: The restriction (None, '+' or '-')\n        \"\"\"\n        # The original expression\n        self.expr = expr\n\n        # The underlying terminal expression\n        self.terminal = terminal\n\n        # Are we seeing the terminal in physical or reference frame\n        self.reference_value = reference_value\n\n        # Get the shape of the core terminal or its reference value,\n        # this is the shape that component and flat_component refers to\n        self.base_shape = base_shape\n        self.base_symmetry = base_symmetry\n\n        # Components\n        self.component = component\n        self.flat_component = flat_component\n\n        # Derivatives\n        self.global_derivatives = global_derivatives\n        self.local_derivatives = local_derivatives\n\n        # Evaluation method (alternatives: { None, 'facet_midpoint',\n        #  'cell_midpoint', 'facet_avg', 'cell_avg' })\n        self.averaged = averaged\n\n        # Restriction to one cell or the other for interior facet integrals\n        self.restriction = restriction\n\n    def as_tuple(self):\n        \"\"\"Return a tuple with hashable values that uniquely identifies this modified terminal.\n\n        Some of the derived variables can be omitted here as long as\n        they are fully determined from the variables that are included here.\n        \"\"\"\n        t = self.terminal  # FIXME: Terminal is not sortable...\n        rv = self.reference_value\n        # bs = self.base_shape\n        # bsy = self.base_symmetry\n        # c = self.component\n        fc = self.flat_component\n        gd = self.global_derivatives\n        ld = self.local_derivatives\n        a = self.averaged\n        r = self.restriction\n        return (t, rv, fc, gd, ld, a, r)\n\n    def argument_ordering_key(self):\n        \"\"\"Return a key for deterministic sorting of argument vertex indices.\n\n        The key is based on the properties of the modified terminal.\n        Used in factorization but moved here for closeness with ModifiedTerminal attributes.\n        \"\"\"\n        t = self.terminal\n        assert isinstance(t, Argument)\n        n = t.number()\n        assert n >= 0\n        p = t.part()\n        rv = self.reference_value\n        # bs = self.base_shape\n        # bsy = self.base_symmetry\n        # c = self.component\n        fc = self.flat_component\n        gd = self.global_derivatives\n        ld = self.local_derivatives\n        a = self.averaged\n        r = self.restriction\n        return (n, p, rv, fc, gd, ld, a, r)\n\n    def __hash__(self):\n        \"\"\"Hash.\"\"\"\n        return hash(self.as_tuple())\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return isinstance(other, ModifiedTerminal) and self.as_tuple() == other.as_tuple()\n\n    def __str__(self):\n        \"\"\"Format as string.\"\"\"\n        return (\n            f\"terminal:           {self.terminal}\\n\"\n            f\"global_derivatives: {self.global_derivatives}\\n\"\n            f\"local_derivatives:  {self.local_derivatives}\\n\"\n            f\"averaged:           {self.averaged}\\n\"\n            f\"component:          {self.component}\\n\"\n            f\"restriction:        {self.restriction}\"\n        )\n\n\ndef is_modified_terminal(v):\n    \"\"\"Check if v is a terminal or a terminal wrapped in terminal modifier types.\"\"\"\n    while not v._ufl_is_terminal_:\n        if v._ufl_is_terminal_modifier_:\n            v = v.ufl_operands[0]\n        else:\n            return False\n    return True\n\n\ndef strip_modified_terminal(v):\n    \"\"\"Extract core Terminal from a modified terminal or return None.\"\"\"\n    while not v._ufl_is_terminal_:\n        if v._ufl_is_terminal_modifier_:\n            v = v.ufl_operands[0]\n        else:\n            return None\n    return v\n\n\ndef analyse_modified_terminal(expr):\n    \"\"\"Analyse a so-called 'modified terminal' expression.\n\n    Return its properties in more compact form as a ModifiedTerminal object.\n\n    A modified terminal expression is an object of a Terminal subtype,\n    wrapped in terminal modifier types.\n\n    The wrapper types can include 0-* Grad or ReferenceGrad objects,\n    and 0-1 ReferenceValue, 0-1 Restricted, 0-1 Indexed,\n    and 0-1 FacetAvg or CellAvg objects.\n    \"\"\"\n    # Data to determine\n    component = None\n    global_derivatives = []\n    local_derivatives = []\n    reference_value = None\n    restriction = None\n    averaged = None\n\n    # Start with expr and strip away layers of modifiers\n    t = expr\n    while not t._ufl_is_terminal_:\n        if isinstance(t, Indexed):\n            if component is not None:\n                raise RuntimeError(\"Got twice indexed terminal.\")\n\n            t, i = t.ufl_operands\n            component = [int(j) for j in i]\n\n            if not all(isinstance(j, FixedIndex) for j in i):\n                raise RuntimeError(\"Expected only fixed indices.\")\n\n        elif isinstance(t, ReferenceValue):\n            if reference_value is not None:\n                raise RuntimeError(\"Got twice pulled back terminal!\")\n\n            (t,) = t.ufl_operands\n            reference_value = True\n\n        elif isinstance(t, ReferenceGrad):\n            if not component:  # covers None or ()\n                raise RuntimeError(\"Got local gradient of terminal without prior indexing.\")\n\n            (t,) = t.ufl_operands\n            local_derivatives.append(component[-1])\n            component = component[:-1]\n\n        elif isinstance(t, Grad):\n            if not component:  # covers None or ()\n                raise RuntimeError(\"Got local gradient of terminal without prior indexing.\")\n\n            (t,) = t.ufl_operands\n            global_derivatives.append(component[-1])\n            component = component[:-1]\n\n        elif isinstance(t, Restricted):\n            if restriction is not None:\n                raise RuntimeError(\"Got twice restricted terminal!\")\n\n            restriction = t._side\n            (t,) = t.ufl_operands\n\n        elif isinstance(t, CellAvg):\n            if averaged is not None:\n                raise RuntimeError(\"Got twice averaged terminal!\")\n\n            (t,) = t.ufl_operands\n            averaged = \"cell\"\n\n        elif isinstance(t, FacetAvg):\n            if averaged is not None:\n                raise RuntimeError(\"Got twice averaged terminal!\")\n\n            (t,) = t.ufl_operands\n            averaged = \"facet\"\n\n        elif t._ufl_terminal_modifiers_:\n            raise RuntimeError(\n                f\"Missing handler for terminal modifier type {type(t)}, object is {t!r}.\"\n            )\n        else:\n            raise RuntimeError(f\"Unexpected type {type(t)} object {t}.\")\n\n    # Make canonical representation of derivatives\n    global_derivatives = tuple(sorted(global_derivatives))\n    local_derivatives = tuple(sorted(local_derivatives))\n\n    # Make reference_value true or false\n    reference_value = reference_value or False\n\n    # Consistency check\n    if isinstance(t, SpatialCoordinate | Jacobian):\n        pass\n    else:\n        if local_derivatives and not reference_value:\n            raise RuntimeError(\"Local derivatives of non-local value is not legal.\")\n        if global_derivatives and reference_value:\n            raise RuntimeError(\"Global derivatives of local value is not legal.\")\n\n    # Make sure component is an integer tuple\n    if component is None:\n        component = ()\n    else:\n        component = tuple(component)\n\n    # Get the shape of the core terminal or its reference value, this is\n    # the shape that component refers to\n    if isinstance(t, FormArgument):\n        element = t.ufl_function_space().ufl_element()\n        if reference_value:\n            # Ignoring symmetry, assuming already applied in conversion\n            # to reference frame\n            base_symmetry = {}\n            base_shape = element.reference_value_shape\n        else:\n            base_symmetry = element.symmetry()\n            base_shape = t.ufl_shape\n    else:\n        base_symmetry = {}\n        base_shape = t.ufl_shape\n\n    # Assert that component is within the shape of the (reference)\n    # terminal\n    if len(component) != len(base_shape):\n        raise RuntimeError(\"Length of component does not match rank of (reference) terminal.\")\n    if not all(c >= 0 and c < d for c, d in zip(component, base_shape)):\n        raise RuntimeError(\"Component indices {component} are outside value shape {base_shape}.\")\n\n    # Flatten component\n    vi2si, _ = build_component_numbering(base_shape, base_symmetry)\n    flat_component = vi2si[component]\n\n    return ModifiedTerminal(\n        expr,\n        t,\n        reference_value,\n        base_shape,\n        base_symmetry,\n        component,\n        flat_component,\n        global_derivatives,\n        local_derivatives,\n        averaged,\n        restriction,\n    )\n"
  },
  {
    "path": "ffcx/ir/analysis/reconstruct.py",
    "content": "# Copyright (C) 2011-2017 Martin Sandve Alnæs and Chris Richardson\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Reconstruct.\"\"\"\n\nimport ufl\n\n\ndef handle_scalar_nary(o, ops):\n    \"\"\"Handle a scalary nary operator.\"\"\"\n    if o.ufl_shape != ():\n        raise RuntimeError(\"Expecting scalar.\")\n    sops = [op[0] for op in ops]\n    return [o._ufl_expr_reconstruct_(*sops)]\n\n\ndef handle_condition(o, ops):\n    \"\"\"Handle a condition.\"\"\"\n    # A condition is always scalar, so len(op) == 1\n    sops = [op[0] for op in ops]\n    return [o._ufl_expr_reconstruct_(*sops)]\n\n\ndef handle_conditional(o, ops):\n    \"\"\"Handle a conditional.\"\"\"\n    # A condition can be non scalar\n    symbols = []\n    n = len(ops[1])\n    if len(ops[0]) != 1:\n        raise RuntimeError(\"Condition should be scalar.\")\n    if n != len(ops[2]):\n        raise RuntimeError(\"Conditional branches should have same shape.\")\n    for i in range(len(ops[1])):\n        sops = (ops[0][0], ops[1][i], ops[2][i])\n        symbols.append(o._ufl_expr_reconstruct_(*sops))\n    return symbols\n\n\ndef handle_elementwise_unary(o, ops):\n    \"\"\"Handle a elementwise unary operator.\"\"\"\n    if len(ops) > 1:\n        raise RuntimeError(\"Expecting unary operator.\")\n    return [o._ufl_expr_reconstruct_(op) for op in ops[0]]\n\n\ndef handle_division(o, ops):\n    \"\"\"Handle a division.\"\"\"\n    if len(ops) != 2:\n        raise RuntimeError(\"Expecting two operands.\")\n    if len(ops[1]) != 1:\n        raise RuntimeError(\"Expecting scalar divisor.\")\n    (b,) = ops[1]\n    return [o._ufl_expr_reconstruct_(a, b) for a in ops[0]]\n\n\ndef handle_sum(o, ops):\n    \"\"\"Handle a sum.\"\"\"\n    if len(ops) != 2:\n        raise RuntimeError(\"Expecting two operands.\")\n    if len(ops[0]) != len(ops[1]):\n        raise RuntimeError(\"Expecting scalar divisor.\")\n    return [o._ufl_expr_reconstruct_(a, b) for a, b in zip(ops[0], ops[1])]\n\n\ndef handle_product(o, ops):\n    \"\"\"Handle a product.\"\"\"\n    if len(ops) != 2:\n        raise RuntimeError(\"Expecting two operands.\")\n\n    # Get the simple cases out of the way\n    if len(ops[0]) == 1:  # True scalar * something\n        (a,) = ops[0]\n        return [ufl.classes.Product(a, b) for b in ops[1]]\n    elif len(ops[1]) == 1:  # Something * true scalar\n        (b,) = ops[1]\n        return [ufl.classes.Product(a, b) for a in ops[0]]\n\n    # Neither of operands are true scalars, this is the tricky part\n    o0, o1 = o.ufl_operands\n\n    # Get shapes and index shapes\n    fi = o.ufl_free_indices\n    fi0 = o0.ufl_free_indices\n    fi1 = o1.ufl_free_indices\n    fid = o.ufl_index_dimensions\n    fid0 = o0.ufl_index_dimensions\n    fid1 = o1.ufl_index_dimensions\n\n    # Need to map each return component to one component of o0 and\n    # one component of o1\n    indices = ufl.permutation.compute_indices(fid)\n\n    # Compute which component of o0 is used in component (comp,ind) of o\n    # Compute strides within free index spaces\n    ist0 = ufl.utils.indexflattening.shape_to_strides(fid0)\n    ist1 = ufl.utils.indexflattening.shape_to_strides(fid1)\n    # Map o0 and o1 indices to o indices\n    indmap0 = [fi.index(i) for i in fi0]\n    indmap1 = [fi.index(i) for i in fi1]\n    indks = [\n        (\n            ufl.utils.indexflattening.flatten_multiindex([ind[i] for i in indmap0], ist0),\n            ufl.utils.indexflattening.flatten_multiindex([ind[i] for i in indmap1], ist1),\n        )\n        for ind in indices\n    ]\n\n    # Build products for scalar components\n    results = [ufl.classes.Product(ops[0][k0], ops[1][k1]) for k0, k1 in indks]\n    return results\n\n\ndef handle_index_sum(o, ops):\n    \"\"\"Handle an index sum.\"\"\"\n    summand, mi = o.ufl_operands\n    ic = mi[0].count()\n    fi = summand.ufl_free_indices\n    fid = summand.ufl_index_dimensions\n    ipos = fi.index(ic)\n    d = fid[ipos]\n\n    # Compute \"macro-dimensions\" before and after i in the total shape of a\n    predim = ufl.product(summand.ufl_shape) * ufl.product(fid[:ipos])\n    postdim = ufl.product(fid[ipos + 1 :])\n\n    # Map each flattened total component of summand to\n    # flattened total component of indexsum o by removing\n    # axis corresponding to summation index ii.\n    ss = ops[0]  # Scalar subexpressions of summand\n    if len(ss) != predim * postdim * d:\n        raise RuntimeError(\"Mismatching number of subexpressions.\")\n    sops = []\n    for i in range(predim):\n        iind = i * (postdim * d)\n        for k in range(postdim):\n            ind = iind + k\n            sops.append([ss[ind + j * postdim] for j in range(d)])\n\n    # For each scalar output component, sum over collected subcomponents\n    # TODO: Need to split this into binary additions to work with future CRSArray format,\n    #       i.e. emitting more expressions than there are symbols for this node.\n    results = [sum(sop) for sop in sops]\n    return results\n\n\n# TODO: To implement compound tensor operators such as dot and inner,\n# we need to identify which index to do the contractions over,\n# and build expressions such as sum(a*b for a,b in zip(aops, bops))\n\n\n_reconstruct_call_lookup = {\n    ufl.classes.MathFunction: handle_scalar_nary,\n    ufl.classes.Abs: handle_scalar_nary,\n    ufl.classes.MinValue: handle_scalar_nary,\n    ufl.classes.MaxValue: handle_scalar_nary,\n    ufl.classes.Real: handle_elementwise_unary,\n    ufl.classes.Imag: handle_elementwise_unary,\n    ufl.classes.Power: handle_scalar_nary,\n    ufl.classes.BesselFunction: handle_scalar_nary,\n    ufl.classes.Atan2: handle_scalar_nary,\n    ufl.classes.Product: handle_product,\n    ufl.classes.Division: handle_division,\n    ufl.classes.Sum: handle_sum,\n    ufl.classes.IndexSum: handle_index_sum,\n    ufl.classes.Conj: handle_elementwise_unary,\n    ufl.classes.Conditional: handle_conditional,\n    ufl.classes.Condition: handle_condition,\n}\n\n\ndef reconstruct(o, *args):\n    \"\"\"Reconstruct.\"\"\"\n    # First look for exact match\n    f = _reconstruct_call_lookup.get(type(o), False)\n    if f:\n        return f(o, *args)\n    else:\n        # Look for parent class types instead\n        for k in _reconstruct_call_lookup.keys():\n            if isinstance(o, k):\n                return _reconstruct_call_lookup[k](o, *args)\n        # Nothing found\n        raise RuntimeError(f\"Not expecting expression of type {type(o)} in here.\")\n"
  },
  {
    "path": "ffcx/ir/analysis/valuenumbering.py",
    "content": "# Copyright (C) 2011-2017 Martin Sandve Alnæs\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Algorithms for value numbering within computational graphs.\"\"\"\n\nimport logging\n\nimport ufl\nfrom ufl.pullback import SymmetricPullback\n\nfrom ffcx.ir.analysis.indexing import (\n    map_component_tensor_arg_components,\n    map_indexed_arg_components,\n)\nfrom ffcx.ir.analysis.modified_terminals import analyse_modified_terminal\n\nlogger = logging.getLogger(\"ffcx\")\n\n\nclass ValueNumberer:\n    \"\"\"Maps scalar components to unique values.\n\n    An algorithm to map the scalar components of an expression node to unique value numbers,\n    with fallthrough for types that can be mapped to the value numbers\n    of their operands.\n    \"\"\"\n\n    def __init__(self, G):\n        \"\"\"Initialise.\"\"\"\n        self.symbol_count = 0\n        self.G = G\n        self.V_symbols = []\n        self.call_lookup = {\n            ufl.classes.Expr: self.expr,\n            ufl.classes.Argument: self.form_argument,\n            ufl.classes.Coefficient: self.form_argument,\n            ufl.classes.Grad: self._modified_terminal,\n            ufl.classes.ReferenceGrad: self._modified_terminal,\n            ufl.classes.FacetAvg: self._modified_terminal,\n            ufl.classes.CellAvg: self._modified_terminal,\n            ufl.classes.Restricted: self._modified_terminal,\n            ufl.classes.ReferenceValue: self._modified_terminal,\n            ufl.classes.Indexed: self.indexed,\n            ufl.classes.ComponentTensor: self.component_tensor,\n            ufl.classes.ListTensor: self.list_tensor,\n            ufl.classes.Variable: self.variable,\n        }\n\n    def new_symbols(self, n):\n        \"\"\"Generate new symbols with a running counter.\"\"\"\n        begin = self.symbol_count\n        end = begin + n\n        self.symbol_count = end\n        return list(range(begin, end))\n\n    def new_symbol(self):\n        \"\"\"Generate new symbol with a running counter.\"\"\"\n        begin = self.symbol_count\n        self.symbol_count += 1\n        return begin\n\n    def get_node_symbols(self, expr):\n        \"\"\"Get node symbols.\"\"\"\n        idx = [i for i, v in self.G.nodes.items() if v[\"expression\"] == expr][0]\n        return self.V_symbols[idx]\n\n    def compute_symbols(self):\n        \"\"\"Compute symbols.\"\"\"\n        for i, v in self.G.nodes.items():\n            expr = v[\"expression\"]\n            symbol = None\n            # First look for exact type match\n            f = self.call_lookup.get(type(expr), False)\n            if f:\n                symbol = f(expr)\n            else:\n                # Look for parent class types instead\n                for k in self.call_lookup.keys():\n                    if isinstance(expr, k):\n                        symbol = self.call_lookup[k](expr)\n                        break\n\n            if symbol is None:\n                # Nothing found\n                raise RuntimeError(f\"Not expecting type {type(expr)} here.\")\n\n            self.V_symbols.append(symbol)\n\n        return self.V_symbols\n\n    def expr(self, v):\n        \"\"\"Create new symbols for expressions that represent new values.\"\"\"\n        n = ufl.product(v.ufl_shape + v.ufl_index_dimensions)\n        return self.new_symbols(n)\n\n    def form_argument(self, v):\n        \"\"\"Create new symbols for expressions that represent new values.\"\"\"\n        e = v.ufl_function_space().ufl_element()\n\n        if isinstance(e.pullback, SymmetricPullback):\n            # Build symbols with symmetric components skipped\n            symbols = []\n            mapped_symbols = {}\n            for c in ufl.permutation.compute_indices(v.ufl_shape):\n                # Build mapped component mc with symmetries from element considered\n                mc = min(i for i, j in e.pullback._symmetry.items() if j == e.pullback._symmetry[c])\n\n                # Get existing symbol or create new and store with mapped component mc as key\n                s = mapped_symbols.get(mc)\n                if s is None:\n                    s = self.new_symbol()\n                    mapped_symbols[mc] = s\n                symbols.append(s)\n        else:\n            n = ufl.product(v.ufl_shape + v.ufl_index_dimensions)\n            symbols = self.new_symbols(n)\n\n        return symbols\n\n    # Handle modified terminals with element symmetries and second derivative symmetries!\n    # terminals are implemented separately, or maybe they don't need to be?\n\n    def _modified_terminal(self, v):\n        \"\"\"Handle modified terminal.\n\n        Modifiers:\n            terminal: the underlying Terminal object\n            global_derivatives: tuple of ints, each meaning derivative\n                in that global direction\n            local_derivatives: tuple of ints, each meaning derivative in\n                that local direction\n            reference_value: bool, whether this is represented in\n                reference frame\n            averaged: None, 'facet' or 'cell'\n            restriction: None, '+' or '-'\n            component: tuple of ints, the global component of the Terminal\n                flat_component: single int, flattened local component of the\n            Terminal, considering symmetry\n        \"\"\"\n        # (1) mt.terminal.ufl_shape defines a core indexing space UNLESS mt.reference_value,\n        #     in which case the reference value shape of the element must be used.\n        # (2) mt.terminal.ufl_element().symmetry() defines core symmetries\n        # (3) averaging and restrictions define distinct symbols, no additional symmetries\n        # (4) two or more grad/reference_grad defines distinct symbols with additional symmetries\n\n        # v is not necessary scalar here, indexing in (0,...,0) picks the first scalar component\n        # to analyse, which should be sufficient to get the base shape and derivatives\n        if v.ufl_shape:\n            mt = analyse_modified_terminal(v[(0,) * len(v.ufl_shape)])\n        else:\n            mt = analyse_modified_terminal(v)\n\n        # Get derivatives\n        num_ld = len(mt.local_derivatives)\n        num_gd = len(mt.global_derivatives)\n        assert not (num_ld and num_gd)\n        if num_ld:\n            domain = ufl.domain.extract_unique_domain(mt.terminal)\n            tdim = domain.topological_dimension\n            d_components = ufl.permutation.compute_indices((tdim,) * num_ld)\n        elif num_gd:\n            domain = ufl.domain.extract_unique_domiain(mt.terminal)\n            gdim = domain.geometric_dimension\n            d_components = ufl.permutation.compute_indices((gdim,) * num_gd)\n        else:\n            d_components = [()]\n\n        # Get base shape without the derivative axes\n        base_components = ufl.permutation.compute_indices(mt.base_shape)\n\n        # Build symbols with symmetric components and derivatives skipped\n        symbols = []\n        mapped_symbols = {}\n        for bc in base_components:\n            for dc in d_components:\n                # Build mapped component mc with symmetries from element\n                # and derivatives combined\n                mbc = mt.base_symmetry.get(bc, bc)\n                mdc = tuple(sorted(dc))\n                mc = mbc + mdc\n\n                # Get existing symbol or create new and store with\n                # mapped component mc as key\n                s = mapped_symbols.get(mc)\n                if s is None:\n                    s = self.new_symbol()\n                    mapped_symbols[mc] = s\n                symbols.append(s)\n\n        # Consistency check before returning symbols\n        assert not v.ufl_free_indices\n        if ufl.product(v.ufl_shape) != len(symbols):\n            raise RuntimeError(\"Internal error in value numbering.\")\n        return symbols\n\n    def indexed(self, Aii):\n        \"\"\"Return indexed value.\n\n        This is implemented as a fall-through operation.\n        \"\"\"\n        # Reuse symbols of arg A for Aii\n        A = Aii.ufl_operands[0]\n\n        # Get symbols of argument A\n        A_symbols = self.get_node_symbols(A)\n\n        # Map A_symbols to Aii_symbols\n        d = map_indexed_arg_components(Aii)\n        symbols = [A_symbols[k] for k in d]\n        return symbols\n\n    def component_tensor(self, A):\n        \"\"\"Component tensor.\"\"\"\n        # Reuse symbols of arg Aii for A\n        Aii = A.ufl_operands[0]\n\n        # Get symbols of argument Aii\n        Aii_symbols = self.get_node_symbols(Aii)\n\n        # Map A_symbols to Aii_symbols\n        d = map_component_tensor_arg_components(A)\n        symbols = [Aii_symbols[k] for k in d]\n        return symbols\n\n    def list_tensor(self, v):\n        \"\"\"List tensor.\"\"\"\n        symbols = []\n        for row in v.ufl_operands:\n            symbols.extend(self.get_node_symbols(row))\n        return symbols\n\n    def variable(self, v):\n        \"\"\"Direct reuse of all symbols.\"\"\"\n        return self.get_node_symbols(v.ufl_operands[0])\n"
  },
  {
    "path": "ffcx/ir/analysis/visualise.py",
    "content": "# Copyright (C) 2018 Chris Richardson\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Utility to draw graphs.\"\"\"\n\nfrom ufl.classes import (\n    Argument,\n    Division,\n    FloatValue,\n    Indexed,\n    IntValue,\n    Product,\n    ReferenceValue,\n    Sum,\n)\n\nfrom ffcx.ir.analysis.modified_terminals import strip_modified_terminal\n\n\ndef visualise_graph(Gx, filename):\n    \"\"\"Visualise a graph.\"\"\"\n    try:\n        import pygraphviz as pgv\n    except ImportError:\n        raise RuntimeError(\"Install pygraphviz\")\n\n    if Gx.number_of_nodes() > 400:\n        print(\"Skipping visualisation\")\n        return\n\n    G = pgv.AGraph(strict=False, directed=True)\n    for nd, v in Gx.nodes.items():\n        ex = v[\"expression\"]\n        label = ex.__class__.__name__\n        if isinstance(ex, Sum):\n            label = \"+\"\n        elif isinstance(ex, Product):\n            label = \"*\"\n        elif isinstance(ex, Division):\n            label = \"/\"\n        elif isinstance(ex, IntValue | FloatValue):\n            label = ex.value()\n        elif isinstance(ex, Indexed | ReferenceValue):\n            label = str(ex)\n        G.add_node(nd, label=f\"[{nd:d}] {label}\")\n\n        arg = strip_modified_terminal(ex)\n        if isinstance(arg, Argument):\n            G.get_node(nd).attr[\"shape\"] = \"box\"\n\n        stat = v.get(\"status\")\n        if stat == \"piecewise\":\n            G.get_node(nd).attr[\"color\"] = \"blue\"\n            G.get_node(nd).attr[\"penwidth\"] = 5\n        elif stat == \"varying\":\n            G.get_node(nd).attr[\"color\"] = \"red\"\n            G.get_node(nd).attr[\"penwidth\"] = 5\n        elif stat == \"inactive\":\n            G.get_node(nd).attr[\"color\"] = \"dimgray\"\n            G.get_node(nd).attr[\"penwidth\"] = 5\n\n        t = v.get(\"target\")\n        if t:\n            G.get_node(nd).attr[\"label\"] += \":\" + str(t)\n            G.get_node(nd).attr[\"shape\"] = \"hexagon\"\n\n        c = v.get(\"component\")\n        if c:\n            G.get_node(nd).attr[\"label\"] += f\", comp={c}\"\n\n    for nd, eds in Gx.out_edges.items():\n        for ed in eds:\n            G.add_edge(nd, ed)\n\n    G.layout(prog=\"dot\")\n    G.draw(filename)\n"
  },
  {
    "path": "ffcx/ir/elementtables.py",
    "content": "# Copyright (C) 2013-2017 Martin Sandve Alnæs\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Tools for precomputed tables of terminal values.\"\"\"\n\nimport logging\nimport typing\n\nimport basix.ufl\nimport numpy as np\nimport numpy.typing as npt\nimport ufl\n\nfrom ffcx.definitions import entity_types\nfrom ffcx.element_interface import basix_index\nfrom ffcx.ir.analysis.modified_terminals import ModifiedTerminal\nfrom ffcx.ir.representationutils import (\n    QuadratureRule,\n    create_quadrature_points_and_weights,\n    integral_type_to_entity_dim,\n    map_integral_points,\n)\n\nlogger = logging.getLogger(\"ffcx\")\n\n# Using same defaults as np.allclose\ndefault_rtol = 1e-6\ndefault_atol = 1e-9\n\ntable_types = typing.Literal[\n    \"piecewise\",\n    \"fixed\",\n    \"ones\",\n    \"zeros\",\n    \"uniform\",\n    \"quadrature\",\n    \"varying\",\n    \"tensor_factor\",\n]\npiecewise_ttypes = (\"piecewise\", \"fixed\", \"ones\", \"zeros\")\nuniform_ttypes = (\"fixed\", \"ones\", \"zeros\", \"uniform\")\n\n\nclass ModifiedTerminalElement(typing.NamedTuple):\n    \"\"\"Modified terminal element.\"\"\"\n\n    element: basix.ufl._ElementBase\n    averaged: str\n    local_derivatives: tuple[int, ...]\n    fc: int\n\n\nclass UniqueTableReferenceT(typing.NamedTuple):\n    \"\"\"Unique table reference.\"\"\"\n\n    name: str\n    values: npt.NDArray[np.float64]\n    is_permuted: (\n        bool  # If table is permuted (integral over interior facets) or mixed dimensional integrals.\n    )\n    ttype: table_types\n    # Optional table references, should not be none if table doesn't\n    # have tensor factors\n    offset: int | None = None\n    block_size: int | None = None\n    tensor_factors: list[\"UniqueTableReferenceT\"] | None = None\n    tensor_permutation: np.typing.NDArray[np.int32] | None = None\n\n    @property\n    def has_tensor_factorisation(self):\n        \"\"\"If table is a tensor factorization.\"\"\"\n        return self.tensor_factors is not None\n\n    @property\n    def is_piecewise(self) -> bool:\n        \"\"\"If constant for each point, but can differ for each entity.\"\"\"\n        return self.ttype in piecewise_ttypes\n\n    @property\n    def is_uniform(self) -> bool:\n        \"\"\"If constant for all points and all entities.\"\"\"\n        return self.ttype in uniform_ttypes\n\n\ndef equal_tables(a, b, rtol=default_rtol, atol=default_atol):\n    \"\"\"Check if two tables are equal.\"\"\"\n    a = np.asarray(a)\n    b = np.asarray(b)\n    if a.shape != b.shape:\n        return False\n    else:\n        return np.allclose(a, b, rtol=rtol, atol=atol)\n\n\ndef clamp_table_small_numbers(\n    table, rtol=default_rtol, atol=default_atol, numbers=(-1.0, 0.0, 1.0)\n):\n    \"\"\"Clamp almost 0,1,-1 values to integers. Returns new table.\"\"\"\n    # Get shape of table and number of columns, defined as the last axis\n    table = np.asarray(table)\n    for n in numbers:\n        table[np.where(np.isclose(table, n, rtol=rtol, atol=atol))] = n\n    return table\n\n\ndef get_ffcx_table_values(\n    points,\n    cell,\n    integral_type,\n    element,\n    avg,\n    entity_type: entity_types,\n    derivative_counts,\n    flat_component,\n    codim,\n):\n    \"\"\"Extract values from FFCx element table.\n\n    Returns a 4D numpy array with axes\n    (1, entity number, quadrature point number, dof number)\n    where the first dimension represents the number of permuted tables.\n    It is here to simplify construction later.\n    \"\"\"\n    deriv_order = sum(derivative_counts)\n\n    if integral_type in ufl.custom_integral_types:\n        # Use quadrature points on cell for analysis in custom integral types\n        integral_type = \"cell\"\n        assert not avg\n\n    if integral_type == \"expression\":\n        # FFCx tables for expression are generated as either interior cell points\n        # or points on a facet\n        if entity_type == \"cell\":\n            integral_type = \"cell\"\n        else:\n            integral_type = \"exterior_facet\"\n\n    if avg in (\"cell\", \"facet\"):\n        # Redefine points to compute average tables\n\n        # Make sure this is not called with points, that doesn't make sense\n        # assert points is None\n\n        # Not expecting derivatives of averages\n        assert not any(derivative_counts)\n        assert deriv_order == 0\n\n        # Doesn't matter if it's exterior or interior facet integral,\n        # just need a valid integral type to create quadrature rule\n        if avg == \"cell\":\n            integral_type = \"cell\"\n        elif avg == \"facet\":\n            integral_type = \"exterior_facet\"\n\n        if isinstance(element, basix.ufl._QuadratureElement):\n            points = element._points\n            weights = element._weights\n        else:\n            # Make quadrature rule and get points and weights\n            points, weights = create_quadrature_points_and_weights(\n                integral_type,\n                cell,\n                element.embedded_superdegree(),\n                \"default\",\n                [element],\n            )\n\n    # Tabulate table of basis functions and derivatives in points for each entity\n    tdim = cell.topological_dimension\n    entity_dim = integral_type_to_entity_dim(integral_type, tdim)\n    num_entities = cell.num_sub_entities(entity_dim)\n\n    # Extract arrays for the right scalar component\n    component_tables = []\n    component_element, offset, stride = element.get_component_element(flat_component)\n    for entity in range(num_entities):\n        if codim == 0:\n            entity_points = map_integral_points(points, integral_type, cell, entity)\n        elif codim == 1 or codim == 2:\n            entity_points = points\n        else:\n            raise RuntimeError(\"Codimension > 1 isn't supported.\")\n        tbl = component_element.tabulate(deriv_order, entity_points)\n        tbl = tbl[basix_index(derivative_counts)]\n        component_tables.append(tbl)\n\n    if avg in (\"cell\", \"facet\"):\n        # Compute numeric integral of the each component table\n        wsum = sum(weights)\n        for entity, tbl in enumerate(component_tables):\n            num_dofs = tbl.shape[1]\n            tbl = np.dot(tbl, weights) / wsum\n            tbl = np.reshape(tbl, (1, num_dofs))\n            component_tables[entity] = tbl\n\n    # Loop over entities and fill table blockwise (each block = points x dofs)\n    # Reorder axes as (points, dofs) instead of (dofs, points)\n    assert len(component_tables) == num_entities\n    num_points, num_dofs = component_tables[0].shape\n    shape = (1, num_entities, num_points, num_dofs)\n    res = np.zeros(shape)\n    for entity in range(num_entities):\n        res[:, entity, :, :] = component_tables[entity]\n\n    return {\"array\": res, \"offset\": offset, \"stride\": stride}\n\n\ndef generate_psi_table_name(\n    quadrature_rule: QuadratureRule,\n    element_counter,\n    averaged: str,\n    entity_type: entity_types,\n    derivative_counts,\n    flat_component,\n):\n    \"\"\"Generate a name for the psi table.\n\n    Format:\n        FE#_C#_D###[_AC|_AF|][_F|V][_Q#], where '#' will be an integer value and:\n        - FE is a simple counter to distinguish the various bases, it will be\n          assigned in an arbitrary fashion.\n        - C is the component number if any (this does not yet take into account\n          tensor valued functions)\n        - D is the number of derivatives in each spatial direction if any.\n          If the element is defined in 3D, then D012 means d^3(*)/dydz^2.\n        - AC marks that the element values are averaged over the cell\n        - AF marks that the element values are averaged over the facet\n        - F marks that the first array dimension enumerates facets on the cell\n        - V marks that the first array dimension enumerates vertices on the cell\n        - Q unique ID of quadrature rule, to distinguish between tables\n          in a mixed quadrature rule setting\n    \"\"\"\n    name = f\"FE{element_counter:d}\"\n    if flat_component is not None:\n        name += f\"_C{flat_component:d}\"\n    if any(derivative_counts):\n        name += \"_D\" + \"\".join(str(d) for d in derivative_counts)\n    name += {None: \"\", \"cell\": \"_AC\", \"facet\": \"_AF\"}[averaged]\n    name += {\"cell\": \"\", \"facet\": \"_F\", \"vertex\": \"_V\", \"ridge\": \"_R\"}[entity_type]\n    name += f\"_Q{quadrature_rule.id()}\"\n    return name\n\n\ndef get_modified_terminal_element(mt) -> ModifiedTerminalElement | None:\n    \"\"\"Get modified terminal element.\"\"\"\n    gd = mt.global_derivatives\n    ld = mt.local_derivatives\n    domain = ufl.domain.extract_unique_domain(mt.terminal)\n    # Extract element from FormArguments and relevant GeometricQuantities\n    if isinstance(mt.terminal, ufl.classes.FormArgument):\n        if gd and mt.reference_value:\n            raise RuntimeError(\"Global derivatives of reference values not defined.\")\n        elif ld and not mt.reference_value:\n            raise RuntimeError(\"Local derivatives of global values not defined.\")\n        element = mt.terminal.ufl_function_space().ufl_element()  # type: ignore\n        fc = mt.flat_component\n    elif isinstance(mt.terminal, ufl.classes.SpatialCoordinate):\n        if mt.reference_value:\n            raise RuntimeError(\"Not expecting reference value of x.\")\n        if gd:\n            raise RuntimeError(\"Not expecting global derivatives of x.\")\n        assert isinstance(domain, ufl.Mesh)\n        element = domain.ufl_coordinate_element()\n        if not ld:\n            fc = mt.flat_component\n        else:\n            # Actually the Jacobian expressed as reference_grad(x)\n            fc = mt.flat_component  # x-component\n            assert len(mt.component) == 1\n            assert mt.component[0] == mt.flat_component\n    elif isinstance(mt.terminal, ufl.classes.Jacobian):\n        if mt.reference_value:\n            raise RuntimeError(\"Not expecting reference value of J.\")\n        if gd:\n            raise RuntimeError(\"Not expecting global derivatives of J.\")\n        assert isinstance(domain, ufl.Mesh)\n        element = domain.ufl_coordinate_element()\n        assert len(mt.component) == 2\n        # Translate component J[i,d] to x element context rgrad(x[i])[d]\n        fc, d = mt.component  # x-component, derivative\n        ld = tuple(sorted((d,) + ld))\n    else:\n        return None\n\n    assert (mt.averaged is None) or not (ld or gd)\n    assert isinstance(domain, ufl.Mesh)\n\n    # Change derivatives format for table lookup\n    tdim = domain.topological_dimension\n    # The input `ld` is a tuple containing the index access of a recursive application of\n    # reference gradient, e.g. [0, 1, 2] means that the modified terminal is\n    # a reference_grad(reference_grad(reference_grad(expr)))[0][1][2],\n    # we have a derivative in each direction (x, y, z).\n    # This is converted into a tuple indicating the counts of derivatives in each direction.\n    # This means that if we have a reference_value as a modified terminal, the\n    # local_derivatives that we store in the `ModifiedTerminalElement` should be a tuple of\n    # length topological dimension with only zeros. This is later used to access the correct\n    # table values from `basix.tabulate`.\n    # To access the correct table values for a 0D domains, we need this index to be `(0, )`,\n    # as `basix.index` does not exist for 0D domains.\n    num_derivatives_per_ref_component = 1 if tdim == 0 else tdim\n    local_derivatives: tuple[int, ...] = tuple(\n        ld.count(i) for i in range(num_derivatives_per_ref_component)\n    )\n    return ModifiedTerminalElement(element, mt.averaged, local_derivatives, fc)\n\n\ndef permute_quadrature_interval(points, reflections=0):\n    \"\"\"Permute quadrature points for an interval.\"\"\"\n    output = points.copy()\n    for p in output:\n        assert len(p) < 2 or np.isclose(p[1], 0)\n        assert len(p) < 3 or np.isclose(p[2], 0)\n    for _ in range(reflections):\n        for n, p in enumerate(output):\n            output[n] = [1 - p[0]]\n    return output\n\n\ndef permute_quadrature_triangle(points, reflections=0, rotations=0):\n    \"\"\"Permute quadrature points for a triangle.\"\"\"\n    output = points.copy()\n    for p in output:\n        assert len(p) < 3 or np.isclose(p[2], 0)\n    for _ in range(rotations):\n        for n, p in enumerate(output):\n            output[n] = [p[1], 1 - p[0] - p[1]]\n    for _ in range(reflections):\n        for n, p in enumerate(output):\n            output[n] = [p[1], p[0]]\n    return output\n\n\ndef permute_quadrature_quadrilateral(points, reflections=0, rotations=0):\n    \"\"\"Permute quadrature points for a quadrilateral.\"\"\"\n    output = points.copy()\n    for p in output:\n        assert len(p) < 3 or np.isclose(p[2], 0)\n    for _ in range(rotations):\n        for n, p in enumerate(output):\n            output[n] = [p[1], 1 - p[0]]\n    for _ in range(reflections):\n        for n, p in enumerate(output):\n            output[n] = [p[1], p[0]]\n    return output\n\n\ndef build_optimized_tables(\n    quadrature_rule: QuadratureRule,\n    cell: ufl.Cell,\n    integral_type: typing.Literal[\"interior_facet\", \"exterior_facet\", \"ridge\", \"cell\", \"vertex\"],\n    entity_type: entity_types,\n    modified_terminals: typing.Iterable[ModifiedTerminal],\n    existing_tables: dict[str, npt.NDArray[np.float64]],\n    use_sum_factorization: bool,\n    is_mixed_dim: bool,\n    rtol: float = default_rtol,\n    atol: float = default_atol,\n) -> dict[str | ModifiedTerminal, UniqueTableReferenceT]:\n    \"\"\"Build the element tables needed for a list of modified terminals.\n\n    Args:\n        quadrature_rule: The quadrature rule used for the tables.\n        cell: The cell type of the domain the tables will be used with.\n        entity_type: The entity type (vertex,edge,facet,cell) that the tables are evaluated for.\n        integral_type: The type of integral the tables are used for.\n        modified_terminals: Ordered sequence of unique modified terminals\n        existing_tables: Register of tables that already exist and reused.\n        use_sum_factorization: Use sum factorization for tensor product elements.\n        is_mixed_dim: Mixed dimensionality of the domain.\n        rtol: Relative tolerance for clamping tables to -1,0 or 1\n        atol: Absolute tolerance for clamping tables to -1,0 or 1\n\n    Returns:\n        Dictionary mapping each modified terminal to the a unique table reference.\n        If ``use_sum_factorization`` is turned on, the map also contains the map\n        from the unique table reference for the tensor product factorization\n        to the name of the modified terminal.\n    \"\"\"\n    # Add to element tables\n    analysis = {}\n    for mt in modified_terminals:\n        res = get_modified_terminal_element(mt)\n        if res:\n            analysis[mt] = res\n\n    # Build element numbering using topological ordering so subelements\n    # get priority\n    all_elements = [res[0] for res in analysis.values()]\n    unique_elements = ufl.algorithms.sort_elements(\n        set(ufl.algorithms.analysis.extract_sub_elements(all_elements))\n    )\n    element_numbers = {element: i for i, element in enumerate(unique_elements)}\n    mt_tables: dict[str | ModifiedTerminal, UniqueTableReferenceT] = {}\n\n    _existing_tables = existing_tables.copy()\n\n    all_tensor_factors: list[UniqueTableReferenceT] = []\n    tensor_n = 0\n\n    for mt in modified_terminals:\n        res = analysis.get(mt)\n        if not res:\n            continue\n        element, avg, local_derivatives, flat_component = res\n\n        # Generate table and store table name with modified terminal\n\n        # Build name for this particular table\n        element_number = element_numbers[element]\n        name = generate_psi_table_name(\n            quadrature_rule,\n            element_number,\n            avg,\n            entity_type,\n            local_derivatives,\n            flat_component,\n        )\n\n        # FIXME - currently just recalculate the tables every time,\n        # only reusing them if they match numerically.\n        # It should be possible to reuse the cached tables by name, but\n        # the dofmap offset may differ due to restriction.\n\n        tdim = cell.topological_dimension\n        codim = tdim - element.cell.topological_dimension\n        assert codim >= 0\n        if codim > 2:\n            raise RuntimeError(\"Codimension > 2 isn't supported.\")\n\n        # Only permute quadrature rules for interior facets integrals and for\n        # the codim zero element in mixed-dimensional integrals. The latter is\n        # needed because a cell may see its sub-entities as being oriented\n        # differently to their global orientation\n        if (\n            integral_type == \"interior_facet\"\n            or integral_type == \"ridge\"\n            or (is_mixed_dim and codim == 0)\n        ):\n            if entity_type == \"facet\":\n                if tdim == 1 or codim == 1:\n                    # Do not add permutations if codim-1 as facets have already gotten a global\n                    # orientation in DOLFINx\n                    t = get_ffcx_table_values(\n                        quadrature_rule.points,\n                        cell,\n                        integral_type,\n                        element,\n                        avg,\n                        entity_type,\n                        local_derivatives,\n                        flat_component,\n                        codim,\n                    )\n                elif tdim == 2:\n                    new_table = []\n                    for ref in range(2):\n                        new_table.append(\n                            get_ffcx_table_values(\n                                permute_quadrature_interval(quadrature_rule.points, ref),\n                                cell,\n                                integral_type,\n                                element,\n                                avg,\n                                entity_type,\n                                local_derivatives,\n                                flat_component,\n                                codim,\n                            )\n                        )\n\n                    t = new_table[0]\n                    t[\"array\"] = np.vstack([td[\"array\"] for td in new_table])\n                elif tdim == 3:\n                    cell_type = cell.cellname\n                    if cell_type == \"tetrahedron\":\n                        new_table = []\n                        for rot in range(3):\n                            for ref in range(2):\n                                new_table.append(\n                                    get_ffcx_table_values(\n                                        permute_quadrature_triangle(\n                                            quadrature_rule.points, ref, rot\n                                        ),\n                                        cell,\n                                        integral_type,\n                                        element,\n                                        avg,\n                                        entity_type,\n                                        local_derivatives,\n                                        flat_component,\n                                        codim,\n                                    )\n                                )\n                        t = new_table[0]\n                        t[\"array\"] = np.vstack([td[\"array\"] for td in new_table])\n                    elif cell_type == \"hexahedron\":\n                        new_table = []\n                        for rot in range(4):\n                            for ref in range(2):\n                                new_table.append(\n                                    get_ffcx_table_values(\n                                        permute_quadrature_quadrilateral(\n                                            quadrature_rule.points, ref, rot\n                                        ),\n                                        cell,\n                                        integral_type,\n                                        element,\n                                        avg,\n                                        entity_type,\n                                        local_derivatives,\n                                        flat_component,\n                                        codim,\n                                    )\n                                )\n                        t = new_table[0]\n                        t[\"array\"] = np.vstack([td[\"array\"] for td in new_table])\n            elif entity_type == \"ridge\":\n                if tdim < 3 or codim == 2:\n                    # If ridge integral over vertex no permutation is needed,\n                    # or if it is a single domain ridge integral,\n                    # as ridges has a global orientation in DOLFINx.\n                    t = get_ffcx_table_values(\n                        quadrature_rule.points,\n                        cell,\n                        integral_type,\n                        element,\n                        avg,\n                        entity_type,\n                        local_derivatives,\n                        flat_component,\n                        codim,\n                    )\n                else:\n                    new_table = []\n                    for ref in range(2):\n                        new_table.append(\n                            get_ffcx_table_values(\n                                permute_quadrature_interval(quadrature_rule.points, ref),\n                                cell,\n                                integral_type,\n                                element,\n                                avg,\n                                entity_type,\n                                local_derivatives,\n                                flat_component,\n                                codim,\n                            )\n                        )\n                    t = new_table[0]\n                    t[\"array\"] = np.vstack([td[\"array\"] for td in new_table])\n        else:\n            t = get_ffcx_table_values(\n                quadrature_rule.points,\n                cell,\n                integral_type,\n                element,\n                avg,\n                entity_type,\n                local_derivatives,\n                flat_component,\n                codim,\n            )\n        # Clean up table\n        tbl = clamp_table_small_numbers(t[\"array\"], rtol=rtol, atol=atol)\n        tabletype = analyse_table_type(tbl)\n\n        if tabletype in piecewise_ttypes:\n            # Reduce table to dimension 1 along num_points axis in generated code\n            tbl = tbl[:, :, :1, :]\n        if tabletype in uniform_ttypes:\n            # Reduce table to dimension 1 along num_entities axis in generated code\n            tbl = tbl[:, :1, :, :]\n        is_permuted = is_permuted_table(tbl)\n        if not is_permuted:\n            # Reduce table along num_perms axis\n            tbl = tbl[:1, :, :, :]\n\n        # Check for existing identical table\n        is_new_table = True\n        for table_name in _existing_tables:\n            # FIXME: should we pass in atol and rtol here?\n            if equal_tables(tbl, _existing_tables[table_name]):\n                name = table_name\n                tbl = _existing_tables[name]\n                is_new_table = False\n                break\n\n        if is_new_table:\n            _existing_tables[name] = tbl\n\n        cell_offset = 0\n\n        if use_sum_factorization and (not quadrature_rule.has_tensor_factors):\n            raise RuntimeError(\"Sum factorization not available for this quadrature rule.\")\n\n        tensor_factors: list[UniqueTableReferenceT] | None = None\n        tensor_perm = None\n        if (\n            use_sum_factorization\n            and element.has_tensor_product_factorisation\n            and len(element.get_tensor_product_representation()) == 1\n            and quadrature_rule.has_tensor_factors\n        ):\n            factors = element.get_tensor_product_representation()\n\n            tensor_factors = []\n            for i, j in enumerate(factors[0]):\n                pts = quadrature_rule.tensor_factors[i][0]\n                d = local_derivatives[i]\n                sub_tbl = j.tabulate(d, pts)[d]\n                sub_tbl = sub_tbl.reshape(1, 1, sub_tbl.shape[0], sub_tbl.shape[1])\n                for tensor_factor in all_tensor_factors:\n                    if tensor_factor.values.shape == sub_tbl.shape and np.allclose(\n                        tensor_factor.values, sub_tbl\n                    ):\n                        tensor_factors.append(tensor_factor)\n                        break\n                else:\n                    ut = UniqueTableReferenceT(\n                        name=f\"FE_TF{tensor_n}\",\n                        values=sub_tbl,\n                        ttype=\"tensor_factor\",\n                        is_permuted=False,\n                    )\n                    all_tensor_factors.append(ut)\n                    tensor_factors.append(ut)\n                    mt_tables[ut.name] = ut\n                    tensor_n += 1\n\n            tensor_perm = factors[0][1]\n\n        if mt.restriction == \"-\" and isinstance(mt.terminal, ufl.classes.FormArgument):\n            # offset = 0 or number of element dofs, if restricted to \"-\"\n            cell_offset = element.dim\n\n        offset = cell_offset + t[\"offset\"]\n        block_size = t[\"stride\"]\n        # tables is just np.arrays, mt_tables hold metadata too\n        mt_tables[mt] = UniqueTableReferenceT(\n            name=name,\n            values=tbl,\n            offset=offset,\n            block_size=block_size,\n            ttype=tabletype,\n            is_permuted=is_permuted,\n            tensor_factors=tensor_factors,\n            tensor_permutation=tensor_perm,\n        )\n    return mt_tables\n\n\ndef is_zeros_table(table, rtol=default_rtol, atol=default_atol):\n    \"\"\"Check if table values are all zero.\"\"\"\n    return np.prod(table.shape) == 0 or np.allclose(\n        table, np.zeros(table.shape), rtol=rtol, atol=atol\n    )\n\n\ndef is_ones_table(table, rtol=default_rtol, atol=default_atol):\n    \"\"\"Check if table values are all one.\"\"\"\n    return np.allclose(table, np.ones(table.shape), rtol=rtol, atol=atol)\n\n\ndef is_quadrature_table(table, rtol=default_rtol, atol=default_atol):\n    \"\"\"Check if table is a quadrature table.\"\"\"\n    _, num_entities, num_points, num_dofs = table.shape\n    Id = np.eye(num_points)\n    return num_points == num_dofs and all(\n        np.allclose(table[0, i, :, :], Id, rtol=rtol, atol=atol) for i in range(num_entities)\n    )\n\n\ndef is_permuted_table(table, rtol=default_rtol, atol=default_atol):\n    \"\"\"Check if table is permuted.\"\"\"\n    return not all(\n        np.allclose(table[0, :, :, :], table[i, :, :, :], rtol=rtol, atol=atol)\n        for i in range(1, table.shape[0])\n    )\n\n\ndef is_piecewise_table(table, rtol=default_rtol, atol=default_atol):\n    \"\"\"Check if table is piecewise.\"\"\"\n    return all(\n        np.allclose(table[0, :, 0, :], table[0, :, i, :], rtol=rtol, atol=atol)\n        for i in range(1, table.shape[2])\n    )\n\n\ndef is_uniform_table(table, rtol=default_rtol, atol=default_atol):\n    \"\"\"Check if table is uniform.\"\"\"\n    return all(\n        np.allclose(table[0, 0, :, :], table[0, i, :, :], rtol=rtol, atol=atol)\n        for i in range(1, table.shape[1])\n    )\n\n\ndef analyse_table_type(table, rtol=default_rtol, atol=default_atol):\n    \"\"\"Analyse table type.\"\"\"\n    if is_zeros_table(table, rtol=rtol, atol=atol):\n        # Table is empty or all values are 0.0\n        ttype = \"zeros\"\n    elif is_ones_table(table, rtol=rtol, atol=atol):\n        # All values are 1.0\n        ttype = \"ones\"\n    elif is_quadrature_table(table, rtol=rtol, atol=atol):\n        # Identity matrix mapping points to dofs (separately on each entity)\n        ttype = \"quadrature\"\n    else:\n        # Equal for all points on a given entity\n        piecewise = is_piecewise_table(table, rtol=rtol, atol=atol)\n        uniform = is_uniform_table(table, rtol=rtol, atol=atol)\n\n        if piecewise and uniform:\n            # Constant for all points and all entities\n            ttype = \"fixed\"\n        elif piecewise:\n            # Constant for all points on each entity separately\n            ttype = \"piecewise\"\n        elif uniform:\n            # Equal on all entities\n            ttype = \"uniform\"\n        else:\n            # Varying over points and entities\n            ttype = \"varying\"\n    return ttype\n"
  },
  {
    "path": "ffcx/ir/integral.py",
    "content": "# Copyright (C) 2013-2025 Martin Sandve Alnæs, Michal Habera and Jørgen S. Dokken\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Main algorithm for building the integral intermediate representation.\"\"\"\n\nimport collections\nimport itertools\nimport logging\nimport typing\nfrom enum import Enum\n\nimport basix\nimport numpy as np\nimport numpy.typing as npt\nimport ufl\nfrom ufl.algorithms.balancing import balance_modifiers\nfrom ufl.checks import is_cellwise_constant\nfrom ufl.classes import QuadratureWeight\n\nfrom ffcx.definitions import entity_types, supported_integral_types\nfrom ffcx.ir.analysis.factorization import compute_argument_factorization\nfrom ffcx.ir.analysis.graph import ExpressionGraph, build_scalar_graph\nfrom ffcx.ir.analysis.modified_terminals import (\n    ModifiedTerminal,\n    analyse_modified_terminal,\n    is_modified_terminal,\n)\nfrom ffcx.ir.analysis.visualise import visualise_graph\nfrom ffcx.ir.elementtables import (\n    UniqueTableReferenceT,\n    build_optimized_tables,\n)\nfrom ffcx.ir.elementtables import (\n    table_types as _table_types,\n)\nfrom ffcx.ir.representationutils import QuadratureRule\n\nlogger = logging.getLogger(\"ffcx\")\n\n\nclass TensorPart(Enum):\n    \"\"\"What part of the tensor to assemble.\"\"\"\n\n    full = 1\n    diagonal = 4\n\n    @classmethod\n    def from_str(cls, value: str):\n        \"\"\"Workaround for string enum prior to Python 3.11.\"\"\"\n        if value == \"full\":\n            return cls.full\n        elif value == \"diagonal\":\n            return cls.diagonal\n        else:\n            raise ValueError(f\"Unknown tensor part: {value}\")\n\n    def __str__(self) -> str:\n        \"\"\"Get string value.\"\"\"\n        if self == TensorPart.full:\n            return \"full\"\n        elif self == TensorPart.diagonal:\n            return \"diagonal\"\n        else:\n            raise ValueError(f\"Unknown cell type: {self}\")\n\n    def __int__(self) -> int:\n        \"\"\"Get integer value.\"\"\"\n        return self.value\n\n\nclass ModifiedArgumentDataT(typing.NamedTuple):\n    \"\"\"Modified argument data.\"\"\"\n\n    ma_index: int\n    tabledata: UniqueTableReferenceT\n\n\nclass BlockDataT(typing.NamedTuple):\n    \"\"\"Block data.\"\"\"\n\n    ttypes: tuple[str, ...]  # list of table types for each block rank\n    factor_indices_comp_indices: list[tuple[int, int]]  # list of (factor index, component index)\n    all_factors_piecewise: bool  # True if all factors for this block are piecewise\n    unames: tuple[str, ...]  # list of unique FE table names for each block rank\n    restrictions: tuple[str | None, ...]  # restriction \"+\" | \"-\" | None for each block rank\n    transposed: bool  # block is the transpose of another\n    is_uniform: bool\n    ma_data: tuple[ModifiedArgumentDataT, ...]  # used in \"full\", \"safe\" and \"partial\"\n    is_permuted: bool  # Do quad points on facets need to be permuted?\n\n\nclass IntermediateIntegrandIR(typing.TypedDict):\n    \"\"\"Intermediate IR for integrands.\"\"\"\n\n    factorization: ExpressionGraph\n    modified_arguments: list[ModifiedTerminal]\n    block_contributions: dict[tuple[tuple[int, ...], ...], list[BlockDataT]]\n\n\nclass IntermediateIntegralIR(typing.TypedDict):\n    \"\"\"Intermediate IR for integrals.\"\"\"\n\n    needs_facet_permutations: bool\n    unique_tables: dict[basix.CellType, dict[str, npt.NDArray[np.float64]]]\n    unique_table_types: dict[basix.CellType, dict[str, _table_types]]\n    integrand: dict[tuple[basix.CellType, QuadratureRule], IntermediateIntegrandIR]\n\n\nclass CommonExpressionIR(typing.NamedTuple):\n    \"\"\"Common-ground for IntegralIR and ExpressionIR.\"\"\"\n\n    integral_type: supported_integral_types | typing.Literal[\"expression\"]\n    entity_type: entity_types  # The entity IR is over\n    tensor_shape: list[int]  # The shape of the locally assembled tensor\n\n    # When an expression is processed in UFL, coefficients might drop out of the\n    # variational form due to for instance differentiation.\n    # This maps from the original coefficient to its new index in the form.\n    coefficient_numbering: dict[ufl.Coefficient, int]\n    # Each coefficient is accessed from a given offset in the input array\n    coefficient_offsets: dict[ufl.Coefficient, int]\n    original_constant_offsets: dict[ufl.Constant, int]\n    unique_tables: dict[basix.CellType, npt.NDArray[np.float64]]\n    unique_table_types: dict[basix.CellType, dict[str, str]]\n    integrand: dict[tuple[basix.CellType, QuadratureRule], dict]\n    name: str\n    needs_facet_permutations: bool\n    shape: list[int]\n    coordinate_element_hash: str\n    number_coordinate_dofs: int\n\n\ndef _compute_integral_ir(\n    expression: ufl.core.expr.Expr,\n    existing_tables: dict[str, npt.NDArray[np.float64]],\n    quadrature_rule: QuadratureRule,\n    cell: ufl.Cell,\n    integral_type: supported_integral_types,\n    entity_type: entity_types,\n    argument_shape: tuple[int, ...],\n    visualise: bool,\n    p: dict,\n) -> tuple[\n    dict[str, npt.NDArray[np.float64]],\n    dict[str, _table_types],\n    ExpressionGraph,\n    list[int],\n    dict[tuple[tuple[int, ...], ...], list[BlockDataT]],\n    dict[int, ModifiedTerminal],\n    bool,\n]:\n    # Remove QuadratureWeight terminals from expression and replace with 1.0\n    expression = replace_quadratureweight(expression)\n\n    # Build initial scalar list-based graph representation\n    S = build_scalar_graph(expression)\n\n    # Build terminal_data from V here before factorization. Then we\n    # can use it to derive table properties for all modified\n    # terminals, and then use that to rebuild the scalar graph more\n    # efficiently before argument factorization. We can build\n    # terminal_data again after factorization if that's necessary.\n\n    initial_terminals: dict[int, ModifiedTerminal] = {\n        i: analyse_modified_terminal(v[\"expression\"])\n        for i, v in S.nodes.items()\n        if is_modified_terminal(v[\"expression\"])\n    }\n\n    # Check if we have a mixed-dimensional integral\n    is_mixed_dim = False\n    for domain in ufl.domain.extract_domains(expression):\n        if domain.topological_dimension != cell.topological_dimension:\n            is_mixed_dim = True\n\n    mt_table_reference = build_optimized_tables(\n        quadrature_rule,\n        cell,\n        integral_type,\n        entity_type,\n        initial_terminals.values(),\n        existing_tables,\n        use_sum_factorization=p[\"sum_factorization\"],\n        is_mixed_dim=is_mixed_dim,\n        rtol=p[\"table_rtol\"],\n        atol=p[\"table_atol\"],\n    )\n    # Fetch unique tables for this quadrature rule\n    table_types: dict[str, _table_types] = {v.name: v.ttype for v in mt_table_reference.values()}\n    tables: dict[str, npt.NDArray[np.float64]] = {\n        v.name: v.values for v in mt_table_reference.values()\n    }\n\n    S_targets = [i for i, v in S.nodes.items() if v.get(\"target\", False)]\n    num_components = np.int32(np.prod(expression.ufl_shape))\n\n    if \"zeros\" in table_types.values():\n        # If there are any 'zero' tables, replace symbolically and rebuild graph\n        for i, mt in initial_terminals.items():\n            # Set modified terminals with zero tables to zero\n            tr = mt_table_reference.get(mt)\n            if tr is not None and tr.ttype == \"zeros\":\n                S.nodes[i][\"expression\"] = ufl.as_ufl(0.0)\n\n        # Propagate expression changes using dependency list\n        for i, v in S.nodes.items():\n            deps = [S.nodes[j][\"expression\"] for j in S.out_edges[i]]\n            if deps:\n                v[\"expression\"] = v[\"expression\"]._ufl_expr_reconstruct_(*deps)\n\n        # Recreate expression with correct ufl_shape\n        expressions = np.empty(num_components, dtype=CommonExpressionIR)\n\n        for target in S_targets:\n            for comp in S.nodes[target][\"component\"]:\n                assert expressions[comp] is None\n                expressions[comp] = S.nodes[target][\"expression\"]\n        expression = ufl.as_tensor(expressions.reshape(expression.ufl_shape))\n        assert all([expr is not None for expr in expressions])\n\n        # Rebuild scalar list-based graph representation\n        S = build_scalar_graph(expression)\n\n    # Output diagnostic graph as pdf\n    if visualise:\n        visualise_graph(S, \"S.pdf\")\n\n    # Compute factorization of arguments\n    if p[\"part\"] == TensorPart.diagonal:\n        assert len(argument_shape) == 2, \"Can only diagonalize bi-linear forms.\"\n        rank = 1\n    else:\n        rank = len(argument_shape)\n    F = compute_argument_factorization(S, rank)\n\n    # Get the 'target' nodes that are factors of arguments, and insert in dict\n    FV_targets = [i for i, v in F.nodes.items() if v.get(\"target\", False)]\n    argument_factorization: dict[tuple[int, ...], list[tuple[int, int]]] = {}\n\n    for fi in FV_targets:\n        # Number of blocks using this factor must agree with number of components\n        # to which this factor contributes. I.e. there are more blocks iff there are more\n        # components\n        assert len(F.nodes[fi][\"target\"]) == len(F.nodes[fi][\"component\"])\n\n        k = 0\n        for w in F.nodes[fi][\"target\"]:\n            comp = F.nodes[fi][\"component\"][k]\n            argument_factorization[w] = argument_factorization.get(w, [])\n\n            # Store tuple of (factor index, component index)\n            argument_factorization[w].append((fi, comp))\n            k += 1\n\n    # Get list of indices in F which are the arguments (should be at start)\n    _argkeys: set[int] = set()\n    for w in argument_factorization:\n        _argkeys = _argkeys | set(w)\n    argkeys = list(_argkeys)\n\n    # Build set of modified_terminals for each mt factorized vertex in F\n    # and attach tables, if appropriate\n    for i, v in F.nodes.items():\n        expr = v[\"expression\"]\n        if is_modified_terminal(expr):\n            mt = analyse_modified_terminal(expr)\n            F.nodes[i][\"mt\"] = mt\n            tr = mt_table_reference.get(mt)\n            if tr is not None:\n                F.nodes[i][\"tr\"] = tr\n\n    # Attach 'status' to each node: 'inactive', 'piecewise' or 'varying'\n    analyse_dependencies(F, mt_table_reference)\n\n    # Output diagnostic graph as pdf\n    if visualise:\n        visualise_graph(F, \"F.pdf\")\n\n    # Loop over factorization terms\n    block_contributions: dict[tuple[tuple[int, ...], ...], list[BlockDataT]] = (\n        collections.defaultdict(list)\n    )\n    for ma_indices, fi_ci in sorted(argument_factorization.items()):\n        # Get a bunch of information about this term\n        if TensorPart.from_str(p[\"part\"]) != TensorPart.diagonal:\n            assert rank == len(ma_indices)\n\n        trs = tuple(F.nodes[ai][\"tr\"] for ai in ma_indices)\n        unames = tuple(tr.name for tr in trs)\n        ttypes = tuple(tr.ttype for tr in trs)\n        assert not any(tt == \"zeros\" for tt in ttypes)\n\n        _blockmap: list[tuple[int, ...]] = []\n        for tr in trs:\n            assert tr is not None\n            begin = tr.offset\n            assert begin is not None\n            num_dofs = tr.values.shape[3]\n            assert tr.block_size is not None\n            dofmap = tuple(begin + i * tr.block_size for i in range(num_dofs))\n            _blockmap.append(dofmap)\n        blockmap = tuple(_blockmap)\n\n        block_is_uniform = all(tr.is_uniform for tr in trs)\n\n        # Collect relevant restrictions to identify blocks correctly\n        # in interior facet integrals\n        _block_restrictions: list[str] = []\n        for i, ai in enumerate(ma_indices):\n            if not trs[i].is_uniform:\n                r = F.nodes[ai][\"mt\"].restriction\n                _block_restrictions.append(r)\n\n        block_restrictions: tuple[str, ...] = tuple(_block_restrictions)\n\n        # Check if each *each* factor corresponding to this argument is piecewise\n        all_factors_piecewise = all(F.nodes[ifi[0]][\"status\"] == \"piecewise\" for ifi in fi_ci)\n        block_is_permuted = False\n        for name in unames:\n            if tables[name].shape[0] > 1:\n                block_is_permuted = True\n        ma_data = []\n        for i, ma in enumerate(ma_indices):\n            ma_data.append(ModifiedArgumentDataT(ma, trs[i]))\n\n        block_is_transposed = False  # FIXME: Handle transposes for these block types\n        block_unames = unames\n        blockdata = BlockDataT(\n            ttypes,\n            fi_ci,\n            all_factors_piecewise,\n            block_unames,\n            block_restrictions,\n            block_is_transposed,\n            block_is_uniform,\n            tuple(ma_data),\n            block_is_permuted,\n        )\n\n        # Insert in expr_ir for this quadrature loop\n        block_contributions[blockmap].append(blockdata)\n\n    # Figure out which table names are referenced\n    active_table_names = set()\n    for i, v in F.nodes.items():\n        tr = v.get(\"tr\")\n        if tr is not None and F.nodes[i][\"status\"] != \"inactive\":\n            if tr.has_tensor_factorisation:\n                assert tr.tensor_factors is not None\n                for t in tr.tensor_factors:\n                    active_table_names.add(t.name)\n            else:\n                active_table_names.add(tr.name)\n\n    # Figure out which table names are referenced in blocks\n    for blockmap, contributions in itertools.chain(block_contributions.items()):\n        for blockdata in contributions:\n            for mad in blockdata.ma_data:\n                if mad.tabledata.has_tensor_factorisation:\n                    assert mad.tabledata.tensor_factors is not None\n                    for t in mad.tabledata.tensor_factors:\n                        active_table_names.add(t.name)\n                else:\n                    active_table_names.add(mad.tabledata.name)\n\n    active_tables: dict[str, npt.NDArray[np.float64]] = {}\n    active_table_types: dict[str, _table_types] = {}\n\n    for name in active_table_names:\n        # Drop tables not referenced from modified terminals\n        if table_types[name] not in (\"zeros\", \"ones\"):\n            active_tables[name] = tables[name]\n            active_table_types[name] = table_types[name]\n    return (\n        active_tables,\n        active_table_types,\n        F,\n        argkeys,\n        block_contributions,\n        initial_terminals,\n        is_mixed_dim,\n    )\n\n\ndef compute_integral_ir(\n    cell: ufl.Cell,\n    integral_type: supported_integral_types,\n    entity_type: entity_types,\n    integrands: dict[basix.CellType, dict[QuadratureRule, ufl.core.expr.Expr]],\n    argument_shape: tuple[int, ...],\n    p: dict,\n    visualise: bool,\n) -> IntermediateIntegralIR:\n    \"\"\"Compute intermediate representation for an integral.\n\n    Args:\n        cell: Cell of integration domain\n        integral_type: Type of integral over cell\n        entity_type: Corresponding entity of the cell that the integral is over\n        integrands: Dictionary mapping a quadrature rule to a sequence of integrands\n        argument_shape: Shape of the output tensor of the integral (used for tensor factorization)\n        p: Parameters used for clamping tables and for activating sum factorization\n        visualise: If True, store the graph representation of the integrand in a pdf file\n            `S.pdf` and `F.pdf`\n    \"\"\"\n    # Data that will populate the intermediate representation.\n    needs_facet_permutations: bool = False\n    unique_tables: dict[basix.CellType, dict[str, npt.NDArray[np.float64]]] = {}\n    unique_table_types: dict[basix.CellType, dict[str, _table_types]] = {}\n    integrand_map: dict[tuple[basix.CellType, QuadratureRule], IntermediateIntegrandIR] = {}\n    for integral_domain, integrands_on_domain in integrands.items():\n        unique_tables[integral_domain] = {}\n        unique_table_types[integral_domain] = {}\n        for quadrature_rule, integrand in integrands_on_domain.items():\n            expression = integrand\n\n            # Rebalance order of nested terminal modifiers\n            expression = balance_modifiers(expression)\n\n            (\n                active_tables,\n                active_table_types,\n                F,\n                argkeys,\n                block_contributions,\n                initial_terminals,\n                is_mixed_dim,\n            ) = _compute_integral_ir(\n                expression,\n                unique_tables[integral_domain],\n                quadrature_rule,\n                cell,\n                integral_type,\n                entity_type,\n                argument_shape,\n                visualise,\n                p,\n            )\n\n            # Add tables and types for this quadrature rule to global tables dict\n            unique_tables[integral_domain].update(active_tables)\n            unique_table_types[integral_domain].update(active_table_types)\n            # Build IR dict for the given expressions\n            # Store final ir for this num_points\n            integrand_map[(integral_domain, quadrature_rule)] = IntermediateIntegrandIR(\n                factorization=F,\n                modified_arguments=[F.nodes[i][\"mt\"] for i in argkeys],\n                block_contributions=block_contributions,\n            )\n\n            restrictions = [i.restriction for i in initial_terminals.values()]\n            if not needs_facet_permutations:\n                needs_facet_permutations = (\n                    \"+\" in restrictions and \"-\" in restrictions\n                ) or is_mixed_dim\n\n    return IntermediateIntegralIR(\n        needs_facet_permutations=needs_facet_permutations,\n        unique_tables=unique_tables,\n        unique_table_types=unique_table_types,\n        integrand=integrand_map,\n    )\n\n\ndef analyse_dependencies(F, mt_unique_table_reference):\n    \"\"\"Analyse dependencies.\n\n    Sets 'status' of all nodes to either: 'inactive', 'piecewise' or 'varying'\n    Children of 'target' nodes are either 'piecewise' or 'varying'.\n    All other nodes are 'inactive'.\n    Varying nodes are identified by their tables ('tr'). All their parent\n    nodes are also set to 'varying' - any remaining active nodes are 'piecewise'.\n    \"\"\"\n    # Set targets, and dependencies to 'active'\n    targets = [i for i, v in F.nodes.items() if v.get(\"target\")]\n    for _, v in F.nodes.items():\n        v[\"status\"] = \"inactive\"\n\n    while targets:\n        s = targets.pop()\n        F.nodes[s][\"status\"] = \"active\"\n        for j in F.out_edges[s]:\n            if F.nodes[j][\"status\"] == \"inactive\":\n                targets.append(j)\n\n    # Build piecewise/varying markers for factorized_vertices\n    varying_ttypes = (\"varying\", \"quadrature\", \"uniform\")\n    varying_indices = []\n    for i, v in F.nodes.items():\n        if v.get(\"mt\") is None:\n            continue\n        tr = v.get(\"tr\")\n        if tr is not None:\n            ttype = tr.ttype\n            # Check if table computations have revealed values varying over points\n            if ttype in varying_ttypes:\n                varying_indices.append(i)\n            else:\n                if ttype not in (\"fixed\", \"piecewise\", \"ones\", \"zeros\"):\n                    raise RuntimeError(f\"Invalid ttype {ttype}.\")\n\n        elif not is_cellwise_constant(v[\"expression\"]):\n            raise RuntimeError(\"Error \" + str(tr))\n            # Keeping this check to be on the safe side,\n            # not sure which cases this will cover (if any)\n            # varying_indices.append(i)\n\n    # Set all parents of active varying nodes to 'varying'\n    while varying_indices:\n        s = varying_indices.pop()\n        if F.nodes[s][\"status\"] == \"active\":\n            F.nodes[s][\"status\"] = \"varying\"\n            for j in F.in_edges[s]:\n                varying_indices.append(j)\n\n    # Any remaining active nodes must be 'piecewise'\n    for _, v in F.nodes.items():\n        if v[\"status\"] == \"active\":\n            v[\"status\"] = \"piecewise\"\n\n\ndef replace_quadratureweight(expression):\n    \"\"\"Remove any QuadratureWeight terminals and replace with 1.0.\"\"\"\n    r = []\n    for node in ufl.corealg.traversal.unique_pre_traversal(expression):\n        if is_modified_terminal(node) and isinstance(node, QuadratureWeight):\n            r.append(node)\n\n    replace_map = {q: 1.0 for q in r}\n    return ufl.algorithms.replace(expression, replace_map)\n"
  },
  {
    "path": "ffcx/ir/representation.py",
    "content": "# Copyright (C) 2009-2026 Anders Logg, Martin Sandve Alnæs, Marie E. Rognes,\n# Kristian B. Oelgaard, Matthew W. Scroggs, Chris Richardson, and others\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Compiler stage 2: Code representation.\n\nModule computes intermediate representations of forms. For each UFC\nfunction, we extract the data needed for code generation at a later\nstage.\n\nThe representation should conform strictly to the naming and order of\nfunctions in UFC. Thus, for code generation of the function \"foo\", one\nshould only need to use the data stored in the intermediate\nrepresentation under the key \"foo\".\n\"\"\"\n\nfrom __future__ import annotations\n\nimport itertools\nimport logging\nimport typing\n\nif typing.TYPE_CHECKING:\n    from ufl.algorithms.formdata import FormData\n    from ufl.core.expr import Expr\nimport warnings\n\nimport basix\nimport numpy as np\nimport numpy.typing as npt\nimport ufl.algorithms\nfrom ufl.classes import Integral\nfrom ufl.sorting import sorted_expr_sum\n\nfrom ffcx import naming\nfrom ffcx.analysis import UFLData\nfrom ffcx.definitions import entity_types, supported_integral_types\nfrom ffcx.ir.integral import CommonExpressionIR, TensorPart, compute_integral_ir\nfrom ffcx.ir.representationutils import (\n    QuadratureRule,\n    create_quadrature_points_and_weights,\n)\n\nlogger = logging.getLogger(\"ffcx\")\n\n\ndef basix_cell_from_string(string: str) -> basix.CellType:\n    \"\"\"Convert a string to a Basix CellType.\"\"\"\n    # Note: vertex -> point, rest identity\n    match string:\n        case \"vertex\":\n            return basix.CellType.point\n        case \"interval\":\n            return basix.CellType.interval\n        case \"triangle\":\n            return basix.CellType.triangle\n        case \"tetrahedron\":\n            return basix.CellType.tetrahedron\n        case \"quadrilateral\":\n            return basix.CellType.quadrilateral\n        case \"hexahedron\":\n            return basix.CellType.hexahedron\n        case \"prism\":\n            return basix.CellType.prism\n        case \"pyramid\":\n            return basix.CellType.pyramid\n        case _:\n            raise KeyError(f\"Can not map '{string}' to a basix type.\")\n\n\nclass FormIR(typing.NamedTuple):\n    \"\"\"Intermediate representation of a form.\"\"\"\n\n    id: int\n    name: str\n    signature: str\n    rank: int\n    num_coefficients: int\n    name_from_uflfile: str\n    original_coefficient_positions: list[int]\n    coefficient_names: list[str]\n    num_constants: int\n    constant_ranks: list[int]\n    constant_shapes: list[list[int]]\n    constant_names: list[str]\n    finite_element_hashes: list[int]\n    integral_names: dict[str, list[str]]\n    integral_domains: dict[str, list[basix.CellType]]\n    subdomain_ids: dict[str, list[int]]\n\n\nclass QuadratureIR(typing.NamedTuple):\n    \"\"\"Intermediate representation of a quadrature rule.\"\"\"\n\n    cell_shape: str\n    points: npt.NDArray[np.float64]\n    weights: npt.NDArray[np.float64]\n\n\nclass IntegralIR(typing.NamedTuple):\n    \"\"\"Intermediate representation of an integral.\"\"\"\n\n    expression: CommonExpressionIR\n    rank: int\n    enabled_coefficients: list[bool]\n    part: TensorPart\n\n\nclass ExpressionIR(typing.NamedTuple):\n    \"\"\"Intermediate representation of a DOLFINx Expression.\"\"\"\n\n    expression: CommonExpressionIR\n    original_coefficient_positions: list[int]\n    coefficient_names: list[str]\n    constant_names: list[str]\n    name_from_uflfile: str\n\n\nclass DataIR(typing.NamedTuple):\n    \"\"\"Intermediate representation of data.\"\"\"\n\n    integrals: list[IntegralIR]\n    forms: list[FormIR]\n    expressions: list[ExpressionIR]\n\n\ndef _group_integrands_by_quadrature_rule(\n    integrals: list[ufl.Integral],\n    argument_elements: tuple[basix.ufl._ElementBase, ...],\n    integral_type: supported_integral_types,\n    ufl_cell: ufl.Cell,\n    sum_factorization: bool,\n) -> dict[basix.CellType, dict[QuadratureRule, list[Expr]]]:\n    \"\"\"Group integrands with the same quadrature rule.\n\n    Given a sequence of integrals, group them by the quadrature\n    (after explicit computation of it).\n    The grouping is nested, meaning that it is first grouped\n    by the cell used for the integration (in most cases the integration domain).\n    However, for integrals with vertex-quadrature, the celltype is changed\n    to be of the correct sub-entity, so that one fetches the correct quadrature points.\n\n    Args:\n        integrals: List of itegrals to group\n        argument_elements: The finite elements for the arguments in the form.\n            Used to get the correct polyset type in the case of tensor-factorization\n            of the element ad thus the quadrature rule.\n        integral_type: Type of (FFCx) integral that is performed (not UFL integral type)\n        ufl_cell: UFL cell for the integration domain.\n        sum_factorization: If True use sum factorization.\n\n    Returns:\n        A nested dictionary over integrands[cell type of quadrature][quadrature_rule].\n    \"\"\"\n    #\n    grouped_integrands: dict[basix.CellType, dict[QuadratureRule, list[Expr]]] = {}\n    # NOTE: this variable changes throughout the loop\n    cell_type = basix_cell_from_string(ufl_cell.cellname)\n    use_sum_factorization = sum_factorization and integral_type == \"cell\"\n    for integral in integrals:\n        md = integral.metadata() or {}\n        scheme = md[\"quadrature_rule\"]\n        tensor_factors = None\n        rules = {}\n        if scheme == \"custom\":\n            points = md[\"quadrature_points\"]\n            weights = md[\"quadrature_weights\"]\n            rules[cell_type] = (points, weights, None)\n        elif scheme == \"vertex\":\n            # The vertex scheme, i.e., averaging the function value in the\n            # vertices and multiplying with the simplex volume, is only of\n            # order 1 and inferior to other generic schemes in terms of\n            # error reduction. Equation systems generated with the vertex\n            # scheme have some properties that other schemes lack, e.g., the\n            # mass matrix is a simple diagonal matrix. This may be\n            # prescribed in certain cases.\n\n            degree = md[\"quadrature_degree\"]\n            if \"facet\" in integral_type:\n                facet_types = basix.cell.subentity_types(cell_type)[-2]\n                assert len(set(facet_types)) == 1\n                cell_type = facet_types[0]\n            elif integral_type == \"ridge\":\n                ridge_types = basix.cell.subentity_types(cell_type)[-3]\n                assert len(set(ridge_types)) == 1\n                cell_type = ridge_types[0]\n\n            if degree > 1:\n                warnings.warn(\n                    \"Explicitly selected vertex quadrature (degree 1), \"\n                    f\"but requested degree is {degree}.\"\n                )\n            points = basix.cell.geometry(cell_type)\n            cell_volume = basix.cell.volume(cell_type)\n            weights = np.full(points.shape[0], cell_volume / points.shape[0], dtype=points.dtype)\n            rules[cell_type] = (points, weights, None)\n        else:\n            degree = md[\"quadrature_degree\"]\n            points, weights, tensor_factors = create_quadrature_points_and_weights(\n                integral_type,\n                ufl_cell,\n                degree,\n                scheme,\n                argument_elements,\n                use_sum_factorization,\n            )\n            rules = {\n                basix_cell_from_string(i): (\n                    points[i],\n                    weights[i],\n                    tensor_factors[i] if i in tensor_factors else None,\n                )\n                for i in points\n            }\n\n        for cell_type, (points, weights, tensor_factors) in rules.items():\n            points = np.asarray(points)\n            weights = np.asarray(weights)\n            rule = QuadratureRule(points, weights, tensor_factors)\n\n            if cell_type not in grouped_integrands:\n                grouped_integrands[cell_type] = {}\n            if rule not in grouped_integrands[cell_type]:\n                grouped_integrands[cell_type][rule] = []\n            grouped_integrands[cell_type][rule].append(integral.integrand())\n    return grouped_integrands\n\n\ndef compute_ir(\n    analysis: UFLData,\n    object_names: dict[int, str],\n    prefix: str,\n    options: dict[str, npt.DTypeLike | int | float | bool],\n    visualise: bool,\n) -> DataIR:\n    \"\"\"Compute intermediate representation.\"\"\"\n    logger.info(79 * \"*\")\n    logger.info(\"Compiler stage 2: Computing intermediate representation of objects\")\n    logger.info(79 * \"*\")\n\n    # Compute object names\n    # NOTE: This is done here for performance reasons, because repeated\n    # calls within each IR computation would be expensive due to UFL\n    # signature computations.\n    integral_names = {}\n    form_names = {}\n    for fd_index, fd in enumerate(analysis.form_data):\n        form_names[fd_index] = naming.form_name(fd.original_form, fd_index, prefix)\n        for itg_index, itg_data in enumerate(fd.integral_data):\n            integral_names[(fd_index, itg_index)] = naming.integral_name(\n                fd.original_form,\n                itg_data.integral_type,\n                fd_index,\n                itg_data.subdomain_id,\n                prefix,\n            )\n\n    irs = [\n        _compute_integral_ir(\n            fd,\n            i,\n            analysis.element_numbers.keys(),\n            integral_names,\n            options,\n            visualise,\n        )\n        for (i, fd) in enumerate(analysis.form_data)\n    ]\n    ir_integrals = list(itertools.chain(*irs))\n\n    integral_domains = {\n        i.expression.name: set(j[0] for j in i.expression.integrand.keys()) for a in irs for i in a\n    }\n    diagonalise = TensorPart.from_str(str(options[\"part\"]))\n    ir_forms = [\n        _compute_form_ir(\n            fd,\n            i,\n            prefix,\n            form_names,\n            integral_names,\n            integral_domains,\n            object_names,\n            diagonalise,\n        )\n        for (i, fd) in enumerate(analysis.form_data)\n    ]\n\n    ir_expressions = [\n        _compute_expression_ir(\n            expr,\n            i,\n            prefix,\n            analysis,\n            options,\n            visualise,\n            object_names,\n        )\n        for i, expr in enumerate(analysis.expressions)\n    ]\n\n    return DataIR(\n        integrals=ir_integrals,\n        forms=ir_forms,\n        expressions=ir_expressions,\n    )\n\n\ndef _compute_integral_ir(\n    form_data: FormData,\n    form_index: int,\n    unique_elements: typing.Iterable[basix.ufl._ElementBase],\n    integral_names: dict[tuple[int, int], str],\n    options,\n    visualise,\n) -> list[IntegralIR]:\n    \"\"\"Compute intermediate representation for form integrals.\n\n    Args:\n        form_data: Data from UFL analysis of the form\n        form_index: Index of form in the sequence of forms.\n        unique_elements: Set of unique elements in the form.\n        integral_names: Map from `(form_index, integral_index)` to the name of the integral.\n        options: Options for the intermediate representation. 'part': If the full tensor or\n            the diagonal of the tensor should be generated. Only valid for bi-linear forms.\n            'sum_factorization': If sum factorization should be used. Only has an effect on cell\n            integrals. 'table_atol',`table_rtol': Absolute and relative tolerance for clamping table\n            values at -1, 0 or 1.\n        visualise: If True, store the graph representation of the integrand in a pdf file\n            `S.pdf` and `F.pdf`\n\n    Returns:\n         A list of intermediate representations for each integral of the Form.\n    \"\"\"\n    _entity_types: dict[supported_integral_types, entity_types] = {\n        \"cell\": \"cell\",\n        \"exterior_facet\": \"facet\",\n        \"interior_facet\": \"facet\",\n        \"vertex\": \"vertex\",\n        \"ridge\": \"ridge\",\n    }\n\n    # Iterate over groups of integrals\n    irs = []\n    for itg_data_index, itg_data in enumerate(form_data.integral_data):\n        logger.info(f\"Computing IR for integral in integral group {itg_data_index}\")\n\n        # Cast integral type as long as we are using strings and not StringEnums\n        integral_type = typing.cast(supported_integral_types, itg_data.integral_type)\n\n        # Determine what kind of integral we are considering, as well checking that each integrand\n        # in the integral is associated with a domain of the same topological dimensio.\n        entity_type = _entity_types[integral_type]\n        ufl_cell = itg_data.domain.ufl_cell()\n        cell_type = basix_cell_from_string(ufl_cell.cellname)\n        tdim = ufl_cell.topological_dimension\n        assert all(tdim == itg.ufl_domain().topological_dimension for itg in itg_data.integrals)\n\n        # Initial population of what will become a CommonExpressionIR\n        expression_ir = {\n            \"integral_type\": integral_type,\n            \"entity_type\": entity_type,\n            \"shape\": (),\n            \"coordinate_element_hash\": itg_data.domain.ufl_coordinate_element().basix_hash(),\n            \"number_coordinate_dofs\": itg_data.domain.ufl_coordinate_element().dim,\n        }\n        # Initial population of what will become the IntegralIR\n        ir = {\n            \"rank\": form_data.rank,\n            \"enabled_coefficients\": itg_data.enabled_coefficients,\n            \"part\": TensorPart.from_str(options[\"part\"]),\n        }\n\n        # Determine if the form compiler has been asked to diagonalize a\n        # bilinear form (by only assembling the diagonal entries, modify rank if True)\n        diagonalise = False\n        if form_data.rank == 2 and ir[\"part\"] == TensorPart.diagonal:\n            diagonalise = True\n            ir[\"rank\"] = 1\n            assert form_data.argument_elements[0] == form_data.argument_elements[1], (\n                \"Can only diagonalise forms with identical arguments.\"\n            )\n\n        # Pre-compute the dimension number of dofs in each local element.\n        # Used to compute the shape of the locally assembled tensor, as well the\n        # coefficient offset\n        element_dimensions = {element: element.dim for element in unique_elements}\n\n        # Create dimensions of primary indices, needed to reset the argument\n        # 'A' given to tabulate_tensor() by the assembler.\n        argument_dimensions = [\n            element_dimensions[element] for element in form_data.argument_elements\n        ]\n\n        # Compute shape of element tensor\n        if expression_ir[\"integral_type\"] == \"interior_facet\":\n            expression_ir[\"tensor_shape\"] = [2 * dim for dim in argument_dimensions]\n        else:\n            expression_ir[\"tensor_shape\"] = argument_dimensions\n\n        if diagonalise:  # Modify output tensor shape if diagonalizing\n            expression_ir[\"tensor_shape\"] = expression_ir[\"tensor_shape\"][:1]\n\n        # Group integrals by quadrature rule by splitting them into separate integrands\n        grouped_integrands = _group_integrands_by_quadrature_rule(\n            itg_data.integrals,\n            form_data.argument_elements,\n            integral_type,\n            ufl_cell,\n            options[\"sum_factorization\"],\n        )\n\n        # Sum up all integrands for a given quadrature rule\n        sorted_integrals: dict[basix.CellType, dict[QuadratureRule, Integral]] = {\n            cell_type: {} for cell_type in grouped_integrands\n        }\n        for cell_type, integrands_by_cell in grouped_integrands.items():\n            for rule, integrands in integrands_by_cell.items():\n                integrands_summed = sorted_expr_sum(integrands)\n\n                integral_new = Integral(\n                    integrands_summed,\n                    itg_data.integral_type,\n                    itg_data.domain,\n                    itg_data.subdomain_id,\n                    {},\n                    None,\n                )\n                sorted_integrals[cell_type][rule] = integral_new\n\n        # Build coefficient numbering for UFCx interface here, to avoid\n        # renumbering in UFL and application of replace mapping.\n        # We extract a map from the original coefficients to their\n        # new index in the form, as well as where in the flattened\n        # input coefficient data a coefficient should start.\n        coefficient_numbering: dict[ufl.Coefficient, int] = {}\n        coefficient_offsets: dict[ufl.Coefficient, int] = {}\n        _offset = 0\n        width = 2 if integral_type in (\"interior_facet\") else 1\n        for i, (coeff, el) in enumerate(\n            zip(form_data.reduced_coefficients, form_data.coefficient_elements)\n        ):\n            coefficient_numbering[coeff] = i\n            coefficient_offsets[coeff] = _offset\n            _offset += width * element_dimensions[el]\n\n        # Add Coefficient numbering and offsets to IR\n        expression_ir[\"coefficient_numbering\"] = coefficient_numbering\n        expression_ir[\"coefficient_offsets\"] = coefficient_offsets\n\n        # Similar procedure with constants, but they are not removed\n        # as they usually contain very little data.\n        original_constant_offsets = {}\n        _offset = 0\n        for constant in form_data.original_form.constants():\n            original_constant_offsets[constant] = _offset\n            _offset += np.prod(constant.ufl_shape, dtype=int)\n\n        expression_ir[\"original_constant_offsets\"] = original_constant_offsets\n\n        # Create map from number of quadrature points -> integrand\n        integrand_map: dict[basix.CellType, dict[QuadratureRule, Expr]] = {\n            cell_type: {rule: integral.integrand() for rule, integral in cell_integrals.items()}\n            for cell_type, cell_integrals in sorted_integrals.items()\n        }\n\n        # Build more specific intermediate representation\n        integral_ir = compute_integral_ir(\n            itg_data.domain.ufl_cell(),\n            integral_type,\n            entity_type,\n            integrand_map,\n            expression_ir[\"tensor_shape\"],\n            options,\n            visualise,\n        )\n\n        expression_ir.update(integral_ir)\n\n        # Fetch name\n        expression_ir[\"name\"] = integral_names[(form_index, itg_data_index)]\n        ir[\"expression\"] = CommonExpressionIR(**expression_ir)\n        irs.append(IntegralIR(**ir))\n\n    return irs\n\n\ndef _compute_form_ir(\n    form_data,\n    form_id,\n    prefix,\n    form_names,\n    integral_names,\n    integral_domains,\n    object_names,\n    tensor_part: TensorPart,\n) -> FormIR:\n    \"\"\"Compute intermediate representation of form.\"\"\"\n    logger.info(f\"Computing IR for form {form_id}\")\n\n    # Store id\n    ir = {\"id\": form_id}\n\n    # Compute common data\n    ir[\"name\"] = form_names[form_id]\n\n    ir[\"signature\"] = form_data.original_form.signature()\n    args = form_data.original_form.arguments()\n    if tensor_part == TensorPart.diagonal and len(args) == 2:\n        assert args[0].ufl_function_space() == args[1].ufl_function_space(), (\n            \"Can only diagonalise forms with identical arguments.\"\n        )\n        ir[\"rank\"] = 1\n    else:\n        ir[\"rank\"] = len(form_data.original_form.arguments())\n\n    ir[\"num_coefficients\"] = len(form_data.reduced_coefficients)\n\n    ir[\"coefficient_names\"] = [\n        object_names.get(id(obj), f\"w{j}\") for j, obj in enumerate(form_data.reduced_coefficients)\n    ]\n\n    ir[\"num_constants\"] = len(form_data.original_form.constants())\n    ir[\"constant_ranks\"] = [len(obj.ufl_shape) for obj in form_data.original_form.constants()]\n    ir[\"constant_shapes\"] = [obj.ufl_shape for obj in form_data.original_form.constants()]\n\n    ir[\"constant_names\"] = [\n        object_names.get(id(obj), f\"c{j}\")\n        for j, obj in enumerate(form_data.original_form.constants())\n    ]\n\n    ir[\"original_coefficient_positions\"] = form_data.original_coefficient_positions\n\n    ir[\"finite_element_hashes\"] = [\n        e.basix_hash() for e in form_data.argument_elements + form_data.coefficient_elements\n    ]\n\n    form_name = object_names.get(id(form_data.original_form), form_id)\n\n    ir[\"name_from_uflfile\"] = f\"form_{prefix}_{form_name}\"\n\n    # Store names of integrals and subdomain_ids for this form, grouped\n    # by integral types since form points to all integrals it contains,\n    # it has to know their names for codegen phase\n    ufcx_integral_types = (\n        \"cell\",\n        \"exterior_facet\",\n        \"interior_facet\",\n        \"vertex\",\n        \"ridge\",\n    )\n    ir[\"subdomain_ids\"] = {itg_type: [] for itg_type in ufcx_integral_types}\n    ir[\"integral_names\"] = {itg_type: [] for itg_type in ufcx_integral_types}\n    ir[\"integral_domains\"] = {itg_type: [] for itg_type in ufcx_integral_types}\n    for itg_index, itg_data in enumerate(form_data.integral_data):\n        # UFL is using \"otherwise\" for default integrals (over whole mesh)\n        # but FFCx needs integers, so otherwise = -1\n        integral_type = itg_data.integral_type\n        subdomain_ids = [sid if sid != \"otherwise\" else -1 for sid in itg_data.subdomain_id]\n\n        if min(subdomain_ids) < -1:\n            raise ValueError(\"Integral subdomain IDs must be non-negative.\")\n        ir[\"subdomain_ids\"][integral_type] += subdomain_ids\n        for _ in range(len(subdomain_ids)):\n            iname = integral_names[(form_id, itg_index)]\n            ir[\"integral_names\"][integral_type] += [iname]\n            ir[\"integral_domains\"][integral_type] += [integral_domains[iname]]\n\n    return FormIR(**ir)\n\n\ndef _compute_expression_ir(\n    expr,\n    index,\n    prefix,\n    analysis,\n    options,\n    visualise,\n    object_names,\n):\n    \"\"\"Compute intermediate representation of an Expression.\"\"\"\n    logger.info(f\"Computing IR for Expression {index}\")\n\n    # Compute representation\n    ir = {}\n    base_ir = {}\n    original_expr = (expr[2], expr[1])\n\n    base_ir[\"name\"] = naming.expression_name(original_expr, prefix)\n\n    original_expr = expr[2]\n    points = expr[1]\n    expr = expr[0]\n\n    expr_domain = max(\n        ufl.domain.extract_domains(expr), default=None, key=lambda d: d.topological_dimension\n    )\n    cell = expr_domain.ufl_cell() if expr_domain else None\n\n    # Prepare dimensions of all unique element in expression, including\n    # elements for arguments, coefficients and coordinate mappings\n    element_dimensions = {element: element.dim for element in analysis.unique_elements}\n\n    # Extract dimensions for elements of arguments only\n    arguments = ufl.algorithms.extract_arguments(expr)\n    argument_elements = tuple(f.ufl_function_space().ufl_element() for f in arguments)\n    argument_dimensions = [element_dimensions[element] for element in argument_elements]\n\n    tensor_shape = argument_dimensions\n    base_ir[\"tensor_shape\"] = tensor_shape\n\n    base_ir[\"shape\"] = list(expr.ufl_shape)\n\n    coefficients = ufl.algorithms.extract_coefficients(expr)\n    coefficient_numbering = {}\n    for i, coeff in enumerate(coefficients):\n        coefficient_numbering[coeff] = i\n\n    # Add coefficient numbering to IR\n    base_ir[\"coefficient_numbering\"] = coefficient_numbering\n\n    original_coefficient_positions = []\n    original_coefficients = ufl.algorithms.extract_coefficients(original_expr)\n    for coeff in coefficients:\n        original_coefficient_positions.append(original_coefficients.index(coeff))\n\n    ir[\"coefficient_names\"] = [\n        object_names.get(id(obj), f\"w{j}\") for j, obj in enumerate(coefficients)\n    ]\n\n    ir[\"constant_names\"] = [\n        object_names.get(id(obj), f\"c{j}\")\n        for j, obj in enumerate(ufl.algorithms.analysis.extract_constants(expr))\n    ]\n\n    expr_name = object_names.get(id(original_expr), index)\n    ir[\"name_from_uflfile\"] = f\"expression_{prefix}_{expr_name}\"\n\n    if len(argument_elements) > 1:\n        raise RuntimeError(\"Expression with more than one Argument not implemented.\")\n\n    ir[\"original_coefficient_positions\"] = original_coefficient_positions\n\n    coefficient_elements = tuple(f.ufl_element() for f in coefficients)\n\n    offsets = {}\n    _offset = 0\n    for i, el in enumerate(coefficient_elements):\n        offsets[coefficients[i]] = _offset\n        _offset += element_dimensions[el]\n\n    # Copy offsets also into IR\n    base_ir[\"coefficient_offsets\"] = offsets\n\n    base_ir[\"integral_type\"] = \"expression\"\n    if cell is not None:\n        if (tdim := cell.topological_dimension) == (pdim := points.shape[1]):\n            base_ir[\"entity_type\"] = \"cell\"\n        elif tdim - 1 == pdim:\n            base_ir[\"entity_type\"] = \"facet\"\n        else:\n            raise ValueError(\n                f\"Expression on domain with topological dimension {tdim}\"\n                + f\"with points of dimension {pdim} not supported.\"\n            )\n    else:\n        # For spatially invariant expressions, all expressions are evaluated in the cell\n        base_ir[\"entity_type\"] = \"cell\"\n\n    # Build offsets for Constants\n    original_constant_offsets = {}\n    _offset = 0\n    for constant in ufl.algorithms.analysis.extract_constants(original_expr):\n        original_constant_offsets[constant] = _offset\n        _offset += np.prod(constant.ufl_shape, dtype=int)\n\n    base_ir[\"original_constant_offsets\"] = original_constant_offsets\n    base_ir[\"coordinate_element_hash\"] = (\n        expr_domain.ufl_coordinate_element().basix_hash() if expr_domain is not None else 0\n    )\n    base_ir[\"number_coordinate_dofs\"] = (\n        0 if expr_domain is None else expr_domain.ufl_coordinate_element().dim\n    )\n\n    weights = np.array([1.0] * points.shape[0])\n    rule = QuadratureRule(points, weights)\n    integrands = {\"\": {rule: expr}}\n\n    if cell is None:\n        assert (\n            len(ir[\"original_coefficient_positions\"]) == 0\n            and len(base_ir[\"original_constant_offsets\"]) == 0\n        )\n\n    expression_ir = compute_integral_ir(\n        cell,\n        base_ir[\"integral_type\"],\n        base_ir[\"entity_type\"],\n        integrands,\n        tensor_shape,\n        options,\n        visualise,\n    )\n\n    base_ir.update(expression_ir)\n    ir[\"expression\"] = CommonExpressionIR(**base_ir)\n    return ExpressionIR(**ir)\n"
  },
  {
    "path": "ffcx/ir/representationutils.py",
    "content": "# Copyright (C) 2012-2017 Marie Rognes\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Utility functions for some code shared between representations.\"\"\"\n\nimport hashlib\nimport itertools\nimport logging\n\nimport numpy as np\nimport ufl\n\nfrom ffcx.element_interface import (\n    create_quadrature,\n    map_edge_points,\n    map_facet_points,\n    reference_cell_vertices,\n)\n\nlogger = logging.getLogger(\"ffcx\")\n\n\nclass QuadratureRule:\n    \"\"\"A quadrature rule.\"\"\"\n\n    def __init__(self, points, weights, tensor_factors=None):\n        \"\"\"Initialise.\"\"\"\n        self.points = np.ascontiguousarray(points)  # TODO: change basix to make this unnecessary\n        self.weights = weights\n        self.tensor_factors = tensor_factors\n        self.has_tensor_factors = tensor_factors is not None\n        self._hash = None\n\n    def __hash__(self):\n        \"\"\"Hash.\"\"\"\n        if self._hash is None:\n            self.hash_obj = hashlib.sha1(self.points)\n            self._hash = int(self.hash_obj.hexdigest(), 32)\n        return self._hash\n\n    def __eq__(self, other):\n        \"\"\"Check equality.\"\"\"\n        return np.allclose(self.points, other.points) and np.allclose(self.weights, other.weights)\n\n    def id(self):\n        \"\"\"Return unique deterministic identifier.\n\n        Note:\n            This identifier is used to provide unique names to tables and symbols\n            in generated code.\n        \"\"\"\n        return self.hash_obj.hexdigest()[-3:]\n\n\ndef create_quadrature_points_and_weights(\n    integral_type, cell, degree, rule, elements, use_tensor_product=False\n):\n    \"\"\"Create quadrature rule and return points and weights.\"\"\"\n    pts = {}\n    wts = {}\n    tensor_factors = {}\n    if integral_type == \"cell\":\n        cell_name = cell.cellname\n        if cell_name in [\"quadrilateral\", \"hexahedron\"] and use_tensor_product:\n            if cell_name == \"quadrilateral\":\n                tensor_factors[cell_name] = [\n                    create_quadrature(\"interval\", degree, rule, elements) for _ in range(2)\n                ]\n            elif cell_name == \"hexahedron\":\n                tensor_factors[cell_name] = [\n                    create_quadrature(\"interval\", degree, rule, elements) for _ in range(3)\n                ]\n            pts[cell_name] = np.array(\n                [\n                    tuple(i[0] for i in p)\n                    for p in itertools.product(*[f[0] for f in tensor_factors[cell_name]])\n                ]\n            )\n            wts[cell_name] = np.array(\n                [np.prod(p) for p in itertools.product(*[f[1] for f in tensor_factors[cell_name]])]\n            )\n        else:\n            pts[cell_name], wts[cell_name] = create_quadrature(cell_name, degree, rule, elements)\n    elif integral_type in ufl.measure.facet_integral_types:\n        for ft in cell.facet_types:\n            pts[ft.cellname], wts[ft.cellname] = create_quadrature(\n                ft.cellname,\n                degree,\n                rule,\n                elements,\n            )\n    elif integral_type in ufl.measure.ridge_integral_types:\n        for rt in cell.ridge_types:\n            pts[rt.cellname], wts[rt.cellname] = create_quadrature(\n                rt.cellname,\n                degree,\n                rule,\n                elements,\n            )\n    elif integral_type in ufl.measure.point_integral_types:\n        pts[\"vertex\"], wts[\"vertex\"] = create_quadrature(\"vertex\", degree, rule, elements)\n    elif integral_type == \"expression\":\n        pass\n    else:\n        logger.exception(f\"Unknown integral type: {integral_type}\")\n\n    return pts, wts, tensor_factors\n\n\ndef integral_type_to_entity_dim(integral_type, tdim):\n    \"\"\"Given integral_type and domain tdim, return the tdim of the integration entity.\"\"\"\n    if integral_type == \"cell\":\n        entity_dim = tdim\n    elif integral_type in ufl.measure.facet_integral_types:\n        entity_dim = tdim - 1\n    elif integral_type in ufl.measure.ridge_integral_types:\n        entity_dim = tdim - 2\n    elif integral_type in ufl.measure.point_integral_types:\n        entity_dim = 0\n    elif integral_type in ufl.custom_integral_types:\n        entity_dim = tdim\n    elif integral_type == \"expression\":\n        entity_dim = tdim\n    else:\n        raise RuntimeError(f\"Unknown integral_type: {integral_type}\")\n    return entity_dim\n\n\ndef map_integral_points(points, integral_type, cell, entity):\n    \"\"\"Map points from reference entity to its parent reference cell.\"\"\"\n    tdim = cell.topological_dimension\n    entity_dim = integral_type_to_entity_dim(integral_type, tdim)\n    if entity_dim == tdim:\n        assert points.shape[1] == tdim\n        assert entity == 0\n        return np.asarray(points)\n    elif entity_dim == tdim - 1:\n        assert points.shape[1] == tdim - 1\n        return np.asarray(map_facet_points(points, entity, cell.cellname))\n    elif entity_dim == tdim - 2:\n        assert points.shape[1] == tdim - 2\n        # Special handling of pushing forward 0D points to cell\n        if entity_dim == 0:\n            assert points.shape[1] == 0\n            points = np.zeros((1, 1))\n        return np.asarray(map_edge_points(points, entity, cell.cellname))\n    elif entity_dim == 0:\n        return np.asarray([reference_cell_vertices(cell.cellname)[entity]])\n    else:\n        raise RuntimeError(f\"Can't map points from entity_dim={entity_dim}\")\n"
  },
  {
    "path": "ffcx/main.py",
    "content": "# Copyright (C) 2004-2025 Anders Logg, Garth N. Wells and Michal Habera\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Command-line interface to FFCx.\n\nParse command-line arguments and generate code from input UFL form\nfiles.\n\"\"\"\n\nimport argparse\nimport cProfile\nimport logging\nimport pathlib\nimport re\nimport string\nfrom collections.abc import Sequence\n\nimport ufl\n\nfrom ffcx import __version__ as FFCX_VERSION\nfrom ffcx import compiler, formatting\nfrom ffcx.options import FFCX_DEFAULT_OPTIONS, get_options\n\nlogger = logging.getLogger(\"ffcx\")\n\nparser = argparse.ArgumentParser(\n    description=\"FEniCS Form Compiler (FFCx, https://fenicsproject.org)\"\n)\nparser.add_argument(\"--version\", action=\"version\", version=f\"%(prog)s (version {FFCX_VERSION})\")\nparser.add_argument(\"-d\", \"--dir\", type=str, default=\".\", help=\"Output directory.\")\nparser.add_argument(\n    \"-o\",\n    \"--outfile\",\n    nargs=\"*\",\n    type=str,\n    default=None,\n    help=\"Generated code filename stem. Defaults to the stem of the UFL file name.\",\n)\nparser.add_argument(\n    \"-n\",\n    \"--namespace\",\n    nargs=\"*\",\n    type=str,\n    default=None,\n    help=\"Namespace prefix used in the generated code. Defaults to the stem of the UFL file name.\",\n)\nparser.add_argument(\"--visualise\", action=\"store_true\", help=\"Visualise the IR graph.\")\nparser.add_argument(\"-p\", \"--profile\", action=\"store_true\", help=\"Enable profiling.\")\n\n# Add all options from FFCx option system\nfor opt_name, (arg_type, opt_val, opt_desc, choices) in FFCX_DEFAULT_OPTIONS.items():\n    if isinstance(opt_val, bool):\n        parser.add_argument(\n            f\"--{opt_name}\", action=\"store_true\", help=f\"{opt_desc} (default={opt_val})\"\n        )\n    else:\n        parser.add_argument(\n            f\"--{opt_name}\", type=arg_type, choices=choices, help=f\"{opt_desc} (default={opt_val})\"\n        )\n\nparser.add_argument(\n    \"-i\",\n    \"--input\",\n    nargs=\"*\",\n    help=\"UFL file(s) to be compiled. This option must be used instead \"\n    \"of positional arguments (ufl_file) when using the options -f or -n.\",\n)\nparser.add_argument(\n    \"ufl_file\",\n    nargs=\"*\",\n    help=\"UFL file(s) to be compiled. Positional arguments can be used \"\n    \"only when the -f and -n options are not used.\",\n)\n\n\ndef main(args: Sequence[str] | None = None) -> int:\n    \"\"\"Run ffcx on a UFL file.\"\"\"\n    logging.captureWarnings(capture=True)\n\n    xargs = parser.parse_args(args)\n\n    # Handle UFL files input\n    if xargs.input is not None:\n        assert len(xargs.ufl_file) == 0, \"Unexpected positional arguments with -i option.\"\n        if xargs.namespace is not None:\n            assert len(xargs.namespace) == len(xargs.input), (\n                \"Number of namespaces must match number of input files.\"\n            )\n        if xargs.outfile is not None:\n            assert len(xargs.outfile) == len(xargs.input), (\n                \"Number of output files must match number of input files.\"\n            )\n\n        filenames = xargs.input\n    else:\n        filenames = xargs.ufl_file\n\n    def sanitise_filename(name: str) -> str:\n        \"\"\"Sanitise name by removing non-alphanumeric characters.\"\"\"\n        name_s = pathlib.Path(name).stem\n        name_s = re.subn(\"[^{}]\".format(string.ascii_letters + string.digits + \"_\"), \"!\", name_s)[0]\n        name_s = re.subn(\"!+\", \"_\", name_s)[0]\n        return name_s\n\n    if xargs.namespace is None:\n        namespaces = [sanitise_filename(name) for name in filenames]\n    else:\n        namespaces = xargs.namespace\n    if xargs.outfile is None:\n        outfiles = [sanitise_filename(name) for name in filenames]\n    else:\n        outfiles = xargs.outfile\n\n    # Parse all other options\n    priority_options = {k: v for k, v in xargs.__dict__.items() if v is not None}\n    options = get_options(priority_options)\n\n    for filename, namespace, outfile in zip(filenames, namespaces, outfiles):\n        # Turn on profiling\n        if xargs.profile:\n            pr = cProfile.Profile()\n            pr.enable()\n\n        # Load UFL file\n        ufd = ufl.algorithms.load_ufl_file(filename)\n\n        # Generate code\n        code, suffixes = compiler.compile_ufl_objects(\n            ufd.forms + ufd.expressions + ufd.elements,\n            options=options,\n            object_names=ufd.object_names,\n            namespace=namespace,\n            visualise=xargs.visualise,\n        )\n\n        # Write to file\n        formatting.write_code(code, outfile, suffixes, xargs.dir)\n\n        # Turn off profiling and write status to file\n        if xargs.profile:\n            pr.disable()\n            pfn = f\"ffcx_{namespace}.profile\"\n            pr.dump_stats(pfn)\n\n    return 0\n"
  },
  {
    "path": "ffcx/naming.py",
    "content": "# Copyright (C) 2009-2020 Anders Logg and Michal Habera\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Naming.\"\"\"\n\nfrom __future__ import annotations\n\nimport hashlib\nimport numbers\nfrom typing import Literal\n\nimport numpy as np\nimport numpy.typing as npt\nimport ufl\n\nimport ffcx\nimport ffcx.codegeneration\n\n\ndef compute_signature(\n    ufl_objects: list[ufl.Form] | list[tuple[ufl.core.expr.Expr, npt.NDArray[np.floating]]],\n    tag: str,\n) -> str:\n    \"\"\"Compute the signature hash.\n\n    Based on the UFL type of the objects and an additional optional 'tag'.\n    \"\"\"\n    object_signature = \"\"\n    for ufl_object in ufl_objects:\n        # Get signature from ufl object\n        if isinstance(ufl_object, ufl.Form):\n            kind = \"form\"\n            object_signature += ufl_object.signature()\n        elif isinstance(ufl_object, tuple) and isinstance(ufl_object[0], ufl.core.expr.Expr):\n            expr = ufl_object[0]\n            points = ufl_object[1]\n\n            # FIXME Move this to UFL, cache the computation\n            coeffs = ufl.algorithms.extract_coefficients(expr)\n            consts = ufl.algorithms.analysis.extract_constants(expr)\n            args = ufl.algorithms.analysis.extract_arguments(expr)\n\n            rn = dict()\n            rn.update(dict((c, i) for i, c in enumerate(coeffs)))\n            rn.update(dict((c, i) for i, c in enumerate(consts)))\n            rn.update(dict((c, i) for i, c in enumerate(args)))\n\n            domains: list[ufl.AbstractDomain] = []\n            for coeff in coeffs:\n                domains.append(*ufl.domain.extract_domains(coeff))\n            for arg in args:\n                domains.append(*ufl.domain.extract_domains(arg))\n            for gc in ufl.algorithms.analysis.extract_type(expr, ufl.classes.GeometricQuantity):\n                domains.append(*ufl.domain.extract_domains(gc))\n            for const in consts:\n                domains.append(*ufl.domain.extract_domains(const))\n            domains = ufl.algorithms.analysis.unique_tuple(domains)\n            assert all([isinstance(domain, ufl.Mesh) for domain in domains])\n            rn.update(dict((d, i) for i, d in enumerate(domains)))\n\n            # Hash on UFL signature and points\n            signature = ufl.algorithms.signature.compute_expression_signature(expr, rn)\n            object_signature += signature\n            object_signature += repr(points)\n\n            kind = \"expression\"\n        else:\n            raise RuntimeError(f\"Unknown ufl object type {ufl_object.__class__.__name__}\")\n\n    # Build combined signature\n    signatures = [\n        object_signature,\n        str(ffcx.__version__),\n        ffcx.codegeneration.get_signature(),\n        kind,\n        tag,\n    ]\n    string = \";\".join(signatures)\n    return hashlib.sha1(string.encode(\"utf-8\")).hexdigest()\n\n\ndef integral_name(\n    original_form: ufl.form.Form,\n    integral_type: str,\n    form_id: int,\n    subdomain_id: Literal[\"everywhere\"] | numbers.Integral | tuple[numbers.Integral, ...],\n    prefix: str,\n) -> str:\n    \"\"\"Get integral name.\"\"\"\n    sig = compute_signature([original_form], str((prefix, integral_type, form_id, subdomain_id)))\n    return f\"integral_{sig}\"\n\n\ndef form_name(original_form: ufl.form.Form, form_id: int, prefix: str) -> str:\n    \"\"\"Get form name.\"\"\"\n    sig = compute_signature([original_form], str((prefix, form_id)))\n    return f\"form_{sig}\"\n\n\ndef expression_name(\n    expression: tuple[ufl.core.expr.Expr, npt.NDArray[np.floating]], prefix: str\n) -> str:\n    \"\"\"Get expression name.\"\"\"\n    assert isinstance(expression[0], ufl.core.expr.Expr)\n    sig = compute_signature([expression], prefix)\n    return f\"expression_{sig}\"\n"
  },
  {
    "path": "ffcx/options.py",
    "content": "# Copyright (C) 2005-2020 Anders Logg, Michal Habera, Jack S. Hale\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Options.\"\"\"\n\nfrom __future__ import annotations\n\nimport functools\nimport json\nimport logging\nimport os\nimport os.path\nimport pprint\nfrom pathlib import Path\n\nimport numpy.typing as npt\n\nlogger = logging.getLogger(\"ffcx\")\n\nFFCX_DEFAULT_OPTIONS = {\n    \"language\": (str, \"C\", \"Language to output kernel in\", None),\n    \"epsilon\": (float, 1e-14, \"Machine precision, used for dropping zero terms in tables.\", None),\n    \"scalar_type\": (\n        str,\n        \"float64\",\n        \"Scalar type to use in generated code.\",\n        (\"float32\", \"float64\", \"complex64\", \"complex128\"),\n    ),\n    \"sum_factorization\": (bool, False, \"Use sum factorization.\", None),\n    \"table_rtol\": (\n        float,\n        1e-6,\n        \"Relative precision to use when comparing finite element table values for reuse.\",\n        None,\n    ),\n    \"table_atol\": (\n        float,\n        1e-9,\n        \"Absolute precision to use when comparing finite element table values reuse.\",\n        None,\n    ),\n    \"verbosity\": (\n        int,\n        30,\n        \"Logger verbosity, follows standard library levels, i.e. INFO=20, DEBUG=10, etc.\",\n        None,\n    ),\n    \"part\": (str, \"full\", \"Part of bilinear tensor to assemble\", (\"full\", \"diagonal\")),\n}\n\n\n@functools.cache\ndef _load_options() -> tuple[dict, dict]:\n    \"\"\"Load options from JSON files.\"\"\"\n    user_config_file = os.getenv(\"XDG_CONFIG_HOME\", default=Path.home().joinpath(\".config\")) / Path(\n        \"ffcx\", \"ffcx_options.json\"\n    )\n    try:\n        with open(user_config_file) as f:\n            user_options = json.load(f)\n    except FileNotFoundError:\n        user_options = {}\n\n    pwd_config_file = Path.cwd().joinpath(\"ffcx_options.json\")\n    try:\n        with open(pwd_config_file) as f:\n            pwd_options = json.load(f)\n    except FileNotFoundError:\n        pwd_options = {}\n\n    return (user_options, pwd_options)\n\n\ndef get_options(\n    priority_options: dict[str, npt.DTypeLike | int | float] | None = None,\n) -> dict[str, int | float | npt.DTypeLike]:\n    \"\"\"Return (a copy of) the merged option values for FFCX.\n\n    Args:\n        priority_options: take priority over all other option values (see notes)\n\n    Returns:\n        merged option values\n\n    Note:\n        This function sets the log level from the merged option values prior to\n        returning.\n\n        The `ffcx_options.json` files are cached on the first call. Subsequent\n        calls to this function use this cache.\n\n        Priority ordering of options from highest to lowest is:\n\n        -  **priority_options** (API and command line options)\n        -  **$PWD/ffcx_options.json** (local options)\n        -  **$XDG_CONFIG_HOME/ffcx/ffcx_options.json** (user options)\n        -  **FFCX_DEFAULT_OPTIONS** in `ffcx.options`\n\n        `XDG_CONFIG_HOME` is `~/.config/` if the environment variable is not set.\n\n        Example `ffcx_options.json` file:\n\n          { \"epsilon\": 1e-7 }\n\n    \"\"\"\n    options: dict[str, npt.DTypeLike | int | float] = {}\n\n    for opt, (_, value, _, _) in FFCX_DEFAULT_OPTIONS.items():\n        options[opt] = value  # type: ignore\n\n    # NOTE: _load_options uses functools.lru_cache\n    user_options, pwd_options = _load_options()\n\n    options.update(user_options)\n    options.update(pwd_options)\n    if priority_options is not None:\n        options.update(priority_options)\n\n    logger.setLevel(int(options[\"verbosity\"]))  # type: ignore\n    logger.info(\"Final option values\")\n    logger.info(pprint.pformat(options))\n\n    return options\n\n\ndef get_language(options: dict[str, int | float | npt.DTypeLike]) -> str:\n    \"\"\"Retrieve the language option from the options database.\n\n    Applies for internal languages the alias conversion.\n    \"\"\"\n    lang = str(options.get(\"language\", FFCX_DEFAULT_OPTIONS[\"language\"][1]))\n    return {\"C\": \"ffcx.codegeneration.C\", \"numba\": \"ffcx.codegeneration.numba\"}.get(lang, lang)\n"
  },
  {
    "path": "ffcx/py.typed",
    "content": ""
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=62\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"fenics-ffcx\"\nversion = \"0.11.0.dev0\"\ndescription = \"The FEniCSx Form Compiler\"\nreadme = \"README.md\"\nrequires-python = \">=3.10\"\nlicense = \"LGPL-3.0-or-later\"\nlicense-files = [\"COPYING*\"]\nauthors = [\n    { email = \"fenics-steering-council@googlegroups.com\" },\n    { name = \"FEniCS Steering Council\" },\n]\ndependencies = [\n    \"numpy>=1.21\",\n    \"cffi\",\n    \"setuptools>=77.0.3\",        # cffi with compilation support requires setuptools\n    \"fenics-basix>=0.11.0.dev0\",\n    \"fenics-ufl>=2025.2.0\",\n]\n\n[project.urls]\nhomepage = \"https://fenicsproject.org\"\nrepository = \"https://github.com/fenics/ffcx.git\"\ndocumentation = \"https://docs.fenicsproject.org\"\n\n[project.scripts]\nffcx = \"ffcx:__main__.main\"\n\n[project.optional-dependencies]\nlint = [\"ruff\"]\ndocs = [\"sphinx\", \"sphinx_rtd_theme\"]\noptional = [\"numba\", \"pygraphviz==1.9\"]\ntest = [\"pytest >= 6.0\", \"sympy\", \"numba\"]\nci = [\n    \"coveralls\",\n    \"coverage>=7.0.0\",\n    \"pytest-cov\",\n    \"pytest-xdist\",\n    \"types-setuptools\",\n    \"mypy\",\n    \"fenics-ffcx[docs]\",\n    \"fenics-ffcx[lint]\",\n    \"fenics-ffcx[test]\",\n]\n\n[tool.setuptools]\npackages = [\n    \"ffcx\",\n    \"ffcx.codegeneration\",\n    \"ffcx.codegeneration.C\",\n    \"ffcx.codegeneration.numba\",\n    \"ffcx.ir\",\n    \"ffcx.ir.analysis\",\n]\n\n[tool.pytest.ini_options]\nminversion = \"6.0\"\naddopts = \"-ra\"\ntestpaths = [\"test\"]\nnorecursedirs = [\"libs\", \"docs\"]\nlog_cli = true\n\n[tool.mypy]\n# Suggested at https://blog.wolt.com/engineering/2021/09/30/professional-grade-mypy-configuration/\n# Goal would be to make all of the below True long-term\ndisallow_untyped_defs = true\ndisallow_any_unimported = true\nno_implicit_optional = true\ncheck_untyped_defs = true\nwarn_return_any = true\nwarn_unused_ignores = true\nshow_error_codes = true\n\n[[tool.mypy.overrides]]\nmodule = ['basix', 'cffi', 'numba.*', 'pygraphviz', 'ufl.*']\nignore_missing_imports = true\n\n[[tool.mypy.overrides]]\nmodule = [\"ffcx.*\", \"ffcx.ir.*\", \"ffcx.codegeneration.*\"]\ndisallow_any_unimported = false                           # most of these come from UFL\n\n[[tool.mypy.overrides]]\nmodule = [\"ffcx.ir.*\", \"ffcx.codegeneration.*\"]\ncheck_untyped_defs = false\ndisallow_untyped_defs = false\n\n[tool.ruff]\nline-length = 100\nindent-width = 4\n\n[tool.ruff.format]\ndocstring-code-format = true\n\n[tool.ruff.lint]\nselect = [\n    # \"N\", # pep8-naming\n    \"E\",   # pycodestyle\n    \"W\",   # pycodestyle\n    \"D\",   # pydocstyle\n    \"F\",   # pyflakes\n    \"I\",   # isort\n    \"RUF\", # Ruff-specific rules\n    \"UP\",  # pyupgrade\n    \"ICN\", # flake8-import-conventions\n    \"NPY\", # numpy-specific rules\n    \"FLY\", # use f-string not static joins\n    \"LOG\", # https://docs.astral.sh/ruff/rules/#flake8-logging-log\n    # \"ISC\", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc\n    # \"B\", # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b\n    # \"A\", # https://docs.astral.sh/ruff/rules/#flake8-builtins-a\n]\nignore = [\"RUF005\", \"RUF012\", \"RUF015\"]\n\n[tool.ruff.lint.per-file-ignores]\n\"test/*\" = [\"D\"]\n\n[tool.ruff.lint.pydocstyle]\nconvention = \"google\"\n"
  },
  {
    "path": "test/conftest.py",
    "content": "# Copyright (C) 2020 Michal Habera\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\"\"\"Test configuration.\"\"\"\n\nimport sys\n\nimport pytest\n\n\n@pytest.fixture(scope=\"module\")\ndef compile_args():\n    \"\"\"Compiler arguments.\"\"\"\n    if sys.platform.startswith(\"win32\"):\n        return [\"-Od\"]\n    else:\n        return [\"-O1\", \"-Wall\", \"-Werror\"]\n"
  },
  {
    "path": "test/poisson.py",
    "content": "# Copyright (C) 2004-2025 Anders Logg and Paul T. Kühner\n#\n# This file is part of FFCx.\n#\n# FFCx is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# FFCx is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with FFCx. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"The bilinear form a(u, v) and linear form L(v) for Poisson's equation.\n\nCompile this form with FFCx: ffcx Poisson.ufl.\n\"\"\"\n\nimport basix.ufl\nfrom ufl import (\n    Coefficient,\n    Constant,\n    FunctionSpace,\n    Mesh,\n    TestFunction,\n    TrialFunction,\n    dx,\n    grad,\n    inner,\n    tr,\n)\n\nmesh = Mesh(basix.ufl.element(\"P\", \"triangle\", 1, shape=(2,)))\n\n# Forms\ne = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\nspace = FunctionSpace(mesh, e)\n\nu = TrialFunction(space)\nv = TestFunction(space)\nf = Coefficient(space)\n\nkappa = Constant(mesh, shape=(2, 2))\n\na = tr(kappa) * inner(grad(u), grad(v)) * dx\nL = f * v * dx\n\n# Expressions\ne_vec = basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2, 3))\nspace_vec = FunctionSpace(mesh, e_vec)\nf_vec = Coefficient(space_vec)\n\nexpressions = [(kappa * f_vec, e_vec.basix_element.points)]\n"
  },
  {
    "path": "test/test_add_mode.py",
    "content": "# Copyright (C) 2019 Chris Richardson\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nimport sys\n\nimport basix.ufl\nimport numpy as np\nimport pytest\nimport ufl\n\nimport ffcx.codegeneration.jit\nfrom ffcx.codegeneration.utils import dtype_to_c_type, dtype_to_scalar_dtype\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float32\",\n        \"float64\",\n        pytest.param(\n            \"complex64\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_additive_facet_integral(dtype, compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    a = ufl.inner(u, v) * ufl.ds\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n\n    integral_offsets = form0.form_integral_offsets\n    ex = module.lib.exterior_facet\n    assert integral_offsets[ex + 1] - integral_offsets[ex] == 1\n    integral_id = form0.form_integral_ids[integral_offsets[ex]]\n    assert integral_id == -1\n\n    default_integral = form0.form_integrals[integral_offsets[ex]]\n\n    A = np.zeros((3, 3), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n    facets = np.array([0], dtype=np.int32)\n    perm = np.array([0], dtype=np.uint8)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array(\n        [0.0, 2.0, 0.0, np.sqrt(3.0), -1.0, 0.0, -np.sqrt(3.0), -1.0, 0.0], dtype=xdtype\n    )\n\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    for i in range(3):\n        facets[0] = i\n        kernel(\n            ffi.cast(f\"{c_type} *\", A.ctypes.data),\n            ffi.cast(f\"{c_type} *\", w.ctypes.data),\n            ffi.cast(f\"{c_type} *\", c.ctypes.data),\n            ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n            ffi.cast(\"int *\", facets.ctypes.data),\n            ffi.cast(\"uint8_t *\", perm.ctypes.data),\n            ffi.NULL,\n        )\n        assert np.isclose(A.sum(), np.sqrt(12) * (i + 1))\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float32\",\n        \"float64\",\n        pytest.param(\n            \"complex64\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_additive_cell_integral(dtype, compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n\n    cell = module.lib.cell\n    offsets = form0.form_integral_offsets\n    num_integrals = offsets[cell + 1] - offsets[cell]\n    assert num_integrals == 1\n    integral_id = form0.form_integral_ids[offsets[cell]]\n    assert integral_id == -1\n\n    default_integral = form0.form_integrals[offsets[cell]]\n\n    A = np.zeros((3, 3), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array(\n        [0.0, 2.0, 0.0, np.sqrt(3.0), -1.0, 0.0, -np.sqrt(3.0), -1.0, 0.0], dtype=xdtype\n    )\n\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    A0 = np.array(A)\n    for i in range(3):\n        kernel(\n            ffi.cast(f\"{c_type} *\", A.ctypes.data),\n            ffi.cast(f\"{c_type} *\", w.ctypes.data),\n            ffi.cast(f\"{c_type} *\", c.ctypes.data),\n            ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n            ffi.NULL,\n            ffi.NULL,\n            ffi.NULL,\n        )\n\n        assert np.all(np.isclose(A, (i + 2) * A0))\n"
  },
  {
    "path": "test/test_cache.py",
    "content": "# Copyright (C) 2019 Chris Richardson\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nimport sys\n\nimport basix.ufl\nimport ufl\n\nimport ffcx.codegeneration.jit\n\n\ndef test_cache_modes(compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n    forms = [a]\n\n    # Load form from /tmp\n    _compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, cffi_extra_compile_args=compile_args\n    )\n    tmpname = module.__name__\n    tmpfile = module.__file__\n    print(tmpname, tmpfile)\n    del sys.modules[tmpname]\n\n    # Load form from cache\n    _compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, cache_dir=\"./compile-cache\", cffi_extra_compile_args=compile_args\n    )\n    newname = module.__name__\n    newfile = module.__file__\n    print(newname, newfile)\n\n    assert newname == tmpname\n    assert newfile != tmpfile\n"
  },
  {
    "path": "test/test_cmdline.py",
    "content": "# Copyright (C) 2018-2025 Chris N. Richardson and Paul T. Kühner\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nimport importlib\nimport subprocess\nfrom pathlib import Path\n\nimport pytest\n\n\ndef test_cmdline_simple():\n    dir = Path(__file__).parent\n    subprocess.run([\"ffcx\", \"-o\", dir, dir / \"poisson.py\"], check=True)\n\n\n@pytest.mark.skipif(\n    importlib.util.find_spec(\"pygraphviz\") is None, reason=\"pygraphviz not installed.\"\n)\ndef test_visualise():\n    dir = Path(__file__).parent\n    subprocess.run([\"ffcx\", \"-o\", dir, \"--visualise\", dir / \"poisson.py\"], check=True)\n    assert (Path.cwd() / \"S.pdf\").is_file()\n    assert (Path.cwd() / \"F.pdf\").is_file()\n"
  },
  {
    "path": "test/test_custom_data.py",
    "content": "# Copyright (C) 2024 Susanne Claus\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nimport numpy as np\nimport pytest\n\n\ndef test_tabulate_tensor_integral_add_values():\n    pytest.importorskip(\"cffi\")\n\n    from cffi import FFI\n\n    # Define custom tabulate tensor function in C with a struct\n    # Step 1: Define the function in C and set up the CFFI builder\n    ffibuilder = FFI()\n    ffibuilder.set_source(\n        \"_cffi_kernelA\",\n        r\"\"\"\n        typedef struct {\n            size_t size;\n            double* values;\n        } cell_data;\n\n        void tabulate_tensor_integral_add_values(double* restrict A,\n                                                const double* restrict w,\n                                                const double* restrict c,\n                                                const double* restrict coordinate_dofs,\n                                                const int* restrict entity_local_index,\n                                                const uint8_t* restrict quadrature_permutation,\n                                                void* custom_data)\n        {\n            // Cast the void* custom_data to cell_data*\n            cell_data* custom_data_ptr = (cell_data*)custom_data;\n\n            // Access the custom data\n            size_t size = custom_data_ptr->size;\n            double* values = custom_data_ptr->values;\n\n            // Use the values in your computations\n            for (size_t i = 0; i < size; i++) {\n                A[0] += values[i];\n            }\n        }\n        \"\"\",\n    )\n    ffibuilder.cdef(\n        \"\"\"\n        typedef struct {\n            size_t size;\n            double* values;\n        } cell_data;\n\n        void tabulate_tensor_integral_add_values(double* restrict A,\n                                                const double* restrict w,\n                                                const double* restrict c,\n                                                const double* restrict coordinate_dofs,\n                                                const int* restrict entity_local_index,\n                                                const uint8_t* restrict quadrature_permutation,\n                                                void* custom_data);\n        \"\"\"\n    )\n\n    # Step 2: Compile the C code\n    ffibuilder.compile(verbose=True)\n\n    # Step 3: Import the compiled library\n    from _cffi_kernelA import ffi, lib\n\n    # Define cell data\n    values = np.array([2.0, 1.0], dtype=np.float64)\n    size = len(values)\n    expected_result = np.array([3.0], dtype=np.float64)\n\n    # Define the input arguments\n    A = np.zeros(1, dtype=np.float64)\n    w = np.array([1.0], dtype=np.float64)\n    c = np.array([0.0], dtype=np.float64)\n    coordinate_dofs = np.array(\n        [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0], dtype=np.float64\n    )\n    entity_local_index = np.array([0], dtype=np.int32)\n    quadrature_permutation = np.array([0], dtype=np.uint8)\n\n    # Cast the arguments to the appropriate C types\n    A_ptr = ffi.cast(\"double*\", A.ctypes.data)\n    w_ptr = ffi.cast(\"double*\", w.ctypes.data)\n    c_ptr = ffi.cast(\"double*\", c.ctypes.data)\n    coordinate_dofs_ptr = ffi.cast(\"double*\", coordinate_dofs.ctypes.data)\n    entity_local_index_ptr = ffi.cast(\"int*\", entity_local_index.ctypes.data)\n    quadrature_permutation_ptr = ffi.cast(\"uint8_t*\", quadrature_permutation.ctypes.data)\n\n    # Use ffi.from_buffer to create a CFFI pointer from the NumPy array\n    values_ptr = ffi.cast(\"double*\", values.ctypes.data)\n\n    # Allocate memory for the struct\n    custom_data = ffi.new(\"cell_data*\")\n    custom_data.size = size\n    custom_data.values = values_ptr\n\n    # Cast the struct to void*\n    custom_data_ptr = ffi.cast(\"void*\", custom_data)\n\n    # Call the function\n    lib.tabulate_tensor_integral_add_values(\n        A_ptr,\n        w_ptr,\n        c_ptr,\n        coordinate_dofs_ptr,\n        entity_local_index_ptr,\n        quadrature_permutation_ptr,\n        custom_data_ptr,\n    )\n\n    # Assert the result\n    np.testing.assert_allclose(A, expected_result, rtol=1e-5)\n"
  },
  {
    "path": "test/test_jit_expression.py",
    "content": "# Copyright (C) 2019-2024 Michal Habera and Jørgen S. Dokken\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nimport basix\nimport basix.ufl\nimport cffi\nimport numpy as np\nimport pytest\nimport ufl\n\nimport ffcx.codegeneration.jit\n\n\ndef test_matvec(compile_args):\n    \"\"\"Test evaluation of linear rank-0 form.\n\n    Evaluates expression c * A_ij * f_j where c is a Constant,\n    A_ij is a user specified constant matrix and f_j is j-th component\n    of user specified vector-valued finite element function (in P1 space).\n\n    \"\"\"\n    e = basix.ufl.element(\"P\", \"triangle\", 1, shape=(2,))\n    mesh = ufl.Mesh(e)\n    V = ufl.FunctionSpace(mesh, e)\n    f = ufl.Coefficient(V)\n\n    a_mat = np.array([[1.0, 2.0], [1.0, 1.0]])\n    a = ufl.as_matrix(a_mat)\n    expr = ufl.Constant(mesh) * ufl.dot(a, f)\n\n    points = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]])\n    obj, _module, _code = ffcx.codegeneration.jit.compile_expressions(\n        [(expr, points)], cffi_extra_compile_args=compile_args\n    )\n\n    ffi = cffi.FFI()\n    expression = obj[0]\n\n    assert mesh.ufl_coordinate_element().basix_hash() == expression.coordinate_element_hash\n\n    dtype = np.float64\n    c_type = \"double\"\n    xdtype = np.float64\n    c_xtype = \"double\"\n\n    A = np.zeros((3, 2), dtype=dtype)\n    f_mat = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])\n\n    # Coefficient storage XYXYXY\n    w = np.array(f_mat.T.flatten(), dtype=dtype)\n    c = np.array([0.5], dtype=dtype)\n    entity_index = np.array([0], dtype=np.intc)\n    quad_perm = np.array([0], dtype=np.dtype(\"uint8\"))\n\n    # Coords storage XYZXYZXYZ\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=xdtype)\n    expression.tabulate_tensor_float64(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", entity_index.ctypes.data),\n        ffi.cast(\"uint8_t *\", quad_perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    # Check the computation against correct NumPy value\n    assert np.allclose(A, 0.5 * np.dot(a_mat, f_mat).T)\n\n    # Prepare NumPy array of points attached to the expression\n    length = expression.num_points * expression.entity_dimension\n    points_kernel = np.frombuffer(\n        ffi.buffer(expression.points, length * ffi.sizeof(\"double\")), np.double\n    )\n    points_kernel = points_kernel.reshape(points.shape)\n    assert np.allclose(points, points_kernel)\n\n    # Check the value shape attached to the expression\n    value_shape = np.frombuffer(\n        ffi.buffer(expression.value_shape, expression.num_components * ffi.sizeof(\"int\")), np.intc\n    )\n    assert np.allclose(expr.ufl_shape, value_shape)\n\n\ndef test_rank1(compile_args):\n    \"\"\"Tests evaluation of rank-1 form.\n\n    Builds a linear operator which takes vector-valued functions in P1 space\n    and evaluates expression [u_y, u_x] + grad(u_x) at specified points.\n\n    \"\"\"\n    e = basix.ufl.element(\"P\", \"triangle\", 1, shape=(2,))\n    mesh = ufl.Mesh(e)\n\n    V = ufl.FunctionSpace(mesh, e)\n    u = ufl.TrialFunction(V)\n\n    expr = ufl.as_vector([u[1], u[0]]) + ufl.grad(u[0])\n\n    points = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]])\n    obj, _module, _code = ffcx.codegeneration.jit.compile_expressions(\n        [(expr, points)], cffi_extra_compile_args=compile_args\n    )\n\n    ffi = cffi.FFI()\n    expression = obj[0]\n\n    dtype = np.float64\n    c_type = \"double\"\n    xdtype = np.float64\n    c_xtype = \"double\"\n\n    # 2 components for vector components of expression\n    # 3 points of evaluation\n    # 6 degrees of freedom for rank1 form\n    A = np.zeros((3, 2, 6), dtype=dtype)\n\n    # Coefficient storage XYXYXY\n    w = np.array([0.0], dtype=dtype)\n    c = np.array([0.0], dtype=dtype)\n    entity_index = np.array([0], dtype=np.intc)\n    quad_perm = np.array([0], dtype=np.dtype(\"uint8\"))\n\n    # Coords storage XYZXYZXYZ\n    coords = np.zeros((points.shape[0], 3), dtype=xdtype)\n    coords[:, :2] = points\n    expression.tabulate_tensor_float64(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", entity_index.ctypes.data),\n        ffi.cast(\"uint8_t *\", quad_perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    f = np.array([[1.0, 2.0, 3.0], [-4.0, -5.0, 6.0]])\n\n    # Apply the operator on some test input data\n    u_ffcx = np.einsum(\"ijk,k\", A, f.T.flatten())\n\n    # Compute the correct values using NumPy\n    # Gradf0 is gradient of f[0], each component of the gradient is constant\n    gradf0 = np.array(\n        [\n            [f[0, 1] - f[0, 0], f[0, 1] - f[0, 0], f[0, 1] - f[0, 0]],\n            [f[0, 2] - f[0, 0], f[0, 2] - f[0, 0], f[0, 2] - f[0, 0]],\n        ]\n    )\n\n    u_correct = np.array([f[1], f[0]]) + gradf0\n    assert np.allclose(u_ffcx, u_correct.T)\n\n\ndef test_elimiate_zero_tables_tensor(compile_args):\n    \"\"\"Test elimination of tensor-valued expressions with zero tables\"\"\"\n    cell = \"tetrahedron\"\n    c_el = basix.ufl.element(\"P\", cell, 1, shape=(3,))\n    mesh = ufl.Mesh(c_el)\n\n    e = basix.ufl.element(\"P\", cell, 1)\n    V = ufl.FunctionSpace(mesh, e)\n    u = ufl.Coefficient(V)\n    expr = ufl.sym(ufl.as_tensor([[u, u.dx(0).dx(0), 0], [u.dx(1), u.dx(1), 0], [0, 0, 0]]))\n\n    # Get vertices of cell\n    # Coords storage XYZXYZXYZ\n    basix_c_e = basix.create_element(\n        basix.ElementFamily.P, basix.CellType[cell], 1, discontinuous=False\n    )\n    coords = basix_c_e.points\n\n    # Using same basix element for coordinate element and coefficient\n    coeff_points = basix_c_e.points\n\n    # Compile expression at interpolation points of second order Lagrange space\n    b_el = basix.create_element(basix.ElementFamily.P, basix.CellType[cell], 0, discontinuous=True)\n    points = b_el.points\n    obj, _module, _code = ffcx.codegeneration.jit.compile_expressions(\n        [(expr, points)], cffi_extra_compile_args=compile_args\n    )\n\n    ffi = cffi.FFI()\n    expression = obj[0]\n\n    dtype = np.float64\n    c_type = \"double\"\n    c_xtype = \"double\"\n\n    output = np.zeros(9 * points.shape[0], dtype=dtype)\n\n    # Define coefficients for u = x + 2 * y\n    u_coeffs = u_coeffs = coeff_points.T[0] + 2 * coeff_points.T[1]\n    consts = np.array([], dtype=dtype)\n    entity_index = np.array([0], dtype=np.intc)\n    quad_perm = np.array([0], dtype=np.dtype(\"uint8\"))\n\n    expression.tabulate_tensor_float64(\n        ffi.cast(f\"{c_type} *\", output.ctypes.data),\n        ffi.cast(f\"{c_type} *\", u_coeffs.ctypes.data),\n        ffi.cast(f\"{c_type} *\", consts.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", entity_index.ctypes.data),\n        ffi.cast(\"uint8_t *\", quad_perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    def exact_expr(x):\n        val = np.zeros((9, x.shape[1]), dtype=dtype)\n        val[0] = x[0] + 2 * x[1]\n        val[1] = 0 + 0.5 * 2\n        val[3] = 0.5 * 2 + 0\n        val[4] = 2\n        return val.T\n\n    exact = exact_expr(points.T)\n\n    assert np.allclose(exact, output)\n\n\ndef test_grad_constant(compile_args):\n    \"\"\"Test constant numbering.\n\n    Test if numbering of constants are correct after UFL eliminates the\n    constant inside the gradient.\n    \"\"\"\n    c_el = basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,))\n    mesh = ufl.Mesh(c_el)\n\n    x = ufl.SpatialCoordinate(mesh)\n    first_constant = ufl.Constant(mesh)\n    second_constant = ufl.Constant(mesh)\n    expr = second_constant * ufl.Dx(x[0] ** 2 + first_constant, 0)\n\n    dtype = np.float64\n    points = np.array([[0.33, 0.25]], dtype=dtype)\n\n    obj, _, _ = ffcx.codegeneration.jit.compile_expressions(\n        [(expr, points)], cffi_extra_compile_args=compile_args\n    )\n\n    ffi = cffi.FFI()\n    expression = obj[0]\n\n    c_type = \"double\"\n    c_xtype = \"double\"\n\n    output = np.zeros(1, dtype=dtype)\n\n    # Define constants\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=dtype)\n    u_coeffs = np.array([], dtype=dtype)\n    consts = np.array([3, 7], dtype=dtype)\n    entity_index = np.array([0], dtype=np.intc)\n    quad_perm = np.array([0], dtype=np.dtype(\"uint8\"))\n\n    expression.tabulate_tensor_float64(\n        ffi.cast(f\"{c_type} *\", output.ctypes.data),\n        ffi.cast(f\"{c_type} *\", u_coeffs.ctypes.data),\n        ffi.cast(f\"{c_type} *\", consts.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", entity_index.ctypes.data),\n        ffi.cast(\"uint8_t *\", quad_perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    assert output[0] == pytest.approx(consts[1] * 2 * points[0, 0])\n\n\ndef test_facet_expression(compile_args):\n    \"\"\"Test facet expression containing a facet normal on a manifold.\"\"\"\n    c_el = basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(3,))\n    mesh = ufl.Mesh(c_el)\n\n    n = ufl.FacetNormal(mesh)\n    expr = n\n\n    dtype = np.float64\n    points = np.array([[0.5]], dtype=dtype)\n\n    obj, _, _ = ffcx.codegeneration.jit.compile_expressions(\n        [(expr, points)], cffi_extra_compile_args=compile_args\n    )\n\n    ffi = cffi.FFI()\n    expression = obj[0]\n\n    c_type = \"double\"\n    c_xtype = \"double\"\n\n    output = np.zeros(3, dtype=dtype)\n\n    # Define constants\n    coords = np.array([[0.3, 0.6, 0.1], [1.2, 0.4, 0.2], [1.3, 1.4, 0.3]], dtype=dtype)\n    u_coeffs = np.array([], dtype=dtype)\n    consts = np.array([], dtype=dtype)\n    entity_index = np.array([0], dtype=np.intc)\n    quad_perm = np.array([0], dtype=np.dtype(\"uint8\"))\n    tangents = np.array([coords[1] - coords[2], coords[2] - coords[0], coords[0] - coords[1]])\n    midpoints = np.array(\n        [\n            coords[1] + (coords[2] - coords[1]) / 2,\n            coords[0] + (coords[2] - coords[0]) / 2,\n            coords[1] + (coords[1] - coords[0]) / 2,\n        ]\n    )\n    for i, (tangent, midpoint) in enumerate(zip(tangents, midpoints)):\n        # normalize tangent\n        tangent /= np.linalg.norm(tangent)\n        # Tabulate facet normal\n        output[:] = 0\n        entity_index[0] = i\n        expression.tabulate_tensor_float64(\n            ffi.cast(f\"{c_type} *\", output.ctypes.data),\n            ffi.cast(f\"{c_type} *\", u_coeffs.ctypes.data),\n            ffi.cast(f\"{c_type} *\", consts.ctypes.data),\n            ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n            ffi.cast(\"int *\", entity_index.ctypes.data),\n            ffi.cast(\"uint8_t *\", quad_perm.ctypes.data),\n            ffi.NULL,\n        )\n        # Assert that facet normal is perpendicular to tangent\n        assert np.isclose(np.dot(output, tangent), 0)\n\n        # Check that norm of facet normal is 1\n        assert np.isclose(np.linalg.norm(output), 1)\n\n        # Check that facet normal is pointing out of the cell\n        assert np.dot(midpoint - coords[i], output) > 0\n\n\ndef test_facet_geometry_expressions(compile_args):\n    \"\"\"Test various geometrical quantities for facet expressions.\"\"\"\n    cell = basix.CellType.triangle\n    mesh = ufl.Mesh(basix.ufl.element(\"Lagrange\", cell, 1, shape=(2,)))\n    dtype = np.float64\n    points = np.array([[0.5]], dtype=dtype)\n    c_type = \"double\"\n    c_xtype = \"double\"\n    ffi = cffi.FFI()\n\n    # Prepare reference geometry and working arrays\n    coords = np.array([[1, 0, 0], [3, 0, 0], [0, 2, 0]], dtype=dtype)\n    u_coeffs = np.array([], dtype=dtype)\n    consts = np.array([], dtype=dtype)\n    entity_index = np.empty(1, dtype=np.intc)\n    quad_perm = np.array([0], dtype=np.dtype(\"uint8\"))\n    ffi_data = {\n        \"const\": ffi.cast(f\"{c_type} *\", consts.ctypes.data),\n        \"coeff\": ffi.cast(f\"{c_type} *\", u_coeffs.ctypes.data),\n        \"coords\": ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        \"entity_index\": ffi.cast(\"int *\", entity_index.ctypes.data),\n        \"quad_perm\": ffi.cast(\"uint8_t *\", quad_perm.ctypes.data),\n    }\n\n    def check_expression(expression_class, output_shape, entity_values, reference_values):\n        obj = ffcx.codegeneration.jit.compile_expressions(\n            [(expression_class(mesh), points)], cffi_extra_compile_args=compile_args\n        )[0][0]\n        output = np.zeros(output_shape, dtype=dtype)\n        for i, ref_val in enumerate(reference_values):\n            output[:] = 0\n            entity_index[0] = i\n            obj.tabulate_tensor_float64(\n                ffi.cast(f\"{c_type} *\", output.ctypes.data),\n                ffi_data[\"coeff\"],\n                ffi_data[\"const\"],\n                ffi_data[\"coords\"],\n                ffi_data[\"entity_index\"],\n                ffi_data[\"quad_perm\"],\n                ffi.NULL,\n            )\n            np.testing.assert_allclose(output, ref_val)\n\n    check_expression(\n        ufl.geometry.CellFacetJacobian, (2, 1), entity_index, basix.cell.facet_jacobians(cell)\n    )\n    check_expression(\n        ufl.geometry.ReferenceFacetVolume,\n        (1,),\n        entity_index,\n        basix.cell.facet_reference_volumes(cell),\n    )\n    check_expression(\n        ufl.geometry.ReferenceCellEdgeVectors,\n        (3, 2),\n        entity_index,\n        np.array(\n            [\n                [\n                    basix.geometry(cell)[j] - basix.geometry(cell)[i]\n                    for i, j in basix.topology(cell)[1]\n                ]\n            ]\n        ),\n    )\n\n\ndef test_facet_geometry_expressions_3D(compile_args):\n    cell = basix.CellType.tetrahedron\n    c_el = basix.ufl.element(\"Lagrange\", cell, 1, shape=(3,))\n    mesh = ufl.Mesh(c_el)\n    dtype = np.float64\n    points = np.array([[0.33, 0.33]], dtype=dtype)\n    c_type = \"double\"\n    c_xtype = \"double\"\n    ffi = cffi.FFI()\n\n    # Prepare reference geometry and working arrays\n    coords = np.array([[1, 0, 0], [3, 0, 0], [0, 2, 0], [0, 0, 1]], dtype=dtype)\n    u_coeffs = np.array([], dtype=dtype)\n    consts = np.array([], dtype=dtype)\n    entity_index = np.empty(1, dtype=np.intc)\n    quad_perm = np.array([0], dtype=np.dtype(\"uint8\"))\n\n    # Check ReferenceFacetEdgeVectors\n    output = np.zeros((3, 3))\n    triangle_edges = basix.topology(basix.CellType.triangle)[1]\n    ref_fev = []\n    topology = basix.topology(cell)\n    geometry = basix.geometry(cell)\n    for facet in topology[-2]:\n        ref_fev += [geometry[facet[j]] - geometry[facet[i]] for i, j in triangle_edges]\n\n    ref_fev_code = ffcx.codegeneration.jit.compile_expressions(\n        [(ufl.geometry.ReferenceFacetEdgeVectors(mesh), points)],\n        cffi_extra_compile_args=compile_args,\n    )[0][0]\n    ref_fev_code.tabulate_tensor_float64(\n        ffi.cast(f\"{c_type} *\", output.ctypes.data),\n        ffi.cast(f\"{c_type} *\", u_coeffs.ctypes.data),\n        ffi.cast(f\"{c_type} *\", consts.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", entity_index.ctypes.data),\n        ffi.cast(\"uint8_t *\", quad_perm.ctypes.data),\n        ffi.NULL,\n    )\n    np.testing.assert_allclose(output, np.asarray(ref_fev)[:3, :])\n\n\ndef test_coordinate_free_expression(compile_args):\n    \"\"\"Test evaluation of expressions that do not have a coordinate element (a domain).\"\"\"\n    # Test simple constant vector\n    expr = ufl.as_vector([1.0, 0.0])\n\n    points = np.array([[0.0, 0.0], [0.1, 0.1], [0.0, 1.0]])\n    obj, _module, _code = ffcx.codegeneration.jit.compile_expressions(\n        [(expr, points)], cffi_extra_compile_args=compile_args\n    )\n\n    ffi = cffi.FFI()\n    expression = obj[0]\n\n    dtype = np.float64\n    c_type = \"double\"\n\n    # 2 components for vector, 3 evaluation points\n    A = np.zeros((3, 2), dtype=dtype)\n\n    # No coefficients or constants needed for this domainless expression\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n    entity_index = np.array([0], dtype=np.intc)\n    quad_perm = np.array([0], dtype=np.dtype(\"uint8\"))\n\n    # Coordinates are not used for domainless expressions but still required\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=dtype)\n\n    expression.tabulate_tensor_float64(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_type} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", entity_index.ctypes.data),\n        ffi.cast(\"uint8_t *\", quad_perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    # Check that the expression evaluates to [1, 0] at all points\n    expected = np.array([[1.0, 0.0], [1.0, 0.0], [1.0, 0.0]])\n    assert np.allclose(A, expected)\n\n\ndef test_mixed_mesh_expression(compile_args):\n    \"\"\"Test facet expression containing quantities from parent and facet mesh.\"\"\"\n    c_el = basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,))\n    mesh = ufl.Mesh(c_el)\n\n    f_c_el = basix.ufl.element(\"Lagrange\", \"interval\", 1, shape=(2,))\n    facet_mesh = ufl.Mesh(f_c_el)\n    f_el = basix.ufl.element(\"P\", \"interval\", 1)\n    V = ufl.FunctionSpace(facet_mesh, f_el)\n    c = ufl.Coefficient(V)\n\n    n = ufl.FacetNormal(mesh)\n    expr = c * n\n\n    dtype = np.float64\n    points = np.array([[0.3], [0.5], [0.8]], dtype=dtype)\n\n    obj, _, _ = ffcx.codegeneration.jit.compile_expressions(\n        [(expr, points)], cffi_extra_compile_args=compile_args\n    )\n\n    ffi = cffi.FFI()\n    expression = obj[0]\n\n    c_type = \"double\"\n    c_xtype = \"double\"\n\n    output = np.zeros(points.shape[0] * 2, dtype=dtype)\n\n    # Define mesh\n    coords = np.array([[0.1, 0.3, 0], [2, 0, 0.0], [0, 1, 0.0]], dtype=dtype)\n\n    # Define exact normals\n    face_normal = np.array([0.0, 0.0, 1.0])\n    e0 = coords[2] - coords[1]\n    e1 = coords[0] - coords[2]\n    e2 = coords[1] - coords[0]\n    edges = np.array([e0, e1, e2])\n    edge_normals = np.cross(edges, face_normal)[:, :2]\n    norms = np.linalg.norm(edge_normals, axis=1, keepdims=True)\n    edge_normals_normalized = edge_normals / norms\n\n    # Pack coefficients, constants, and other data for expression evaluation\n    u_coeffs = np.array([0.1, 0.5], dtype=dtype)\n    consts = np.array([], dtype=dtype)\n    entity_index = np.array([0], dtype=np.intc)\n    quad_perm = np.array([0], dtype=np.uint8)\n\n    ref_coeff = u_coeffs[0] + points[:, 0] * (u_coeffs[1] - u_coeffs[0])\n    for i, normal in enumerate(edge_normals_normalized):\n        # Tabulate facet normal\n        output[:] = 0\n        entity_index[0] = i\n        expression.tabulate_tensor_float64(\n            ffi.cast(f\"{c_type} *\", output.ctypes.data),\n            ffi.cast(f\"{c_type} *\", u_coeffs.ctypes.data),\n            ffi.cast(f\"{c_type} *\", consts.ctypes.data),\n            ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n            ffi.cast(\"int *\", entity_index.ctypes.data),\n            ffi.cast(\"uint8_t *\", quad_perm.ctypes.data),\n            ffi.NULL,\n        )\n        ref_sol = ref_coeff.reshape(-1, 1) * normal\n\n        np.testing.assert_allclose(output, ref_sol.flatten())\n"
  },
  {
    "path": "test/test_jit_forms.py",
    "content": "# Copyright (C) 2018-2026 Garth N. Wells, Matthew Scroggs, Paul T. Kühner and Jørgen S. Dokken\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nimport os\nimport sys\nimport typing\n\nimport basix.ufl\nimport numpy as np\nimport numpy.typing as npt\nimport pytest\nimport sympy\nimport ufl\nfrom sympy.abc import x, y, z\n\nimport ffcx.codegeneration.jit\nfrom ffcx.codegeneration.utils import dtype_to_c_type, dtype_to_scalar_dtype\n\n\n@pytest.mark.parametrize(\n    \"dtype,expected_result\",\n    [\n        (\n            \"float64\",\n            np.array([[1.0, -0.5, -0.5], [-0.5, 0.5, 0.0], [-0.5, 0.0, 0.5]], dtype=np.float64),\n        ),\n        pytest.param(\n            \"complex128\",\n            np.array(\n                [\n                    [1.0 + 0j, -0.5 + 0j, -0.5 + 0j],\n                    [-0.5 + 0j, 0.5 + 0j, 0.0 + 0j],\n                    [-0.5 + 0j, 0.0 + 0j, 0.5 + 0j],\n                ],\n                dtype=np.complex128,\n            ),\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_laplace_bilinear_form_2d(dtype, expected_result, compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    kappa = ufl.Constant(domain, shape=(2, 2))\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n\n    a = ufl.tr(kappa) * ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n\n    offsets = form0.form_integral_offsets\n    cell = module.lib.cell\n    assert offsets[cell + 1] - offsets[cell] == 1\n    integral_id = form0.form_integral_ids[offsets[cell]]\n    assert integral_id == -1\n\n    default_integral = form0.form_integrals[offsets[cell]]\n\n    assert domain.ufl_coordinate_element().basix_hash() == default_integral.coordinate_element_hash\n\n    A = np.zeros((3, 3), dtype=dtype)\n    w = np.array([], dtype=dtype)\n\n    kappa_value = np.array([[1.0, 2.0], [3.0, 4.0]])\n    c = np.array(kappa_value.flatten(), dtype=dtype)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    assert np.allclose(A, np.trace(kappa_value) * expected_result)\n\n\n@pytest.mark.parametrize(\n    \"dtype,expected_result\",\n    [\n        (\n            np.float32,\n            np.array(\n                [\n                    [1.0 / 12.0, 1.0 / 24.0, 1.0 / 24.0],\n                    [1.0 / 24.0, 1.0 / 12.0, 1.0 / 24.0],\n                    [1.0 / 24.0, 1.0 / 24.0, 1.0 / 12.0],\n                ],\n                dtype=np.float32,\n            ),\n        ),\n        # (\"longdouble\",\n        #  np.array(\n        #      [[1.0 / 12.0, 1.0 / 24.0, 1.0 / 24.0], [1.0 / 24.0, 1.0 / 12.0, 1.0 / 24.0],\n        #       [1.0 / 24.0, 1.0 / 24.0, 1.0 / 12.0]],\n        #      dtype=np.longdouble)),\n        (\n            np.float64,\n            np.array(\n                [\n                    [1.0 / 12.0, 1.0 / 24.0, 1.0 / 24.0],\n                    [1.0 / 24.0, 1.0 / 12.0, 1.0 / 24.0],\n                    [1.0 / 24.0, 1.0 / 24.0, 1.0 / 12.0],\n                ],\n                dtype=np.float64,\n            ),\n        ),\n        pytest.param(\n            np.complex128,\n            np.array(\n                [\n                    [1.0 / 12.0, 1.0 / 24.0, 1.0 / 24.0],\n                    [1.0 / 24.0, 1.0 / 12.0, 1.0 / 24.0],\n                    [1.0 / 24.0, 1.0 / 24.0, 1.0 / 12.0],\n                ],\n                dtype=np.complex128,\n            ),\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n        pytest.param(\n            np.complex64,\n            np.array(\n                [\n                    [1.0 / 12.0, 1.0 / 24.0, 1.0 / 24.0],\n                    [1.0 / 24.0, 1.0 / 12.0, 1.0 / 24.0],\n                    [1.0 / 24.0, 1.0 / 24.0, 1.0 / 12.0],\n                ],\n                dtype=np.complex64,\n            ),\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_mass_bilinear_form_2d(dtype, expected_result, compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    a = ufl.inner(u, v) * ufl.dx\n    L = ufl.conj(v) * ufl.dx\n    forms = [a, L]\n    _compiled_forms, _module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n\n@pytest.mark.parametrize(\n    \"dtype,expected_result\",\n    [\n        (\n            \"float64\",\n            np.array([[1.0, -0.5, -0.5], [-0.5, 0.5, 0.0], [-0.5, 0.0, 0.5]], dtype=np.float64)\n            - (1.0 / 24.0) * np.array([[2, 1, 1], [1, 2, 1], [1, 1, 2]], dtype=np.float64),\n        ),\n        pytest.param(\n            \"complex128\",\n            np.array([[1.0, -0.5, -0.5], [-0.5, 0.5, 0.0], [-0.5, 0.0, 0.5]], dtype=np.complex128)\n            - (1.0j / 24.0) * np.array([[2, 1, 1], [1, 2, 1], [1, 1, 2]], dtype=np.complex128),\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_helmholtz_form_2d(dtype, expected_result, compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    if np.issubdtype(dtype, np.complexfloating):\n        k = ufl.constantvalue.ComplexValue(1j)\n    elif np.issubdtype(dtype, np.floating):\n        k = 1.0\n    else:\n        raise RuntimeError(\n            \"Unknown mode type\",\n        )\n\n    a = (ufl.inner(ufl.grad(u), ufl.grad(v)) - ufl.inner(k * u, v)) * ufl.dx\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    form0 = compiled_forms[0].form_integrals[0]\n\n    A = np.zeros((3, 3), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    ffi = module.ffi\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel = getattr(form0, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    np.testing.assert_allclose(A, expected_result)\n\n\n@pytest.mark.parametrize(\n    \"dtype,expected_result\",\n    [\n        (\n            \"float64\",\n            np.array(\n                [\n                    [0.5, -1 / 6, -1 / 6, -1 / 6],\n                    [-1 / 6, 1 / 6, 0.0, 0.0],\n                    [-1 / 6, 0.0, 1 / 6, 0.0],\n                    [-1 / 6, 0.0, 0.0, 1 / 6],\n                ],\n                dtype=np.float64,\n            ),\n        ),\n        pytest.param(\n            \"complex128\",\n            np.array(\n                [\n                    [0.5 + 0j, -1 / 6 + 0j, -1 / 6 + 0j, -1 / 6 + 0j],\n                    [-1 / 6 + 0j, 1 / 6 + 0j, 0.0 + 0j, 0.0 + 0j],\n                    [-1 / 6 + 0j, 0.0 + 0j, 1 / 6 + 0j, 0.0 + 0j],\n                    [-1 / 6 + 0j, 0.0 + 0j, 0.0 + 0j, 1 / 6 + 0j],\n                ],\n                dtype=np.complex128,\n            ),\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_laplace_bilinear_form_3d(dtype, expected_result, compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"tetrahedron\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"tetrahedron\", 1, shape=(3,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    form0 = compiled_forms[0].form_integrals[0]\n\n    A = np.zeros((4, 4), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    ffi = module.ffi\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel = getattr(form0, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    assert np.allclose(A, expected_result)\n\n\ndef test_form_coefficient(compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TestFunction(space), ufl.TrialFunction(space)\n    g = ufl.Coefficient(space)\n    a = g * ufl.inner(u, v) * ufl.dx\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, cffi_extra_compile_args=compile_args\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    form0 = compiled_forms[0].form_integrals[0]\n    A = np.zeros((3, 3), dtype=np.float64)\n    w = np.array([1.0, 1.0, 1.0], dtype=np.float64)\n    c = np.array([], dtype=np.float64)\n    perm = np.array([0], dtype=np.uint8)\n\n    ffi = module.ffi\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=np.float64)\n\n    kernel = getattr(form0, \"tabulate_tensor_float64\")\n    kernel(\n        ffi.cast(\"double  *\", A.ctypes.data),\n        ffi.cast(\"double  *\", w.ctypes.data),\n        ffi.cast(\"double  *\", c.ctypes.data),\n        ffi.cast(\"double  *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.cast(\"uint8_t *\", perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    A_analytic = np.array([[2, 1, 1], [1, 2, 1], [1, 1, 2]], dtype=np.float64) / 24.0\n    A_diff = A - A_analytic\n    assert np.isclose(A_diff.max(), 0.0)\n    assert np.isclose(A_diff.min(), 0.0)\n\n\ndef test_subdomains(compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    a0 = ufl.inner(u, v) * ufl.dx + ufl.inner(u, v) * ufl.dx(2)\n    a1 = ufl.inner(u, v) * ufl.dx(2) + ufl.inner(u, v) * ufl.dx\n    a2 = ufl.inner(u, v) * ufl.dx(2) + ufl.inner(u, v) * ufl.dx(1)\n    a3 = ufl.inner(u, v) * ufl.ds(210) + ufl.inner(u, v) * ufl.ds(0)\n    forms = [a0, a1, a2, a3]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": \"float64\"}, cffi_extra_compile_args=compile_args\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    form0 = compiled_forms[0]\n    offsets = form0.form_integral_offsets\n    cell = module.lib.cell\n    ids = [form0.form_integral_ids[j] for j in range(offsets[cell], offsets[cell + 1])]\n    assert ids[0] == -1 and ids[1] == 2\n\n    form1 = compiled_forms[1]\n    offsets = form1.form_integral_offsets\n    ids = [form1.form_integral_ids[j] for j in range(offsets[cell], offsets[cell + 1])]\n    assert ids[0] == -1 and ids[1] == 2\n\n    form2 = compiled_forms[2]\n    offsets = form2.form_integral_offsets\n    ids = [form2.form_integral_ids[j] for j in range(offsets[cell], offsets[cell + 1])]\n    assert ids[0] == 1 and ids[1] == 2\n\n    form3 = compiled_forms[3]\n    offsets = form3.form_integral_offsets\n    assert offsets[cell + 1] - offsets[cell] == 0\n    exf = module.lib.exterior_facet\n    ids = [form3.form_integral_ids[j] for j in range(offsets[exf], offsets[exf + 1])]\n    assert ids[0] == 0 and ids[1] == 210\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float64\",\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_interior_facet_integral(dtype, compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    a0 = ufl.inner(ufl.jump(ufl.grad(u)), ufl.jump(ufl.grad(v))) * ufl.dS\n    forms = [a0]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    ffi = module.ffi\n\n    form0 = compiled_forms[0]\n\n    ffi = module.ffi\n\n    integral0 = form0.form_integrals[0]\n    A = np.zeros((6, 6), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    facets = np.array([0, 2], dtype=np.intc)\n    perms = np.array([0, 1], dtype=np.uint8)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array(\n        [\n            [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0],\n            [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0],\n        ],\n        dtype=xdtype,\n    )\n\n    c_type = dtype_to_c_type(dtype)\n    c_xtype = dtype_to_c_type(xdtype)\n    kernel = getattr(integral0, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type}  *\", A.ctypes.data),\n        ffi.cast(f\"{c_type}  *\", w.ctypes.data),\n        ffi.cast(f\"{c_type}  *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", facets.ctypes.data),\n        ffi.cast(\"uint8_t *\", perms.ctypes.data),\n        ffi.NULL,\n    )\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float64\",\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_conditional(dtype, compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    x = ufl.SpatialCoordinate(domain)\n    condition = ufl.Or(ufl.ge(ufl.real(x[0] + x[1]), 0.1), ufl.ge(ufl.real(x[1] + x[1] ** 2), 0.1))\n    c1 = ufl.conditional(condition, 2.0, 1.0)\n    a = c1 * ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n\n    x1x2 = ufl.real(x[0] + ufl.as_ufl(2) * x[1])\n    c2 = ufl.conditional(ufl.ge(x1x2, 0), 6.0, 0.0)\n    b = c2 * ufl.conj(v) * ufl.dx\n\n    forms = [a, b]\n\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    form0 = compiled_forms[0].form_integrals[0]\n    form1 = compiled_forms[1].form_integrals[0]\n\n    ffi = module.ffi\n\n    A1 = np.zeros((3, 3), dtype=dtype)\n    w1 = np.array([1.0, 1.0, 1.0], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel0 = ffi.cast(\n        f\"ufcx_tabulate_tensor_{dtype} *\", getattr(form0, f\"tabulate_tensor_{dtype}\")\n    )\n    kernel0(\n        ffi.cast(f\"{c_type} *\", A1.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w1.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    expected_result = np.array([[2, -1, -1], [-1, 1, 0], [-1, 0, 1]], dtype=dtype)\n    assert np.allclose(A1, expected_result)\n\n    A2 = np.zeros(3, dtype=dtype)\n    w2 = np.array([1.0, 1.0, 1.0], dtype=dtype)\n\n    kernel1 = ffi.cast(\n        f\"ufcx_tabulate_tensor_{dtype} *\", getattr(form1, f\"tabulate_tensor_{dtype}\")\n    )\n    kernel1(\n        ffi.cast(f\"{c_type} *\", A2.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w2.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    expected_result = np.ones(3, dtype=dtype)\n    assert np.allclose(A2, expected_result)\n\n\ndef test_custom_quadrature(compile_args):\n    ve = basix.ufl.element(\"P\", \"triangle\", 1, shape=(2,))\n    mesh = ufl.Mesh(ve)\n\n    e = basix.ufl.element(\"P\", mesh.ufl_cell().cellname, 2)\n    V = ufl.FunctionSpace(mesh, e)\n    u, v = ufl.TrialFunction(V), ufl.TestFunction(V)\n\n    points = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.5, 0.5], [0.0, 0.5], [0.5, 0.0]]\n    weights = [1 / 12] * 6\n    a = (\n        u\n        * v\n        * ufl.dx(\n            metadata={\n                \"quadrature_rule\": \"custom\",\n                \"quadrature_points\": points,\n                \"quadrature_weights\": weights,\n            }\n        )\n    )\n\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, cffi_extra_compile_args=compile_args\n    )\n\n    ffi = module.ffi\n    form = compiled_forms[0]\n    default_integral = form.form_integrals[0]\n\n    A = np.zeros((6, 6), dtype=np.float64)\n    w = np.array([], dtype=np.float64)\n    c = np.array([], dtype=np.float64)\n\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=np.float64)\n\n    kernel = getattr(default_integral, \"tabulate_tensor_float64\")\n    kernel(\n        ffi.cast(\"double *\", A.ctypes.data),\n        ffi.cast(\"double *\", w.ctypes.data),\n        ffi.cast(\"double *\", c.ctypes.data),\n        ffi.cast(\"double *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    # Check that A is diagonal\n    assert np.count_nonzero(A - np.diag(np.diagonal(A))) == 0\n\n\ndef test_curl_curl(compile_args):\n    V = basix.ufl.element(\"N1curl\", \"triangle\", 2)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, V)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    a = ufl.inner(ufl.curl(u), ufl.curl(v)) * ufl.dx\n\n    forms = [a]\n    _compiled_forms, _module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, cffi_extra_compile_args=compile_args\n    )\n\n\ndef lagrange_triangle_symbolic(order, corners=((1, 0), (2, 0), (0, 1)), fun=lambda i: i):\n    from sympy import S\n\n    poly_basis = [x**i * y**j for i in range(order + 1) for j in range(order + 1 - i)]\n    # vertices\n    eval_points = [S(c) for c in corners]\n    # edges\n    for e in [(1, 2), (0, 2), (0, 1)]:\n        p0 = corners[e[0]]\n        p1 = corners[e[1]]\n        if order > 3:\n            raise NotImplementedError\n        elif order == 3:\n            eval_points += [\n                tuple(S(a) + (b - a) * i for a, b in zip(p0, p1))\n                for i in [(1 - 1 / sympy.sqrt(5)) / 2, (1 + 1 / sympy.sqrt(5)) / 2]\n            ]\n        else:\n            eval_points += [\n                tuple(S(a) + sympy.Rational((b - a) * i, order) for a, b in zip(p0, p1))\n                for i in range(1, order)\n            ]\n    # face\n    for f in [(0, 1, 2)]:\n        p0 = corners[f[0]]\n        p1 = corners[f[1]]\n        p2 = corners[f[2]]\n        eval_points += [\n            tuple(\n                S(a) + sympy.Rational((b - a) * i, order) + sympy.Rational((c - a) * j, order)\n                for a, b, c in zip(p0, p1, p2)\n            )\n            for i in range(1, order)\n            for j in range(1, order - i)\n        ]\n\n    dual_mat = [[f.subs(x, p[0]).subs(y, p[1]) for p in eval_points] for f in poly_basis]\n    dual_mat = sympy.Matrix(dual_mat)\n    mat = dual_mat.inv()\n    functions = [sum(i * j for i, j in zip(mat.row(k), poly_basis)) for k in range(mat.rows)]\n    results = []\n    for f in functions:\n        integrand = fun(f)\n        results.append(integrand.integrate((x, 1 - y, 2 - 2 * y), (y, 0, 1)))\n    return results\n\n\n@pytest.mark.parametrize(\"dtype\", [\"float64\"])\n@pytest.mark.parametrize(\n    \"sym_fun,ufl_fun\",\n    [\n        (lambda i: i, lambda i: i),\n        (lambda i: i.diff(x), lambda i: ufl.grad(i)[0]),\n        (lambda i: i.diff(y), lambda i: ufl.grad(i)[1]),\n    ],\n)\n@pytest.mark.parametrize(\"order\", [1, 2, 3])\ndef test_lagrange_triangle(compile_args, order, dtype, sym_fun, ufl_fun):\n    sym = lagrange_triangle_symbolic(order, fun=sym_fun)\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", order)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, element)\n    v = ufl.TestFunction(space)\n\n    a = ufl_fun(v) * ufl.dx\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n\n    assert form0.form_integral_offsets[module.lib.cell + 1] == 1\n    default_integral = form0.form_integrals[0]\n\n    b = np.zeros((order + 2) * (order + 1) // 2, dtype=dtype)\n    w = np.array([], dtype=dtype)\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([[1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", b.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.NULL,\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    # Check that the result is the same as for sympy\n    assert np.allclose(b, [float(i) for i in sym])\n\n\ndef lagrange_tetrahedron_symbolic(\n    order, corners=((1, 0, 0), (2, 0, 0), (0, 1, 0), (0, 0, 1)), fun=lambda i: i\n):\n    from sympy import S\n\n    poly_basis = [\n        x**i * y**j * z**k\n        for i in range(order + 1)\n        for j in range(order + 1 - i)\n        for k in range(order + 1 - i - j)\n    ]\n    # vertices\n    eval_points = [S(c) for c in corners]\n    # edges\n    for e in [(2, 3), (1, 3), (1, 2), (0, 3), (0, 2), (0, 1)]:\n        p0 = corners[e[0]]\n        p1 = corners[e[1]]\n        if order > 3:\n            raise NotImplementedError\n        elif order == 3:\n            eval_points += [\n                tuple(S(a) + (b - a) * i for a, b in zip(p0, p1))\n                for i in [(1 - 1 / sympy.sqrt(5)) / 2, (1 + 1 / sympy.sqrt(5)) / 2]\n            ]\n        else:\n            eval_points += [\n                tuple(S(a) + sympy.Rational((b - a) * i, order) for a, b in zip(p0, p1))\n                for i in range(1, order)\n            ]\n    # face\n    for f in [(1, 2, 3), (0, 2, 3), (0, 1, 3), (0, 1, 2)]:\n        p0 = corners[f[0]]\n        p1 = corners[f[1]]\n        p2 = corners[f[2]]\n        eval_points += [\n            tuple(\n                S(a) + sympy.Rational((b - a) * i, order) + sympy.Rational((c - a) * j, order)\n                for a, b, c in zip(p0, p1, p2)\n            )\n            for i in range(1, order)\n            for j in range(1, order - i)\n        ]\n    # interior\n    for v in [(0, 1, 2, 3)]:\n        p0 = corners[v[0]]\n        p1 = corners[v[1]]\n        p2 = corners[v[2]]\n        p3 = corners[v[3]]\n        eval_points += [\n            tuple(\n                S(a)\n                + sympy.Rational((b - a) * i, order)\n                + sympy.Rational((c - a) * j, order)\n                + sympy.Rational((d - a) * k, order)\n                for a, b, c, d in zip(p0, p1, p2, p3)\n            )\n            for i in range(1, order)\n            for j in range(1, order - i)\n            for k in range(1, order - i - j)\n        ]\n\n    dual_mat = [\n        [f.subs(x, p[0]).subs(y, p[1]).subs(z, p[2]) for p in eval_points] for f in poly_basis\n    ]\n    dual_mat = sympy.Matrix(dual_mat)\n    mat = dual_mat.inv()\n    functions = [sum(i * j for i, j in zip(mat.row(k), poly_basis)) for k in range(mat.rows)]\n    results = []\n    for f in functions:\n        integrand = fun(f)\n        results.append(\n            integrand.integrate((x, 1 - y - z, 2 - 2 * y - 2 * z), (y, 0, 1 - z), (z, 0, 1))\n        )\n    return results\n\n\n@pytest.mark.parametrize(\"dtype\", [\"float64\"])\n@pytest.mark.parametrize(\n    \"sym_fun,ufl_fun\",\n    [\n        (lambda i: i, lambda i: i),\n        (lambda i: i.diff(x), lambda i: ufl.grad(i)[0]),\n        (lambda i: i.diff(y), lambda i: ufl.grad(i)[1]),\n    ],\n)\n@pytest.mark.parametrize(\"order\", [1, 2, 3])\ndef test_lagrange_tetrahedron(compile_args, order, dtype, sym_fun, ufl_fun):\n    sym = lagrange_tetrahedron_symbolic(order, fun=sym_fun)\n    element = basix.ufl.element(\"Lagrange\", \"tetrahedron\", order)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"tetrahedron\", 1, shape=(3,)))\n    space = ufl.FunctionSpace(domain, element)\n    v = ufl.TestFunction(space)\n\n    a = ufl_fun(v) * ufl.dx\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n\n    assert form0.form_integral_offsets[module.lib.cell + 1] == 1\n\n    default_integral = form0.form_integrals[0]\n\n    b = np.zeros((order + 3) * (order + 2) * (order + 1) // 6, dtype=dtype)\n    w = np.array([], dtype=dtype)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([1.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", b.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.NULL,\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    # Check that the result is the same as for sympy\n    assert np.allclose(b, [float(i) for i in sym])\n\n\ndef test_prism(compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"prism\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"prism\", 1, shape=(3,)))\n    space = ufl.FunctionSpace(domain, element)\n    v = ufl.TestFunction(space)\n    L = v * ufl.dx\n    forms = [L]\n    compiled_forms, module, _ = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": \"float64\"}, cffi_extra_compile_args=compile_args\n    )\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n    assert form0.form_integral_offsets[module.lib.cell + 1] == 1\n\n    default_integral = form0.form_integrals[0]\n    b = np.zeros(6, dtype=np.float64)\n    coords = np.array(\n        [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0],\n        dtype=np.float64,\n    )\n    kernel = getattr(default_integral, \"tabulate_tensor_float64\")\n    kernel(\n        ffi.cast(\"double *\", b.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.cast(\"double *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    assert np.isclose(sum(b), 0.5)\n\n\n@pytest.mark.xfail(\n    sys.platform.startswith(\"win32\"), raises=NotImplementedError, reason=\"missing _Complex\"\n)\ndef test_complex_operations(compile_args):\n    dtype = \"complex128\"\n    cell = \"triangle\"\n    c_element = basix.ufl.element(\"Lagrange\", cell, 1, shape=(2,))\n    mesh = ufl.Mesh(c_element)\n    element = basix.ufl.element(\"DG\", cell, 0, shape=(2,))\n    V = ufl.FunctionSpace(mesh, element)\n    u = ufl.Coefficient(V)\n    J1 = ufl.real(u)[0] * ufl.imag(u)[1] * ufl.conj(u)[0] * ufl.dx\n    J2 = ufl.real(u[0]) * ufl.imag(u[1]) * ufl.conj(u[0]) * ufl.dx\n    forms = [J1, J2]\n\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    form0 = compiled_forms[0].form_integrals[0]\n    form1 = compiled_forms[1].form_integrals[0]\n\n    ffi = module.ffi\n    w1 = np.array([3 + 5j, 8 - 7j], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=xdtype)\n    J_1 = np.zeros((1), dtype=dtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel0 = ffi.cast(\n        f\"ufcx_tabulate_tensor_{dtype} *\", getattr(form0, f\"tabulate_tensor_{dtype}\")\n    )\n    kernel0(\n        ffi.cast(f\"{c_type} *\", J_1.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w1.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    expected_result = np.array(\n        [0.5 * np.real(w1[0]) * np.imag(w1[1]) * (np.real(w1[0]) - 1j * np.imag(w1[0]))],\n        dtype=dtype,\n    )\n    assert np.allclose(J_1, expected_result)\n\n    J_2 = np.zeros((1), dtype=dtype)\n\n    kernel1 = ffi.cast(\n        f\"ufcx_tabulate_tensor_{dtype} *\", getattr(form1, f\"tabulate_tensor_{dtype}\")\n    )\n    kernel1(\n        ffi.cast(f\"{c_type} *\", J_2.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w1.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    assert np.allclose(J_2, expected_result)\n\n    assert np.allclose(J_1, J_2)\n\n\ndef test_invalid_function_name(compile_args):\n    # Monkey patch to force invalid name\n    old_str = ufl.Coefficient.__str__\n    ufl.Coefficient.__str__ = lambda self: \"invalid function name\"\n\n    V = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    space = ufl.FunctionSpace(domain, V)\n    u = ufl.Coefficient(space)\n    a = ufl.inner(u, u) * ufl.dx\n    forms = [a]\n    try:\n        _compiled_forms, _module, _code = ffcx.codegeneration.jit.compile_forms(\n            forms, cffi_extra_compile_args=compile_args\n        )\n    except ValueError:\n        pass\n    except Exception:\n        raise RuntimeError(\"Compilation should fail with ValueError.\")\n\n    # Revert monkey patch for other tests\n    ufl.Coefficient.__str__ = old_str\n\n\ndef test_interval_vertex_quadrature(compile_args):\n    c_el = basix.ufl.element(\"Lagrange\", \"interval\", 1, shape=(1,))\n    mesh = ufl.Mesh(c_el)\n\n    x = ufl.SpatialCoordinate(mesh)\n    dx = ufl.Measure(\"dx\", metadata={\"quadrature_rule\": \"vertex\"})\n    b = x[0] * dx\n\n    forms = [b]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, cffi_extra_compile_args=compile_args\n    )\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n    assert form0.form_integral_offsets[module.lib.cell + 1] == 1\n\n    default_integral = form0.form_integrals[0]\n    J = np.zeros(1, dtype=np.float64)\n    a = np.pi\n    b = np.exp(1)\n    coords = np.array([a, 0.0, 0.0, b, 0.0, 0.0], dtype=np.float64)\n\n    kernel = getattr(default_integral, \"tabulate_tensor_float64\")\n    kernel(\n        ffi.cast(\"double *\", J.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.cast(\"double *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n    assert np.isclose(J[0], (0.5 * a + 0.5 * b) * np.abs(b - a))\n\n\n@pytest.mark.filterwarnings(\"ignore:Explicitly selected vertex\")\ndef test_facet_vertex_quadrature(compile_args):\n    \"\"\"Test facet vertex quadrature\"\"\"\n    c_el = basix.ufl.element(\"Lagrange\", \"quadrilateral\", 1, shape=(2,))\n    mesh = ufl.Mesh(c_el)\n\n    x = ufl.SpatialCoordinate(mesh)\n    ds = ufl.Measure(\"ds\", metadata={\"quadrature_rule\": \"vertex\"})\n    expr = x[0] + ufl.cos(x[1])\n    b1 = expr * ds\n    ds_c = ufl.Measure(\n        \"ds\",\n        metadata={\n            \"quadrature_rule\": \"custom\",\n            \"quadrature_points\": np.array([[0.0], [1.0]]),\n            \"quadrature_weights\": np.array([1.0 / 2.0, 1.0 / 2.0]),\n        },\n    )\n    b2 = expr * ds_c\n    forms = [b1, b2]\n    compiled_forms, module, _ = ffcx.codegeneration.jit.compile_forms(\n        forms, cffi_extra_compile_args=compile_args\n    )\n\n    ffi = module.ffi\n    assert len(compiled_forms) == 2\n    solutions = []\n    for form in compiled_forms:\n        offsets = form.form_integral_offsets\n        exf = module.lib.exterior_facet\n        assert offsets[exf + 1] - offsets[exf] == 1\n\n        default_integral = form.form_integrals[offsets[exf]]\n        J = np.zeros(1, dtype=np.float64)\n        a = np.pi\n        b = np.exp(1)\n        coords = np.array(\n            [a, 0.1, 0.0, a + b, 0.0, 0.0, a, a, 0.0, a + 2 * b, a, 0.0], dtype=np.float64\n        )\n        # First facet is between vertex 0 and 1 in coords\n        facets = np.array([0], dtype=np.intc)\n\n        kernel = getattr(default_integral, \"tabulate_tensor_float64\")\n        kernel(\n            ffi.cast(\"double *\", J.ctypes.data),\n            ffi.NULL,\n            ffi.NULL,\n            ffi.cast(\"double *\", coords.ctypes.data),\n            ffi.cast(\"int *\", facets.ctypes.data),\n            ffi.NULL,\n            ffi.NULL,\n        )\n        solutions.append(J[0])\n        # Test against exact result\n        assert np.isclose(\n            J[0], (0.5 * (a + np.cos(0.1)) + 0.5 * (a + b + np.cos(0))) * np.sqrt(b**2 + 0.1**2)\n        )\n\n    # Compare custom quadrature with vertex quadrature\n    assert np.isclose(solutions[0], solutions[1])\n\n\ndef test_manifold_derivatives(compile_args):\n    \"\"\"Test higher order derivatives on manifolds\"\"\"\n    c_el = basix.ufl.element(\"Lagrange\", \"interval\", 1, shape=(2,))\n    mesh = ufl.Mesh(c_el)\n\n    x = ufl.SpatialCoordinate(mesh)\n    dx = ufl.Measure(\"dx\", domain=mesh)\n    order = 4\n    el = basix.ufl.element(\"Lagrange\", \"interval\", order)\n    V = ufl.FunctionSpace(mesh, el)\n\n    u = ufl.Coefficient(V)\n    d = 5.3\n    f_ex = d * order * (order - 1) * x[1] ** (order - 2)\n    expr = u.dx(1).dx(1) - f_ex\n    J = expr * expr * dx\n\n    compiled_forms, module, _ = ffcx.codegeneration.jit.compile_forms(\n        [J], cffi_extra_compile_args=compile_args\n    )\n\n    default_integral = compiled_forms[0].form_integrals[0]\n    scale = 2.5\n    coords = np.array([0.0, 0.0, 0.0, 0.0, scale, 0.0], dtype=np.float64)\n    dof_coords = scale * el._element.points.reshape(-1)\n\n    w = np.array([d * d_c**order for d_c in dof_coords], dtype=np.float64)\n    c = np.array([], dtype=np.float64)\n    perm = np.array([0], dtype=np.uint8)\n\n    ffi = module.ffi\n    J = np.zeros(1, dtype=np.float64)\n    kernel = getattr(default_integral, \"tabulate_tensor_float64\")\n    kernel(\n        ffi.cast(\"double *\", J.ctypes.data),\n        ffi.cast(\"double  *\", w.ctypes.data),\n        ffi.cast(\"double  *\", c.ctypes.data),\n        ffi.cast(\"double  *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.cast(\"uint8_t *\", perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    assert np.isclose(J[0], 0.0)\n\n\ndef test_integral_grouping(compile_args):\n    \"\"\"We group integrals with common integrands to avoid duplicated\n    integration kernels. This means that `inner(u, v)*dx((1,2,3))  +\n    inner(grad(u), grad(v))*dx(2) + inner(u,v)*dx` is grouped as\n    1. `inner(u,v)*dx((\"everywhere\", 1, 3))`\n    2. `(inner(grad(u), grad(v)) + inner(u, v))*dx(2)`\n    Each of the forms has one generated `tabulate_tensor_*` function,\n    which is referred to multiple times in `integrals_` and\n    `integral_ids_`\n\n    \"\"\"\n    mesh = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,)))\n    V = ufl.FunctionSpace(mesh, basix.ufl.element(\"Lagrange\", \"triangle\", 1))\n    u = ufl.TrialFunction(V)\n    v = ufl.TestFunction(V)\n    a = (\n        ufl.inner(u, v) * ufl.dx((1, 2, 3))\n        + ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx(2)\n        + ufl.inner(u, v) * ufl.dx\n    )\n    compiled_forms, module, _ = ffcx.codegeneration.jit.compile_forms(\n        [a], cffi_extra_compile_args=compile_args\n    )\n    # NOTE: This assumes that the first integral type is cell integrals, see UFCx.h\n    cell = module.lib.cell\n    num_integrals = (\n        compiled_forms[0].form_integral_offsets[cell + 1]\n        - compiled_forms[0].form_integral_offsets[cell]\n    )\n    assert num_integrals == 4\n    unique_integrals = set(\n        [\n            compiled_forms[0].form_integrals[compiled_forms[0].form_integral_offsets[cell] + i]\n            for i in range(num_integrals)\n        ]\n    )\n    assert len(unique_integrals) == 2\n\n\ndef test_derivative_domains(compile_args):\n    \"\"\"Test a form with derivatives on two different domains will generate valid code.\"\"\"\n\n    V_ele = basix.ufl.element(\"Lagrange\", \"triangle\", 2)\n    W_ele = basix.ufl.element(\"Lagrange\", \"interval\", 1)\n\n    gdim = 2\n    V_domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(gdim,)))\n    W_domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"interval\", 1, shape=(gdim,)))\n\n    V = ufl.FunctionSpace(V_domain, V_ele)\n    W = ufl.FunctionSpace(W_domain, W_ele)\n\n    u = ufl.TrialFunction(V)\n    q = ufl.TestFunction(W)\n\n    ds = ufl.Measure(\"ds\", domain=V_domain)\n\n    forms = [ufl.inner(u.dx(0), q.dx(0)) * ds]\n    _compiled_forms, _module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": np.float64}, cffi_extra_compile_args=compile_args\n    )\n\n\n@pytest.mark.parametrize(\"dtype\", [\"float64\"])\n@pytest.mark.parametrize(\"permutation\", [[0], [1]])\ndef test_mixed_dim_form(compile_args, dtype, permutation):\n    \"\"\"Test that the local element tensor corresponding to a mixed-dimensional form is correct.\n    The form involves an integral over a facet of the cell. The trial function and a coefficient f\n    are of codim 0. The test function and a coefficient g are of codim 1. We compare against another\n    form where the test function and g are codim 0 but have the same trace on the facet.\n    \"\"\"\n\n    def tabulate_tensor(ele_type, V_cell_type, W_cell_type, coeffs):\n        \"Helper function to create a form and compute the local element tensor\"\n        V_ele = basix.ufl.element(ele_type, V_cell_type, 2)\n        W_ele = basix.ufl.element(ele_type, W_cell_type, 1)\n\n        gdim = 2\n        V_domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", V_cell_type, 1, shape=(gdim,)))\n        W_domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", W_cell_type, 1, shape=(gdim,)))\n\n        V = ufl.FunctionSpace(V_domain, V_ele)\n        W = ufl.FunctionSpace(W_domain, W_ele)\n\n        u = ufl.TrialFunction(V)\n        q = ufl.TestFunction(W)\n\n        f = ufl.Coefficient(V)\n        g = ufl.Coefficient(W)\n\n        ds = ufl.Measure(\"ds\", domain=V_domain)\n\n        n = ufl.FacetNormal(V_domain)\n        forms = [ufl.inner(f * g * ufl.grad(u), n * q) * ds]\n        compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n            forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n        )\n        form0 = compiled_forms[0]\n        default_integral = form0.form_integrals[0]\n        kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n\n        A = np.zeros((W_ele.dim, V_ele.dim), dtype=dtype)\n        w = np.array(coeffs, dtype=dtype)\n        c = np.array([], dtype=dtype)\n        facet = np.array([0], dtype=np.intc)\n        perm = np.array(permutation, dtype=np.uint8)\n\n        xdtype = dtype_to_scalar_dtype(dtype)\n        coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=xdtype)\n\n        c_type = dtype_to_c_type(dtype)\n        c_xtype = dtype_to_c_type(xdtype)\n\n        ffi = module.ffi\n        kernel(\n            ffi.cast(f\"{c_type}  *\", A.ctypes.data),\n            ffi.cast(f\"{c_type}  *\", w.ctypes.data),\n            ffi.cast(f\"{c_type}  *\", c.ctypes.data),\n            ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n            ffi.cast(\"int *\", facet.ctypes.data),\n            ffi.cast(\"uint8_t *\", perm.ctypes.data),\n            ffi.NULL,\n        )\n\n        return A\n\n    # Define the element type\n    ele_type = \"Lagrange\"\n    # Define the cell type for each space\n    V_cell_type = \"triangle\"\n    Vbar_cell_type = \"interval\"\n\n    # Coefficient data\n    # f is a quadratic on each edge that is 0 at the vertices and 1 at the midpoint\n    f_data = [0, 0, 0, 1, 1, 1]\n    # g is a linear function along the edge that is 0 at one vertex and 1 at the other\n    g_data = [0, 1]\n    # Collect coefficient data\n    coeffs = f_data + g_data\n\n    # Tabulate the tensor for the mixed-dimensional form\n    A = tabulate_tensor(ele_type, V_cell_type, Vbar_cell_type, coeffs)\n\n    # Compare to a reference result. Here, we compare against the same kernel but with\n    # the interval element replaced with a triangle.\n    # We create some data for g on the triangle whose trace coincides with g on the interval\n    g_data = [0, 0, 1]\n    coeffs_ref = f_data + g_data\n    A_ref = tabulate_tensor(ele_type, V_cell_type, V_cell_type, coeffs_ref)\n    # Remove the entries for the extra test DOF on the triangle element\n    A_ref = A_ref[1:][:]\n\n    # The orientation of the interval is assumed to be fixed (since it is given uniquely\n    # by its global orientation in the mesh). Let us focus on local facet 0. It can have\n    # two possible orientations depending on the cell topology:\n    #\n    # 2   1          1   1\n    # | \\  \\         | \\  \\\n    # |  \\  \\   or   |  \\  \\\n    # 0---1  0       0---2  0\n    #\n    # In the second case, local facet 0 is flipped relative to the interval. If we look at\n    # the degrees of freedom second-order Lagrange on the triangle and first-order Lagrange\n    # on the interval, we have\n    #\n    # 2    1           1    1\n    # | \\   \\          | \\   \\\n    # 4  3   \\         5  3   \\\n    # |    \\  \\    or  |    \\  \\\n    # 0--5--1  0       0--4--2  0\n    #\n    # Since the trial function is defined on the triangle, the second case (where the\n    # permutation is 1) is thus the same as swapping cols 1 and 2 and cols 4 and 5 of the\n    # first case result.\n    # NOTE: Although we choose to fix the orientation of the interval, the same result\n    # can be obtained by fixing the triangle and considering flipping the interval.\n    if permutation[0] == 1:\n        A_ref[:, [1, 2]] = A_ref[:, [2, 1]]\n        A_ref[:, [4, 5]] = A_ref[:, [5, 4]]\n\n    assert np.allclose(A, A_ref)\n\n\n@pytest.mark.parametrize(\"dtype\", [\"float64\"])\ndef test_ds_prism(compile_args, dtype):\n    element = basix.ufl.element(\"Lagrange\", \"prism\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"prism\", 1, shape=(3,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n\n    a = ufl.inner(u, v) * ufl.ds\n    forms = [a]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n\n    offsets = form0.form_integral_offsets\n    cell = module.lib.cell\n    exterior_facet = module.lib.exterior_facet\n    interior_facet = module.lib.interior_facet\n    assert offsets[cell + 1] - offsets[cell] == 0\n    assert offsets[exterior_facet + 1] - offsets[exterior_facet] == 2\n    assert offsets[interior_facet + 1] - offsets[interior_facet] == 0\n\n    integral_id0 = form0.form_integral_ids[offsets[exterior_facet]]\n    integral_id1 = form0.form_integral_ids[offsets[exterior_facet] + 1]\n    assert integral_id0 == integral_id1 == -1\n\n    integral0 = form0.form_integrals[offsets[exterior_facet]]\n    integral1 = form0.form_integrals[offsets[exterior_facet] + 1]\n\n    if basix.CellType(integral0.domain) == basix.CellType.triangle:\n        assert basix.CellType(integral1.domain) == basix.CellType.quadrilateral\n        integral_tri = integral0\n        integral_quad = integral1\n    else:\n        assert basix.CellType(integral0.domain) == basix.CellType.quadrilateral\n        assert basix.CellType(integral1.domain) == basix.CellType.triangle\n        integral_tri = integral1\n        integral_quad = integral0\n\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n    entity_perm = np.array([0], dtype=np.uint8)\n\n    # Test integral over triangle (facet 0)\n    A = np.zeros((6, 6), dtype=dtype)\n    entity_index = np.array([0], dtype=int)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array(\n        [\n            [0.0, 0.0, 0.0],\n            [1.0, 0.0, 0.0],\n            [0.0, 1.0, 0.0],\n            [0.0, 0.0, 1.0],\n            [1.0, 0.0, 1.0],\n            [0.0, 1.0, 1.0],\n        ],\n        dtype=xdtype,\n    )\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n\n    kernel = getattr(integral_tri, f\"tabulate_tensor_{dtype}\")\n\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", entity_index.ctypes.data),\n        ffi.cast(\"uint8_t *\", entity_perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    assert np.allclose(\n        A,\n        np.array(\n            [\n                [1 / 12, 1 / 24, 1 / 24, 0, 0, 0],\n                [1 / 24, 1 / 12, 1 / 24, 0, 0, 0],\n                [1 / 24, 1 / 24, 1 / 12, 0, 0, 0],\n                [0, 0, 0, 0, 0, 0],\n                [0, 0, 0, 0, 0, 0],\n                [0, 0, 0, 0, 0, 0],\n            ]\n        ),\n    )\n\n    # Test integral over quadrilateral (facet 1)\n    A = np.zeros((6, 6), dtype=dtype)\n    entity_index = np.array([1], dtype=np.int64)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array(\n        [\n            [0.0, 0.0, 0.0],\n            [1.0, 0.0, 0.0],\n            [0.0, 1.0, 0.0],\n            [0.0, 0.0, 1.0],\n            [1.0, 0.0, 1.0],\n            [0.0, 1.0, 1.0],\n        ],\n        dtype=xdtype,\n    )\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n\n    kernel = getattr(integral_quad, f\"tabulate_tensor_{dtype}\")\n\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", entity_index.ctypes.data),\n        ffi.cast(\"uint8_t *\", entity_perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    assert np.allclose(\n        A,\n        np.array(\n            [\n                [1 / 9, 1 / 18, 0, 1 / 18, 1 / 36, 0],\n                [1 / 18, 1 / 9, 0, 1 / 36, 1 / 18, 0],\n                [0, 0, 0, 0, 0, 0],\n                [1 / 18, 1 / 36, 0, 1 / 9, 1 / 18, 0],\n                [1 / 36, 1 / 18, 0, 1 / 18, 1 / 9, 0],\n                [0, 0, 0, 0, 0, 0],\n            ]\n        ),\n    )\n\n\n@pytest.mark.parametrize(\"geometry\", [(\"interval\", 1), (\"triangle\", 2), (\"tetrahedron\", 3)])\n@pytest.mark.parametrize(\"rank\", [0, 1, 2])\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        np.float32,\n        np.float64,\n        pytest.param(\n            np.complex64,\n            marks=pytest.mark.skipif(\n                os.name == \"nt\", reason=\"win32 platform does not support C99 _Complex numbers\"\n            ),\n        ),\n        pytest.param(\n            np.complex128,\n            marks=pytest.mark.skipif(\n                os.name == \"nt\", reason=\"win32 platform does not support C99 _Complex numbers\"\n            ),\n        ),\n    ],\n)\n@pytest.mark.parametrize(\"element_type\", [\"Lagrange\", \"Discontinuous Lagrange\"])\ndef test_vertex_integral(compile_args, geometry, rank, dtype, element_type):\n    cell, gdim = geometry\n    rdtype = np.real(dtype(0)).dtype\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", cell, 1, shape=(gdim,), dtype=rdtype))\n    element = basix.ufl.element(element_type, cell, 1)\n    dP = ufl.Measure(\"dP\")\n    x = ufl.SpatialCoordinate(domain)\n    V = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(V), ufl.TestFunction(V)\n    # With a Lagrange space for test and trial functions, all these forms should evaluate as the\n    # x-coordinate times an indicator for alignment between the argument(s) and the vertices.\n    if rank == 0:\n        F = x[0] * dP\n    elif rank == 1:\n        F = x[0] * ufl.conj(v) * dP\n    else:\n        F = x[0] * u * ufl.conj(v) * dP\n\n    if element.discontinuous:\n        if rank == 0:\n            # No elements involved in assembly.\n            return\n\n        with pytest.raises(TypeError):\n            ffcx.codegeneration.jit.compile_forms(\n                [F], options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n            )\n        return\n\n    (form0,), module, _ = ffcx.codegeneration.jit.compile_forms(\n        [F], options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n    assert form0.rank == rank\n\n    ffi = module.ffi\n    kernel = getattr(\n        form0.form_integrals[0],\n        f\"tabulate_tensor_{dtype().dtype.name}\",\n    )\n\n    vertex_count = domain.ufl_coordinate_element().basix_element.points.shape[0]\n\n    for a, b in np.array([(0, 1), (1, 0), (2, 0), (5, -2)], dtype=rdtype):\n        # General geometry for simplices of gdim 1,2 and 3.\n        # gdim 1: creates the interval (a, b)\n        # gdim 2: creates the triangle with vertices (a, 0), (b, 0), (0,0)\n        # gdim 3: creates the tetrahedron with vertices (a, 0, 0), (b, 0, 0), (0, 0, 0), (0, 0, 1)\n        coords = np.array([a, 0.0, 0.0, b, 0.0, 0.0, 0, 0, 0, 0, 0, 1], dtype=rdtype)\n\n        for vertex in range(vertex_count):\n            J = np.zeros(vertex_count**2, dtype=dtype)\n            e = np.array([vertex], dtype=np.int32)\n            kernel(\n                ffi.cast(f\"{dtype_to_c_type(dtype)} *\", J.ctypes.data),\n                ffi.NULL,\n                ffi.NULL,\n                ffi.cast(f\"{dtype_to_c_type(rdtype)} *\", coords.ctypes.data),\n                ffi.cast(\"int *\", e.ctypes.data),\n                ffi.NULL,\n                ffi.NULL,\n            )\n            idx = (rank > 0) * vertex + (rank > 1) * vertex * vertex_count\n            assert np.isclose(coords[vertex * 3], J[idx], atol=1e2 * np.finfo(dtype).eps)\n            # Check all other entries are not touched\n            assert np.allclose(np.delete(J, [idx]), 0)\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float64\",\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_vertex_mesh(compile_args, dtype):\n    \"\"\"Test that a mesh with only vertices can be created and used.\"\"\"\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n\n    cell = basix.CellType.point\n    c_el = basix.ufl.element(\"Lagrange\", cell, 0, shape=(2,), discontinuous=True, dtype=xdtype)\n    msh = ufl.Mesh(c_el)\n\n    el = basix.ufl.element(\"Lagrange\", cell, 0, discontinuous=True, dtype=xdtype)\n    V = ufl.FunctionSpace(msh, el)\n    u = ufl.Coefficient(V)\n    dx = ufl.Measure(\"dx\", domain=msh)\n    Jh = u * dx\n\n    forms = [Jh]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n    assert form0.form_integral_offsets[module.lib.cell + 1] == 1\n    default_integral = form0.form_integrals[0]\n    J = np.zeros(1, dtype=dtype)\n    coords = np.array([2.0, 3.0, 0.0], dtype=xdtype)\n    coeffs = np.array([-5.2], dtype=dtype)\n    w = np.array(coeffs, dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    c_type = dtype_to_c_type(dtype)\n    c_xtype = dtype_to_c_type(xdtype)\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type}  *\", J.ctypes.data),\n        ffi.cast(f\"{c_type}  *\", w.ctypes.data),\n        ffi.cast(f\"{c_type}  *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    assert np.isclose(J[0], coeffs[0])\n\n\n@pytest.mark.parametrize(\"dtype\", [\"float64\"])\n@pytest.mark.parametrize(\"permutation\", [[0], [1]])\n@pytest.mark.parametrize(\"local_entity_index\", [0, 1, 2, 3, 4, 5])\ndef test_mixed_dim_form_codim2(compile_args, dtype, permutation, local_entity_index):\n    \"\"\"Test that the local element tensor corresponding to a mixed-dimensional\n    form of codim 2 is correct. The form involves an integral over an edge of\n    the cell. The trial function and a coefficient `f` are of codim 0.\n    The test function is of codim 2.\n    \"\"\"\n\n    def tabulate_tensor(ele_type, V_cell_type, W_cell_type, coeffs, l_i):\n        V_ele = basix.ufl.element(ele_type, V_cell_type, 2)\n        W_ele = basix.ufl.element(ele_type, W_cell_type, 1)\n\n        gdim = 3\n        V_domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", V_cell_type, 1, shape=(gdim,)))\n        W_domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", W_cell_type, 1, shape=(gdim,)))\n\n        V = ufl.FunctionSpace(V_domain, V_ele)\n        W = ufl.FunctionSpace(W_domain, W_ele)\n\n        u = ufl.TrialFunction(V)\n        q = ufl.TestFunction(W)\n\n        f = ufl.Coefficient(V)\n\n        dr = ufl.Measure(\"dr\", domain=V_domain)\n        forms = [u.dx(0) * f * q * dr]\n\n        compiled_forms, module, _ = ffcx.codegeneration.jit.compile_forms(\n            forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n        )\n        form0 = compiled_forms[0]\n        default_integral = form0.form_integrals[0]\n        kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n\n        A = np.zeros((W_ele.dim, V_ele.dim), dtype=dtype)\n        w = np.array(coeffs, dtype=dtype)\n        c = np.array([], dtype=dtype)\n        edge = np.array([l_i], dtype=np.intc)\n        perm = np.array(permutation, dtype=np.uint8)\n\n        xdtype = dtype_to_scalar_dtype(dtype)\n        coords = np.array(\n            [[0.0, 0.0, 0.0], [2.5, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 1.3]], dtype=xdtype\n        )\n\n        c_type = dtype_to_c_type(dtype)\n        c_xtype = dtype_to_c_type(xdtype)\n\n        ffi = module.ffi\n        kernel(\n            ffi.cast(f\"{c_type}  *\", A.ctypes.data),\n            ffi.cast(f\"{c_type}  *\", w.ctypes.data),\n            ffi.cast(f\"{c_type}  *\", c.ctypes.data),\n            ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n            ffi.cast(\"int *\", edge.ctypes.data),\n            ffi.cast(\"uint8_t *\", perm.ctypes.data),\n            ffi.NULL,\n        )\n\n        return A\n\n    # Define the element type\n    ele_type = \"Lagrange\"\n    # Define the cell type for each space\n    V_cell_type = \"tetrahedron\"\n    Vbar_cell_type = \"interval\"\n\n    # Coefficient data\n    f_data = [5.0, 6.0, 7.0, -1.0, 2.0, 3.0, 5.0, 5.0, 6.0, 2.0]\n    coeff_data = f_data\n\n    # Tabulate the tensor for the mixed-dimensional form\n    A = tabulate_tensor(ele_type, V_cell_type, Vbar_cell_type, coeff_data, local_entity_index)\n\n    # Compare to a reference result. Here, we compare against the same form but with\n    # the interval element replaced with a triangle.\n    A_ref = tabulate_tensor(ele_type, V_cell_type, V_cell_type, coeff_data, local_entity_index)\n\n    # Map from local edge index to (local) DOF indices on that edge\n    local_index_to_slice = {0: [2, 3], 1: [1, 3], 2: [1, 2], 3: [0, 3], 4: [0, 2], 5: [0, 1]}\n\n    A_ref = A_ref[local_index_to_slice[local_entity_index]]\n\n    # See test_mixed_dim_form for an explanation of the permutation logic. Here, for simplicity,\n    # we choose to fix the orientation of the tet and flip the interval.\n    if permutation[0] == 1:\n        A_ref[[0, 1], :] = A_ref[[1, 0], :]\n\n    assert np.allclose(A, A_ref)\n\n\n@pytest.mark.parametrize(\"dtype\", [\"float64\"])\n@pytest.mark.parametrize(\"local_entity_index\", [0, 1, 2])\ndef test_mixed_dim_form_codim2_2D(compile_args, dtype, local_entity_index):\n    \"\"\"Test that mixed assembly between 2D and 0D gives a consistent result\"\"\"\n\n    # Define the element type\n    ele_type = \"Lagrange\"\n    # Define the cell type for each space\n    V_cell_type = \"triangle\"\n    W_cell_type = \"point\"\n\n    # Coefficient data\n    coeffs = [3, 4, 7, 8, 5, 2]\n\n    # Tabulate the tensor for the mixed-dimensional form\n    V_ele = basix.ufl.element(ele_type, V_cell_type, 2)\n    W_ele = basix.ufl.element(ele_type, W_cell_type, 0, discontinuous=True)\n\n    gdim = 2\n    V_domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", V_cell_type, 1, shape=(gdim,)))\n    W_domain = ufl.Mesh(\n        basix.ufl.element(\"Lagrange\", W_cell_type, 0, shape=(gdim,), discontinuous=True)\n    )\n\n    V = ufl.FunctionSpace(V_domain, V_ele)\n    W = ufl.FunctionSpace(W_domain, W_ele)\n\n    u = ufl.TrialFunction(V)\n    q = ufl.TestFunction(W)\n\n    f = ufl.Coefficient(V)\n\n    dr = ufl.Measure(\"dr\", domain=V_domain)\n    x = ufl.SpatialCoordinate(V_domain)\n    forms = [u.dx(0) * f * q * x[1] * dr]\n\n    compiled_forms, module, _ = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n    form0 = compiled_forms[0]\n    default_integral = form0.form_integrals[0]\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n\n    A = np.zeros((W_ele.dim, V_ele.dim), dtype=dtype)\n    w = np.array(coeffs, dtype=dtype)\n    c = np.array([], dtype=dtype)\n    edge = np.array([local_entity_index], dtype=np.intc)\n    perm = np.zeros(1, dtype=np.uint8)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([[-0.1, 0.2, 0.0], [2.5, 0.0, 0.0], [0.0, 2.0, 0.0]], dtype=xdtype)\n\n    c_type = dtype_to_c_type(dtype)\n    c_xtype = dtype_to_c_type(xdtype)\n\n    ffi = module.ffi\n    kernel(\n        ffi.cast(f\"{c_type}  *\", A.ctypes.data),\n        ffi.cast(f\"{c_type}  *\", w.ctypes.data),\n        ffi.cast(f\"{c_type}  *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", edge.ctypes.data),\n        ffi.cast(\"uint8_t *\", perm.ctypes.data),\n        ffi.NULL,\n    )\n\n    # Tabulate reference solution, equivalent of assembling a vector with the same spaces from V\n    # and vertex quadrature\n    vertices = basix.topology(basix.cell.CellType.triangle)[0]\n    vertex_coords = basix.geometry(basix.cell.CellType.triangle)\n    qp = np.array(vertex_coords[vertices[local_entity_index]])\n    qw = np.ones(qp.shape[0], dtype=xdtype)\n    assert len(qw) == 1\n    dx = ufl.Measure(\n        \"dx\",\n        domain=V_domain,\n        metadata={\"quadrature_rule\": \"custom\", \"quadrature_points\": qp, \"quadrature_weights\": qw},\n    )\n    forms = [u.dx(0) * f * x[1] / abs(ufl.JacobianDeterminant(V_domain)) * dx]\n    compiled_forms, module, _ = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n    form0 = compiled_forms[0]\n    default_integral = form0.form_integrals[0]\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n\n    b = np.zeros(V_ele.dim, dtype=dtype)\n    ffi = module.ffi\n    kernel(\n        ffi.cast(f\"{c_type}  *\", b.ctypes.data),\n        ffi.cast(f\"{c_type}  *\", w.ctypes.data),\n        ffi.cast(f\"{c_type}  *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.cast(\"int *\", edge.ctypes.data),\n        ffi.cast(\"uint8_t *\", perm.ctypes.data),\n        ffi.NULL,\n    )\n    np.testing.assert_allclose(A[0, :], b)\n\n\n@pytest.mark.parametrize(\"dtype\", [\"float64\"])\n@pytest.mark.parametrize(\"permutation\", [[0], [1]])\ndef test_ridge_integral(compile_args, dtype, permutation):\n    \"\"\"Test that one can assemble a ridge integral on a single domain.\"\"\"\n    y_ext = 1.7\n    z_ext = 1.3\n\n    def tabulate_tensor(ele_type, V_cell_type, l_i):\n        V_ele = basix.ufl.element(ele_type, V_cell_type, 1)\n\n        gdim = 3\n        domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", V_cell_type, 1, shape=(gdim,)))\n\n        V = ufl.FunctionSpace(domain, V_ele)\n        x = ufl.SpatialCoordinate(domain)\n        u = ufl.TestFunction(V)\n\n        dr = ufl.Measure(\"dr\", domain=domain)\n        forms = [u * x[2] * dr]\n\n        compiled_forms, module, _ = ffcx.codegeneration.jit.compile_forms(\n            forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n        )\n        form0 = compiled_forms[0]\n        default_integral = form0.form_integrals[0]\n        kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n\n        A = np.zeros((V_ele.dim,), dtype=dtype)\n        w = np.array([], dtype=dtype)\n        c = np.array([], dtype=dtype)\n        edge = np.array([l_i], dtype=np.intc)\n        perm = np.array(permutation, dtype=np.uint8)\n\n        xdtype = dtype_to_scalar_dtype(dtype)\n        coords = np.array(\n            [[0.0, 0.0, 0.0], [2.5, 0.0, 0.0], [0.0, y_ext, 0.0], [0.0, 0.0, z_ext]], dtype=xdtype\n        )\n\n        c_type = dtype_to_c_type(dtype)\n        c_xtype = dtype_to_c_type(xdtype)\n\n        ffi = module.ffi\n        kernel(\n            ffi.cast(f\"{c_type}  *\", A.ctypes.data),\n            ffi.cast(f\"{c_type}  *\", w.ctypes.data),\n            ffi.cast(f\"{c_type}  *\", c.ctypes.data),\n            ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n            ffi.cast(\"int *\", edge.ctypes.data),\n            ffi.cast(\"uint8_t *\", perm.ctypes.data),\n            ffi.NULL,\n        )\n\n        return A\n\n    # Define the element type\n    ele_type = \"Lagrange\"\n    # Define the cell type for each space\n    V_cell_type = \"tetrahedron\"\n\n    # Tabulate the tensor for the mixed-dimensional form\n    b = tabulate_tensor(ele_type, V_cell_type, 0)\n\n    # Ref first ridge integral length\n    rl = np.sqrt(y_ext**2 + z_ext**2) / y_ext\n\n    # Compute z as a function of y along the ridge\n    y = sympy.Symbol(\"y\")\n    z = z_ext - z_ext / y_ext * y\n    # Define the test functions along the ridge\n    phi_2 = y / y_ext\n    phi_3 = 1 - y / y_ext\n\n    # Use sympy for numerical integration\n    b_2 = sympy.integrate(phi_2 * z * rl, (y, 0, y_ext))\n    b_3 = sympy.integrate(phi_3 * z * rl, (y, 0, y_ext))\n    b_ref = np.array([0, 0, b_2, b_3], dtype=b.dtype)\n    np.testing.assert_allclose(b_ref, b, atol=1e-10)\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float64\",\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_diagonal_form(dtype, compile_args):\n    element = basix.ufl.element(\"Lagrange\", \"tetrahedron\", 1)\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"tetrahedron\", 1, shape=(3,)))\n    space = ufl.FunctionSpace(domain, element)\n    u, v = ufl.TrialFunction(space), ufl.TestFunction(space)\n    a = (\n        ufl.inner(u.dx(0), v.dx(2)) * ufl.dx\n        - ufl.inner(u, v) * ufl.dx\n        + ufl.inner(u.dx(0), v) * ufl.dx\n    )\n    forms = [a]\n    compiled_diag_forms, diag_module, _ = ffcx.codegeneration.jit.compile_forms(\n        forms,\n        options={\"scalar_type\": dtype, \"part\": \"diagonal\"},\n        cffi_extra_compile_args=compile_args,\n    )\n\n    for _, compiled_f in zip(forms, compiled_diag_forms):\n        assert compiled_f.rank == 1\n    diag_form0 = compiled_diag_forms[0].form_integrals[0]\n\n    A_diag = np.zeros((4,), dtype=dtype)\n    A = np.zeros((4, 4), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    ffi = diag_module.ffi\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    diag_kernel = getattr(diag_form0, f\"tabulate_tensor_{dtype}\")\n    diag_kernel(\n        ffi.cast(f\"{c_type} *\", A_diag.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n    ffi = module.ffi\n\n    form0 = compiled_forms[0].form_integrals[0]\n    kernel = getattr(form0, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n    np.testing.assert_allclose(A_diag, np.diag(A))\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float64\",\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_diagonal_mixed_form(dtype, compile_args):\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"tetrahedron\", 1, shape=(3,)))\n\n    element_u = basix.ufl.element(\"Lagrange\", \"tetrahedron\", 3, shape=(3,))\n    element_p = basix.ufl.element(\"Lagrange\", \"tetrahedron\", 2)\n    element = basix.ufl.mixed_element([element_u, element_p])\n    space = ufl.FunctionSpace(domain, element)\n    u, p = ufl.TrialFunctions(space)\n    v, q = ufl.TestFunctions(space)\n    a = (\n        ufl.inner(ufl.grad(u), ufl.grad(v))\n        - ufl.inner(p, ufl.div(v))\n        + ufl.inner(ufl.div(u), q)\n        + ufl.inner(p.dx(0), q.dx(1))\n    ) * ufl.dx\n    forms = [a]\n    compiled_diag_forms, diag_module, _ = ffcx.codegeneration.jit.compile_forms(\n        forms,\n        options={\"scalar_type\": dtype, \"part\": \"diagonal\"},\n        cffi_extra_compile_args=compile_args,\n    )\n\n    for _, compiled_f in zip(forms, compiled_diag_forms):\n        assert compiled_f.rank == 1\n    diag_form0 = compiled_diag_forms[0].form_integrals[0]\n\n    A_diag = np.zeros((element.dim,), dtype=dtype)\n    A = np.zeros((element.dim, element.dim), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    ffi = diag_module.ffi\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    diag_kernel = getattr(diag_form0, f\"tabulate_tensor_{dtype}\")\n    diag_kernel(\n        ffi.cast(f\"{c_type} *\", A_diag.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n    ffi = module.ffi\n\n    form0 = compiled_forms[0].form_integrals[0]\n    kernel = getattr(form0, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n    np.testing.assert_allclose(A_diag, np.diag(A))\n\n\n@pytest.mark.parametrize(\"shape\", [(2,), (3,), (2, 2), (3, 3), (), (1,), (1, 1)])\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float64\",\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_diagonal_mixed_block(dtype, compile_args, shape):\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"tetrahedron\", 1, shape=(3,)))\n\n    element_u = basix.ufl.element(\"Lagrange\", \"tetrahedron\", 2, shape=shape)\n    element_v = basix.ufl.element(\"Lagrange\", \"tetrahedron\", 2, shape=shape)\n    element = basix.ufl.mixed_element([element_u, element_v])\n    space = ufl.FunctionSpace(domain, element)\n\n    u, p = ufl.TrialFunctions(space)\n    v, q = ufl.TestFunctions(space)\n    a = (ufl.inner(ufl.grad(u), ufl.grad(v)) - ufl.inner(p, v) + ufl.inner(u, q)) * ufl.dx\n    forms = [a]\n    compiled_diag_forms, diag_module, _ = ffcx.codegeneration.jit.compile_forms(\n        forms,\n        options={\"scalar_type\": dtype, \"part\": \"diagonal\"},\n        cffi_extra_compile_args=compile_args,\n    )\n\n    for _, compiled_f in zip(forms, compiled_diag_forms):\n        assert compiled_f.rank == 1\n    diag_form0 = compiled_diag_forms[0].form_integrals[0]\n\n    A_diag = np.zeros((element.dim,), dtype=dtype)\n    A = np.zeros((element.dim, element.dim), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    ffi = diag_module.ffi\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    diag_kernel = getattr(diag_form0, f\"tabulate_tensor_{dtype}\")\n    diag_kernel(\n        ffi.cast(f\"{c_type} *\", A_diag.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n    ffi = module.ffi\n\n    form0 = compiled_forms[0].form_integrals[0]\n    kernel = getattr(form0, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n    np.testing.assert_allclose(A_diag, np.diag(A))\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float64\",\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_multiple_integrands_same_quadrature(compile_args, dtype):\n    domain = ufl.Mesh(basix.ufl.element(\"Lagrange\", \"quadrilateral\", 1, shape=(2,)))\n\n    element = basix.ufl.element(\"Lagrange\", \"quadrilateral\", 1)\n    space = ufl.FunctionSpace(domain, element)\n\n    u = ufl.TrialFunction(space)\n    v = ufl.TestFunction(space)\n\n    q_4 = basix.quadrature.make_quadrature(basix.CellType.quadrilateral, 4)\n    q_5 = basix.quadrature.make_quadrature(basix.CellType.quadrilateral, 5)\n    np.testing.assert_allclose(q_4[0], q_5[0])\n    np.testing.assert_allclose(q_4[1], q_5[1])\n    dx_4 = ufl.Measure(\"dx\", domain=domain, metadata={\"quadrature_degree\": 4})\n    a4 = ufl.inner(ufl.grad(u), ufl.grad(v)) * dx_4\n\n    dx_5 = ufl.Measure(\"dx\", domain=domain, metadata={\"quadrature_degree\": 5})\n    a5 = ufl.inner(u, v) * dx_5\n\n    ref4 = (ufl.inner(ufl.grad(u), ufl.grad(v)) + ufl.inner(u, v)) * dx_4\n\n    forms = [a4 + a5, ref4]\n    compiled_forms, diag_module, _ = ffcx.codegeneration.jit.compile_forms(\n        forms,\n        options={\"scalar_type\": dtype},\n        cffi_extra_compile_args=compile_args,\n    )\n\n    A_mixed = np.zeros((element.dim, element.dim), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    ffi = diag_module.ffi\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array(\n        [\n            [0.0, 0.0, 0.0],\n            [1.0, 0.0, 0.0],\n            [0.0, 1.0, 0.0],\n            [1.0, 1.0, 0.0],\n        ],\n        dtype=xdtype,\n    )\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel = getattr(compiled_forms[0].form_integrals[0], f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", A_mixed.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    A_ref = np.zeros_like(A_mixed)\n    ref_kernel = getattr(compiled_forms[1].form_integrals[0], f\"tabulate_tensor_{dtype}\")\n    ref_kernel(\n        ffi.cast(f\"{c_type} *\", A_ref.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n    np.testing.assert_allclose(A_mixed, A_ref)\n\n\n@pytest.mark.parametrize(\n    \"dtype, operator\",\n    [\n        (\"float64\", ufl.real),\n        (\"float32\", ufl.real),\n        pytest.param(\n            \"complex128\",\n            ufl.real,\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n        pytest.param(\n            \"complex128\",\n            ufl.imag,\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n        pytest.param(\n            \"complex64\",\n            ufl.real,\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n        pytest.param(\n            \"complex64\",\n            ufl.imag,\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_ufl_complex_extraction(\n    compile_args: list[str],\n    dtype: npt.DTypeLike,\n    operator: typing.Callable[[typing.Any], typing.Any],\n) -> None:\n    xdtype = dtype_to_scalar_dtype(dtype)\n    c_el = basix.ufl.element(\"Lagrange\", \"interval\", 1, shape=(1,), dtype=xdtype)\n    mesh = ufl.Mesh(c_el)\n    el = basix.ufl.element(\"DG\", \"interval\", 0, shape=())\n    V = ufl.FunctionSpace(mesh, el)\n    u = ufl.Coefficient(V)\n\n    dx = ufl.Measure(\"dx\")\n    b = ufl.conditional(ufl.gt(operator(u), 0), u, -u) * dx\n    val = 5 + 6j if \"complex\" in dtype else 5\n    w = np.array([val], dtype=dtype)\n    forms = [b]\n    compiled_forms, module, _code = ffcx.codegeneration.jit.compile_forms(\n        forms, cffi_extra_compile_args=compile_args, options={\"scalar_type\": dtype}\n    )\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n\n    assert form0.form_integral_offsets[module.lib.cell + 1] == 1\n\n    default_integral = form0.form_integrals[0]\n    J = np.zeros(1, dtype=dtype)\n    coords = np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", J.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.NULL,\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n    assert np.isclose(J[0], val)\n"
  },
  {
    "path": "test/test_lnodes.py",
    "content": "import importlib\n\nimport numpy as np\nimport pytest\nfrom cffi import FFI\n\nfrom ffcx.codegeneration import lnodes as L\nfrom ffcx.codegeneration.C import Formatter\nfrom ffcx.codegeneration.utils import dtype_to_c_type\n\n\n@pytest.mark.parametrize(\"dtype\", (\"float32\", \"float64\", \"intc\"))\ndef test_gemm(dtype):\n    # Test LNodes simple matrix-matrix multiply in C\n    p, q, r = 5, 16, 12\n\n    A = L.Symbol(\"A\", dtype=L.DataType.SCALAR)\n    B = L.Symbol(\"B\", dtype=L.DataType.SCALAR)\n    C = L.Symbol(\"C\", dtype=L.DataType.SCALAR)\n    code = [L.Comment(f\"Matrix multiply A{p, r} = B{p, q} * C{q, r}\")]\n\n    i = L.Symbol(\"i\", dtype=L.DataType.INT)\n    j = L.Symbol(\"j\", dtype=L.DataType.INT)\n    k = L.Symbol(\"k\", dtype=L.DataType.INT)\n    m_ij = L.MultiIndex([i, j], [p, q])\n    m_ik = L.MultiIndex([i, k], [p, r])\n    m_jk = L.MultiIndex([j, k], [q, r])\n\n    body = [L.AssignAdd(A[m_ik], B[m_ij] * C[m_jk])]\n    body = [L.ForRange(i, 0, p, body=body)]\n    body = [L.ForRange(j, 0, q, body=body)]\n    code += [L.ForRange(k, 0, r, body=body)]\n\n    # Format into C and compile with CFFI\n    format = Formatter(dtype=dtype)\n    c_scalar = dtype_to_c_type(dtype)\n    decl = f\"void gemm({c_scalar} *A, {c_scalar} *B, {c_scalar} *C)\"\n    c_code = decl + \"{\\n\" + format(L.StatementList(code)) + \"\\n}\\n\"\n\n    ffibuilder = FFI()\n    ffibuilder.cdef(decl + \";\")\n    ffibuilder.set_source(f\"_gemm_{c_scalar}\", c_code)\n    ffibuilder.compile(verbose=True)\n    _gemm = importlib.import_module(f\"_gemm_{c_scalar}\")\n    gemm = _gemm.lib.gemm\n    ffi = _gemm.ffi\n\n    A = np.zeros((p, r), dtype=dtype)\n    B = np.ones((p, q), dtype=dtype)\n    C = np.ones((q, r), dtype=dtype)\n    pA = ffi.cast(f\"{c_scalar} *\", A.ctypes.data)\n    pB = ffi.cast(f\"{c_scalar} *\", B.ctypes.data)\n    pC = ffi.cast(f\"{c_scalar} *\", C.ctypes.data)\n\n    gemm(pA, pB, pC)\n    assert np.all(A == q)\n\n\n@pytest.mark.parametrize(\"dtype\", (\"float32\", \"float64\", \"intc\"))\ndef test_gemv(dtype):\n    # Test LNodes simple matvec multiply in C\n    p, q = 5, 16\n\n    y = L.Symbol(\"y\", dtype=L.DataType.SCALAR)\n    A = L.Symbol(\"A\", dtype=L.DataType.SCALAR)\n    x = L.Symbol(\"x\", dtype=L.DataType.SCALAR)\n    code = [L.Comment(f\"Matrix-vector multiply y({p}) = A{p, q} * x({q})\")]\n\n    i = L.Symbol(\"i\", dtype=L.DataType.INT)\n    j = L.Symbol(\"j\", dtype=L.DataType.INT)\n    m_ij = L.MultiIndex([i, j], [p, q])\n\n    body = [L.AssignAdd(y[i], A[m_ij] * x[j])]\n    body = [L.ForRange(i, 0, p, body=body)]\n    code += [L.ForRange(j, 0, q, body=body)]\n\n    # Format into C and compile with CFFI\n    format = Formatter(dtype=dtype)\n    c_scalar = dtype_to_c_type(dtype)\n    decl = f\"void gemm({c_scalar} *y, {c_scalar} *A, {c_scalar} *x)\"\n    c_code = decl + \"{\\n\" + format(L.StatementList(code)) + \"\\n}\\n\"\n\n    ffibuilder = FFI()\n    ffibuilder.cdef(decl + \";\")\n    ffibuilder.set_source(f\"_gemv_{c_scalar}\", c_code)\n    ffibuilder.compile(verbose=True)\n    _gemv = importlib.import_module(f\"_gemv_{c_scalar}\")\n    gemv = _gemv.lib.gemm\n    ffi = _gemv.ffi\n\n    y = np.arange(p, dtype=dtype)\n    x = np.arange(q, dtype=dtype)\n    A = np.outer(y, x)\n\n    py = ffi.cast(f\"{c_scalar} *\", y.ctypes.data)\n    pA = ffi.cast(f\"{c_scalar} *\", A.ctypes.data)\n    px = ffi.cast(f\"{c_scalar} *\", x.ctypes.data)\n\n    # Compute expected result\n    s2 = q * (q - 1) * (2 * q - 1) // 6 + 1\n    result = np.arange(p, dtype=dtype) * s2\n\n    gemv(py, pA, px)\n    assert np.all(y == result)\n"
  },
  {
    "path": "test/test_numba.py",
    "content": "# Copyright (C) 2025 Paul T. Kühner\n#\n# This file is part of FFCx.(https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nimport ctypes\nimport importlib\nfrom pathlib import Path\n\nimport numpy as np\nimport numpy.typing as npt\nimport pytest\n\nimport ffcx.main\nfrom ffcx.codegeneration.utils import dtype_to_scalar_dtype, numba_ufcx_kernel_signature\n\nnumba = pytest.importorskip(\"numba\")\n\n\ndef wrap_kernel(scalar_type, real_type):\n    c_signature = numba_ufcx_kernel_signature(scalar_type, real_type)\n    return numba.cfunc(c_signature, nopython=True)\n\n\ndef as_C_array(np_array: npt.NDArray):\n    dtype_C = np.ctypeslib.as_ctypes_type(np_array.dtype)\n    return np_array.ctypes.data_as(ctypes.POINTER(dtype_C))\n\n\ndef test_external_module():\n    \"\"\"Inject ffcx.codegeneration.numba as external module, to test support for custom backends.\"\"\"\n    opts = \"--language ffcx.codegeneration.numba --scalar_type float64\"\n    dir = Path(__file__).parent\n    assert ffcx.main.main([str(dir / \"poisson.py\"), *opts.split(\" \")]) == 0\n\n\n@pytest.fixture\ndef generate_poisson() -> None:\n    dir = Path(__file__).parent\n    assert ffcx.main.main([str(dir / \"poisson.py\"), \"--language\", \"numba\"]) == 0\n\n\n@pytest.mark.parametrize(\"scalar_type\", [\"float32\", \"float64\"])  # TODO: complex limited by ctypes\ndef test_integral(scalar_type: str, generate_poisson) -> None:\n    poisson = importlib.import_module(\"poisson_numba\")\n\n    dtype = np.dtype(scalar_type).type\n    dtype_r = dtype_to_scalar_dtype(dtype)\n\n    kernel_a = wrap_kernel(dtype, dtype_r)(poisson.form_poisson_a.form_integrals[0].tabulate_tensor)\n\n    A = np.zeros((3, 3), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    kappa_value = np.array([[1.0, 2.0], [3.0, 4.0]])\n    c = np.array(kappa_value.flatten(), dtype=dtype)\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=dtype_r)\n    empty = np.empty((0,), dtype=dtype_r)\n\n    kernel_a(\n        as_C_array(A),\n        as_C_array(w),\n        as_C_array(c),\n        as_C_array(coords),\n        as_C_array(empty),\n        as_C_array(empty),\n        0,\n    )\n    A_expected = np.array(\n        [[1.0, -0.5, -0.5], [-0.5, 0.5, 0.0], [-0.5, 0.0, 0.5]], dtype=scalar_type\n    )\n\n    assert np.allclose(A, np.trace(kappa_value) * A_expected)\n\n    kernel_L = wrap_kernel(dtype, dtype_r)(poisson.form_poisson_L.form_integrals[0].tabulate_tensor)\n\n    b = np.zeros((3,), dtype=dtype)\n    w = np.full((3,), 0.5, dtype=dtype)\n    c = np.empty((0,), dtype=dtype)\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=dtype_r)\n    empty = np.empty((0,), dtype=dtype_r)\n\n    kernel_L(\n        as_C_array(b),\n        as_C_array(w),\n        as_C_array(c),\n        as_C_array(coords),\n        as_C_array(empty),\n        as_C_array(empty),\n        0,\n    )\n\n    b_expected = np.full((3,), 1 / 6, dtype=np.float64)\n    assert np.allclose(b, 0.5 * b_expected)\n\n\n@pytest.mark.parametrize(\"scalar_type\", [\"float32\", \"float64\"])  # TODO: complex limited by ctypes\ndef test_expression(scalar_type: str, generate_poisson) -> None:\n    poisson = importlib.import_module(\"poisson_numba\")\n\n    dtype = np.dtype(scalar_type).type\n    dtype_r = dtype_to_scalar_dtype(dtype)\n\n    kernel_expr = wrap_kernel(dtype, dtype_r)(poisson.expression_poisson_0.tabulate_tensor)\n\n    e = np.zeros((6 * 3,), dtype=dtype)\n    w = np.array(\n        [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11], dtype=dtype\n    )\n    kappa_value = np.array([[1.0, 2.0], [3.0, 4.0]])\n    c = np.array(kappa_value.flatten(), dtype=dtype)\n    coords = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], dtype=dtype_r)\n    empty = np.empty((0,), dtype=dtype_r)\n\n    kernel_expr(\n        as_C_array(e),\n        as_C_array(w),\n        as_C_array(c),\n        as_C_array(coords),\n        as_C_array(empty),\n        as_C_array(empty),\n        0,\n    )\n    e_expected = np.array(\n        [5, 7, 8, 11, 15, 18, 14, 16, 17, 32, 36, 39, 23, 25, 26, 53, 57, 60], dtype=dtype\n    )\n    assert np.allclose(e, e_expected)\n"
  },
  {
    "path": "test/test_signatures.py",
    "content": "# Copyright (C) 2024 Igor A. Baratta\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nimport sys\n\nimport basix.ufl\nimport cffi\nimport numpy as np\nimport pytest\nimport ufl\n\nimport ffcx.codegeneration.jit\nimport ffcx.codegeneration.utils as utils\n\n\ndef generate_kernel(forms, scalar_type, options):\n    \"\"\"Generate kernel for given forms.\"\"\"\n    compiled_forms, module, code = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": scalar_type}\n    )\n\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    form0 = compiled_forms[0]\n\n    offsets = form0.form_integral_offsets\n    cell = module.lib.cell\n    assert offsets[cell + 1] - offsets[cell] == 1\n    integral_id = form0.form_integral_ids[offsets[cell]]\n    assert integral_id == -1\n    default_integral = form0.form_integrals[offsets[cell]]\n    kernel = getattr(default_integral, f\"tabulate_tensor_{scalar_type}\")\n    return kernel, code, module\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float32\",\n        \"float64\",\n        pytest.param(\n            \"complex64\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_numba_kernel_signature(dtype):\n    try:\n        import numba\n    except ImportError:\n        pytest.skip(\"Numba not installed\")\n\n    # Create a simple form\n    mesh = ufl.Mesh(basix.ufl.element(\"P\", \"triangle\", 2, shape=(2,)))\n    e = basix.ufl.element(\"Lagrange\", \"triangle\", 2)\n\n    V = ufl.FunctionSpace(mesh, e)\n    u = ufl.TrialFunction(V)\n    v = ufl.TestFunction(V)\n\n    a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n\n    # Generate and compile the kernel\n    kernel, _code, _module = generate_kernel([a], dtype, {})\n\n    # Convert to numpy dtype\n    np_dtype = np.dtype(dtype)\n\n    # Generate the Numba signature\n    xtype = utils.dtype_to_scalar_dtype(dtype)\n    signature = utils.numba_ufcx_kernel_signature(np_dtype, xtype)\n    assert isinstance(signature, numba.core.typing.templates.Signature)\n\n    # Get the signature from the compiled kernel\n    ffi = cffi.FFI()\n    args = ffi.typeof(kernel).args\n\n    # check that the signature is equivalent to the one in the generated code\n    assert len(args) == len(signature.args)\n    for i, (arg, sig) in enumerate(zip(args, signature.args)):\n        type_name = sig.name.replace(str(np_dtype), utils.dtype_to_c_type(np_dtype))\n        ctypes_name = type_name.replace(\" *\", \"*\")\n        assert ctypes_name == type_name\n"
  },
  {
    "path": "test/test_submesh.py",
    "content": "# Copyright (C) 2024 Jørgen S. Dokken\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nfrom __future__ import annotations\n\nimport sys\n\nimport basix.ufl\nimport numpy as np\nimport pytest\nimport ufl\n\nimport ffcx.codegeneration.jit\nfrom ffcx.codegeneration.utils import dtype_to_c_type, dtype_to_scalar_dtype\n\n\ndef compute_tensor(forms: list[ufl.form.Form], dtype: str, compile_args: list[str]):\n    \"\"\"Helper-function to compute matrix for a P1-Lagrange problem\"\"\"\n    compiled_forms, module, _ = ffcx.codegeneration.jit.compile_forms(\n        forms, options={\"scalar_type\": dtype}, cffi_extra_compile_args=compile_args\n    )\n\n    ffi = module.ffi\n    form0 = compiled_forms[0]\n    offsets = form0.form_integral_offsets\n    cell = module.lib.cell\n    assert offsets[cell + 1] - offsets[cell] == 1\n    integral_id = form0.form_integral_ids[offsets[cell]]\n    assert integral_id == -1\n\n    default_integral = form0.form_integrals[offsets[cell]]\n\n    A = np.zeros((3, 3), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    coords = np.array([[1.0, 2.0, 0.0], [1.5, 2.3, 0.0], [6.0, 1.8, 0.0]], dtype=xdtype)\n\n    c_type, c_xtype = dtype_to_c_type(dtype), dtype_to_c_type(xdtype)\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n    return A\n\n\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        \"float64\",\n        pytest.param(\n            \"complex128\",\n            marks=pytest.mark.xfail(\n                sys.platform.startswith(\"win32\"),\n                raises=NotImplementedError,\n                reason=\"missing _Complex\",\n            ),\n        ),\n    ],\n)\ndef test_multiple_mesh_codim0(dtype, compile_args):\n    # Define coordinate element and element used in parent and sub-mesh\n    element = basix.ufl.element(\"Lagrange\", \"triangle\", 1)\n    coordinate_element = basix.ufl.element(\"Lagrange\", \"triangle\", 1, shape=(2,))\n\n    domain = ufl.Mesh(coordinate_element)\n    space = ufl.FunctionSpace(domain, element)\n    u_parent = ufl.TrialFunction(space)\n\n    # Create submesh and functionspace on submesh\n    sub_domain = ufl.Mesh(coordinate_element)\n    subspace = ufl.FunctionSpace(sub_domain, element)\n    v_sub = ufl.TestFunction(subspace)\n\n    #\n    a = ufl.inner(u_parent.dx(0), v_sub.dx(0)) * ufl.dx(domain=domain)\n\n    A = compute_tensor([a], dtype, compile_args)\n\n    # Compute reference solution on with test and trial function from same mesh\n    v_parent = ufl.TestFunction(space)\n    a_org = ufl.inner(u_parent.dx(0), v_parent.dx(0)) * ufl.dx(domain=domain)\n    A_org = compute_tensor([a_org], dtype, compile_args)\n\n    np.testing.assert_allclose(A, A_org)\n"
  },
  {
    "path": "test/test_tensor_product.py",
    "content": "# Copyright (C) 2023 Igor A. Baratta\n#\n# This file is part of FFCx. (https://www.fenicsproject.org)\n#\n# SPDX-License-Identifier:    LGPL-3.0-or-later\n\nimport basix.ufl\nimport numpy as np\nimport pytest\nimport ufl\n\nimport ffcx.codegeneration.jit\nfrom ffcx.codegeneration.utils import dtype_to_c_type, dtype_to_scalar_dtype\n\n\ndef cell_to_gdim(cell_type):\n    \"\"\"Return geometric dimension of cell.\"\"\"\n    if cell_type == basix.CellType.quadrilateral:\n        return 2\n    elif cell_type == basix.CellType.hexahedron:\n        return 3\n    else:\n        raise NotImplementedError\n\n\ndef create_tensor_product_element(cell_type, degree, variant, shape=None):\n    \"\"\"Create tensor product element.\"\"\"\n    family = basix.ElementFamily.P\n    element = basix.create_tp_element(family, cell_type, degree, variant)\n    uflelement = basix.ufl.wrap_element(element)\n    if shape is None:\n        return uflelement\n    else:\n        return basix.ufl.blocked_element(uflelement, shape=shape)\n\n\ndef generate_kernel(forms, dtype, options):\n    \"\"\"Generate kernel for given forms.\"\"\"\n    # use a different cache directory for each option\n    sf = options.get(\"sum_factorization\", False)\n    cache_dir = f\"./ffcx-cache-{sf}\"\n\n    compiled_forms, module, code = ffcx.codegeneration.jit.compile_forms(\n        forms, cache_dir=cache_dir, options=options\n    )\n    for f, compiled_f in zip(forms, compiled_forms):\n        assert compiled_f.rank == len(f.arguments())\n\n    form0 = compiled_forms[0]\n\n    offsets = form0.form_integral_offsets\n    cell = module.lib.cell\n    assert offsets[cell + 1] - offsets[cell] == 1\n    integral_id = form0.form_integral_ids[offsets[cell]]\n    assert integral_id == -1\n    default_integral = form0.form_integrals[offsets[cell]]\n    kernel = getattr(default_integral, f\"tabulate_tensor_{dtype}\")\n    return kernel, code, module\n\n\n@pytest.mark.parametrize(\"dtype\", [\"float32\", \"float64\"])\n@pytest.mark.parametrize(\"P\", [1, 2, 3])\n@pytest.mark.parametrize(\"cell_type\", [basix.CellType.quadrilateral, basix.CellType.hexahedron])\ndef test_bilinear_form(dtype, P, cell_type):\n    gdim = cell_to_gdim(cell_type)\n    element = create_tensor_product_element(cell_type, P, basix.LagrangeVariant.gll_warped)\n    coords = create_tensor_product_element(\n        cell_type, 1, basix.LagrangeVariant.gll_warped, shape=(gdim,)\n    )\n    mesh = ufl.Mesh(coords)\n    V = ufl.FunctionSpace(mesh, element)\n\n    u, v = ufl.TrialFunction(V), ufl.TestFunction(V)\n    a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx\n\n    ndofs = element.dim\n\n    A = np.zeros((ndofs, ndofs), dtype=dtype)\n    w = np.array([], dtype=dtype)\n    c = np.array([], dtype=dtype)\n\n    xdtype = dtype_to_scalar_dtype(dtype)\n    if cell_type == basix.CellType.quadrilateral:\n        coords = np.array(\n            [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]], dtype=xdtype\n        )\n    elif cell_type == basix.CellType.hexahedron:\n        coords = np.array(\n            [\n                [0.0, 0.0, 0.0],\n                [1.0, 0.0, 0.0],\n                [0.0, 1.0, 0.0],\n                [1.0, 1.0, 0.0],\n                [0.0, 0.0, 1.0],\n                [1.0, 0.0, 1.0],\n                [0.0, 1.0, 1.0],\n                [1.0, 1.0, 1.0],\n            ],\n            dtype=xdtype,\n        )\n\n    c_type = dtype_to_c_type(dtype)\n    c_xtype = dtype_to_c_type(xdtype)\n    kernel, _code, module = generate_kernel([a], dtype, options={\"scalar_type\": dtype})\n    ffi = module.ffi\n    kernel(\n        ffi.cast(f\"{c_type} *\", A.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    # Use sum factorization\n    A1 = np.zeros((ndofs, ndofs), dtype=dtype)\n    kernel, _code, module = generate_kernel(\n        [a], dtype, options={\"scalar_type\": dtype, \"sum_factorization\": True}\n    )\n    ffi = module.ffi\n    kernel(\n        ffi.cast(f\"{c_type} *\", A1.ctypes.data),\n        ffi.cast(f\"{c_type} *\", w.ctypes.data),\n        ffi.cast(f\"{c_type} *\", c.ctypes.data),\n        ffi.cast(f\"{c_xtype} *\", coords.ctypes.data),\n        ffi.NULL,\n        ffi.NULL,\n        ffi.NULL,\n    )\n\n    np.testing.assert_allclose(A, A1, rtol=1e-6, atol=1e-6)\n"
  }
]