← Back to team overview

dolfin team mailing list archive

Re: Work on a new function.py

 

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)

> 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)

_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.

>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>)

--
Martin


Follow ups

References