Anders Logg wrote:
> On Tue, Sep 09, 2008 at 04:31:41PM +0200, Martin Sandve Alnæs wrote:
>> 2008/9/9 Garth N. Wells <[EMAIL PROTECTED]>:
>>>
>>> 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 <[EMAIL PROTECTED]>:
>>>>>>>>>> On Mon, Sep 08, 2008 at 11:12:14AM +0200, Martin Sandve Alnæs wrote:
>>>>>>>>>>> 2008/9/8 Johan Hoffman <[EMAIL PROTECTED]>:
>>>>>>>>>>>>> 2008/9/8 Dag Lindbo <[EMAIL PROTECTED]>:
>>>>>>>>>>>>>> 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
>
>
> ------------------------------------------------------------------------
>
> _______________________________________________
> DOLFIN-dev mailing list
> [email protected]
> http://www.fenics.org/mailman/listinfo/dolfin-dev
_______________________________________________
DOLFIN-dev mailing list
[email protected]
http://www.fenics.org/mailman/listinfo/dolfin-dev