dolfin team mailing list archive
-
dolfin team
-
Mailing list archive
-
Message #10656
Re: PyDOLFIN interface
2008/11/5 Johan Hake <johan.hake@xxxxxxxxx>:
> 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
Not a problem, since my point was that we _shouldn't_ implement repr
for these. :-)
We definitely don't need repr for mesh.
--
Martin
References