← Back to team overview

dolfin team mailing list archive

Re: PyDOLFIN interface

 

2008/11/4 Johan Hake <hake@xxxxxxxxx>:
> On Tuesday 04 November 2008 15:49:37 Martin Sandve Alnæs wrote:
>> 2008/11/4 Martin Sandve Alnæs <martinal@xxxxxxxxx>:
>> > 2008/11/4 Johan Hake <hake@xxxxxxxxx>:
>> >> On Tuesday 04 November 2008 13:59:22 Martin Sandve Alnæs wrote:
>> >>> 2008/11/4 Johan Hake <hake@xxxxxxxxx>:
>> >>> > On Tuesday 04 November 2008 13:07:07 Martin Sandve Alnæs wrote:
>> >>> >> 2008/11/4 Johan Hake <hake@xxxxxxxxx>:
>> >>> >> > Hello!
>> >>> >> >
>> >>> >> > I have started the work on the PyDOLFIN. We can now define the
>> >>> >> > forms in the poisson demo, using the syntax previously discussed,
>> >>> >> > see python poisson demo.
>> >>> >> >
>> >>> >> > A FunctionSpace now inherits both dolfin::FunctionSpace and
>> >>> >> > ffc.FiniteElement, and it can be used to instantiate user defined
>> >>> >> > Functions which can be used to define forms.
>> >>> >> >
>> >>> >> > We need to discuss how to implement a discrete function. This is a
>> >>> >> > bit complicated using the metaclass magic that is implemented now.
>> >>> >> > Now we cannot do:
>> >>> >> >
>> >>> >> >  u = Function(V)
>> >>> >> >  x = u.vector()
>> >>> >> >
>> >>> >> > as Function is just a dummy class for creation of userdefined
>> >>> >> > functions.
>> >>>
>> >>> I don't quite understand the problem here.
>> >>> Are you saying that type(u) is not a subclass of cpp_Function or what?
>> >>
>> >> With the metaclass implementation, Function cannot inherit cpp_Function
>> >> or ffc.Function. A derived class of Function will inherit Function,
>> >> cpp_Function and ffc.Function though. Function is as it is now, only an
>> >> more or less empty class that can be inherited.
>> >>
>> >> The syntax above can be valid with some __new__ magic in Function,
>> >> probably much in line with what you had in mind.
>> >>
>> >>> >> We don't have to use metaclasses, it would be enough to implement
>> >>> >> Function.__new__(cls, *args). This function can return objects of
>> >>> >> a different type, e.g. a compiled function that doesn't inherit from
>> >>> >> dolfin.Function but directly from dolfin::Function.
>> >>> >
>> >>> > and from ufl.Function too?
>> >>>
>> >>> Yes.
>> >>>
>> >>> >> (I didn't understand this stuff fully until last week...)
>> >>> >
>> >>> > Yes I have thought about that solution, but then the created class
>> >>> > wont be a Function. It can be handy to have a class that all python
>> >>> > Function can be checked if isinstance of. With the __new__ function
>> >>> > we create different classes.
>> >>>
>> >>> This is not a problem. On the contrary, you either wish to
>> >>> check if a function is a cpp_Function or a ufl.Function.
>> >>> I would consider "isinstance(f, dolfin.Function)" a bug in
>> >>> most circumstances. That's one of the things I don't
>> >>> like about this design...
>> >>
>> >> Yes, but if you check if u is a Function you allready know it is a
>> >> cpp_Function _and_ an ufl.Function. Just one to keep in mind ;)
>> >
>> > That's the bug right there. You (almost) never want to check for a
>> > dolfin.Function, because you're usually _either_ working with
>> > ufl.Function functionality _or_ working with cpp_Function functionality.
>> > Checking for dolfin.Function is then _wrong_, since a real ufl.Function
>> > or cpp_Function won't pass the test.
>
> Ok, you are right.
>
> Then I realise that we do not need the dolfin.Function class, other than to
> produce user defined functors, in python and compiled ones, and for
> instantiate a discrete function, where all of these functions also are
> ufl/ffc.Functions.

Unless dolfin.Function needs to store a reference to its FunctionSpace?

> We agree on this?

Depends.

