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:
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:
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.
If yes, what are common reasons for this check_type_traits_consistency assertion to fail in containerized installs?
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.
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.
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)
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