← Back to team overview

dolfin team mailing list archive

Re: Function and DofMap

 

2008/9/9 Garth N. Wells <gnw20@xxxxxxxxx>:
>
>
> 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()) {}

When shared_V is deleted, its destructor will decrement its reference
counter. If it reaches 0, it would usually call "delete p;" where p is
the pointer it holds. But in this case, instead of calling "delete
p;", it will call upon the given deleter objects () operator like
"user_defined_deleter(p);", where user_defined_deleter is the object
of type NoDeleter created above.

This user-defined deleter object can also hold pointers to external
reference counters, and decrement those instead. Or simply do nothing
like here.

--
Martin


Follow ups

References