> Then we need to deside how we will implement it, through metaclasses or
> __new__.
>
> For functors defined in python the syntax will be the same for both
> alternatives and the same as today, but they are instantiated by a
> FunctionSpace.
>
> For compiled functions this can either be done more or less as it is today:
>
>  function_list = compile_functions(["exp(alpha)",
>                     ("sin(x[0])", "cos(x[1])", "0.0"),
>                     (("sin(x[0])", "cos(x[1])"), ("0.0", "1.0"))],
>                     [V1, V2, V3])
>
>  f1, f2, f3 = tuple(function_list)
>
> or with metafunctions:
>
>  class MyFunction1(Function):
>      cppcode = "exp(alpha)"
>
>  class MyFunction2(Function):
>      cppcode = ("sin(x[0])", "cos(x[1])", "0.0")
>
>  class MyFunction3(Function):
>      cppcode = (("sin(x[0])", "cos(x[1])"), ("0.0", "1.0"))
>
>  f1 = MyFunction1(V1)
>  f2 = MyFunction2(V2)
>  f3 = MyFunction3(V3)
>
> The syntax for a discrete function would also be looking the same.

An alternative is of course simply

class MyFunction3(CompiledFunction):
    cppcode = (("sin(x[0])", "cos(x[1])"), ("0.0", "1.0"))
f3 = MyFunction3(V3)

where we get your nice metaclass syntax, but don't _have_ to combine
everything into one dolfin.Function if it proves too difficult.
And we can even keep compile_functions on top, for batch compiling.
(Note that we also have compile_subdomains, and could do something
similar there).

