Matt,
Very nice.
Barry
Of course you did ignore the obvious statement that we should have written
PETSc in Objective C, instead of inventing our own pseudo Objective C like
beast :-) Objective C is way cool and is the only way to program the fastest
growing market for applications, iOS :-)
On Sep 8, 2012, at 7:19 AM, Matthew Knepley <knepley at gmail.com> wrote:
> On Sat, Sep 8, 2012 at 5:39 AM, Barry Smith <bsmith at mcs.anl.gov> wrote:
>
> Some discussion came up today about the use of "factories" in C++ (and
> other OO languages) code and I thought it would be useful to related it to
> how PETSc handles the same issue since some numerical libraries (the next
> generation of Trilinos's ML and Chombo) are using factories extensively and
> recklessly (and will, I am confident, alienate a lot of users. I love it when
> our competitors make the mistake of chasing every new idea down the wrong
> road).
>
> "In object-oriented computer programming, a factory is an object for
> creating other objects ", "it deals with the problem of creating objects
> (products) without specifying the exact class of object that will be
> created." http://en.wikipedia.org/wiki/Factory_method_pattern
> http://en.wikipedia.org/wiki/Factory_(software_concept)
>
> So for example if in my code I need a KSP solver object I could do
> something like
>
> void myroutine( ?..) {
> KSPObject *kspobject = new KSPGMRESObject
>
> where KSPObject is my abstract class and KSPGMRESObject is a specific
> implementation some one has written. Now in the code I can go off and use
> the KSPObject to solve something and the rest of my code does not need to
> "know" that the KSPObject is actually a KSPGMRESObject. But since when I
> create the object I cannot create an abstract class and can only create a
> specific implementation my code has now hardwired the KSPGMRESObject.
> Factories are a way of "avoiding" this hardwiring. I can introduce a new
> class KSPObjectFactory that has a method newKSPObject() and then reorganize
> my code as
>
> void myroutine(KSPObjectFactory *factory, ?.) {
> KSPObject *kspobject = factor->newKSPObject();
>
> and I've removed the explicit use of a particular implementation
> constructor from my routine. The actual decision of what type of KSPObject
> to create is "pushed up higher in the code" and involves the factory. So
> for example I could have a KSPObjectFactory() that produces a particular
> implementation of a KSPObject by setting a string name into the factory. So
> if BarrysKSPObjectFactory is a particular implementation of KSPObjectFactory
> then I could write "higher up in my code"
>
> BarrysKSPObjectFactory *kspobjectfactory = new BarrysKSPObjectFactory;
> kspobjectfactory->setimplementationbyname("gmres");
> myroutine(kspobjectfactory);
>
> one could also consider a method on BarrysKSPObjectFactory
> setimplementationbyCommandLineArgs(argsc,args); now I can "push up" the
> decision of what KSPObject to actually use to runtime as a command option.
>
> A drawback to factories is that it can easily double the number of
> different classes that users have to deal with and many people (at least Mark
> and I) find it cumbersome.
>
> PETSc factories 101
> ----------------------------
>
> In PETSc because all objects are essentially delegator objects ("the
> delegation pattern is a design pattern in object-oriented programming where
> an object, instead of performing one of its stated tasks, delegates that task
> to an associated helper object"
> http://en.wikipedia.org/wiki/Delegation_pattern) when we "create" a PETSc
> object we have not yet actually created the delegated object and thus do not
> need traditional factories for the purpose listed above. For example
>
> KSP ksp;
> KSPCreate(comm,&ksp);
>
> gives me a KSP solver object that I can pass around to other code, keep
> references to, and even set options on but it does not have a specific
> implementation of a solver wired to it yet. When I call
>
> KSPSetType(ksp,"gmres"); or KSPSetFromOptions(ksp);
>
> what happens is the KSP object looks for the "gmres factory" that has
> been registered with KSPRegister("gmres",KSPCreate_GMRES,..) and calls
> KSPCreate_GMRES() to generate the delegate that will actually do the solving.
>
> Since the delegate is completely encapsulated inside the ksp object I
> can change the delegate at a later time in the code to have a different
> implementation by just calling
>
> KSPSetType(ksp,"cg")
>
> The old delegate is freed and the new solver implementation is put in
> place. And all references to the ksp object continue to work (just using the
> new solver).
>
> So you see the design of PETSc allows "pushing up" the specific choice
> of implementations of classes in essentially the same way as factory objects
> do but without the need for users to explicitly create and manipulate the
> factories.
>
> PETSc factories 102
> ---------------------------
>
> The other place PETSc uses factories is to allow "mesh information" to
> determine algebraic objects that are created within algebraic solvers. This
> is done with the DM abstract class which you can think of as a factory for
> Vecs and Mats (though it does other things as well).
>
> Consider the nonlinear solver SNES in PETSc. I can use
>
> SNESSetJacobian(snes,J,J,func,ctx)
>
> to provide the matrix that Newton's method is going to use. But these means
> that my code that creates the SNES object and sets is various parameters has
> to explicit know how to create the J Mat object. If I am using a complicated
> meshing package that generated some grid and is going to use finite elements
> to compute the Jacobian J I'd like the figuring out of the size and sparsity
> pattern of the J to be handled by the meshing package. Thus I would make an
> implementation of the DM abstract class (say MattsDM) that does all this
> yucky figuring out. Then I could call
>
> DMCreateMatrix(dm,&J) /* now my solver code sees nothing of the yuckyness
> of particular mesh details */
>
> and then pass the J to
>
> SNESSetJacobian(snes,J,J,func,ctx)
>
> Similarly I can do the same thing to create vectors.
>
> We can take this one step further. So far I've been assuming that the
> application programmer is explicitly creating the algebraic objects (Mats and
> Vecs) and passing them to the solver object.
>
> Once solver objects start getting complicated; for example with multigrid
> methods it is painful for the user to create ALL the vectors and matrices
> needed for the multiple levels and provide them all to PCMG (though it is
> possible and we provide interfaces for doing that).
>
> Instead we can create DMs that can generate appropriate sized vectors and
> matrices and give those DMs to the solver object and the solver then calls
> the methods to get new vectors and matrices wherever it needs them inside the
> solver. PCMG would still need several DMs, one for each level. But rather
> than requiring the user to create these several DMs the user can create a
> single DM and the DM objects have methods in them that generate coarser or
> finer DMs that can be used to generate the vectors and matrices on the other
> levels. This is why a simple call of SNESSetDM(snes,dm) allows the nonlinear
> and linear solver objects (including all the levels of multigrid) to create
> all their various needed vectors and matrices.
>
> When composing complicated solvers this approach can be extremely powerful,
> one can envision DMs being able to generate sub DMs that represent just
> pieces of the physics and using those DMs to generate the vectors and
> matrices for the solver associated with the sub physics (in PCFieldSplit).
> Thus we can generate all the algebraic pieces for very complicated nested
> solvers with multigrid inside fieldsplit and fieldsplit into multigrid etc
> for any number of levels with one simple paradigm.
>
>
> In conclusion, you can think of PETSc as having one important visible factory
> class the DM and then one factory completely transparent to the user for each
> abstract PETSc object: IS, Vec,Mat,KSP,SNES, and yes even DM. IMHO factories
> are a powerful and useful tool for large libraries but they should be used
> sparingly and most of them though thoughtful design need never be seen the
> users (because if seen by the users that steepens the learning curve a great
> deal)
>
> Questions, clarifications?
>
> I have written up a discussion of 101 for the Encyclopedia of Programming
> Languages (Hans Petter is editing), which is attached. We
> should certainly write up 102 since it is more interesting.
>
> I think DMCreateSubDM() is fine for field pieces, but we need some way of
> doing domain pieces, and so far I don't like any of the suggestions.
>
> Matt
>
> Barry
>
> We actually have another factory in PETSc, MatGetVecs() that returns for a
> given matrix appropriately sized vectors. I don't have some grand
> philosophical reason for it to be around, but it is a very useful utility
> since often when you have a matrix you need vectors to perform operations
> with it and it is cumbersome to have to pass some vector through several
> layers of routines to get it to where it is needed to interact with the
> matrix.
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> --
> What most experimenters take for granted before they begin their experiments
> is infinitely more interesting than any results to which their experiments
> lead.
> -- Norbert Wiener
> <ProgrammingLanguagesForScientificComputing_Full.pdf>