← Back to team overview

dolfin team mailing list archive

Re: VariationalProblem interface

 


On 14/06/11 22:02, Martin Sandve Alnæs wrote:
> I think one of the main problems with VariationalProblem
> is the mix of problem and solver in one abstraction.
> 

This point may be worth discussing. It depends on how the 'Problem' ius
defined. This may or may not include the solution method.

> A problem is usually formulated:
> 
> Find u in V such that
>   a = L in Omega // pde
>   u = g on Gamma // bcs
> 
> Find u in V such that
>   F = 0 in Omega // pde
>   u = g on Gamma // bcs
> 
> Here's my suggested variation of VariationalProblem:
> 
> # Generic form:
> problem = VariationalProblem(unknown, lhsform, rhsform, bcs)
> solve(problem)
> 
> # Examples and variations follow, all consistent with the above:
> 
> # Typical linear system
> p = VariationalProblem(u, a, L, bcs)
> # Note that ordering and sign is consistent with a linear system
> p = VariationalProblem(u, J, -F, bcs)
> # Possibly(!) nonlinear system F(u) = 0
> p = VariationalProblem(u, F, 0, bcs)
> 
> # Note the 0 which maybe makes this less ambiguous(?)
> 

This brings us almost full circle to where we are now. If we prefix
'Linear' and 'Nonlinear' we use a descriptive word rather than just '0'.

Having a free function 'solve' removes the potential for more powerful
solvers since it removes the possibility for data structure and solver
re-use and fine tuning.

> # The fully automated variant of solve:
> solve(p) # Autodetect linear or nonlinear and differentiate if necessary
> 

In Python. It is a nice feature that we should add, but it doesn't
impact on the C++ side.

> # With optional behaviour changes:
> solve(p, nonlinear=True) # Force nonlinear solver
> solve(p, nonlinear=False) # Force linear solver
> solve(p, u=u2) # place solution in u2 instead of p.u, useful for linear solvers
> solve(p, solver=mysolver) # Use nondefault solver (need solver API)
> 

I think that there is a consensus to not use "linear/nonlinear =
true/false" arguments. We had this for a long time and then removed it.

Garth

