← Back to team overview

dolfin team mailing list archive

Re: new Function design

 

On Wednesday 22 October 2008 21:49:10 Martin Sandve Alnæs wrote:
> 2008/10/22 Anders Logg <logg@xxxxxxxxx>:
> > On Wed, Oct 22, 2008 at 09:36:27PM +0200, Johan Hake wrote:
> >> On Wednesday 22 October 2008 18:37:44 Anders Logg wrote:
> >> > On Wed, Oct 22, 2008 at 11:55:10AM +0200, Martin Sandve Alnæs wrote:
> >> > > 2008/10/22 Johan Hake <hake@xxxxxxxxx>:
> >> > > > On Wednesday 22 October 2008 11:12:02 Martin Sandve Alnæs wrote:
> >> > > >> 2008/10/22 Johan Hake <hake@xxxxxxxxx>:
> >> > > >> > On Wednesday 22 October 2008 10:17:43 Martin Sandve Alnæs wrote:
> >> > > >> >> 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.
> >> > > >> >> >> >> >> >>
> >> > > >> >> >> >> >> >> 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.
> >> > > >> >
> >> > > >> > But won't you have this circular dependency in UFL already?
> >> > > >>
> >> > > >> In UFL this is simple:
> >> > > >>
> >> > > >>   FiniteElement depends on nothing
> >> > > >>   Function depends on FiniteElement
> >> > > >>   Form depends on Function
> >> > > >
> >> > > > I ment for a potential FunctionSpace class in UFL.
> >> > >
> >> > > Then it is simply:
> >> > >
> >> > >    FiniteElement depends on nothing
> >> > >    FunctionSpace depends on FiniteElement
> >> > >    Function depends on FunctionSpace
> >> > >    Form depends on Function
> >> > >
> >> > > >> The relation between UFL and UFC code is (at a certain level)
> >> > > >> simple:
> >> > > >>
> >> > > >>   ufc::* is generated by form compilers from ufl.* (equivalently
> >> > > >> ffc.*) once generated, ufc::* depends on nothing
> >> > > >>
> >> > > >> In DOLFIN (C++) it is also simple:
> >> > > >>
> >> > > >>   dolfin::* does not depend directly on ufl.* (equivalently
> >> > > >> ffc.*) dolfin::* depends on ufc::*
> >> > > >>
> >> > > >> PyDOLFIN could (should!) be kept simple. I've been using PyDOLFIN
> >> > > >> without the FFC-dependent multiple inheritance stuff all the time
> >> > > >> with the dolfin.cpp_* classes.
> >> > > >
> >> > > > Me too.
> >> > > >
> >> > > >> I believe that's also how it must be
> >> > > >> done if you have external UFC code, e.g. precompiled in a library
> >> > > >> or manually written.
> >> > > >
> >> > > > Agree.
> >> > > >
> >> > > >> So no matter what, it should at least be possible to avoid
> >> > > >> relying on the multiple inheritance and JIT in PyDOLFIN.
> >> > > >
> >> > > > and more or less have the possibilities we have today. I agree.
> >> > > >
> >> > > > Johan
> >> > >
> >> > > Seems at least we're on the same page here.
> >> >
> >> > I've been in meetings the whole day and it took me a while to get
> >> > through the discussion... :-)
> >> >
> >> > I agree that life would be easier if we did not need to worry about
> >> > integrating UFL, UFC, and DOLFIN, but I think we should. I don't think
> >> > a user should need to worry about the relations between all the
> >> > different flavours of for example the finite element abstractions. We
> >> > have quite a few:
> >> >
> >> >   ufl.FiniteElement
> >> >   ufc::finite_element
> >> >   dolfin::FiniteElement
> >> >   dolfin.FiniteElement
> >> >   FIAT.FiniteElement
> >> >   (ffc.FiniteElement)
> >> >   ...
> >> >
> >> > DOLFIN plays two roles here:
> >> >
> >> > 1. It functions as a user interface/problem-solving environment and as
> >> >    such should present a clean and simple interface where only one of
> >> > the above flavours should be visible.
> >> >
> >> > 2. It provides a general assembler, and as such should provide an
> >> >    assembly algorithm that just works with ufc:: types (+ mesh and
> >> >    linear algebra).
> >> >
> >> > So, we need to provide (1) for general use (and make it efficient) but
> >> > also allow (2) for experts.
> >> >
> >> > So far, I like Martin's idea with dynamic inheritance to solve (1).
> >>
> >> So you don't like the way we have it now, where a jit compiled function
> >> can't be used in a form statement? Or are you fine with it? If you are
> >> fine with it we do not need the dynamics inheritance.
> >>
> >> Johan
> >
> > I think that there should be an easier way to use jit-compiled
> > functions. The optimal would be if one could do something like
> >
> >  f = Function(V, cppstring)
>
> That should be possible to do with some Python tricks (metaclasses).

Isn't the above example only possible with an envelope design? I.e. you 
compile the string and get a cpp_function. 

A metaclass kicks in when you creates a python class. You could e.g.

  __metaclass__ = compiler_class_builder
  MyFunction(Function):
      cppstring = "whatever"

and the metaclass compiler_class_builder can compile the cppstring while 
createing the class, and the instantiation would be:

  f = MyFunction(V)

That still does not help us using that class for dynamical inheritance.

We could use:

  f = CompiledFunction(V,cppstring)

which resembles what we have today. Here CompiledFunction is a function not a 
class. Maybee still not what we want?

Johan


Follow ups

References