← Back to team overview

dolfin team mailing list archive

Re: PyDOLFIN interface

 

On Wed, Nov 05, 2008 at 08:01:06AM +0100, Martin Sandve Alnæs wrote:
> 2008/11/5 Anders Logg <logg@xxxxxxxxx>:
> > On Wed, Nov 05, 2008 at 12:35:03AM +0100, Martin Sandve Alnæs wrote:
> >> 2008/11/4 Anders Logg <logg@xxxxxxxxx>:
> >> > On Tue, Nov 04, 2008 at 11:23:55PM +0100, 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?
> >> >>
> >> >> > 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".
> >> >
> >> > __repr__ is used instead of __str__ in FFC since I found out at some
> >> > point that __repr__ is called rather than __str__ when printing lists
> >> > or tuples of objects (the reason we have lstr etc in UFL). Anyway, I
> >> > don't think this is really used in FFC other than for pretty-printing
> >> > so we shouldn't need to worry about breaking anything.
> >>
> >> Ok.
> >>
> >> > Concerning the design, would it be possible to use is-a relationship
> >> > to form-compiler classes and has-a to DOLFIN classes for functions and
> >> > basis functions?
> >> >
> >> >  class Function(ufl_Function):
> >> >
> >> >      def __init__(self, V):
> >> >          self.cpp_function = cpp_Function(V)
> >> >          ufl_Function.__init__(self, V.element())
> >> >
> >> > Then the Function will behave mostly as a ufl.Function (which is the
> >> > complicated part) and it will own a dolfin::Function instance which
> >> > can be used in the few places we need it:
> >> >
> >> >  def plot(v):
> >> >      viper.plot(v.cpp_function)
> >> >
> >> >  def assemble(...):
> >> >      use v.cpp_function
> >> >
> >> >  saving to file
> >>
> >> That's an extremely simplified world view. There will be lots of checks and
> >> special-cases all over. Lots of application code will expect a cpp_Function,
> >> and this would mess up all that code. External codes like PyCC depends
> >> on subclassing cpp_Function in C++ and won't work with this. ++++
> >
> > I don't agree. DOLFIN::Function only does a few things that may be of
> > interest in Python. We just need to implement a few member functions
> > in dolfin.Function to make it work:
> >
> >  def vector(self):
> >      return self._cpp_function.vector()
> >
> >  etc.
> >
> > The functions that may be of interest are maybe just vector() and
> > function_space(). Accessing interpolate() etc is of limited interest
> > in Python.
> 
> That's beside my main point here. What I mean is that the
> Function-is-a-cpp_Function
> relationship is important because otherwise the user will have to
> fetch the ._cpp_function
> manually each time a cpp_Function is expected in non-dolfin code.
> That's very ugly.

Who would want to write Python code that expects a cpp_Function?
And why?

If we implement a nice Python interface with a class Function then
users should only worry about dolfin.Function, not
dolfin._cpp_function.

-- 
Anders

Attachment: signature.asc
Description: Digital signature


Follow ups

References