Fix right DOF transformation for mixed elements with block size > 1 - #4464
Fix right DOF transformation for mixed elements with block size > 1#4464PaulXiCao wants to merge 4 commits into
Conversation
In the mixed-element branch of dof_transformation_right_fn, the data passed to each sub-element was sliced as data.subspan(offset, data.size() - offset) which treats the buffer as a single contiguous run. For a right-applied transformation the layout is (block_size rows) x (ndofs columns), so sub-element e owns columns [offset, offset + dims[e]) of *every* row. Basix derives the row stride as data.size() / n, so trimming the front of the span shortened the stride by offset / n and misaligned every row after the first. Only block_size == 1, where the trimmed prefix is absorbed exactly, was correct. Transform each row separately over the sub-element's own column range. This affects bilinear-form matrix assembly for mixed elements needing DOF transformations, where the transposed transformation is applied with one row per test-function DOF. Note that the sub-element transformation is now invoked once per row rather than once per sub-element, which loses the batching Basix does internally over n. Passing a column offset down to Basix would restore it without the per-row calls. Closes FEniCS#4294 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The sub-span slicing in the mixed-element branch of dof_transformation_right_fn is shared by all four doftransform values, so cover them all rather than only the transpose that matrix assembly happens to use. Each case failed independently before the slicing fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Could you write the tests in the Python at python/test/unit/fem? Perhaps there is an appropriate file with tests already related to transforms. We prefer Python for test writing unless there is a good specific reason to use C++. |
Hi @jhale . I am a new contributor and might need some guidance, but AFAI can tell there is no wrapper for either $ rg "dof_transformation" python
python/dolfinx/fem/element.py
301: def needs_dof_transformations(self) -> bool:
314: return self._cpp_object.needs_dof_transformations
python/dolfinx/wrappers/dolfinx_wrappers/fem.h
341: .def_prop_ro("needs_dof_transformations",
342: &dolfinx::fem::FiniteElement<T>::needs_dof_transformations)Do you know if these were deliberately left out of the python wrapper? Otherwise i could look into adding them to the wrapper in a follow-up issue. Or was your idea to write a python test where we directly call down into the c++ layer (similar to writing a temporary wrapper just for the test)? |
|
They were probably left out because they are performance critical functions that should not be called from Python. What I would propose is that they are wrapped into dolfinx.cpp as _function_name, indicating that they are private, but not wrapped into the Python layer, which defines our public API. We can then unit test them from Python. |
Fixes #4294.
In the mixed-element branch of
FiniteElement::dof_transformation_right_fn,the data passed to each sub-element was sliced as
which treats the buffer as a single contiguous run. For a right-applied transformation the layout is
(block_size rows) x (ndofs columns), so sub-elementeowns columns[offset, offset + dims[e])of every row. Basix derives the row stride asdata.size() / n, so trimming the front of the span shortened the stride byoffset / nand misaligned every row after the first. Onlyblock_size == 1, where the trimmed prefix is absorbed exactly, was correct.Each row is now transformed separately over the sub-element's own column range. This affects bilinear-form matrix assembly for mixed elements needing DOF transformations, where the transposed transformation is applied with one row per test-function DOF.
Tests
cpp/test/fem/finite_element.cppcompares a multi-row application against a row-by-row application through theblock_size == 1path, for a mixed element with the transforming sub-element at a non-zero DOF offset (mixed(P1 Lagrange, N1curl degree 2) on a triangle). Two control cases cover the same element at offset 0 and the non-mixed leaf path, both of which were already correct. All cases are parametrised over the fourdoftransformvalues, each of which failed independently before the fix.Note on performance
The sub-element transformation is now invoked once per row rather than once per sub-element, which loses the batching Basix does internally over
n. Basix's right-apply already supports a column offset internally (apply_tranpose_matrix_rightindexesdata[data_size * b + offset + i]), so exposing that in the public*_apply_rightsignature would let this be a single call per sub-element again. Happy to follow up upstream if wanted.AI assistance: I used Claude Code (Opus 5) to draft parts of this PR. I reviewed, edited, tested, and take responsibility for the final contribution.