> # Pseudocode for solve, showing it's feasible:
> def solve(p, nonlinear=None, solver="auto"):
>     # Allow alternative solvers in some way
>     if solver != "auto":
>         check properties of solver and adjust what is necessary below
> 
>     # Differentiate if necessary (skip if we use a solver that doesn't need it):
>     if isinstance(p.rhs, int) and p.rhs == 0:
>         lhs, rhs = derivative(p.lhs, p.u), -p.lhs
>     else:
>         lhs, rhs = p.lhs, p.rhs
> 
>     # Detect nonlinear or linear equation unless forced
>     if nonlinear is None: # Set to True or False to force this
>         # I have this function in my private sandbox
>         nonlinear = ufl.algorithms.depends_on(lhs, p.u)
> 
>     # Solve with linear or nonlinear default solver
>     if solver == "auto":
>         if nonlinear:
>             return solve_nonlinear(p.u, lhs, rhs, p.bcs)
>         else:
>             return solve_linear(p.u, lhs, rhs, p.bcs)
>     else:
>         return solver.solve(...)
> 
> 
> Good properties of this solution:
> - Makes the VariationalProblem independent of the solver method
> - Allows explicit or automatic choice of nonlinear/linear solver
> - Consistent ordering of forms given to VariationalProblem
> - Binds u to the problem, which is in accordance with (*) and allows
> differentiation
> There may be something I've forgotten of course, so speak up.
> 
> The binding of u to the problem is not really necessary in
> the case of a linear problem, but makes things consistent.
> With solve(p, u=u2) we still allow reuse of the VariationalProblem.
> 
> Martin
> 
> On 14 June 2011 22:09, Anders Logg <logg@xxxxxxxxx> wrote:
>> That's too bad... I will think of an alternate route.
>>
>> --
>> Anders
>>
>>
>> On Tue, Jun 14, 2011 at 10:01:47PM +0200, Martin Sandve Alnæs wrote:
>>> I agree it is pretty in a way, but here comes the showstopper... Sorry! ;)
>>>
>>> There can be no ufl.Form.__eq__ implementation
>>> that does not conform to the Python conventions
>>> of actually comparing objects and returning
>>> True or False, because that will break usage
>>> of Form objects in built in Python data structures.
>>>
>>> Martin
>>>
>>> On 14 June 2011 21:53, Anders Logg <logg@xxxxxxxxx> wrote:
>>>> On Tue, Jun 14, 2011 at 09:03:31PM +0200, Marie E. Rognes wrote:
>>>>> On 06/14/2011 08:35 PM, Garth N. Wells wrote:
>>>>>>
>>>>>>
>>>>>> On 14/06/11 19:24, Anders Logg wrote:
>>>>>>> On Tue, Jun 14, 2011 at 10:19:20AM -0700, Johan Hake wrote:
>>>>>>>> On Tuesday June 14 2011 03:33:59 Anders Logg wrote:
>>>>>>>>> On Tue, Jun 14, 2011 at 09:25:17AM +0100, Garth N. Wells wrote:
>>>>>>>>>> On 14/06/11 08:53, Anders Logg wrote:
>>>>>>>>>>> 14 jun 2011 kl. 09:18 skrev "Garth N. Wells"<gnw20@xxxxxxxxx>:
>>>>>>>>>>>> On 14/06/11 08:03, Marie E. Rognes wrote:
>>>>>>>>>>>>> On 06/13/2011 11:16 PM, Anders Logg wrote:
>>>>>>>>>>>>>>>> But while we are heading in that direction, how about
>>>>>>>>>>>>>>>> abolishing the *Problem class(es) altogether, and just use
>>>>>>>>>>>>>>>> LinearVariationalSolver and
>>>>>>>>>>>>>>>> NonlinearVariationalSolver/NewtonSolver taking as input (a,
>>>>>>>>>>>>>>>> L,
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> bc)
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> and (F, dF, bcs), respectively.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> This will be in line with an old blueprint. We noted some time
>>>>>>>>>>>>>> ago that problems/solvers are designed differently for linear
>>>>>>>>>>>>>> systems Ax = b than for variational problems a(u, v) = L(v).
>>>>>>>>>>>>>> For linear systems, we have solvers while for variational
>>>>>>>>>>>>>> problems we have both problem and solver classes.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I mean, the main difference lies in how to solve the
>>>>>>>>>>>>>>>> problems, right?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> It looks like the only property a VariationalProblem has in
>>>>>>>>>>>>>> addition to (forms, bc) + solver parameters is the parameter
>>>>>>>>>>>>>> symmetric=true/false.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> If we go this route, we could mimic the design of the linear
>>>>>>>>>>>>>> algebra solvers and provide two different options, one that
>>>>>>>>>>>>>> offers more control, solver = KrylovSolver() + solver.solve(),
>>>>>>>>>>>>>> and one quick option that just calls solve:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> 1. complex option
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> solver = LinearVariationalSolver() # which arguments to
>>>>>>>>>>>>>> constructor? solver.parameters["foo"] = ... u = solver.solve()
>>>>>>>>>>>>
>>>>>>>>>>>> I favour this option, but I think that the name
>>>>>>>>>>>> 'LinearVariationalSolver' is misleading since it's not a
>>>>>>>>>>>> 'variational solver', but solves variational problems, nor should
>>>>>>>>>>>> it be confused with a LinearSolver that solves Ax = f.
>>>>>>>>>>>> LinearVariationalProblem is a better name. For total control, we
>>>>>>>>>>>> could have a LinearVariationalProblem constructor that accepts a
>>>>>>>>>>>> GenericLinearSolver as an argument (as the NewtonSolver does).
>>>>>>>>>>>>
>>>>>>>>>>>>> For the eigensolvers, all arguments go in the call to solve.
>>>>>>>>>>>>>
>>>>>>>>>>>>>> 2. simple option
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> u = solve(a, L, bc)
>>>>>>>>>>>>
>>>>>>>>>>>> I think that saving one line of code and making the code less
>>>>>>>>>>>> explicit isn't worthwhile. I can foresee users trying to solve
>>>>>>>>>>>> nonlinear problems with this.
>>>>>>>>>>>
>>>>>>>>>>> With the syntax suggested below it would be easy to check for errors.
>>>>>>>>>>>
>>>>>>>>>>>>> Just for linears?
>>>>>>>>>>>>>
>>>>>>>>>>>>>> 3. very tempting option (simple to implement in both C++ and
>>>>>>>>>>>>>> Python)
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> u = solve(a == L, bc)    # linear u = solve(F == 0, J, bc) #
>>>>>>>>>>>>>> nonlinear
>>>>>>>>>>>>
>>>>>>>>>>>> I don't like this on the same grounds that I don't like the
>>>>>>>>>>>> present design. Also, I don't follow the above syntax
>>>>>>>>>>>
>>>>>>>>>>> I'm not surprised you don't like it. But don't understand why. It's
>>>>>>>>>>> very clear which is linear and which is nonlinear. And it would be
>>>>>>>>>>> easy to check for errors. And it would just be a thin layer on top of
>>>>>>>>>>> the very explicit linear/nonlinear solver classes. And it would
>>>>>>>>>>> follow the exact same design as for la with solver classes plus a
>>>>>>>>>>> quick access solve function.
>>>>>>>>>>
>>>>>>>>>> Is not clear to me - possibly because, as I wrote above, I don't
>>>>>>>>>> understand the syntax. What does the '==' mean?
>>>>>>>>>
>>>>>>>>> Here's how I see it:
>>>>>>>>>
>>>>>>>>> 1. Linear problems
>>>>>>>>>
>>>>>>>>>   solve(a == L, bc)
>>>>>>>>>
>>>>>>>>>   solve the linear variational problem a = L subject to bc
>>>>>>>>>
>>>>>>>>> 2. Nonlinear problems
>>>>>>>>>
>>>>>>>>>   solve(F == 0, bc)
>>>>>>>>>
>>>>>>>>>   solve the nonlinear variational problem F = 0 subject to bc
>>>>>>>>>
>>>>>>>>> It would be easy to in the first case check that the first operand (a)
>>>>>>>>> is a bilinear form and the second (L) is a linear form.
>>>>>>>>>
>>>>>>>>> And it would be easy to check in the second case that the first
>>>>>>>>> operand (F) is a linear form and the second is an integer that must be
>>>>>>>>> zero.
>>>>>>>>>
>>>>>>>>> In both cases one can print an informative error message and catch any
>>>>>>>>> pitfalls.
>>>>>>>>>
>>>>>>>>> The nonlinear case would in C++ accept an additional argument J for
>>>>>>>>> the Jacobian (and in Python an optional additional argument):
>>>>>>>>>
>>>>>>>>>   solve(F == 0, J, bc);
>>>>>>>>>
>>>>>>>>> The comparison operator == would for a == L return an object of class
>>>>>>>>> LinearVariationalProblem and in the second case
>>>>>>>>> NonlinearVariationalProblem. These two would just be simple classes
>>>>>>>>> holding shared pointers to the forms. Then we can overload solve() to
>>>>>>>>> take either of the two and pass the call on to either
>>>>>>>>> LinearVariationalSolver or NonlinearVariationalSolver.
>>>>>>>>>
>>>>>>>>> I'm starting to think this would be an ideal solution. It's compact,
>>>>>>>>> fairly intuitive, and it's possible to catch errors.
>>>>>>>>>
>>>>>>>>> The only problem I see is overloading operator== in Python if that
>>>>>>>>> has implications for UFL that Martin objects to... :-)
>>>>>>>>
>>>>>>>> Wow, you really like magical syntaxes ;)
>>>>>>>
>>>>>>> Yes, a pretty syntax has been a priority for me ever since we
>>>>>>> started. I think it is worth a lot.
>>>>>>>
>>>>>>
>>>>>> Magic and pretty are not the same thing.
>>>>
>>>> That's true, but some magic is usually required to make pretty.
>>>>
>>>> Being able to write dot(grad(u), grad(v))*dx is also a bit magic.
>>>> The step from there to solve(a == L) is short.
>>>>
>>>>>>>> The problem with this syntax is that who on earth would expect a
>>>>>>>> VariationalProblem to be the result of an == operator...
>>>>>>>
>>>>>>> I don't think that's an issue. Figuring out how to solve variational
>>>>>>> problems is not something one picks up by reading the Programmer's
>>>>>>> Reference. It's something that will be stated on the first page of any
>>>>>>> FEniCS tutorial or user manual.
>>>>>>>
>>>>>>> I think the solve(a == L) is the one missing piece to make the form
>>>>>>> language complete. We have all the nice syntax for expressing forms in
>>>>>>> a declarative way, but then it ends with
>>>>>>>
>>>>>>> problem = VariationalProblem(a, L)
>>>>>>> problem.solve()
>>>>>>>
>>>>>>> which I think looks ugly. It's not as extreme as this example taken
>>>>>> >from cppunit, but it follows the same "create object, call method on
>>>>>>> object" paradigm which I think is ugly:
>>>>>>>
>>>>>>>   TestResult result;
>>>>>>>   TestResultCollector collected_results;
>>>>>>>   result.addListener(&collected_results);
>>>>>>>   TestRunner runner;
>>>>>>>   runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
>>>>>>>   runner.run(result);
>>>>>>>   CompilerOutputter outputter(&collected_results, std::cerr);
>>>>>>>   outputter.write ();
>>>>>>>
>>>>>>>> I see the distinction between FEniCS developers who have programming versus
>>>>>>>> math in mind when designing the api ;)
>>>>>>>
>>>>>>> It's always been one of the top priorities in our work on FEniCS to
>>>>>>> build an API with the highest possible level of mathematical
>>>>>>> expressiveness to the API. That sometimes leads to challenges, like
>>>>>>> needing to develop a special form language, form compilers, JIT
>>>>>>> compilation, the Expression class etc, but that's the sort of thing
>>>>>>> we're pretty good at and one of the main selling points of FEniCS.
>>>>>>>
>>>>>>
>>>>>> This is an exaggeration to me. The code
>>>>>>
>>>>>>   problem = [Linear]VariationalProblem(a, L)
>>>>>>   u = problem.solve()
>>>>>>
>>>>>> is compact and explicit. It's a stretch to call it ugly.
>>>>
>>>> Yes, of course it's a stretch. It's not very ugly, but enough to
>>>> bother me.
>>>>
>>>>>>>> Also __eq__ is already used in ufl.Form to compare two forms.
>>>>>>>
>>>>>>> I think it would be worth replacing the use of form0 == form1 by
>>>>>>> repr(form0) == repr(form1) in UFL to be able to use __eq__ for this:
>>>>>>>
>>>>>>> class Equation:
>>>>>>>   def __init__(self, lhs, rhs):
>>>>>>>       self.lhs = lhs
>>>>>>>       self.rhs = rhs
>>>>>>>
>>>>>>> class Form:
>>>>>>>
>>>>>>>   def __eq__(self, other):
>>>>>>>       return Equation(self, other)
>>>>>>>
>>>>>>> I understand there are other priorities, and others don't care as much
>>>>>>> as I do about how fancy we can make the DOLFIN Python and C++ interface,
>>>>>>> but I think this would make a nice final touch to the interface.
>>>>>>>
>>>>>>
>>>>>> I don't see value in it. In fact the opposite - it introduces complexity
>>>>>> and a degree of ambiguity.
>>>>
>>>> Complexity yes (but not much, it would require say around 50-100
>>>> additional lines of code that I will gladly contribute), but I don't
>>>> think it's ambiguous. We could perform very rigorous and helpful
>>>> checks on the input arguments.
>>>>
>>>>> Evidently, we all see things differently. I fully support Anders in
>>>>> that mathematical expressiveness is one of the key features of
>>>>> FEniCS, and I think that without pushing these types of boundaries
>>>>> with regard to the language, it will end up as yet another finite
>>>>> element library.
>>>>>
>>>>> Could we compromise on having the two versions, one explicit (based
>>>>> on LinearVariational[Problem|Solver] or something of the kind) and
>>>>> one terse (based on solve(x == y)) ?
>>>>
>>>> That's what I'm suggesting. The solve(x == y) would just rely on the
>>>> more "explicit" version and do
>>>>
>>>>  <lots of checks>
>>>>  LinearVariationalSolver solver(x, y, ...);
>>>>  solver.solve(u);
>>>>
>>>> So in essence what I'm asking for is please let me add that tiny layer
>>>> on top of what we already have + remove the Problem classes (replaced
>>>> by the Solver classes).
>>>>
>>>>
>>>> _______________________________________________
>>>> Mailing list: https://launchpad.net/~dolfin
>>>> Post to     : dolfin@xxxxxxxxxxxxxxxxxxx
>>>> Unsubscribe : https://launchpad.net/~dolfin
>>>> More help   : https://help.launchpad.net/ListHelp
>>>>
>>
> 
> _______________________________________________
> Mailing list: https://launchpad.net/~dolfin
> Post to     : dolfin@xxxxxxxxxxxxxxxxxxx
> Unsubscribe : https://launchpad.net/~dolfin
> More help   : https://help.launchpad.net/ListHelp



Follow ups

References