← Back to team overview

dolfin team mailing list archive

Re: PyDOLFIN interface

 

On Tuesday 04 November 2008 23:23:55 Martin Sandve Alnæs wrote:
> 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?

Yes, and this is done either way. What i ment was that I thought we did not 
need to instantiate it without "subclassing" it, like:

  f = Function(V)

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

This is a nice alternative. Then it is more explicit what the user define.

> where we get your nice metaclass syntax, but don't _have_ to combine
> everything into one dolfin.Function if it proves too difficult.

It is actually very doable :)

> And we can even keep compile_functions on top, for batch compiling.

How would this be done?

> (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".

I agree that we should try to follow the protocol. 

It is probably the mesh that is the most difficult to repr. If we want that we 
can extend the wrapper code for the built in meshes with a repr, and store 
the file name of a mesh and use that in a repr for mesh that come from files. 
These will probably cover most bases but there will be other meshes that we 
cant repr, e.g., mesh that has been changed after creation.

It will probably be notoriously difficult to follow this protocol. How can we 
repr a mesh? It won't be difficult to fix the built in meshes, but meshes

Johan


Follow ups

References