dolfin team mailing list archive
-
dolfin team
-
Mailing list archive
-
Message #10259
Re: new Function design
2008/10/22 Johan Hake <hake@xxxxxxxxx>:
> On Wednesday 22 October 2008 09:32:31 Martin Sandve Alnæs wrote:
>> 2008/10/22 Johan Hake <hake@xxxxxxxxx>:
>> > On Tuesday 21 October 2008 23:23:27 Martin Sandve Alnæs wrote:
>> >> 2008/10/21 Johan Hake <hake@xxxxxxxxx>:
>> >> > On Tuesday 21 October 2008 22:34:04 Martin Sandve Alnæs wrote:
>> >> >> 2008/10/21 Johan Hake <hake@xxxxxxxxx>:
>> >> >> > On Tuesday 21 October 2008 21:37:13 Martin Sandve Alnæs wrote:
>> >> >> >> 2008/10/21 Anders Logg <logg@xxxxxxxxx>:
>> >> >> >> > On Tue, Oct 21, 2008 at 06:01:53PM +0100, Garth N. Wells wrote:
>> >> >> >> >> Anders Logg wrote:
>> >> >> >> >> > On Tue, Oct 21, 2008 at 04:45:01PM +0100, Garth N. Wells
> wrote:
>> >> >> >> >> >> I have a few questions and thoughts regarding the new
>> >> >> >> >> >> Function design
>> >> >> >> >> >>
>> >> >> >> >> >> * It's not clear to me what the intention is with
>> >> >> >> >> >> user-defined functions. The functions
>> >> >> >> >> >> Function::interpolate(...) never call eval(..), so they
>> >> >> >> >> >> can't pick up user-defined values. Should
>> >> >> >> >> >> Function::interpolate test for the presence of a
>> >> >> >> >> >> GenericVector to decide whether or not the Function is
>> >> >> >> >> >> discrete or user-defined?
>> >> >> >> >> >
>> >> >> >> >> > Yes, sorry. I've missed this. I'll fix it.
>> >> >> >> >> >
>> >> >> >> >> >> * It would be useful to declare user-defined functions
>> >> >> >> >> >> without associating a FunctionSpace. If we want to
>> >> >> >> >> >> interpolate the function, a FunctionSpace must then be
>> >> >> >> >> >> provided. Anyone see any problems with this?
>> >> >> >> >> >
>> >> >> >> >> > The reasoning here is that all Functions must always be
>> >> >> >> >> > associated with a FunctionSpace so that they may be correctly
>> >> >> >> >> > interpreted in forms and correctly plotted. When a Function
>> >> >> >> >> > is created in PyDOLFIN, it must always be associated with a
>> >> >> >> >> > certain FiniteElement (and in a while FunctionSpace). It
>> >> >> >> >> > would simplify the handling of Functions if they are always
>> >> >> >> >> > associated with a FunctionSpace.
>> >> >> >> >>
>> >> >> >> >> I agree that is makes life simple if every function has a
>> >> >> >> >> space, but it is a bit clunky for declaring user-defined
>> >> >> >> >> functions. The forms must be declared first to extract the
>> >> >> >> >> finite element to create the function space. Could look nasty
>> >> >> >> >> when a lot of functions are involved.
>> >> >> >> >>
>> >> >> >> >> We have a function Function::interpolate which takes a function
>> >> >> >> >> space V as an argument and it interpolates the function u in V.
>> >> >> >> >> What if we permit undefined function spaces (which perhaps only
>> >> >> >> >> have a domain)? We would then interpolate the user defined
>> >> >> >> >> function u in the provided space V.
>> >> >> >> >>
>> >> >> >> >> Garth
>> >> >> >> >
>> >> >> >> > Are user-defined functions ever used without being related to a
>> >> >> >> > particular element/function space?
>> >> >> >> >
>> >> >> >> > It don't think it will be very clumsy. The clumsy thing will be
>> >> >> >> > to (in C++) get from something compiled by a form compiler to a
>> >> >> >> > FunctionSpace.
>> >> >> >> >
>> >> >> >> > If we can make that operation smooth, then creating
>> >> >> >> > (user-defined) functions will be very simple and convenient. One
>> >> >> >> > just needs to supply the variable V holding the function space.
>> >> >> >> >
>> >> >> >> > The current way of extracting function space data from the form
>> >> >> >> > is not very nice (in C++). What would be the optimal way to
>> >> >> >> > initialize a FunctionSpace in C++? We could think of extending
>> >> >> >> > the code generation to generate code that makes this convenient.
>> >> >> >> >
>> >> >> >> > --
>> >> >> >> > Anders
>> >> >> >>
>> >> >> >> The current way of extracting function space data from the form is
>> >> >> >> not very nice in Python either, since it doesn't work with
>> >> >> >> compiled functions. (Never mind that the current code is
>> >> >> >> FFC-specific, this will be the same with UFL).
>> >> >> >>
>> >> >> >> Using Python functors can easily make the assembly slower than
>> >> >> >> solving the linear system, so it's not really interesting to do in
>> >> >> >> real applications...
>> >> >> >>
>> >> >> >> To make a function object that is both of a C++ subclass of
>> >> >> >> dolfin::Function and of the Python class ufl.Function, we can't
>> >> >> >> use the fixed multiple inheritance
>> >> >> >> solution in the current PyDOLFIN.
>> >> >> >>
>> >> >> >> We would have to define a new class dynamically in python,
>> >> >> >> inheriting from both ufl.Function and the freshly compiled C++
>> >> >> >> Function subclass. After all this work cleaning up the Function
>> >> >> >> class hierarchy, is that really something you want?
>> >> >> >>
>> >> >> >> I'm not sure if that is even possible to do while maintaining
>> >> >> >> efficiency, with cross-language inheritance and SWIG directors and
>> >> >> >> all that.
>> >> >> >>
>> >> >> >> If anyone has another solution, I'm very interested in hearing it!
>> >> >> >> Otherwise, I'm all for keeping the ufl.Function objects used in
>> >> >> >> form definition separated from dolfin.Function objects used in
>> >> >> >> assembly.
>> >> >> >
>> >> >> > I agree with Martin that we need to have a solution for PyDOLFIN
>> >> >> > users that does not depend on using python functors, as it will
>> >> >> > take forever for a complex form together with a moderate mesh to
>> >> >> > just assemble the form.
>> >> >> >
>> >> >> > Is it possible to let compile_functions compile a cpp function,
>> >> >> > with a FunctionSpace and all, instead of a mesh as it is today.
>> >> >> > Then after doing
>> >> >>
>> >> >> If you have a dolfin::FunctionSpace object already, there's no reason
>> >> >> compile_functions can't take this instead of dolfin::Mesh.
>> >> >> That's exactly the same and no problem at all.
>> >> >>
>> >> >> > this compile_function extract the element, and instantiate a
>> >> >> > UFL/FFC/PyFunction-function, and "attach" the compiled version to
>> >> >> > it. This
>> >> >>
>> >> >> What I state above is that this "attachment" must be done with
>> >> >> dynamic creation of a new class with multiple inheritance.
>> >> >> And I am unsure whether this will work out properly with SWIG
>> >> >> directors etc. I believe it _may_ work, but I don't dare to keep my
>> >> >> hopes up :-)
>> >> >
>> >> > Ok, I get it. For a moment I thought we could get away by defineing
>> >> > our own PyDOLFIN::Function class that could inherit from UFL/FFC, and
>> >> > then have a cpp_Function, but I realise this will not work.
>> >> >
>> >> >> See the attached python file for a prototype of dynamic class
>> >> >> creation with multiple inheritance using pure python classes.
>> >> >> (I think this is called "aspect oriented programming" by some people)
>> >> >>
>> >> >> > can be used to define forms, but more important it can be handed to
>> >> >> > the python assembly that check if the function has a compiled
>> >> >> > version attached to it and send this to the cpp_assembler?
>> >> >>
>> >> >> If the "attachment" is anything other than inheritance, it will have
>> >> >> to be checked with manually written python code _everywhere_
>> >> >> a dolfin::Function is expected... We can't have one kind of functions
>> >> >> for assembly and one for other stuff.
>> >> >
>> >> > Ok, I guess we have three different cases:
>> >> >
>> >> > 1) PyFunctions inherting from both UFL/FFC and cpp_Function as today,
>> >> > taking a functionsspace in its constructor. This will work with
>> >> > both user defined and discrete functions, more or less as we have it
>> >> > today.
>> >> >
>> >> > 2) The special functions, MeshSize, etc, can also be defined in the
>> >> > same way as now, right?
>> >> >
>> >> > 3) Using compile_functions, that creates a multi inheritance object
>> >> > that can be sent to any function expecting a cpp_Function, without
>> >> > manually extending the python interface.
>> >>
>> >> I'm with you up to this point.
>> >>
>> >> > Could the last be done by letting compile_function create a muliti
>> >> > inheritance Function. Instantiate the cpp_one with the function space
>> >> > and by that creating a dummy cpp_function. Then "attach" the compiled
>> >> > function to a protected attribute and define eval, by overloading it
>> >> > in python. This will then just call the attached and compiled
>> >> > cpp_functions eval.
>> >>
>> >> What you describe here sounds like the envelope-letter design
>> >> that was just _removed_ from dolfin.
>> >
>> > Yes, but only for compiled functions in Python. No other places.
>> >
>> >> What I'm suggesting is that
>> >> compile_functions dynamically creates a Python class that inherits
>> >> from ufl.Function and the freshly compiled C++ class, which is
>> >> a dolfin::Function subclass. Then it can construct an object of this
>> >> new class, passing a FunctionSpace object given by the user to
>> >> the dolfin::Function constructor, and an ufl.FiniteElement to the
>> >> ufl.Function constructor.
>> >
>> > This sounds doable. I realize now that this was what you were talking
>> > about in your previous emails, but I did not get it until now ;)
>> >
>> >> This of course requires that dolfin.FunctionSpace
>> >> is a Python subclass of dolfin::FunctionSpace with an additional
>> >> ufl.FiniteElement member variable. Using jit, dolfin.FunctionSpace
>> >> can compile the ufc::finite_element and ufc::dof_map classes it needs
>> >> from an ufl.FiniteElement. And then there's the issue of reusing
>> >> dofmaps, where DofMapSet enters the play...
>> >
>> > Do we need to jit compile ufc::finite_elements and ufc::dof_maps from the
>> > created ufl.FiniteElement? What about the one that follows from the
>> > FunctionSpace?
>>
>> I was thinking about when _constructing_ the FunctionSpace.
>> Just like PyDOLFIN uses jit in Function.__init__ today.
>
> Ok, something like:
>
> # Note pseudo code...
> class FunctionSpace(cpp_FunctionSpace):
> def __init__(self,ufl_finite_element,mesh):
> ufc_finit_element = jit(ufl_finite_element)
> form = ufl.FiniteElement*ufl.TestFunction*ufl.dx
> dof_map = jit(form)
> cpp_FucntionSpace.__init__(mesh,ufc_FinitElement,dof_map)
> self._UFL_FiniteElement = ufl_finite_element
>
> def UFL_FiniteElement(self):
> return self._UFL_FiniteElement
>
> By this the the ufc_element, ufl_element, the dofmaps and the mesh, are cached
> in the FunctionSpace.
>
> The Function would then be something like:
>
> class Function(cpp_Function,ufl.Function):
> def __init__(self,function_space):
> cpp_Function.__init__(function_space):
> ufl.Function.__init__(function_space.UFL_FiniteElement())
>
> and dynamical created code in compile_functions()
>
> class MyFunction(MyCompiledFunction,ufl.Function):
> def __init__(self,function_space):
> MyCompiledFunction.__init__(function_space):
> ufl.Function.__init__(function_space.UFL_FiniteElement())
Something like that, yes. This is close to the current PyDOLFIN.
But FunctionSpace might become a subclass of ufl.FunctionSpace
if we introduce that in UFL, and it should be possible to get
cached initialized and renumbered DofMaps from a DofMapSet.
Since a DofMapSet will typically be initialized with a Form,
a Form depends on a Function, and a Function depends on
a FunctionSpace which should be initialized by the DofMapSet,
we have a cirular dependency right there.
I still want to avoid this mess and let UFL be UFL and DOLFIN be DOLFIN...
>> Then there's the discussion of introducing FunctionSpace in UFL,
>> but lets leave that for another thread.
>>
>> >> > This will hopefully work in all cases that expect a cpp_Function? In
>> >> > assembly where we need speed we extract the compiled function and send
>> >> > that one to cpp_assembly.
>> >>
>> >> I'm not sure what you mean by extracting.
>> >
>> > Well, we could define a function 'compiled_function', in the python
>> > interface, that returnes the compiled c++ function, and then in
>> > Py_assemble we could check each coeffisient function for
>> > hasattr('compiled_function'), but nevermind. Your approach is cleaner and
>> > should work.
>>
>> Although, if we get problems with SWIG, your envelope approach
>> should work, at the cost of extra virtual function calls in eval etc.
>
> Where is speed needed other than during assembly? Will it for example be a
> problem when the function is plotted?
>
> Again note that the envelope design will potential only be applied in the
> compiled function approach.
>
>
> Johan
This overhead would be the same as the old Function design had anyway.
--
Martin
Follow ups
References