← Back to team overview

dolfin team mailing list archive

Re: Work on a new function.py

 

On Friday 24 October 2008 11:22:51 Martin Sandve Alnæs wrote:
> 2008/10/24 Johan Hake <hake@xxxxxxxxx>:
> > On Friday 24 October 2008 10:42:23 Martin Sandve Alnæs wrote:
> >> 2008/10/23 Johan Hake <hake@xxxxxxxxx>:
> >> > Hello!
> >> >
> >> > I have worked on a "new" function.py. Please have a look. It is built
> >> > around a metaclass for Functions. It makes it "easy" to create both a)
> >> > userdefined python functions and b) compiled functions.
> >> >
> >> > The work flow is the same for both cases:
> >> >
> >> >  1) Define a class
> >> >  2) Instantiate it with a FunctionSpace, and any user specific
> >> > arguments
> >> >
> >> > With the suggested design a user can define classes a la:
> >> >
> >> >    class MyCustomFunction(Function):
> >> >        def __init__(self,V,dummy):
> >> >            # Note no call to Function.__init__!
> >> >            self.dummy = dummy
> >> >
> >> >        def eval(self,val,x):
> >> >            val[0] = 0.5
> >> >            val[1] = 0.5
> >> >
> >> >        def dim(self):
> >> >            return 2
> >> >
> >> >        def rank(self):
> >> >            return 1
> >> >
> >> >    # Can also declare dim and rank as int attributes, see below
> >> >
> >> >    class MyCustomCompiledFunction(Function):
> >> >        cppcode = "Jada"
> >> >        dim  = 1
> >> >        rank = 0
> >> >
> >> > They are then instantiated by:
> >> >
> >> >    mesh = UnitSquare(10,10)
> >> >    element = FiniteElement('Lagrange','triangle',1)
> >> >
> >> >    print ""
> >> >    V = FunctionSpace(mesh,element)
> >> >
> >> >    print ""
> >> >    f0 = MyCustomFunction(V,"Dummy")
> >> >
> >> >    print ""
> >> >    f1 = MyCustomCompiledFunction(V)
> >> >
> >> >    try:
> >> >        # Cannot instantiate the Function class
> >> >        f2 = Function(V)
> >> >    except Exception, e:
> >> >        print e
> >> >
> >> > With this interface only one user defined function can be compiled at
> >> > a time, but we will gain in similare workflow between pure python and
> >> > compiled functions.
> >> >
> >> > I realize that the C++ design must be in place first before we can
> >> > advance, but this is a proof of principle, that we can use metaclasses
> >> > to do nice stuff for the end user.
> >> >
> >> > Johan
> >>
> >> A possible improvement: deducting dim and rank from cppcode like
> >> compile_functions does today (can probably just reuse the code).
> >
> > I had that in mind. But a common error for user defined functions is to
> > not define the dim and rank, producing nasty errors. My thought was to
> > force the user to define these, at least in the python interface. Then we
> > can choose to not force them to be set in the compiled interface, but
> > forcing the user to do it make the interfaces consistent.
>
> I think forcing the user to set them when we can't deduct them
> and give good error messages is the best of both worlds.
>
> >> Also, compile_functions extracts variable names from the strings.
> >> Then we can do just:
> >>
> >>     class MyFunc(Function):
> >>         cppcode = ("t*sin(x[0]", "cos(x[1])", "x[0]*x[2]")
> >>
> >>     f = MyFunc(V)
> >>     f.t = 0.0
> >
> > That's right. We could also extract them from the cppcode before we send
> > them to the code compiler, createing an __init__ function on the fly,
> > which the code already do, so we will get:
> >
> >     class MyFunc(Function):
> >         cppcode = ("t*sin(x[0]", "cos(x[1])", "x[0]*x[2]")
> >
> >     f = MyFunc(V,0.0)
>
> Perhaps better to require keyword arguments for those:
>
>     class MyFunc(Function):
>         cppcode = ("t*sin(x[0]", "cos(x[1])", "x[0]*x[2]")
>
>     f = MyFunc(V, t = 0.0)

Agree

> > Maybe this would be a bit too magic? This is only possible when the
> > cppcode is given in the nice tuple representation you already have
> > implemented. This will be a bit more cumbersome when a whole code segment
> > is handed, but probaby doable with the right regexp? If not we could add
> > an extra attribute, 'args' which take a list of strings that are added to
> > the created __init__ function and then passed to the compiled
> > cpp_function.
>
> What do you mean by "whole code segment"? Implementation of eval?
> Or code for the whole class? We could check for either "cppcode"
> or "evalcode" to have a distinction between the two. In the latter case,
> the constructor arguments can be found with a regexp.
>
>
>     class MyFunc(Function):
>         evalcode = ("t*sin(x[0]", "cos(x[1])", "x[0]*x[2]")
>
>     f = MyFunc(V, t = 0.0)
>
>
>     class MyFunc(Function):
>         cppcode = """
>                 class MyFunc(Function)
>                 {
>                     MyFunc(FunctionSpace & V, MeshFunction & mf):
>                         Function(V), mf(mf) {}
>                     void eval(...) {}
>                 };"""
>
>     f = MyFunc(V, mf = my_mesh_function)

This looks nice.

> _However_, the big question is whether SWIG directors will
> kick in and destroy performance or not with this code.
> As long as we inherit cpp_Function in any Python base class
> of MyCompiledFunction, I'm not sure if this will work out.

Yes I am a bit worried about this too. I have a gut feeling that you are right 
in you worries. If so, we probably have to go for the envelope design? 

We already have the mulitple inheritance for special functions, ala MeshSize. 
Do they suffer in performance?

> From function.py:
>
>     f1 = MyCustomCompiledFunction(V)
>     print "f1 bases:"
>     print f1.__class__.__bases__
>
> f1 bases:
> (<class '__main__.Function'>, <class '__main__.custom_cpp_Function'>,
> <class ffc.compiler.language.algebra.Function at 0x83ce77c>)

Yes, isn't it nice!

Johan


References