← Back to team overview

dolfin team mailing list archive

Re: Function and DofMap

 

2008/9/9 Anders Logg <logg@xxxxxxxxx>:
> On Tue, Sep 09, 2008 at 03:47:10PM +0100, Garth N. Wells wrote:
>>
>>
>> Anders Logg wrote:
>> > On Tue, Sep 09, 2008 at 04:31:41PM +0200, Martin Sandve Alnæs wrote:
>> >> 2008/9/9 Garth N. Wells <gnw20@xxxxxxxxx>:
>> >>>
>> >>> Anders Logg wrote:
>> >>>> On Tue, Sep 09, 2008 at 11:30:26AM +0100, Garth N. Wells wrote:
>> >>>>> Anders Logg wrote:
>> >>>>>> On Tue, Sep 09, 2008 at 09:06:00AM +0200, Anders Logg wrote:
>> >>>>>>> On Mon, Sep 08, 2008 at 11:53:09PM +0200, Johan Hake wrote:
>> >>>>>>>> On Monday 08 September 2008 21:45:27 Martin Sandve Alnæs wrote:
>> >>>>>>>>> 2008/9/8 Anders Logg <logg@xxxxxxxxx>:
>> >>>>>>>>>> On Mon, Sep 08, 2008 at 11:12:14AM +0200, Martin Sandve Alnæs wrote:
>> >>>>>>>>>>> 2008/9/8 Johan Hoffman <jhoffman@xxxxxxxxxx>:
>> >>>>>>>>>>>>> 2008/9/8 Dag Lindbo <dag@xxxxxxxxxx>:
>> >>>>>>>>>>>>>> Anders Logg wrote:
>> >>>>>>>>>>>>>>> There seems to be a problem (among many) with the current design of
>> >>>>>>>>>>>>>>> the Function classes (see thread "evaluating higher order mesh
>> >>>>>>>>>>>>>>> function").
>> >>>>>>>>>>>>>>>
>> >>>>>>>>>>>>>>> In particular, the finite element is missing in DiscreteFunction.
>> >>>>>>>>>>>>>>> My suggestion would be to just add it and let a DiscreteFunction
>> >>>>>>>>>>>>>>> consist of the following four items which are always available:
>> >>>>>>>>>>>>>>>
>> >>>>>>>>>>>>>>>   mesh, x, dof_map, finite_element
>> >>>>>>>>>>>>>>>
>> >>>>>>>>>>>>>>> Is this enough, and what other issues to we need to fix?
>> >>>>>>>>>>>>>> I'm not sure I agree that the dof map and finite element should be
>> >>>>>>>>>>>>>> owned by the discrete function. There was a great suggestion from
>> >>>>>>>>>>>>>> Martin, in a thread "Abstraction idea" from 06/05/2008, to create a
>> >>>>>>>>>>>>>> class FunctionSpace where the mesh, element and dof_map(s) are
>> >>>>>>>>>>>>>> aggregated. Citing Martin:
>> >>>>>>>>>>>>>> U = FunctionSpace(mesh, dofmapset, form, 0) # or something similar
>> >>>>>>>>>>>>>> u = Function(U)
>> >>>>>>>>>>>>>> v = Function(U)
>> >>>>>>>>>>>>>>
>> >>>>>>>>>>>>>> This seems a solid approach to me since it would provide a way of
>> >>>>>>>>>>>>>> encapsulating the mathematical formulation of the problem, which is
>> >>>>>>>>>>>>>> more or less const and likely to be reused by many discrete
>> >>>>>>>>>>>>>> functions in a solver.
>> >>>>>>>>>>>>>>
>> >>>>>>>>>>>>>> It seems to me that there is an obvious risk that a lot of redundant
>> >>>>>>>>>>>>>> initialization would occur if all discrete functions should own
>> >>>>>>>>>>>>>> their own elements and dof maps. There seems to be consensus that
>> >>>>>>>>>>>>>> the mesh should be "global" for efficiency reasons, so why not treat
>> >>>>>>>>>>>>>> the function space the same way?
>> >>>>>>>>>>>>>>
>> >>>>>>>>>>>>>> Is there a problem with an approach where the funciton _always_ owns
>> >>>>>>>>>>>>>> the vector and _never_ owns the function space (and mesh)? A very
>> >>>>>>>>>>>>>> strict design would avoid shared/smart pointers, provide a
>> >>>>>>>>>>>>>> comprehensible user interface and probably help the parallellization
>> >>>>>>>>>>>>>> effort.
>> >>>>>>>>>>>>>>
>> >>>>>>>>>>>>>> /Dag
>> >>>>>>>>>>>>> If the Function always owns the vector, there are cases you'll have
>> >>>>>>>>>>>>> to make unneccessary copies of a vector, in particular such scenarios
>> >>>>>>>>>>>>> may occur when trying to combine dolfin with something else.
>> >>>>>>>>>>>>>
>> >>>>>>>>>>>>> If the Function never owns the function space, it must always be
>> >>>>>>>>>>>>> constructed explicitly by the user. This may not be a bad thing.
>> >>>>>>>>>>>>> However, if the Function is loaded from a file, nobody owns the
>> >>>>>>>>>>>>> FunctionSpace.
>> >>>>>>>>>>>> Conceptually, I agree with Dag (and Martin?) that it is natural to
>> >>>>>>>>>>>> have global function spaces. And if the explicit construction of such
>> >>>>>>>>>>>> spaces can be made simple, it may not be a bad thing but a natural
>> >>>>>>>>>>>> part in setting up the mathematical problem. And I do not really like
>> >>>>>>>>>>>> that functions should be initialized from a form, which defines an
>> >>>>>>>>>>>> equation.
>> >>>>>>>>>>>>
>> >>>>>>>>>>>> I think one idea was to not force less mathematically oriented users
>> >>>>>>>>>>>> to worry about function spaces. I guess there are (at least) 2 types
>> >>>>>>>>>>>> of functions: (i) functions part of the form, and (ii) functions not
>> >>>>>>>>>>>> part of the form, but used in pre/postprocessing etc.
>> >>>>>>>>>>>>
>> >>>>>>>>>>>> For (i) it may be natural to construct the function space from the
>> >>>>>>>>>>>> form, and for (ii) it may be convenient in some cases, but it is not
>> >>>>>>>>>>>> really obvious that this is the best solution.
>> >>>>>>>>>>>>
>> >>>>>>>>>>>> Maybe an explicit construction of a function space can come with a
>> >>>>>>>>>>>> default, such as a nodal basis of piecewise linears?
>> >>>>>>>>>>>>
>> >>>>>>>>>>>> /Johan
>> >>>>>>>>>>> So:
>> >>>>>>>>>>>   FunctionSpace V(mesh);
>> >>>>>>>>>>>   Function f(V);
>> >>>>>>>>>>> gives a function f on piecewise linears?
>> >>>>>>>>>>> That's ok with me.
>> >>>>>>>>>>>
>> >>>>>>>>>>>
>> >>>>>>>>>>> About ownership, I think the only both robust and intuitive solution
>> >>>>>>>>>>> is that an object never should store a reference (or regular pointer)
>> >>>>>>>>>>> to another object. But as long as we are aware of the cost of doing
>> >>>>>>>>>>> this and state it clearly in the documentation, I'm ok with keeping
>> >>>>>>>>>>> e.g. the Mesh references like we do.
>> >>>>>>>>>>>
>> >>>>>>>>>>> Any time object A stores a reference to object B, the user must
>> >>>>>>>>>>> take care that the lifetime of B exceeds the lifetime of A. There
>> >>>>>>>>>>> are no exceptions to this. This puts some real limitations on the
>> >>>>>>>>>>> way the user must structure his program, e.g. he must sometimes
>> >>>>>>>>>>> (often?) keep objects around longer than they're explicitly needed.
>> >>>>>>>>>>>
>> >>>>>>>>>>> This may be a good thing, since it forces the user to think about
>> >>>>>>>>>>> dependencies and object lifetimes, and the objects in question
>> >>>>>>>>>>> use some memory.
>> >>>>>>>>>> I think this is ok. There are many ways to create a segfault in C++.
>> >>>>>>>>>> If you program in C++, you will have to think about memory.
>> >>>>>>>>>>
>> >>>>>>>>>>> But if we use references instead of shared_ptr,
>> >>>>>>>>>>> we should never have default values:
>> >>>>>>>>>>> - A Function has a reference to a Mesh, which is ok since
>> >>>>>>>>>>>   it's always created outside.
>> >>>>>>>>>>> - If a DiscreteFunction is to have a reference to a Vector, or a
>> >>>>>>>>>>>   Function is to have a reference to a FunctionSpace, it cannot
>> >>>>>>>>>>>   create its own without adding memory management code.
>> >>>>>>>>>>>
>> >>>>>>>>>>> Every place we accept these limitations and requirements of how
>> >>>>>>>>>>> the user structures his programs, we can use references and be
>> >>>>>>>>>>> done with it. But don't think that the pretty syntax means the user
>> >>>>>>>>>>> doesn't have to think about memory management, since all the
>> >>>>>>>>>>> responsibility for memory management (object destruction order)
>> >>>>>>>>>>> is in fact placed on the user, and errors from the users side will
>> >>>>>>>>>>> lead to invalid references we cannot detect and segfaults.
>> >>>>>>>>>> I want the syntax to be simple and pretty, but I don't necessarily
>> >>>>>>>>>> want to hide the user from problems that are part of the design of
>> >>>>>>>>>> C++. It isn't Python or Java. You should be expected to know what
>> >>>>>>>>>> you are doing. :-)
>> >>>>>>>>> It's not only about knowing what you're doing. It forces very
>> >>>>>>>>> hard restrictions on the design/flow of your program, which can
>> >>>>>>>>>
>> >>>>>>>>> 1) Be a major source of bugs in nontrivial apps (see Garths email), which
>> >>>>>>>>> are not locally visible because they depend on the global program flow.
>> >>>>>>>>>
>> >>>>>>>>> 2) Make it impossible to initialize e.g. Function in e.g a file reader,
>> >>>>>>>>> since the caller of the file reader would need to get the objects
>> >>>>>>>>> Function depends on. This is not limited to file readers, but is
>> >>>>>>>>> a recurring pattern in nontrivial apps.
>> >>>>>>>>>
>> >>>>>>>>> If we want to use dolfin or want dolfin to be used in apps that
>> >>>>>>>>> are more complicated than the traditional "read input, compute
>> >>>>>>>>> something, output something" app, these restrictions become
>> >>>>>>>>> a larger problem.
>> >>>>>>>>>
>> >>>>>>>>>> Anyway, I like the idea about having a FunctionSpace class which
>> >>>>>>>>>> several Functions may share. The problem we need to solve is
>> >>>>>>>>>> reading from file:
>> >>>>>>>>>>
>> >>>>>>>>>>  FunctionSpace V(mesh);
>> >>>>>>>>>>  Function u(V);
>> >>>>>>>>>>  file >> u;
>> >>>>>>>>>>
>> >>>>>>>>>> The last line just fills out the data in both u and V.
>> >>>>>>>>>>
>> >>>>>>>>>> This will lead to side effects as V might be changed when doing
>> >>>>>>>>>>
>> >>>>>>>>>>  FunctionSpace V(mesh);
>> >>>>>>>>>>  Function u(V);
>> >>>>>>>>>>  Function v(V);
>> >>>>>>>>>>  file >> u;
>> >>>>>>>>>>
>> >>>>>>>>>> V will be changed, both for u and v. In fact, the mesh will also be
>> >>>>>>>>>> changed.
>> >>>>>>>>>>
>> >>>>>>>>>> The best thing would be if we could do
>> >>>>>>>>>>
>> >>>>>>>>>>  file >> (mesh, V, u);
>> >>>>>>>>> This is _exactly_ the kind of issue that smart pointers solve.
>> >>>>>>>>>
>> >>>>>>>>> Btw, I tried to search the swig documentation for shared_ptr, and
>> >>>>>>>>> found nothing...
>> >>>>>>>>> I don't know what exactly they mean by "shared_ptr support".
>> >>>>>>>> It seems to be a set of typemaps that should kick in at the "right places".
>> >>>>>>>> They are defined in
>> >>>>>>>>
>> >>>>>>>>    <Lib/python/boost_shared_ptr.i>
>> >>>>>>>>
>> >>>>>>>> and used very rudimentary in
>> >>>>>>>>
>> >>>>>>>>   <Examples/test_suite/li_boost_shared_ptr.i>
>> >>>>>>>>
>> >>>>>>>> in the source tree of the 1.3.36 release. It seems that it is not specific for
>> >>>>>>>> boost::share_ptr but should also figure out tr1::shared_ptr
>> >>>>>>>>
>> >>>>>>>> I think:
>> >>>>>>>>
>> >>>>>>>>   %include <boost_shared_ptr.i>
>> >>>>>>>>
>> >>>>>>>> at the appropriate place should do the trick.
>> >>>>>>>>
>> >>>>>>>> Johan
>> >>>>>>> I was about to start sketching on a FunctionSpace class when I
>> >>>>>>> realized that this class might look different for different types of
>> >>>>>>> Functions.
>> >>>>>>>
>> >>>>>>> In particular, some functions (DiscreteFunctions) need to have a
>> >>>>>>> dof map and a finite element, while this is not needed for other
>> >>>>>>> functions.
>> >>>>>>>
>> >>>>>>> This means that we might need to duplicate the hierarchy of different
>> >>>>>>> function classes in a number of different function space classes.
>> >>>>>>>
>> >>>>>>> I see two other options:
>> >>>>>>>
>> >>>>>>> 1. Require that a function space consists of
>> >>>>>>>
>> >>>>>>>   mesh, finite_element, dof_map
>> >>>>>>>
>> >>>>>>> and pick a suitable (trivial) dof map for example constant functions.
>> >>>>>>>
>> >>>>>>> 2. Implement a number of different function space classes but have a
>> >>>>>>> single Function class which holds data but where each function call
>> >>>>>>> is passed on to the specific type of function call in the
>> >>>>>>> corresponding function space class.
>> >>>>>> Here are some more thoughts about the shared_ptr issue.
>> >>>>>>
>> >>>>>> As I understand it, using shared_ptr to store the data in
>> >>>>>> DiscreteFunction means that users will need to use shared_ptr.
>> >>>>>> In particular, a Function must be initialized with a shared_ptr to a
>> >>>>>> Mesh, not a reference to a Mesh. Then in all demos, we would have to
>> >>>>>> change from
>> >>>>>>
>> >>>>>>   Mesh mesh("mesh.xml");
>> >>>>>>
>> >>>>>> to
>> >>>>>>
>> >>>>>>   shared_ptr<Mesh> mesh = new Mesh("mesh.xml");
>> >>>>>>
>> >>>>>> This doesn't look very pretty, and it would make the interface
>> >>>>>> inconsistent. One would need to know that some classes likes Mesh
>> >>>>>> should "always" be stored in a shared_ptr (since it will most
>> >>>>>> certainly be used to initialize a Function), while other classes like
>> >>>>>> Matrix, File, KrylovSolver, can be created as usual.
>> >>>>>>
>> >>>>>> There seem to be two options:
>> >>>>>>
>> >>>>>> 1. Don't use shared_ptr anywhere (at least not in public interfaces)
>> >>>>>>
>> >>>>>> If we choose this option, we will need to decide who owns what. For
>> >>>>>> example, we could say that a Function always owns the Vector, but
>> >>>>>> never the Mesh.
>> >>>>>>
>> >>>>>> This works out also for reading from files:
>> >>>>>>
>> >>>>>>   Mesh mesh;
>> >>>>>>   Function u(mesh);
>> >>>>>>   file >> u;
>> >>>>>>
>> >>>>>> This would lead to side effects (the mesh will be changed), but that
>> >>>>>> might be ok.
>> >>>>>>
>> >>>>>> 2. Use shared_ptr everywhere
>> >>>>>>
>> >>>>>> If we choose this option, we need to redefine Mesh so that it is not a
>> >>>>>> mesh class, but a shared_ptr for a mesh class (which we rename to
>> >>>>>> something else). Sundance does something like this and it gives a nice
>> >>>>>> and pretty syntax:
>> >>>>>>
>> >>>>>>   Mesh mesh = new Mesh();             // no need to delete
>> >>>>>>   Function u = new Function(mesh);    // no need to delete
>> >>>>>>
>> >>>>>> Essentially, it would look like Java. See here:
>> >>>>>>
>> >>>>>>   http://software.sandia.gov/sundance/basics.html
>> >>>>>>
>> >>>>>> in the section "Handles and Memory Management".
>> >>>>>>
>> >>>>>> I think we need to pick one of these options and not a mix since it
>> >>>>>> would make the interface inconsistent.
>> >>>>>>
>> >>>>>> Opinions? 1 or 2?
>> >>>>>>
>> >>>>> I don't think that mixing is inconsistent. At the moment we use a
>> >>>>> mixture of references and pointers, and there is usually a logical
>> >>>>> choice. Most users never need to work with the pointers. If we use smart
>> >>>>> pointers, they will be used primarily internally in place of where we
>> >>>>> currently use plain pointers.
>> >>>>>
>> >>>>> For the specific case of a Function, part of the issue will be resolved
>> >>>>> by requiring that a Function own it's own vector (forgetting about
>> >>>>> sub-functions for the time being). Ownership of a finite element, or
>> >>>>> possibly a FunctionSpace in the future, remains an issue because it's
>> >>>>> natural that these are shared.
>> >>>>>
>> >>>>> If you look at uBLASVector which now uses a smart pointer, not a single
>> >>>>> piece of code outside uBLASVector had to be changed, but an advanced
>> >>>>> user can now create a uBLASVector from a uBLAS object and not worry
>> >>>>> about it going out of scope,
>> >>>>>
>> >>>>> Garth
>> >>>> I'm all for it if it can be hidden (or visible only by choice), but
>> >>>> isn't it the case that the mesh (or function space) will always have
>> >>>> to be a shared_ptr?
>> >>>>
>> >>>> In all applications, one will need to write
>> >>>>
>> >>>>   shared_ptr<Mesh> mesh = new Mesh(...);
>> >>>>
>> >>> I think that it's logical for a Function to have a reference to a Mesh.
>> >>> I'm not sure exactly the nicest way to deal with a FunctionSpace. If we
>> >>> want pretty syntax + plus smart pointer flexibility, a Function could
>> >>> have a pointer to the FunctionSpace, in which case it *does not* own the
>> >>> FunctionSpace, and a smart pointer in which case it may or may not  own
>> >>> the FunctionSpace.
>> >>>
>> >>> The class Function would look like
>> >>>
>> >>>   class Function
>> >>>   {
>> >>>     public:
>> >>>
>> >>>       Function(FunctionSpace& space) : V(space) {}
>> >>>
>> >>>       Function(shared_prt<FunctionSpace> space) : V(0), shared_V(space)
>> >>>         {}
>> >>>
>> >>>     private:
>> >>>       FunctionSpace* V;
>> >>>       shared_ptr<FunctionSpace>  shared_V;
>> >>>   }
>> >>>
>> >>> and a Function could be initialised by
>> >>>
>> >>>   FunctionSpace V;
>> >>>   Function u(V);
>> >>>
>> >>> in which case it does not own the FunctionSpace, or by
>> >>>
>> >>>   shared_ptr<FunctionSpace> V(new FunctionSpace);
>> >>>   Function u(V);
>> >>>
>> >>> in which case it may or may not own the FunctionSpace. The former would
>> >>> be used in simple programs, and the latter in, for example,
>> >>> LinearPDE::solve.
>> >>>
>> >>> Garth
>> >>>
>> >>>> ?
>> >> Sounds a bit dangerous to me... It does enable sharing objects
>> >> if you want to, but keeps the possibility to shoot of your foot.
>> >>
>> >> Anyway, a little tip: shared_ptr can take a deleter policy as input
>> >> to its constructor, so you don't need the extra FunctionSpace* to
>> >> implement this approach. Something like this should do the trick:
>> >>
>> >>       class NoDeleter {
>> >>         public:
>> >>           void operator() (FunctionSpace *p) {}
>> >>       };
>> >>
>>
>> I don't follow this.
>>
>> >> ...
>> >>
>> >>       Function(FunctionSpace& space) : shared_V(&space, NoDeleter()) {}
>> >>
>> >> (not tested, but modified from some working code).
>> >>
>> >>
>> >> And by the way, a thing to look out for is when using shared_ptr
>> >> for array pointers. Then you need to use a deleter policy like above,
>> >> except with function body { delete [] p; } instead.
>> >
>> > This sounds like the trick I was asking for earlier: a way to provide
>> > two different ways to initialize objects, either passing a reference
>> > or a shared_ptr (something like decrease_count).
>> >
>> > Do we want to do it this way?
>> >
>>
>> It's not ideal in terms of safety, but it does provide what we want  -
>> simplicity and flexibility.
>>
>> Garth
>
> ok, so yes? Martin?
>
> --
> Anders

As Garth says. This allows the user to decide whether to shoot away
his foot or not. :-/

Also, this won't break the python interface since we
can continue to use references as before.

--
Martin


Follow ups

References