>> >>> > The advantage, as I see it with your suggestion would be that we can
>> >>> > use the present feature of compiling many functions at a time, but we
>> >>> > loose the consistent syntax:
>> >>> >
>> >>> >  class MyFunction(Function):
>> >>> >      def eval(v,x):
>> >>> >           do something
>> >>> >
>> >>> >  class MyCompiledFunction(Function):
>> >>> >      cpp_code = do something
>> >>> >
>> >>> >  f = MyFunction(V)
>> >>> >  g = MyCompiledFunction(V)
>> >>> >
>> >>> > and it could be complicated to pass the right FunctionSpaces to the
>> >>> > compile_function function.
>> >>>
>> >>> I forgot about this syntax. It's nice, but personally I'll be fine
>> >>> without it :-)
>> >>>
>> >>> >> > Is it possible to define a DiscreteFunction class in c++ (or just
>> >>> >> > in swig?) that inherits dolfin::Function, and in its constructor
>> >>> >> > calls vector()?
>> >>> >> >
>> >>> >> > Then we can use this class in python to create discrete functions.
>> >>> >> > We then avoid the director class that is created by swig for all
>> >>> >> > functions that inherits the cpp_Function. The obvious syntax would
>> >>> >> > then be
>> >>> >> >
>> >>> >> >  u = DiscreteFunction(V)
>> >>> >> >
>> >>> >> > in python. I think with some python magic we still can have the
>> >>> >> > syntax
>> >>> >> >
>> >>> >> >  u = Function(V)
>> >>> >> >
>> >>> >> > which would imply that a discrete function is created, but I
>> >>> >> > haven't implemented it.
>> >>> >>
>> >>> >> That would basically be duplicating the design that has been
>> >>> >> replaced... I think dropping the metaclass is a much easier
>> >>> >> solution.
>> >>> >
>> >>> > No, this is just for the python interface. This could come handy with
>> >>> > the __new__ implementation you want too. By this we circumvent the
>> >>> > director class that swig creates for dolfin::functions. We dont want
>> >>> > to call eval on such a class to often do we? :)
>> >>> >
>> >>> >> > We also have a problem with MixedElements. Now the FunctionSpace
>> >>> >> > inherits ffc.FiniteElement and a MixedElement is not a
>> >>> >> > FiniteElement. I suppose we could overload the __add__ operator
>> >>> >> > for the FunctionSpace together with a new class
>> >>> >> > MixedFunctionSpace, to fix this?
>> >>> >> >
>> >>> >> > Johan
>> >>> >>
>> >>> >> We also have (in UFL at least) the classes VectorElement and
>> >>> >> TensorElement, so this gets complicated. I think we should just make
>> >>> >> FunctionSpace own an element instead.
>> >>> >>
>> >>> >> element = FiniteElement(...)
>> >>> >> V = FunctionSpace(mesh, element)
>> >>> >> f = Function(V) # calls FiniteElement.__init__(self, element)
>> >>> >
>> >>> > Thats looks nice. We could also use the syntax Anders suggested,
>> >>> >
>> >>> >  V = FunctionSpace(mesh, "Lagrange", 1)
>> >>> >
>> >>> > and then instantiate the FiniteElement in the __init__ function.
>> >>>
>> >>> No, we can't, that's exactly the issue you brought up with
>> >>> MixedElement. ("Lagrange", 1) doesn't carry all information about an
>> >>> element. In UFL we have classes FiniteElement, MixedElement,
>> >>> VectorElement, and TensorElement. In addition to
>> >>> (family,domain,degree), a
>> >>> VectorElement can have a dim, and a TensorElement
>> >>> can have a shape and symmetries...
>> >>
>> >> Of course :P
>> >>
>> >> But would it be possible to create a MixedFunctionSpace by adding two
>> >> other function spaces, with some overloading of __add__? And would it be
>> >> possible to extend the initialization of FunctionSpace, with kwargs and
>> >> some __new__ magic, to instantiate FunctionSpace of different elements?
>> >
>> > If you do that, you suddenly get a hierarchy of FunctionSpace classes.
>> > My challenge to Anders would then be:
>> >
>> > V1 = TensorFunctionSpace(mesh, "CG", 2) # , shape=(2,2),
>> > symmetry=True) # optional args
>> > V2 = VectorFunctionSpace(mesh, "CG", 1) #, dim=2) # optional args
>> > V3 = FunctionSpace(mesh, "DG", 0)
>> > V = MixedFunctionSpace(V1, V2, V3)
>> >
>> > Here each of these will be a new cpp_FunctionSpace,
>> > calling jit and constructing dofmaps and whatnot.
>> > It seems possible, but...
>> >
>> >> Maybee more relevant do we want it?
>> >
>> > Anders does.
>
> :)
>
>> >>> > This wont work for the basis functions though, but we could just add
>> >>> > a class that inherits the ffc.BasisFunction and which can be
>> >>> > instantiated with both a FunctionSpace and a FiniteElement.
>> >>>
>> >>> We'd need to consider both BasisFunction, and BasisFunctions, which is
>> >>> a function and a bit more complicated. Also TestFunction(s) and
>> >>> TrialFunction(s), but those are just syntactic sugar.
>> >>
>> >> Ok.
>> >>
>> >>> I'd very much prefer letting BasisFunction be and just passing it an
>> >>> element.
>> >>
>> >> I see you point.
>> >
>> > In particular, what happens with
>> >    t, v, s = BasisFunctions(V)
>> > with V a mixed function space like above?
>> > That's getting quite difficult to follow.
>> >
>> >
>> > But my largest concern is that inheriting from ufl classes and
>> > overriding their behaviour may have unanticipated effects inside UFL.
>> > For example, operators like +-*/ can't be implemented on a
>> > dolfin.Function or cpp_Function.
>>
>> Speaking of which, does FunctionSpace and Function need to implement
>> __repr__? If so, why? It won't work anyway since it just uses the string
>> "mesh". In UFL I've used repr for quite a few things, and this would break
>> a lot.
>
> Yes I thought that could be problematic, but repr on the resulting form
> produced a "nice" representation, (it is eventually that is used by ffc?) so
> I thought I implemented a more verbal __repr__ and __str__, as the default
> repr was the swig generated one.

I didn't think about the swig-generated repr.

If repr is needed for the current ffc-based code to work for the time being,
that's fine of course.

It is my experience that following protocols are very important for well-formed
python programs, and repr has a very specific meaning.
If eval(repr(foo)) doesn't reproduce foo, it shouldn't be implemented.
str(foo) is for "nice".

> If we need ufl's repr we just change the order of the bases in the bases
> tuple, and _not_ implement a repr.

Ok!

-- 
Martin


Follow ups

References