Function space model in python layer of dolfin is flawed
In UFL there are various element types, e.g. FiniteElement and VectorElement.
In UFC all elements are represented behind a single ufc::finite_element/dofmap interface.
In DOLFIN/C++ there is only one FunctionSpace (ignoring MultiMeshFunctionSpace), no MixedFunctionSpace or VectorFunctionSpace etc.
In DOLFIN/Python, there is FunctionSpace, MixedFunctionSpace, VectorFunctionSpace etc. This is wrong. The python layer should wrap the C++ library, which has the concept that a FunctionSpace has a Mesh and a finite element. This also matches the new ufl.FunctionSpace(mesh,element). The element can be a vector element or mixed element, and dolfin.FunctionSpace should handle that.
In fact, you can create a function space in C++ using a VectorElement, and when passed to python it will not be a dolfin.VectorFunctionSpace but a dolfin.FunctionSpaceFromCpp. This means the dolfin.VectorFunctionSpace is only usable as a construction method, it cannot be relied upon for type checking etc. since it's not the only way to represent a vector element based function space.
I propose the following steps:
-
Introduce a constructor dolfin.FunctionSpace(mesh, element)
-
Change dolfin.FooFunctionSpace into factory functions and add deprecation warnings.
-
Merge classes dolfin.FunctionSpace and dolfin.FunctionSpaceFromCpp
Comments (25)
-
reporter -
As a side note, I'd also like to see a deprecation of
FunctionSpace(mesh, family, degree)
. It creates an overhead of constructing an unneeded dofmap in user codes (and our demo codes!):P2 = VectorFunctionSpace(mesh, 'Lagrange', 2) # its dofmap not needed anywhere P1 = FunctionSpace(mesh, 'Lagrange', 1) # its dofmap not needed anywhere TH = P2 * P1
Moreover it tends to support a wrong idea that
TH
has "something" in common withP1
andP2
. Construction from UFL element should be preferred.Sorry for polluting the thread but if there is a time to interface change, this can be solved alongside.
It this interface change is not welcome, other solution would be a lazy construction of dofmap while keeping the
FunctionSpace(mesh, family, degree)
interface. -
Changes proposed by @martinal sound good to me.
@blechta :
-
Your first point is a common cause of confusion, and would be good to fix (but the necessary interface change could cause some consternation).
-
Lazy construction of dofmaps would be an easy fix for the efficiency issue.
-
-
-
assigned issue to
-
assigned issue to
-
The principal problem is that
ufl.FunctionSpace
uses__slots__
- We need to dynamically change
__class__
ofcpp.FunctionSpace
instance toFunctionSpace
(which should inherit fromufl.FunctionSpace
).
Presence of both 1. and 2. results in
Traceback (most recent call last): File "space.py", line 5, in <module> W.sub(0) File "/home/jan/dev/fenics-master/src/dolfin/local.jan.fix-issue-576/lib/python2.7/site-packages/dolfin/functions/functionspace.py", line 259, in sub return FunctionSpaceFromCpp(cpp.FunctionSpace.sub(self, i)) File "/home/jan/dev/fenics-master/src/dolfin/local.jan.fix-issue-576/lib/python2.7/site-packages/dolfin/functions/functionspace.py", line 367, in __new__ cppV.__class__ = FunctionSpace TypeError: __class__ assignment: 'FunctionSpace' object layout differs from 'FunctionSpace'
when running
from dolfin import * mesh = UnitSquareMesh(3, 3) V = FunctionSpace(mesh, 'CG', 1) W = V*V W.sub(0)
with
jan/fix-issue-576
. Any ideas, @martinal ? Do you really need slots? If yes, I'll try looking what can be done. -
@martinal, anyway is it intended that
ufl.FunctionSpace
has a__dict__
(probably because of inheritance of nonslottedufl.AbstractFunctionSpace
)?import ufl e = ufl.FiniteElement('Lagrange', ufl.triangle, 1) V = ufl.FunctionSpace(ufl.triangle, e) V.__dict__
-
Ok, now I see that you've disable
__slots__
forCoefficient
so I've done the same in UFLjan/fix-dolfin-issue-576
. -
How the design should correspond to UFL where is special class
MixedFunctionSpace
(andTensorProductFunctionSpace
). Or did you mean to keepMixedFunctionSpace
class in PyDOLFIN? -
reporter Disabling slots for functionspace is fine.
But is the design with dynamic class change necessary? From what I remember we don't do that with Function: dolfin.Function inherits from cpp.Function and ufl.Coefficient. It would be nice if we had only one pattern for how to join ufl and dolfin classes.
-
reporter The classes ufl.MixedFunctionSpace and ufl.TensorProductFunctionSpace are not used yet. We might have to use a different name than MixedFunctionSpace to avoid confusion. The idea is that MixedElement is local and MixedFunctionSpace is global and can contain different meshes or meshviews in its subspaces. Function spaces on different meshes could be useful for the MultiMesh functionality that Anders is working on, but function spaces on different meshviews of the same mesh is something we will need, typically for coupled multiphysics problems.
mesh = ... element1 = ... element2 = ... # Space of locally mixed elements M1 = FunctionSpace(mesh, MixedElement(element1, element2)) # Space of globally mixed spaces over different parts of same mesh: meshview1 = MeshView(mesh, ...) meshview2 = MeshView(mesh, ...) V1 = FunctionSpace(meshview1, element1) V2 = FunctionSpace(meshview2, element2) M2 = MixedFunctionSpace(V1, V2) # Space of globally mixed spaces over different meshes: mesh2 = ... V1 = FunctionSpace(mesh, element1) V2 = FunctionSpace(mesh2, element2) M3 = MixedFunctionSpace(V1, V2)
But we need to keep the
MixedFunctionSpace
notation (not the class) in dolfin at least for a while.One naming alternative is to follow the FiniteElement/MixedElement pattern and use FunctionSpace/MixedSpace for the new globally mixed class. However while VectorElement works ok as a name, VectorSpace is too loaded with other meaning.
-
But is the design with dynamic class change necessary? From what I remember we don't do that with Function.
Function.__init_cpp_copy_constructor
does that and there is the same purpose for it: wrapcpp.Function
intoFunction
. Thanks for pointing me toFunction
- now I can follow the pattern more closely.The name of the method is misleading - it does not behave like a copy constructor:
from dolfin import * mesh = UnitSquareMesh(3, 3) V = FunctionSpace(mesh, 'CG', 1) u = cpp.Function(V) v = Function(u) # "copy" of cpp.Function w = Function(v) # copy of Function print u.vector().id(), v.vector().id(), w.vector().id()
yields
5 5 7
. I guess we need construction (not copy) fromcpp.Function
but the difference between two is quite subtle and prone to bugs. -
As a side note, we could probably move
Function[Space]
to SWIG layer, merging withcpp.Function[Space]
and subclassingufl.(Coefficient|FunctionSpace)
. ThenFunction[Space]
objects returned in C++ would already have correct type and no dynamic retyping would be needed. But this like a big rewrite so let's avoid it while not necessary. -
Let's simply deprecate
Function(Function)
copy constructor and keep onlyFunction(cpp.Function)
wrapper constructor. There isFunction.copy(deepcopy=False)
for copying. -
reporter Yes, please fix that bug and deprecate the copy constructor. Is the wrapper constructor necessary? Maybe it can be a stand-alone function if it's used internally. Changing the way construction works tends to be hard to follow for developers other than the original author.
But is it possible to inject the ufl base class in a .i file? I don't think I've seen that anywhere. If so it might not be much more work than moving the 'class Function' body to a .i file. It could have unforeseen side effects though.
Currently the python interface of dolfin classes use some subclassing in .py files and some %extend in .i files.
Another pattern is in meshes.py, where cpp.Mesh.init is 'patched' with a python override. That way UnitSquareMesh etc. actually use that same init function. I'm also not sure this is possible to do in a .i file. I don't particularly like this solution though.
-
reporter Actually, you can just remove slots from all FunctionSpace classes, no need for the ufl_noslots marker outside of the Expr class hierarchy.
-
reporter I'll just do it and merge your branch.
-
reporter I suggest not enabling a deprecation warning for {Vector|Tensor|Mixed}FunctionSpace at least yet. The fallout of that operation is rather large and should be discussed more broadly. It's good that they're not classes but we need to keep the syntax to not break the world.
-
Don't we want to support
count
kwarg forufl.FunctionSpace.__init__
as forufl.Coefficient
for a finer coupling between UFL count and DOLFIN id? -
-
I'll put the deprecation of
{Mixed|Enriched}FunctionSpace
into separate pull request so that we can discuss it on next meeting with the world. -
I'll revert deprecation of
{convenience_constructor|Vector|Tensor}FunctionSpace
. I went really overboard here.
Note 1. and 2. are really different story.
TH = P2v * P1
is not semantically correct. We, in fact, do not support something like creation ofTH
fromP2v
andP1
. It actually meansTH = FunctionSpace(mesh, P2v.ufl_element() * P1.ufl_element())
. -
-
reporter No, the FunctionSpace doesn't have a count in ufl. I don't see the need for one. The Mesh has a ufl_id (same thing, more descriptive) however, and mesh id + element should be sufficiently unique for the ufl/ffc/ufc side of things.
1., 2. good.
Yes, and ideally P2v * P1 should later become an actual MixedFunctionSpace where each subspace can have a different mesh(view), e.g. for including Lagrange multiplier on boundary as part of a coupled system.
-
Fix in pull request #250 without deprecations. Deprecations to appear later when this PR is merged.
-
But is it possible to inject the ufl base class in a .i file?
Ok, haven't found the way.
-
Partially fixed by mergin pull request #250. The deprecations will come in another PR.
-
- changed status to resolved
Resolved by deprecations in pull request #251, commit e424f4b.
-
- removed milestone
Removing milestone: 1.7 (automated comment)
- Log in to comment
See also the bottom of this page:
https://bitbucket.org/fenics-project/fenics-developer-tools/wiki/FunctionSpace_in_UFL