Installing legacy FEniCS: Compatibility of ufl_legacy 2022 with FEniCS 2019

Hi everyone,

I’m setting up legacy FEniCS 2019.1 on a new HPC system using Apptainer containers. I need to remain on the legacy stack because several other codes I use depend on it, so migrating to FEniCSx isn’t an option right now.

I installed fenics-ufl-legacy (version 2022.3.0) with pip, since it is described as the compatible package for legacy FEniCS. A simple import ufl_legacy test works fine. Direct imports from the legacy package also work:

apptainer exec --containall ~/fenics-legacy-updated.sif python3 -c "from ufl_legacy.tensors import ListTensor; print('UFL ok')"

However, many of my scripts import from ufl.*. To accommodate them, I tried aliasing ufl_legacy to ufl. That succeeds for light, top-level symbols, but importing the tensors module fails:

apptainer exec --containall ~/fenics-legacy-updated.sif python3 -c "import sys; import ufl_legacy as ufl; sys.modules['ufl']=ufl; from ufl.tensors import ListTensor; print('UFL ok')"

Error:

  File "/usr/lib/python3/dist-packages/ufl_legacy/tensors.py", line 25, in <module>
    class ListTensor(Operator):
  File "/usr/lib/python3/dist-packages/ufl_legacy/core/ufl_type.py", line 370, in _ufl_type_decorator_
    check_type_traits_consistency(cls)
  File "/usr/lib/python3/dist-packages/ufl_legacy/core/ufl_type.py", line 132, in check_type_traits_consistency
    assert Expr._ufl_num_typecodes_ == len(Expr._ufl_all_handler_names_)
AssertionError

What I think might be happening is that somehow two different versions of UFL (or parts of UFL and UFL-legacy) are being imported at the same time, leading to mismatched type registries. I have verified there is no separate ufl package in the container (import ufl fails unless I add the alias). So I’m unsure whether this is a problem with my environment setup or whether ufl_legacy 2022.x is in fact not fully compatible with dolfin 2019.1.

My questions:

  1. Can someone confirm whether ufl_legacy==2022.3.0 is officially compatible with FEniCS 2019.1? I could not find an installation for ufl version 2019.
  2. If yes, what are common reasons for this check_type_traits_consistency assertion to fail in containerized installs?
  3. If sticking with ufl_legacy, is there a supported way to alias the entire tree safely, or should I avoid aliasing altogether and patch imports in my scripts to ufl_legacy.*? Please note that using Apptainer containers is a requirement of the HPC and other options like conda environments are not feasible.

Thanks in advance for any guidance on this issue.

If you are on 2019.1.0 and not the «master»-branch of legacy fenics, I would use Bitbucket and the 2019.1 tag from there, rather than going the ufl legacy route.

If you want a recipe for running on UFL 2022.3.0 with the master branch of legacy DOLFIN, I would suggest having a look at:

for inspiration.

However, I tested your “renaming” workaround, and that does not work in that docker image, which
to me indicates that the workaround is not quite correct.
In general, what I would change in scripts depending on UFL is:

try:
   import ufl
except ModuleNotFoundError:
   import ufl_legacy as ufl

Maybe @minrk or @dparsons can comment on the feasibility to aliasing the whole tree safely, as I don’t know an easy way of doing it.

1 Like

It looks like the alias needs more hacking to rename the lower level API.
There is a pattern for this used in the debian h5py package. Different users variously wanted h5py-serial, or h5py-mpi. So we built both and made them available as h5py/_debian_h5py_serial and h5py/_debian_h5py_mpi. An aliasing script was then constructed as h5py/__init__.py, see Files · master · Debian Science Team / h5py · GitLab

You can see that script scans through the lower level API, whether in serial or in mpi, and registers it under _h5py.

Evidentally ufl_legacy needs the same kind of registration to access the lower level submodules.
This seems to work, at least for your ListTensor test case:

import sys
import ufl_legacy as ufl
api = [ k for k in ufl.__dict__.keys() if not k.startswith('__') and not k.endswith('__') ]
for key in api:
    # "imports" symbols (makes them accessible)
    setattr(ufl,key,getattr(ufl,key))
    # rename symbols as properties of toplevel ufl module
    sys.modules['ufl.{}'.format(key)] = getattr(ufl,key)

then

from ufl.tensors import ListTensor
print(ListTensor)

shows

<class 'ufl_legacy.tensors.ListTensor'>
2 Likes

I think this middle line was needed when distinguishing _h5py_mpi from _h5py. I think it is not needed for ufl_legacy→ufl.

You might find it works simply with

import sys
import ufl_legacy as ufl
api = [ k for k in ufl.__dict__.keys() if not k.startswith('__') and not k.endswith('__') ]
for key in api:
    # rename symbols as properties of toplevel ufl module
    sys.modules['ufl.{}'.format(key)] = getattr(ufl,key)
del api

Thank you very much @dokken and @dparsons
This fix solved the issue.